diff --git a/.oat/repo/research/2026-03-15-oat-dashboard-design.md b/.oat/repo/research/2026-03-15-oat-dashboard-design.md new file mode 100644 index 000000000..8f5d93ba1 --- /dev/null +++ b/.oat/repo/research/2026-03-15-oat-dashboard-design.md @@ -0,0 +1,500 @@ +# OAT Dashboard — Design Spec + +## Overview + +A project-aware control plane and web dashboard for managing OAT workflows across repos, worktrees, and agent sessions. Dispatches skills to agent CLIs (Claude Code, Codex) running in Zellij terminal panes, with smart defaults and context-aware recommendations. + +### Core differentiator + +Existing tools (Superset, Broomy, Agentastic, emdash) are agent-session-centric — they help you run agents in parallel. The OAT dashboard is **workflow-phase-centric** — it understands the OAT project lifecycle (discovery → [spec] → design → plan → implement), knows where you are, tells you what's next, and dispatches the right skill with the right arguments. It also works over SSH (web dashboard + Zellij), unlike all surveyed alternatives. + +### Reference projects surveyed + +- **Broomy** (Electron) — terminal activity detection via output timing, persisted vs runtime state partitioning +- **Superset** (Electron) — worktree-per-task model, dispatch UX patterns (isolation dropdown, branch picker, collapsible advanced options) +- **Agentastic** (native macOS/Swift) — Ghostty embedding, kanban worktree organization +- **emdash** (Electron) — worktree pool pre-creation, skills/MCP system, service-oriented IPC architecture, SSH support + +## Architecture + +Three layers with clear separation: + +``` +┌────────────────────────────────────────────┐ +│ UI Layer │ +│ ├── Web Dashboard (React + Tailwind) │ +│ └── Zellij Plugin (Rust/WASM, future) │ +└──────────────┬─────────────────────────────┘ + │ +┌──────────────▼─────────────────────────────┐ +│ Dashboard Server │ +│ (Node.js, HTTP + WebSocket) │ +│ Wraps control plane, serves web UI, │ +│ pushes live updates via WebSocket │ +└──────────────┬─────────────────────────────┘ + │ +┌──────────────▼─────────────────────────────┐ +│ Control Plane (@oat/control-plane) │ +│ Pure TypeScript library │ +│ ├── Project state reader │ +│ ├── Skill registry + recommender │ +│ ├── Session manager (detect + dispatch) │ +│ └── Worktree manager │ +└────────────────────────────────────────────┘ + │ +┌──────────────▼─────────────────────────────┐ +│ Agent Runtimes │ +│ Claude CLI, Codex CLI running in │ +│ Zellij panes (Ghostty or any terminal) │ +└────────────────────────────────────────────┘ +``` + +### Monorepo structure + +``` +packages/control-plane/ # @oat/control-plane — pure TypeScript library +├── src/ +│ ├── projects/ +│ │ ├── scanner.ts # Discover projects across repos + worktrees +│ │ ├── parser.ts # Parse state.md, plan.md, implementation.md +│ │ ├── lifecycle.ts # Determine phase, progress, pending actions +│ │ └── watcher.ts # File watchers, emit update events +│ ├── skills/ +│ │ ├── registry.ts # Discover + parse skills from .agents/skills/ +│ │ └── recommender.ts # Context-aware "what to do next" logic +│ ├── sessions/ +│ │ ├── detector.ts # Process scanning for running agents +│ │ ├── launcher.ts # Spawn agent CLIs with -p flag +│ │ ├── zellij.ts # Zellij CLI API integration +│ │ └── worktree.ts # Git worktree create/list/cleanup +│ └── index.ts # Public API + +packages/dashboard-server/ # Lightweight HTTP + WebSocket server +├── src/ +│ ├── api.ts # REST endpoints +│ ├── ws.ts # WebSocket push for live updates +│ └── index.ts # Server entry, static file serving + +apps/dashboard-ui/ # React web UI +├── src/ +│ ├── views/ +│ │ ├── ProjectList.tsx # Left sidebar — all projects across repos +│ │ ├── ProjectDetail.tsx # Center — phase, progress, inbox, artifacts +│ │ ├── SessionPanel.tsx # Right sidebar — active sessions +│ │ └── DispatchModal.tsx # Dispatch flow (send to existing / launch new) +│ └── main.tsx +``` + +## Control Plane (`@oat/control-plane`) + +Pure TypeScript library with no UI or server dependencies. Exposes typed functions that any consumer (server, CLI, extension) can call. + +### Project discovery and state + +**Discovery** — scans filesystem to find all OAT projects: + +1. Takes a list of repo root paths (from `~/.oat/dashboard.json`) +2. For each repo, runs `git worktree list` to find all worktrees +3. Scans each checkout/worktree for `.oat/projects/` directories +4. Returns a unified list of all discovered projects with their repo and worktree context +5. Deduplicates: same project across worktrees appears as one entry with multiple locations + +**State parsing** — reads OAT markdown artifacts into structured data: + +```typescript +interface ProjectState { + id: string; // "repo:scope:name" — unique across repos + name: string; + scope: string; // "shared" | custom + repo: string; // repo root path + locations: ProjectLocation[]; // main checkout + worktrees + phase: Phase; // matches oat_phase frontmatter + phaseStatus: PhaseStatus; // matches oat_phase_status frontmatter + workflowMode: WorkflowMode; // determines which phases exist + progress: { completed: number; total: number; currentTask?: string }; + pendingActions: PendingAction[]; // things waiting on human input + artifacts: Map; // which files exist + last modified + blockers: string[]; // from oat_blockers frontmatter + timestamps: { + created: string; // oat_project_created (ISO 8601) + completed?: string; // oat_project_completed (ISO 8601) + stateUpdated: string; // oat_project_state_updated (ISO 8601) + }; +} + +interface ProjectLocation { + path: string; // absolute path to project dir + worktree?: string; // worktree path if not main checkout + branch: string; // current branch +} + +// Matches oat_phase values exactly +type Phase = 'discovery' | 'spec' | 'design' | 'plan' | 'implement'; + +// Matches oat_phase_status values exactly +type PhaseStatus = 'in_progress' | 'complete'; + +// Matches oat_workflow_mode values exactly +type WorkflowMode = 'spec-driven' | 'quick' | 'import'; + +// Derived display state — computed from phase + phaseStatus + artifact presence +type DisplayState = + | { kind: 'in-phase'; phase: Phase } // phase is in_progress + | { kind: 'reviewing'; phase: Phase } // review artifact exists for current phase, awaiting response + | { kind: 'complete' }; // oat_project_completed is set + +type ArtifactType = + | 'discovery' + | 'spec' + | 'design' + | 'plan' + | 'implementation' + | 'reviews'; + +// Full snapshot sent on WebSocket connect/reconnect +interface FullState { + projects: ProjectState[]; + sessions: AgentSession[]; +} + +interface PendingAction { + type: + | 'review-pending' + | 'blocked-task' + | 'hill-checkpoint' + | 'approval-needed'; + description: string; + artifact?: string; // file path if applicable +} + +interface ArtifactInfo { + exists: boolean; + path: string; + lastModified?: Date; + active?: boolean; // currently being worked on +} +``` + +Note: `Phase` represents the raw OAT phase value from `state.md` frontmatter. "Reviewing" and "complete" are not phases — they are derived from `phaseStatus`, review artifact presence, and `oat_project_completed`. The UI computes `DisplayState` for rendering. + +**Pending action detection** — scans for signals requiring human input: + +- Review artifacts in `reviews/` with no corresponding "received" response +- `implementation.md` showing a task with blocker status +- `state.md` indicating a HiLL checkpoint was reached +- Plan exists but implementation hasn't started +- Design review requested but not addressed + +**File watching** — monitors OAT artifacts for changes and emits events: + +- Uses chokidar or `fs.watch` on `.oat/projects/` directories +- Emits typed events: `project-updated`, `project-created`, `project-removed` +- Consumers (dashboard server) subscribe and push updates to connected clients + +### Skill registry and recommender + +**Registry** — reads `.agents/skills/` and parses frontmatter: + +```typescript +interface SkillInfo { + name: string; + description: string; + version?: string; + userInvocable: boolean; + argumentHint?: string; // e.g. ' [--force]' + category: 'lifecycle' | 'review' | 'utility'; // inferred from name prefix (oat-project-* = lifecycle, oat-*review* = review, else utility) +} +``` + +**Recommender** — given a project state, returns ordered skill suggestions with pre-filled arguments: + +```typescript +interface SkillRecommendation { + skill: SkillInfo; + reason: string; // why this is recommended now + suggestedArgs?: string; // pre-filled based on project state + priority: 'primary' | 'secondary'; +} +``` + +Recommendation logic (uses `oat_phase` + `oat_phase_status` + `oat_workflow_mode`): + +| `oat_phase` | `oat_phase_status` | `oat_workflow_mode` | Primary recommendation | Suggested args | +| ----------- | ------------------ | ------------------- | ---------------------------- | ----------------- | +| `discovery` | `in_progress` | any | — (in progress, no action) | — | +| `discovery` | `complete` | `spec-driven` | `oat-project-spec` | — | +| `discovery` | `complete` | `quick` / `import` | `oat-project-design` | — | +| `spec` | `in_progress` | `spec-driven` | — | — | +| `spec` | `complete` | `spec-driven` | `oat-project-review-provide` | `artifact spec` | +| `design` | `in_progress` | any | — | — | +| `design` | `complete` | any | `oat-project-review-provide` | `artifact design` | +| `plan` | `in_progress` | any | — | — | +| `plan` | `complete` | any | `oat-project-implement` | — | +| `implement` | `in_progress` | any | `oat-project-implement` | — | +| `implement` | `complete` | any | `oat-project-review-provide` | `code final` | + +Additional recommendations (appended as secondary): + +- If review artifacts exist in `reviews/` with no received response → `oat-project-review-receive` +- If `oat_blockers` is non-empty → show blocker info, no skill recommendation +- If a HiLL checkpoint from `oat_hill_checkpoints` has been reached (present in `oat_hill_completed`) → flag for human review + +Arguments are always shown as editable fields — the recommendation is a default, not a constraint. + +**Known limitation:** When a phase's review has been received and passed but `oat_phase` hasn't advanced yet (brief window), the recommender may re-suggest the review skill. Acceptable for v1 — the user naturally advances to the next phase. Future improvement: check for archived review artifacts to detect this state. + +### Session manager + +**Detection** — finds running agent sessions: + +- Primary: uses Zellij CLI API to list panes and their processes (most reliable) +- Fallback: scans running processes for `claude`, `codex` binaries via `ps` +- Matches to repo/worktree by inspecting process cwd (macOS: `lsof -p PID | grep cwd`; Linux: `/proc/PID/cwd` symlink) +- If Zellij is available, uses `zellij action` CLI to: + - List panes and their running processes + - Associate sessions with specific Zellij pane IDs + - Read pane output for activity state (working/idle heuristic) + +```typescript +interface AgentSession { + runtime: 'claude' | 'codex'; + pid: number; + repo: string; + worktree?: string; + zellijPane?: { id: number; tab: string }; + status: 'working' | 'idle' | 'unknown'; + duration: number; // seconds since session started +} +``` + +**Dispatch — send to existing session:** + +- Uses `zellij action write-chars` to type a command into a specific pane +- Command format: `/ \n` + +**Dispatch — launch new session:** + +Pipeline executed as a chained shell command: + +1. `git worktree add -b ` (if worktree isolation selected) +2. `cd ` +3. Run setup script (e.g. `pnpm run worktree:init`) if configured +4. `claude -p "/ "` or `codex ""` — agent starts with the skill already invoked via initial prompt flag + +The entire pipeline is a single `&&`-chained command run in a new Zellij pane via `zellij run`. No "wait for agent to be ready" detection needed. + +**Error handling for dispatch:** + +- Worktree creation fails (branch exists, dirty state): surface git error in dashboard, don't proceed +- Setup script fails (non-zero exit): show output in dashboard, offer retry or skip +- Zellij not running: show error with instructions ("start Zellij first" or offer to launch it) +- `write-chars` to a pane that has exited: detect via pane listing first, show "session no longer active" +- Agent CLI not found: check PATH before dispatch, surface "claude/codex not installed" with install link + +```typescript +interface DispatchOptions { + skill?: { name: string; args?: string }; // skill to invoke (optional — can launch empty session) + runtime: 'claude' | 'codex'; + repo: string; + isolation: 'current-branch' | 'new-worktree'; + worktreeOptions?: { + baseBranch: string; + branchName: string; // auto-generated default, editable + }; + setupScript?: string; // from repo config, toggleable +} +``` + +### Worktree manager + +Thin wrapper around git worktree operations: + +- `listWorktrees(repo)` — returns all worktrees with branch info +- `createWorktree(repo, baseBranch, branchName)` — creates and returns path +- `removeWorktree(repo, path)` — prune merged worktrees + +## Dashboard Server (`@oat/dashboard-server`) + +Lightweight Node.js server. Started via `oat dashboard` CLI command. + +### HTTP API + +``` +GET /api/projects # All discovered projects with state +GET /api/projects/:id # Single project detail (id = repo:scope:name) +GET /api/projects/:id/artifacts # Artifact metadata +GET /api/skills # All skills +GET /api/skills/recommendations/:project # Context-aware recommendations for a project +GET /api/sessions # Active agent sessions +POST /api/sessions/dispatch # Launch new session or send to existing +GET /api/worktrees/:repo # List worktrees for a repo +POST /api/open-file # Open file in system editor (open/xdg-open) +GET /api/config # Dashboard config +PUT /api/config # Update dashboard config +``` + +### WebSocket + +Push events to connected dashboard clients: + +```typescript +type DashboardEvent = + | { type: 'connection-established'; state: FullState } // sent on connect/reconnect + | { type: 'project-updated'; project: ProjectState } + | { type: 'project-discovered'; project: ProjectState } + | { type: 'session-started'; session: AgentSession } + | { type: 'session-ended'; session: AgentSession } + | { type: 'session-activity'; session: AgentSession }; +``` + +File watchers on OAT project directories trigger `project-updated` events. Session detection polls on a configurable interval (default 5s). + +**Reconnection:** On WebSocket disconnect, the client reconnects with exponential backoff. On reconnect, the server sends a `connection-established` event with the full current state — the client replaces its local state entirely rather than trying to reconcile missed events. + +### Static serving + +Serves the built dashboard UI from `apps/dashboard-ui/dist/`. Single `oat dashboard` command starts both the API and serves the frontend. + +## Web UI (`apps/dashboard-ui`) + +React + Tailwind application. Three-panel layout. + +### Left sidebar — Project List + +- All discovered projects across all repos and worktrees +- Each entry shows: project name, current phase, progress indicator (e.g. "3/7"), pending action badge +- Grouped by repo +- Selected project highlighted, drives center panel +- If a project exists in multiple worktrees, shows the worktree context inline + +### Center panel — Project Detail + +- **Header**: project name, repo, path, phase badge +- **Progress bar**: visual indicator of phase progress (X of Y tasks) +- **Pending Actions (inbox)**: amber-highlighted items waiting on human input, with "Open" button to view the relevant artifact +- **Recommended Next**: skill cards with pre-filled arguments, primary recommendation highlighted. Each card shows skill name, description, and suggested args as editable text. Clicking a card opens the dispatch modal with that skill pre-selected. +- **Artifacts**: grid of project artifacts (discovery.md, design.md, plan.md, implementation.md, reviews/) showing existence and last modified date. Click triggers `POST /api/open-file` which shells out to `open` (macOS) / `xdg-open` (Linux) to open in the system default editor. + +### Right sidebar — Sessions + +- Active agent sessions with status indicators (green dot = working, gray = idle) +- Each session shows: runtime (Claude/Codex), repo/worktree, duration, Zellij pane reference +- "New Session" button opens dispatch modal + +### Dispatch Modal + +Two modes, toggled by target: + +**Send to existing session:** + +- Session selector dropdown (only shows running sessions, with status) +- Recommended skill chips (quick-pick based on project state) +- Free-form prompt input as fallback +- "Send to Session" button + +**Launch new session:** + +- Skill/Prompt tabs — skill picker with argument field, or free-form prompt +- Agent selector dropdown (Claude Code / Codex CLI) +- Isolation mode dropdown (Current branch / New worktree) +- Collapsible "Advanced options": + - Base branch picker (searchable, shows available branches) + - Branch name (auto-generated from project + skill, editable) + - Setup script toggle (reads from repo config, shows the command) +- "Create Session" button +- Pipeline preview at bottom: shows the steps that will execute (worktree → setup → agent -p "command") + +Skill arguments are shown as an editable field below the skill picker, pre-filled with the recommender's suggestion based on project state. The user can always override. + +## Zellij Plugin (future, not v1) + +Minimal TUI companion for terminal-only workflows (SSH to Mac Mini, EC2 devbox). + +**Shows:** + +- Current project name + phase + progress (single status line) +- Pending action count +- Active session count with status dots + +**Does:** + +- Keybinding to cycle projects +- Keybinding for compact fuzzy skill picker → sends command to active pane + +**Tech:** Rust/WASM per Zellij plugin API. Reads OAT files directly from filesystem — independent of dashboard server. + +## Configuration + +### Global — `~/.oat/dashboard.json` + +```json +{ + "repos": ["~/Code/open-agent-toolkit", "~/Code/api-service"], + "port": 7400, + "defaults": { + "runtime": "claude", + "isolation": "worktree", + "baseBranch": "main" + } +} +``` + +`repos` is a list of root directories to scan — the dashboard discovers projects and worktrees within them automatically. Tilde (`~`) is expanded to `$HOME` at read time. + +**Creation and fallback behavior:** + +- `oat dashboard` works without this file — defaults to scanning CWD only +- First run prompts: "No dashboard config found. Scan current directory only, or configure repos?" with option to create the file +- `oat dashboard init` creates the file interactively, scanning for `.oat/` directories under common paths + +### Per-repo — field in `.oat/config.json` (committed, shared) + +```json +{ + "dashboard": { + "setupScript": "pnpm run worktree:init", + "branchPrefix": "oat/" + } +} +``` + +Lives in `.oat/config.json` (not `.oat/config.local.json`) because setup scripts and branch prefixes are typically team-shared conventions. This is additive to the existing config schema — existing fields are unchanged. + +### No separate database + +All project state is read from OAT's existing markdown artifacts on disk. Session state is runtime-only (process detection + Zellij API). No SQLite, no state duplication. The filesystem and running processes are the source of truth. + +## Tech Stack + +| Component | Technology | Rationale | +| ----------------- | ------------------------ | --------------------------------------------- | +| Control plane | TypeScript, pure library | Shares types/tooling with OAT monorepo | +| Dashboard server | Node.js + Hono | Lightweight HTTP + WebSocket, minimal deps | +| Web UI | React + Tailwind | Fast to build, familiar ecosystem | +| Zellij plugin | Rust + WASM | Required by Zellij plugin API | +| File watching | chokidar | Monitor OAT artifact changes for live updates | +| Zellij IPC | `zellij action` CLI | No custom IPC — use existing Zellij commands | +| Process detection | `ps` + cwd inspection | Detect running claude/codex processes | +| Build | Turborepo (existing) | Consistent with monorepo build system | + +## v1 Scope + +### In scope + +- Control plane: project discovery (repos + worktrees), state parsing, skill registry + recommender, session detection, dispatch (existing + new), worktree management +- Dashboard server: HTTP API, WebSocket live updates, static UI serving +- Web UI: project list, project detail (phase/progress/inbox/artifacts), session panel, dispatch modal with smart defaults and skill arguments +- Zellij integration: dispatch into panes via CLI, detect existing sessions in panes +- `oat dashboard` CLI command + +### Out of scope (future) + +- Zellij TUI plugin (status bar companion) +- Git/branch context panel in dashboard +- Activity timeline / recent events feed +- Tauri desktop wrapper +- VS Code extension +- emdash / Superset integration adapters +- Mobile client +- Multi-user / team features +- Authentication / access control (single-user, localhost assumed; SSH tunnel for remote access) diff --git a/.oat/repo/research/2026-03-15-oat-dashboard.md b/.oat/repo/research/2026-03-15-oat-dashboard.md new file mode 100644 index 000000000..160f939fd --- /dev/null +++ b/.oat/repo/research/2026-03-15-oat-dashboard.md @@ -0,0 +1,1437 @@ +# OAT Dashboard Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a project-aware control plane, dashboard server, and web UI for managing OAT workflows across repos, worktrees, and agent sessions. + +**Architecture:** Three-layer stack — `@oat/control-plane` (pure TypeScript library) → `@oat/dashboard-server` (Hono HTTP + WebSocket) → `apps/dashboard-ui` (React + Tailwind). The control plane reads OAT markdown artifacts from disk and exposes typed APIs. The server wraps those APIs for the web client. A new `oat dashboard` CLI command starts everything. + +**Tech Stack:** TypeScript 5.8, Vitest, Hono, React 19, Tailwind CSS 4, chokidar, Turborepo, pnpm workspaces. + +**Spec:** `.superpowers/specs/2026-03-15-oat-dashboard-design.md` + +--- + +## Chunk 1: Control Plane — Types and Project State + +### Task 1: Package scaffold for `@oat/control-plane` + +**Files:** + +- Create: `packages/control-plane/package.json` +- Create: `packages/control-plane/tsconfig.json` +- Create: `packages/control-plane/src/index.ts` +- Create: `packages/control-plane/src/types.ts` + +- [ ] **Step 1: Create `packages/control-plane/package.json`** + +```json +{ + "name": "@oat/control-plane", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "engines": { + "node": ">=22.17.0" + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "vitest run", + "test:watch": "vitest", + "lint": "oxlint .", + "format": "oxfmt --check .", + "format:fix": "oxfmt --write .", + "type-check": "tsc --noEmit", + "clean": "rm -rf dist" + }, + "dependencies": { + "yaml": "^2.8.2", + "chokidar": "^4.0.0" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "typescript": "^5.8.3", + "vitest": "^4.0.18" + } +} +``` + +Note: `yaml` is used for parsing `state.md` frontmatter (same package used by `@oat/cli`). `chokidar` is for file watching (Task 5). + +- [ ] **Step 2: Create `packages/control-plane/tsconfig.json`** + +```json +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "declarationMap": true + }, + "include": ["src/**/*.ts"], + "exclude": ["**/*.test.ts", "**/*.spec.ts"] +} +``` + +Note: No path aliases — use relative imports (`./projects/parser.js`) throughout. The package is small enough that relative imports are clean. This avoids needing `tsc-alias` in the build pipeline. + +- [ ] **Step 3: Create `packages/control-plane/src/types.ts`** + +All shared types from the spec — `Phase`, `PhaseStatus`, `WorkflowMode`, `ProjectState`, `ProjectLocation`, `DisplayState`, `ArtifactType`, `ArtifactInfo`, `PendingAction`, `FullState`, `SkillInfo`, `SkillRecommendation`, `AgentSession`, `DispatchOptions`. + +Copy the interfaces directly from the spec at `.superpowers/specs/2026-03-15-oat-dashboard-design.md` lines 104–175 and 198–306. + +- [ ] **Step 4: Create `packages/control-plane/src/index.ts`** + +```typescript +export type { + Phase, + PhaseStatus, + WorkflowMode, + ProjectState, + ProjectLocation, + DisplayState, + ArtifactType, + ArtifactInfo, + PendingAction, + FullState, + SkillInfo, + SkillRecommendation, + AgentSession, + DispatchOptions, +} from './types.js'; +``` + +- [ ] **Step 5: Add to pnpm workspace and verify build** + +Run: `pnpm install && pnpm --filter @oat/control-plane build` +Expected: Clean compile, `dist/` contains `.js` and `.d.ts` files. + +- [ ] **Step 6: Commit** + +```bash +git add packages/control-plane/ +git commit -m "feat(control-plane): scaffold package with shared types" +``` + +--- + +### Task 2: State.md frontmatter parser + +**Files:** + +- Create: `packages/control-plane/src/projects/parser.ts` +- Create: `packages/control-plane/src/projects/parser.test.ts` + +This module parses `state.md` YAML frontmatter into typed `ProjectState` fields. It also checks for artifact file existence and reads `plan.md`/`implementation.md` to extract task progress. + +**Reference:** Look at an existing `state.md` for the frontmatter format — e.g. `.oat/projects/shared/deep-research/state.md`. Key fields: `oat_phase`, `oat_phase_status`, `oat_workflow_mode`, `oat_blockers`, `oat_current_task`, `oat_hill_checkpoints`, `oat_hill_completed`, `oat_project_created`, `oat_project_completed`, `oat_project_state_updated`. + +- [ ] **Step 1: Write failing test — parse state.md frontmatter** + +```typescript +// packages/control-plane/src/projects/parser.test.ts +import { describe, expect, it } from 'vitest'; +import { parseStateFrontmatter } from './parser.js'; + +describe('parseStateFrontmatter', () => { + it('parses valid state.md content into structured fields', () => { + const content = `--- +oat_phase: implement +oat_phase_status: in_progress +oat_workflow_mode: quick +oat_blockers: [] +oat_current_task: "task-3" +oat_hill_checkpoints: [] +oat_hill_completed: [] +oat_project_created: '2026-03-10T00:00:00.000Z' +oat_project_completed: null +oat_project_state_updated: '2026-03-15T00:00:00.000Z' +--- + +# Project Name +`; + const result = parseStateFrontmatter(content); + expect(result.phase).toBe('implement'); + expect(result.phaseStatus).toBe('in_progress'); + expect(result.workflowMode).toBe('quick'); + expect(result.blockers).toEqual([]); + expect(result.timestamps.created).toBe('2026-03-10T00:00:00.000Z'); + expect(result.timestamps.completed).toBeUndefined(); + }); + + it('handles spec-driven workflow with blockers', () => { + const content = `--- +oat_phase: design +oat_phase_status: complete +oat_workflow_mode: spec-driven +oat_blockers: + - "waiting on API decision" +oat_current_task: null +oat_hill_checkpoints: + - discovery + - spec +oat_hill_completed: + - discovery +oat_project_created: '2026-03-08T00:00:00.000Z' +oat_project_completed: null +oat_project_state_updated: '2026-03-14T00:00:00.000Z' +--- +`; + const result = parseStateFrontmatter(content); + expect(result.phase).toBe('design'); + expect(result.phaseStatus).toBe('complete'); + expect(result.workflowMode).toBe('spec-driven'); + expect(result.blockers).toEqual(['waiting on API decision']); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `pnpm --filter @oat/control-plane test -- src/projects/parser.test.ts` +Expected: FAIL — `parseStateFrontmatter` not found. + +- [ ] **Step 3: Implement `parseStateFrontmatter`** + +```typescript +// packages/control-plane/src/projects/parser.ts + +interface ParsedStateFrontmatter { + phase: Phase; + phaseStatus: PhaseStatus; + workflowMode: WorkflowMode; + blockers: string[]; + currentTask: string | null; + hillCheckpoints: string[]; + hillCompleted: string[]; + timestamps: { + created: string; + completed?: string; + stateUpdated: string; + }; +} +``` + +Parse the YAML frontmatter between `---` delimiters. Split content on `---`, take the middle section, parse with the `yaml` package (`import { parse } from 'yaml'` — same package already used by `@oat/cli`). + +Extract each `oat_*` field and map to the typed interface. Handle `null` values for `oat_project_completed` (map to `undefined`). Handle arrays for `oat_blockers`, `oat_hill_checkpoints`, `oat_hill_completed`. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `pnpm --filter @oat/control-plane test -- src/projects/parser.test.ts` +Expected: PASS + +- [ ] **Step 5: Write failing test — parse task progress from plan.md** + +Test `parseTaskProgress(planContent, implementationContent)` that extracts completed/total task counts. + +Sample `plan.md` input (tasks are `### Task N:` headings with `- [ ]`/`- [x]` steps): + +```markdown +### Task 1: Setup database + +- [x] Step 1: Create schema +- [x] Step 2: Run migration + +### Task 2: Build API + +- [x] Step 1: Define routes +- [ ] Step 2: Implement handlers + +### Task 3: Add tests + +- [ ] Step 1: Write unit tests +``` + +Sample `implementation.md` input (current task marked in a status table): + +```markdown +| Task | Status | +| ---------------------- | ----------- | +| Task 1: Setup database | complete | +| Task 2: Build API | in-progress | +| Task 3: Add tests | pending | +``` + +Expected output: `{ completed: 1, total: 3, currentTask: 'Task 2: Build API' }` + +The parser counts `### Task N:` headings as the total, checks implementation.md status table for completed count, and finds the `in-progress` row for currentTask. + +- [ ] **Step 6: Implement `parseTaskProgress`** + +- [ ] **Step 7: Run tests, verify pass** + +Run: `pnpm --filter @oat/control-plane test -- src/projects/parser.test.ts` +Expected: All PASS + +- [ ] **Step 8: Write failing test — `checkArtifacts` function** + +Test `checkArtifacts(projectDir)` that checks which artifact files exist (`discovery.md`, `spec.md`, `design.md`, `plan.md`, `implementation.md`, `reviews/`) and returns a `Map`. Use `fs.stat` to get `lastModified`. + +- [ ] **Step 9: Implement `checkArtifacts`** + +- [ ] **Step 10: Run tests, verify pass** + +- [ ] **Step 11: Commit** + +```bash +git add packages/control-plane/src/projects/ +git commit -m "feat(control-plane): state.md parser, task progress, artifact checker" +``` + +--- + +### Task 3: Project scanner — discover projects across repos and worktrees + +**Files:** + +- Create: `packages/control-plane/src/projects/scanner.ts` +- Create: `packages/control-plane/src/projects/scanner.test.ts` + +This module discovers OAT projects by scanning repo root paths and their worktrees. + +- [ ] **Step 1: Write failing test — `listWorktrees`** + +Test that `listWorktrees(repoPath)` runs `git worktree list --porcelain` and parses the output into `{ path: string; branch: string; isMain: boolean }[]`. + +Mock `child_process.execFile` to return known porcelain output: + +``` +worktree /Users/me/Code/repo +HEAD abc123 +branch refs/heads/main + +worktree /Users/me/Code/repo/.worktrees/feature +HEAD def456 +branch refs/heads/feature-branch +``` + +- [ ] **Step 2: Implement `listWorktrees`** + +- [ ] **Step 3: Run test, verify pass** + +- [ ] **Step 4: Write failing test — `scanForProjects`** + +Test that `scanForProjects(repoPath)` reads `.oat/projects/` directory structure and returns discovered project entries with `{ name, scope, path }`. It should find projects under all scopes (e.g. `shared/`, `local/`). + +Use a temp directory fixture with a mock `.oat/projects/shared/my-project/state.md`. + +- [ ] **Step 5: Implement `scanForProjects`** + +Read `.oat/projects/` directory. For each scope directory, for each project directory within it, check if `state.md` exists. If yes, return it as a discovered project. + +- [ ] **Step 6: Run test, verify pass** + +- [ ] **Step 7: Write failing test — `discoverAllProjects`** + +Test the top-level function that ties it together: given repo roots, list worktrees for each, scan for projects in each worktree, deduplicate by `repo:scope:name`, merge locations. + +- [ ] **Step 8: Implement `discoverAllProjects`** + +Calls `listWorktrees` for each repo, then `scanForProjects` for each worktree path, then `parseStateFrontmatter` + `checkArtifacts` + `parseTaskProgress` for each discovered project. Deduplicates by building the `id` (`repo:scope:name`) and merging `ProjectLocation` arrays. + +- [ ] **Step 9: Run all tests, verify pass** + +Run: `pnpm --filter @oat/control-plane test` +Expected: All PASS + +- [ ] **Step 10: Commit** + +```bash +git add packages/control-plane/src/projects/ +git commit -m "feat(control-plane): project scanner with worktree discovery" +``` + +--- + +### Task 4: Pending action detection and lifecycle + +**Files:** + +- Create: `packages/control-plane/src/projects/lifecycle.ts` +- Create: `packages/control-plane/src/projects/lifecycle.test.ts` + +- [ ] **Step 1: Write failing test — `detectPendingActions`** + +Test scenarios: + +- Review artifact exists in `reviews/` with no `received` counterpart → `review-pending` +- `oat_blockers` non-empty → `blocked-task` +- HiLL checkpoint reached (in `hillCompleted` but action needed) → `hill-checkpoint` +- Phase complete but next phase not started → `approval-needed` + +- [ ] **Step 2: Implement `detectPendingActions`** + +Takes parsed state + artifact info, returns `PendingAction[]`. + +- [ ] **Step 3: Write failing test — `computeDisplayState`** + +Test that `computeDisplayState(projectState)` returns the correct `DisplayState`: + +- Phase `implement`, status `in_progress` → `{ kind: 'in-phase', phase: 'implement' }` +- Phase `design`, status `complete`, review artifact exists → `{ kind: 'reviewing', phase: 'design' }` +- `oat_project_completed` set → `{ kind: 'complete' }` + +- [ ] **Step 4: Implement `computeDisplayState`** + +- [ ] **Step 5: Run tests, verify pass** + +- [ ] **Step 6: Commit** + +```bash +git add packages/control-plane/src/projects/ +git commit -m "feat(control-plane): pending action detection and display state" +``` + +--- + +### Task 5: File watcher + +**Files:** + +- Create: `packages/control-plane/src/projects/watcher.ts` +- Create: `packages/control-plane/src/projects/watcher.test.ts` +- Modify: `packages/control-plane/package.json` (add chokidar dependency) + +- [ ] **Step 1: Add chokidar dependency** + +```bash +cd packages/control-plane && pnpm add chokidar +``` + +- [ ] **Step 2: Write failing test — `ProjectWatcher` emits events** + +Test that `ProjectWatcher` watches `.oat/projects/` directories and emits `project-updated` when a `state.md` file changes. Use a temp directory, create the watcher, write a file, assert the event fires. + +- [ ] **Step 3: Implement `ProjectWatcher`** + +```typescript +// EventEmitter-based watcher +export class ProjectWatcher extends EventEmitter { + constructor(repoPaths: string[]) { ... } + start(): void { ... } // starts chokidar watchers + stop(): void { ... } // closes all watchers +} +``` + +Watch `/.oat/projects/**/state.md` and `/.oat/projects/**/implementation.md` and `/.oat/projects/**/reviews/*.md`. On change, re-parse the affected project and emit `project-updated` with the new `ProjectState`. Debounce changes by 500ms to avoid rapid-fire events during writes. + +- [ ] **Step 4: Run test, verify pass** + +- [ ] **Step 5: Commit** + +```bash +git add packages/control-plane/ +git commit -m "feat(control-plane): file watcher for project state changes" +``` + +--- + +### Task 6: Export project modules from control plane index + +**Files:** + +- Modify: `packages/control-plane/src/index.ts` + +- [ ] **Step 1: Update index.ts to export project modules** + +```typescript +export type { ... } from './types.js'; +export { parseStateFrontmatter, parseTaskProgress, checkArtifacts } from './projects/parser.js'; +export { listWorktrees, scanForProjects, discoverAllProjects } from './projects/scanner.js'; +export { detectPendingActions, computeDisplayState } from './projects/lifecycle.js'; +export { ProjectWatcher } from './projects/watcher.js'; +``` + +- [ ] **Step 2: Run full build and tests** + +Run: `pnpm --filter @oat/control-plane build && pnpm --filter @oat/control-plane test` +Expected: All PASS, clean build. + +- [ ] **Step 3: Commit** + +```bash +git add packages/control-plane/src/index.ts +git commit -m "feat(control-plane): export project modules from package index" +``` + +--- + +## Chunk 2: Control Plane — Skills and Sessions + +### Task 7: Skill registry + +**Files:** + +- Create: `packages/control-plane/src/skills/registry.ts` +- Create: `packages/control-plane/src/skills/registry.test.ts` + +- [ ] **Step 1: Write failing test — `loadSkillRegistry`** + +Test that `loadSkillRegistry(agentsSkillsDir)` reads `.agents/skills/` directory, parses each `SKILL.md` frontmatter, and returns `SkillInfo[]`. Use the actual `.agents/skills/` directory structure as reference — skill dirs contain `SKILL.md` with YAML frontmatter including `name`, `description`, `version`, `user-invocable`, `argument-hint`. + +Test cases: + +- Parses a skill with all fields +- Infers category from name prefix (`oat-project-` → lifecycle, `oat-*review*` → review, else utility) +- Filters to `userInvocable: true` skills only when requested + +- [ ] **Step 2: Implement `loadSkillRegistry`** + +Read directory entries under the skills dir. For each, read `SKILL.md`, extract YAML frontmatter, map to `SkillInfo`. Category inference: check name starts with `oat-project-` → `lifecycle`, name contains `review` → `review`, else `utility`. + +- [ ] **Step 3: Run test, verify pass** + +- [ ] **Step 4: Commit** + +```bash +git add packages/control-plane/src/skills/ +git commit -m "feat(control-plane): skill registry with frontmatter parsing" +``` + +--- + +### Task 8: Skill recommender + +**Files:** + +- Create: `packages/control-plane/src/skills/recommender.ts` +- Create: `packages/control-plane/src/skills/recommender.test.ts` + +- [ ] **Step 1: Write failing tests — recommendation matrix** + +Test each row from the spec's recommendation table. Example test cases: + +```typescript +it('recommends oat-project-implement when plan is complete', () => { + const state = makeProjectState({ phase: 'plan', phaseStatus: 'complete' }); + const skills = makeSkillRegistry(); + const recs = recommend(state, skills); + expect(recs[0]?.skill.name).toBe('oat-project-implement'); + expect(recs[0]?.priority).toBe('primary'); +}); + +it('recommends review-provide with artifact design args when design complete', () => { + const state = makeProjectState({ phase: 'design', phaseStatus: 'complete' }); + const skills = makeSkillRegistry(); + const recs = recommend(state, skills); + expect(recs[0]?.skill.name).toBe('oat-project-review-provide'); + expect(recs[0]?.suggestedArgs).toBe('artifact design'); +}); + +it('recommends spec after discovery for spec-driven workflow', () => { + const state = makeProjectState({ + phase: 'discovery', + phaseStatus: 'complete', + workflowMode: 'spec-driven', + }); + const skills = makeSkillRegistry(); + const recs = recommend(state, skills); + expect(recs[0]?.skill.name).toBe('oat-project-spec'); +}); + +it('recommends design after discovery for quick workflow', () => { + const state = makeProjectState({ + phase: 'discovery', + phaseStatus: 'complete', + workflowMode: 'quick', + }); + const skills = makeSkillRegistry(); + const recs = recommend(state, skills); + expect(recs[0]?.skill.name).toBe('oat-project-design'); +}); + +it('returns no primary recommendation when phase is in_progress', () => { + const state = makeProjectState({ + phase: 'design', + phaseStatus: 'in_progress', + }); + const skills = makeSkillRegistry(); + const recs = recommend(state, skills); + const primary = recs.filter((r) => r.priority === 'primary'); + expect(primary).toHaveLength(0); +}); + +it('appends review-receive as secondary when review artifact pending', () => { + const state = makeProjectState({ + phase: 'design', + phaseStatus: 'complete', + pendingActions: [{ type: 'review-pending', description: 'design review' }], + }); + const skills = makeSkillRegistry(); + const recs = recommend(state, skills); + const secondary = recs.filter((r) => r.priority === 'secondary'); + expect( + secondary.some((r) => r.skill.name === 'oat-project-review-receive'), + ).toBe(true); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +- [ ] **Step 3: Implement `recommend`** + +Implement the recommendation matrix from the spec table. The function takes `ProjectState` and `SkillInfo[]`, finds matching skills by name, and returns `SkillRecommendation[]` sorted by priority. + +- [ ] **Step 4: Run tests, verify pass** + +- [ ] **Step 5: Commit** + +```bash +git add packages/control-plane/src/skills/ +git commit -m "feat(control-plane): skill recommender with full recommendation matrix" +``` + +--- + +### Task 9: Session detector + +**Files:** + +- Create: `packages/control-plane/src/sessions/detector.ts` +- Create: `packages/control-plane/src/sessions/detector.test.ts` + +- [ ] **Step 1: Write failing test — `detectSessions`** + +Test that `detectSessions()` returns `AgentSession[]` by: + +- Parsing `ps` output to find `claude` and `codex` processes +- Extracting PIDs and matching to repo paths via cwd detection + +Mock `child_process.execFile` for both `ps` and `lsof` (macOS cwd detection). + +- [ ] **Step 2: Implement `detectSessions`** + +```typescript +export async function detectSessions(): Promise; +``` + +Run `ps -eo pid,comm` to find processes matching `claude` or `codex`. For each PID, run `lsof -p PID -Fn` and look for `cwd` entries (type `n` with `cwd` file descriptor) to determine working directory. Match cwd to known repo paths. Return sessions with `status: 'unknown'` (process detection alone can't determine working/idle). + +- [ ] **Step 3: Run test, verify pass** + +- [ ] **Step 4: Commit** + +```bash +git add packages/control-plane/src/sessions/ +git commit -m "feat(control-plane): agent session detection via process scanning" +``` + +--- + +### Task 10: Zellij integration + +**Files:** + +- Create: `packages/control-plane/src/sessions/zellij.ts` +- Create: `packages/control-plane/src/sessions/zellij.test.ts` + +- [ ] **Step 1: Write failing test — `isZellijAvailable`** + +Test that `isZellijAvailable()` returns `true` when `ZELLIJ` env var is set or `zellij` is in PATH. + +- [ ] **Step 2: Write failing test — `listZellijPanes`** + +Test that `listZellijPanes()` parses `zellij action dump-layout` output and returns pane info. + +- [ ] **Step 3: Write failing test — `writeToPane`** + +Test that `writeToPane(paneId, text)` calls `zellij action write-chars --pane-id ""`. + +- [ ] **Step 4: Write failing test — `runInNewPane`** + +Test that `runInNewPane(command)` calls `zellij run -- `. + +- [ ] **Step 5: Implement all Zellij functions** + +All functions shell out to `zellij` CLI via `child_process.execFile`. The functions are thin wrappers — no complex logic, just correct command construction and output parsing. + +- [ ] **Step 6: Run tests, verify all pass** + +- [ ] **Step 7: Commit** + +```bash +git add packages/control-plane/src/sessions/ +git commit -m "feat(control-plane): zellij CLI integration for pane management" +``` + +--- + +### Task 11: Session launcher (dispatch) + +**Files:** + +- Create: `packages/control-plane/src/sessions/launcher.ts` +- Create: `packages/control-plane/src/sessions/launcher.test.ts` + +- [ ] **Step 1: Write failing test — `buildLaunchCommand`** + +Test that `buildLaunchCommand(options: DispatchOptions)` constructs the correct `&&`-chained shell command. + +```typescript +it('builds command for new worktree with setup and skill', () => { + const cmd = buildLaunchCommand({ + runtime: 'claude', + repo: '/Users/me/Code/repo', + isolation: 'new-worktree', + worktreeOptions: { baseBranch: 'main', branchName: 'oat/feature' }, + setupScript: 'pnpm run worktree:init', + skill: { name: 'oat-project-implement', args: undefined }, + }); + expect(cmd).toBe( + 'git worktree add /Users/me/Code/repo/.worktrees/oat/feature -b oat/feature main' + + ' && cd /Users/me/Code/repo/.worktrees/oat/feature' + + ' && pnpm run worktree:init' + + ' && claude -p "/oat-project-implement"', + ); +}); + +it('builds command for current branch with no skill', () => { + const cmd = buildLaunchCommand({ + runtime: 'codex', + repo: '/Users/me/Code/repo', + isolation: 'current-branch', + }); + expect(cmd).toBe('cd /Users/me/Code/repo && codex'); +}); +``` + +- [ ] **Step 2: Implement `buildLaunchCommand`** + +- [ ] **Step 3: Write failing test — `dispatchToExisting`** + +Test that dispatching to an existing session calls `writeToPane` with the formatted skill command. + +- [ ] **Step 4: Implement `dispatchToExisting`** + +- [ ] **Step 5: Write failing test — `launchNewSession`** + +Test that launching a new session calls `runInNewPane` with the built command. + +- [ ] **Step 6: Implement `launchNewSession`** + +- [ ] **Step 7: Run tests, verify all pass** + +- [ ] **Step 8: Commit** + +```bash +git add packages/control-plane/src/sessions/ +git commit -m "feat(control-plane): session launcher with dispatch pipeline" +``` + +--- + +### Task 12: Worktree manager + +**Files:** + +- Create: `packages/control-plane/src/sessions/worktree.ts` +- Create: `packages/control-plane/src/sessions/worktree.test.ts` + +- [ ] **Step 1: Write failing test — `createWorktree`** + +Test that `createWorktree(repo, baseBranch, branchName)` runs the correct git command and returns the worktree path. The path convention is `/.worktrees/`. + +- [ ] **Step 2: Implement `createWorktree`** + +- [ ] **Step 3: Write failing test — `removeWorktree`** + +- [ ] **Step 4: Implement `removeWorktree`** + +- [ ] **Step 5: Run tests, verify pass** + +- [ ] **Step 6: Commit** + +```bash +git add packages/control-plane/src/sessions/ +git commit -m "feat(control-plane): worktree manager for create/remove operations" +``` + +--- + +### Task 13: Final control plane exports and integration test + +**Files:** + +- Modify: `packages/control-plane/src/index.ts` +- Create: `packages/control-plane/src/integration.test.ts` + +- [ ] **Step 1: Update index.ts with all exports** + +Add exports for skills and sessions modules. + +- [ ] **Step 2: Write integration test** + +Test the full flow: given a mock repo directory with `.oat/projects/shared/test-project/` containing `state.md`, `plan.md`, and `implementation.md`: + +- `discoverAllProjects` finds it +- `recommend` returns appropriate skills +- `detectPendingActions` returns correct pending items +- `computeDisplayState` returns correct state + +Use a temp directory with fixture files. + +- [ ] **Step 3: Run integration test, verify pass** + +- [ ] **Step 4: Run full test suite and build** + +Run: `pnpm --filter @oat/control-plane build && pnpm --filter @oat/control-plane test` +Expected: All pass, clean build. + +- [ ] **Step 5: Commit** + +```bash +git add packages/control-plane/ +git commit -m "feat(control-plane): complete package with integration tests" +``` + +--- + +## Chunk 3: Dashboard Server + +### Task 14: Package scaffold for `@oat/dashboard-server` + +**Files:** + +- Create: `packages/dashboard-server/package.json` +- Create: `packages/dashboard-server/tsconfig.json` +- Create: `packages/dashboard-server/src/index.ts` + +- [ ] **Step 1: Create `packages/dashboard-server/package.json`** + +```json +{ + "name": "@oat/dashboard-server", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": ">=22.17.0" + }, + "scripts": { + "build": "tsc", + "dev": "tsx watch src/index.ts", + "test": "vitest run", + "lint": "oxlint .", + "format": "oxfmt --check .", + "format:fix": "oxfmt --write .", + "type-check": "tsc --noEmit", + "clean": "rm -rf dist" + }, + "dependencies": { + "@oat/control-plane": "workspace:*", + "hono": "^4.0.0", + "@hono/node-server": "^1.0.0", + "ws": "^8.0.0" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "@types/ws": "^8.0.0", + "typescript": "^5.8.3", + "vitest": "^4.0.18", + "tsx": "^4.21.0" + } +} +``` + +Note: Uses `ws` directly for WebSocket instead of `@hono/node-ws` — simpler to wire into a Node HTTP server alongside Hono. The `ws` library attaches to the same HTTP server that Hono uses via `@hono/node-server`. + +- [ ] **Step 2: Create tsconfig.json** + +Same pattern as control-plane — extends root, outputs to `dist/`. + +- [ ] **Step 3: Create minimal `src/index.ts`** + +```typescript +import { serve } from '@hono/node-server'; +import { Hono } from 'hono'; + +const app = new Hono(); + +app.get('/api/health', (c) => c.json({ status: 'ok' })); + +serve({ fetch: app.fetch, port: 7400 }, (info) => { + console.log(`OAT Dashboard running at http://localhost:${info.port}`); +}); +``` + +- [ ] **Step 4: Install deps and verify** + +Run: `pnpm install && pnpm --filter @oat/dashboard-server build` + +- [ ] **Step 5: Commit** + +```bash +git add packages/dashboard-server/ +git commit -m "feat(dashboard-server): scaffold package with Hono server" +``` + +--- + +### Task 15a: Read-only API endpoints (projects, skills, sessions, worktrees) + +**Files:** + +- Create: `packages/dashboard-server/src/control-plane-instance.ts` +- Create: `packages/dashboard-server/src/api.ts` +- Create: `packages/dashboard-server/src/api.test.ts` +- Modify: `packages/dashboard-server/src/index.ts` + +- [ ] **Step 1: Define `ControlPlaneInstance` interface** + +```typescript +// packages/dashboard-server/src/control-plane-instance.ts +// Aggregates all control plane functions into a single object for DI +export interface ControlPlaneInstance { + discoverProjects(): Promise; + getProject(id: string): Promise; + getArtifacts(id: string): Promise>; + loadSkills(): Promise; + recommend(project: ProjectState, skills: SkillInfo[]): SkillRecommendation[]; + detectSessions(): Promise; + listWorktrees(repo: string): Promise; +} +``` + +- [ ] **Step 2: Write failing tests for GET endpoints** + +Use Hono's test client (`app.request()`) with a mock `ControlPlaneInstance`. Test that: + +- `GET /api/projects` returns the array from `discoverProjects()` +- `GET /api/projects/:id` returns a single project or 404 +- `GET /api/skills` returns skills array +- `GET /api/skills/recommendations/:project` calls recommend with correct project +- `GET /api/sessions` returns sessions array +- `GET /api/worktrees/:repo` returns worktrees + +- [ ] **Step 3: Implement read-only routes** + +```typescript +export function createApiRoutes(cp: ControlPlaneInstance): Hono { + const api = new Hono(); + api.get('/projects', async (c) => c.json(await cp.discoverProjects())); + api.get('/projects/:id', async (c) => { ... }); + // ... etc + return api; +} +``` + +- [ ] **Step 4: Run tests, verify pass** + +- [ ] **Step 5: Commit** + +```bash +git add packages/dashboard-server/src/ +git commit -m "feat(dashboard-server): read-only API endpoints for projects, skills, sessions" +``` + +--- + +### Task 15b: Dispatch and action API endpoints + +**Files:** + +- Modify: `packages/dashboard-server/src/api.ts` +- Modify: `packages/dashboard-server/src/api.test.ts` + +- [ ] **Step 1: Write failing test — POST /api/sessions/dispatch** + +Test two cases: dispatch to existing session (calls `dispatchToExisting`) and launch new session (calls `launchNewSession`). Test error cases: missing fields returns 400, Zellij not available returns 503. + +- [ ] **Step 2: Implement dispatch endpoint** + +Parse request body as `DispatchOptions`. If `targetSession` is provided, call `dispatchToExisting`. Otherwise call `launchNewSession`. Return `{ ok: true }` on success, `{ ok: false, error: "..." }` on failure. + +- [ ] **Step 3: Write failing test — POST /api/open-file** + +Test that it validates the file path is within a known repo/project directory before executing. Test that it rejects paths outside known repos. + +- [ ] **Step 4: Implement open-file endpoint** + +Validate that the requested path starts with one of the configured repo roots. If valid, shell out to `open` (macOS, detected via `process.platform === 'darwin'`) or `xdg-open` (Linux). Return `{ ok: true }`. + +- [ ] **Step 5: Write failing test — GET/PUT /api/config** + +- [ ] **Step 6: Implement config endpoints** + +GET reads `~/.oat/dashboard.json`, PUT writes it. + +- [ ] **Step 7: Run all API tests, verify pass** + +- [ ] **Step 8: Commit** + +```bash +git add packages/dashboard-server/src/ +git commit -m "feat(dashboard-server): dispatch, open-file, and config API endpoints" +``` + +--- + +### Task 16: WebSocket for live updates + +**Files:** + +- Create: `packages/dashboard-server/src/ws.ts` +- Create: `packages/dashboard-server/src/ws.test.ts` +- Modify: `packages/dashboard-server/src/index.ts` + +- [ ] **Step 1: Write failing test — connection sends full state** + +Test that on WebSocket connect, the server sends a `connection-established` event with `FullState`. + +- [ ] **Step 2: Implement WebSocket handler** + +Use the `ws` library attached to the same Node HTTP server. On connection, send `connection-established` with current `FullState`. Subscribe to `ProjectWatcher` events and forward as `project-updated` events. + +For session polling: maintain a `Map` keyed by `pid`. Every 5 seconds, call `detectSessions()` and diff against previous state: + +- New PIDs → emit `session-started` +- Missing PIDs → emit `session-ended` +- Same PID but different `status` → emit `session-activity` + +```typescript +// Wiring pattern: +import { WebSocketServer } from 'ws'; +import { serve } from '@hono/node-server'; + +const server = serve({ fetch: app.fetch, port }); +const wss = new WebSocketServer({ server }); +wss.on('connection', (ws) => { ... }); +``` + +- [ ] **Step 3: Run test, verify pass** + +- [ ] **Step 4: Wire everything together in `src/index.ts`** + +Initialize control plane, create API routes, set up WebSocket, start watcher, start server. + +- [ ] **Step 5: Integration test — start server, connect WebSocket, verify events** + +- [ ] **Step 6: Commit** + +```bash +git add packages/dashboard-server/ +git commit -m "feat(dashboard-server): WebSocket with live project and session updates" +``` + +--- + +### Task 17: Dashboard config loading + +**Files:** + +- Create: `packages/dashboard-server/src/config.ts` +- Create: `packages/dashboard-server/src/config.test.ts` + +- [ ] **Step 1: Write failing test — `loadDashboardConfig`** + +Test that it reads `~/.oat/dashboard.json`, expands `~` in repo paths, and returns typed config. Test fallback when file doesn't exist (returns default config with CWD as sole repo). + +- [ ] **Step 2: Implement `loadDashboardConfig`** + +- [ ] **Step 3: Run test, verify pass** + +- [ ] **Step 4: Write failing test — `loadRepoConfig`** + +Test that `loadRepoConfig(repoPath)` reads `.oat/config.json` and extracts the `dashboard` section (`setupScript`, `branchPrefix`). Test fallback when the key doesn't exist (returns `undefined` for both fields). + +- [ ] **Step 5: Implement `loadRepoConfig`** + +Read `.oat/config.json`, parse JSON, return `dashboard` section if present. This data is used by the dispatch flow to pre-fill setup script and branch name prefix. + +- [ ] **Step 6: Run tests, verify pass** + +- [ ] **Step 7: Commit** + +```bash +git add packages/dashboard-server/src/ +git commit -m "feat(dashboard-server): dashboard and per-repo config loading" +``` + +--- + +## Chunk 4: Web UI + +> **Testing approach for UI:** UI components in this chunk are verified visually during development rather than with automated component tests. The `api.ts` and hooks (`useWebSocket`, `useDashboard`) are tested in Task 19. Component-level tests (e.g. with React Testing Library) are deferred — the control plane and server layers carry the automated test coverage for v1. + +### Task 18: Scaffold `apps/dashboard-ui` + +**Files:** + +- Create: `apps/dashboard-ui/package.json` +- Create: `apps/dashboard-ui/tsconfig.json` +- Create: `apps/dashboard-ui/index.html` +- Create: `apps/dashboard-ui/src/main.tsx` +- Create: `apps/dashboard-ui/vite.config.ts` +- Create: `apps/dashboard-ui/tailwind.config.ts` (or CSS import for Tailwind 4) + +- [ ] **Step 1: Scaffold with Vite React template structure** + +```json +{ + "name": "dashboard-ui", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.0.0", + "vite": "^6.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.8.3" + } +} +``` + +- [ ] **Step 2: Create `vite.config.ts`** + +Configure build output to `dist/`, proxy `/api` and `/ws` to `localhost:7400` during dev. + +- [ ] **Step 3: Create `index.html` and `src/main.tsx`** + +Minimal React app that renders "OAT Dashboard" text. + +- [ ] **Step 4: Verify dev server works** + +Run: `pnpm --filter dashboard-ui dev` +Expected: Vite dev server starts, shows the placeholder page. + +- [ ] **Step 5: Commit** + +```bash +git add apps/dashboard-ui/ +git commit -m "feat(dashboard-ui): scaffold React + Vite + Tailwind app" +``` + +--- + +### Task 19: API client and WebSocket hook + +**Files:** + +- Create: `apps/dashboard-ui/src/api.ts` +- Create: `apps/dashboard-ui/src/hooks/useWebSocket.ts` +- Create: `apps/dashboard-ui/src/hooks/useDashboard.ts` + +- [ ] **Step 1: Create `api.ts`** + +Typed fetch wrappers for each REST endpoint. Returns typed responses matching the control plane interfaces. + +```typescript +export async function fetchProjects(): Promise { + const res = await fetch('/api/projects'); + return res.json(); +} + +export async function dispatchSession( + options: DispatchOptions, +): Promise<{ ok: boolean; error?: string }> { + const res = await fetch('/api/sessions/dispatch', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(options), + }); + return res.json(); +} +// ... etc for all endpoints +``` + +- [ ] **Step 2: Write test for `useWebSocket` hook** + +Test with a mock WebSocket server: verify it reconnects on disconnect, replaces state on `connection-established`, and merges on update events. + +- [ ] **Step 3: Implement `useWebSocket` hook** + +Connects to `ws://localhost:7400/ws`. On `connection-established`, sets full state. On other events, merges into state. Reconnects with exponential backoff on disconnect (1s, 2s, 4s, max 30s). + +- [ ] **Step 4: Create `useDashboard` hook** + +Combines the WebSocket state with API calls. Provides `{ projects, sessions, selectedProject, dispatch, openFile }` to components. + +- [ ] **Step 5: Run hook tests, verify pass** + +- [ ] **Step 6: Commit** + +```bash +git add apps/dashboard-ui/src/ +git commit -m "feat(dashboard-ui): API client and WebSocket state hooks" +``` + +--- + +### Task 20: Project List sidebar + +**Files:** + +- Create: `apps/dashboard-ui/src/views/ProjectList.tsx` +- Modify: `apps/dashboard-ui/src/main.tsx` + +- [ ] **Step 1: Implement `ProjectList` component** + +Renders list of projects from `useDashboard().projects`, grouped by repo. Each item shows: + +- Project name (bold) +- Phase + progress (e.g. "implement · 3/7") +- Pending action badge (amber dot with count) +- Selected state (highlighted background) + +Click sets `selectedProject` in the dashboard context. + +- [ ] **Step 2: Wire into main layout** + +Three-column CSS grid layout in `main.tsx`. Left column renders `ProjectList`. + +- [ ] **Step 3: Verify visually** + +Run dev server, check that the sidebar renders with mock data or data from a running dashboard server. + +- [ ] **Step 4: Commit** + +```bash +git add apps/dashboard-ui/src/ +git commit -m "feat(dashboard-ui): project list sidebar component" +``` + +--- + +### Task 21: Project Detail center panel + +**Files:** + +- Create: `apps/dashboard-ui/src/views/ProjectDetail.tsx` + +- [ ] **Step 1: Implement `ProjectDetail` component** + +Renders for the selected project: + +- Header: name, repo, path, phase badge +- Progress bar: filled proportional to `progress.completed / progress.total` +- Pending Actions: amber cards with description and "Open" button (calls `POST /api/open-file`) +- Recommended Next: skill cards from `/api/skills/recommendations/:project`, each with name, reason, editable suggested args. Click opens dispatch modal. +- Artifacts grid: cards for each artifact type showing existence, last modified. Click calls open-file. + +- [ ] **Step 2: Wire into main layout center column** + +- [ ] **Step 3: Verify visually** + +- [ ] **Step 4: Commit** + +```bash +git add apps/dashboard-ui/src/ +git commit -m "feat(dashboard-ui): project detail center panel" +``` + +--- + +### Task 22: Session Panel right sidebar + +**Files:** + +- Create: `apps/dashboard-ui/src/views/SessionPanel.tsx` + +- [ ] **Step 1: Implement `SessionPanel` component** + +Renders active sessions from `useDashboard().sessions`: + +- Each session card: runtime icon, repo/worktree label, status dot (green/gray), duration, Zellij pane ref +- "New Session" button at bottom + +- [ ] **Step 2: Wire into main layout right column** + +- [ ] **Step 3: Commit** + +```bash +git add apps/dashboard-ui/src/ +git commit -m "feat(dashboard-ui): session panel sidebar" +``` + +--- + +### Task 23: Dispatch Modal + +**Files:** + +- Create: `apps/dashboard-ui/src/views/DispatchModal.tsx` + +This is the most complex UI component. Two modes: send to existing, launch new. + +- [ ] **Step 1: Implement "Send to existing" mode** + +- Session selector dropdown (populated from `useDashboard().sessions`) +- Skill quick-pick chips (from recommendations) +- Free-form prompt textarea fallback +- "Send to Session" button calls `dispatchSession` API + +- [ ] **Step 2: Implement "Launch new session" mode** + +- Skill/Prompt tabs +- Skill picker dropdown with editable argument field below +- Agent selector (Claude Code / Codex CLI) +- Isolation mode selector (Current branch / New worktree) +- Collapsible Advanced options: base branch input, branch name input (auto-generated default), setup script toggle +- Pipeline preview at bottom (text showing the `&&`-chained steps) +- "Create Session" button calls `dispatchSession` API + +- [ ] **Step 3: Wire modal open/close to session panel and skill cards** + +- [ ] **Step 4: Verify full dispatch flow visually** + +- [ ] **Step 5: Commit** + +```bash +git add apps/dashboard-ui/src/ +git commit -m "feat(dashboard-ui): dispatch modal with send-to-existing and launch-new modes" +``` + +--- + +## Chunk 5: CLI Integration and Final Assembly + +### Task 24: `oat dashboard` CLI command + +**Files:** + +- Create: `packages/cli/src/commands/dashboard/index.ts` +- Modify: `packages/cli/src/commands/index.ts` +- Modify: `packages/cli/package.json` (add `@oat/dashboard-server` dependency) + +- [ ] **Step 1: Create `createDashboardCommand`** + +Uses the simpler command pattern (no DI) since this command orchestrates server startup rather than testable business logic: + +```typescript +export function createDashboardCommand(): Command { + const cmd = new Command('dashboard') + .description('Start the OAT dashboard server and web UI') + .option('-p, --port ', 'Port to listen on', '7400') + .option('--no-open', 'Do not open browser automatically'); + + cmd + .command('init') + .description('Create ~/.oat/dashboard.json interactively') + .action(async () => { + // Scan common paths for .oat/ directories + // Prompt user to select repos + // Write ~/.oat/dashboard.json + }); + + cmd.action(async (options) => { + // Load config from ~/.oat/dashboard.json (fallback to CWD) + // Initialize control plane with repo paths + // Start dashboard server + // Open browser if --no-open not set (use `open` on macOS) + }); + + return cmd; +} +``` + +- [ ] **Step 2: Register in `commands/index.ts`** + +Add `program.addCommand(createDashboardCommand())`. + +- [ ] **Step 3: Test manually** + +Run: `pnpm run cli -- dashboard` +Expected: Server starts, browser opens to the dashboard. + +Run: `pnpm run cli -- dashboard init` +Expected: Interactive prompts for repo selection, writes config file. + +- [ ] **Step 4: Commit** + +```bash +git add packages/cli/ +git commit -m "feat(cli): add 'oat dashboard' and 'oat dashboard init' commands" +``` + +--- + +### Task 25: Static file serving for built UI + +**Files:** + +- Modify: `packages/dashboard-server/src/index.ts` + +- [ ] **Step 1: Add static file serving** + +Serve `apps/dashboard-ui/dist/` as static files at `/`. For any non-API, non-WS request that doesn't match a static file, serve `index.html` (SPA fallback). + +Use Hono's `serveStatic` middleware. The path to the dist directory is passed as config when the server is initialized. + +- [ ] **Step 2: Update Turborepo pipeline** + +Add a custom pipeline entry in `turbo.json` so the server build waits for the UI build (they aren't package.json dependencies, so `^build` won't catch it): + +```json +{ + "@oat/dashboard-server#build": { + "dependsOn": ["dashboard-ui#build", "^build"], + "outputs": ["dist/**"] + } +} +``` + +- [ ] **Step 3: Test full flow** + +Run: `pnpm build && pnpm run cli -- dashboard` +Expected: Dashboard serves at localhost, shows project state from the current repo. + +- [ ] **Step 4: Commit** + +```bash +git add packages/dashboard-server/ turbo.json +git commit -m "feat(dashboard-server): serve built dashboard UI as static files" +``` + +--- + +### Task 26: End-to-end smoke test + +**Files:** + +- Create: `packages/dashboard-server/src/e2e.test.ts` + +- [ ] **Step 1: Write E2E test** + +Start the server programmatically pointing at a temp repo directory with a fixture OAT project. Verify: + +- `GET /api/projects` returns the fixture project +- `GET /api/skills` returns skills +- `GET /api/skills/recommendations/:project` returns recommendations +- WebSocket connects and receives `connection-established` + +- [ ] **Step 2: Run test, verify pass** + +- [ ] **Step 3: Run full monorepo build and test** + +Run: `pnpm build && pnpm test && pnpm lint && pnpm type-check` +Expected: All pass. + +- [ ] **Step 4: Commit** + +```bash +git add packages/dashboard-server/ +git commit -m "test(dashboard-server): end-to-end smoke test" +``` diff --git a/.oat/repo/research/2026-04-03-control-plane-current-state.md b/.oat/repo/research/2026-04-03-control-plane-current-state.md new file mode 100644 index 000000000..191e8afd5 --- /dev/null +++ b/.oat/repo/research/2026-04-03-control-plane-current-state.md @@ -0,0 +1,597 @@ +--- +skill: deep-research +schema: architectural +topic: 'OAT Control Plane & Dashboard — Current State and Options (April 3, 2026)' +model: opus-4-6 +generated_at: 2026-04-03 +--- + +# OAT Control Plane & Dashboard — Current State + +This document consolidates everything from the April 3 brainstorming session and the March 15 prior art into a single picture of where we are and what the options are. It's intended to be the authoritative reference for future sessions. + +## What We're Building + +A **control plane** (pure TypeScript library) that understands OAT project state and can power any UI surface — terminal TUI, web dashboard, Zellij plugin, VS Code extension, or a Tauri/Electron desktop app. The control plane is the foundation; UI choices are layered on top. + +The product vision that emerged from brainstorming is: **terminal-native superset.sh with OAT workflow intelligence** — a workspace manager that shows repos, worktrees, and agent sessions, with OAT project state as an additive layer when `.oat/` exists. But the control plane itself is UI-agnostic. + +## Prior Art + +### March 15 Design (internal) + +Two artifacts exist from a previous session: + +- **`2026-03-15-oat-dashboard-design.md`** — Full design spec: three-layer architecture (`@oat/control-plane` → `@oat/dashboard-server` → `apps/dashboard-ui`), typed interfaces, skill recommender logic, session detection, dispatch, config schema +- **`2026-03-15-oat-dashboard.md`** — TDD implementation plan: 26 tasks across 5 chunks, covering control plane, server, web UI, and CLI integration + +The design is **structurally sound but needs updates** for 39 commits of OAT evolution since March 15. See `2026-04-03-march15-design-audit.md` for the full delta analysis. Key changes: new `pr_open` phase status, PR tracking fields, 6-step post-implementation router, 9 new lifecycle skills, boundary tier detection, and config schema additions. + +### External Landscape (9 tools surveyed) + +Full analysis in `oat-workflow-panel-opus-4-6.md`. Key references: + +| Tool | Type | Key Lesson | +| ------------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [superset.sh](https://superset.sh) | Electron desktop app | UX target — workspace sidebar, diff viewer, agent monitoring. But vendor-locked, Electron overhead, no OAT awareness. | +| [agentastic.dev](https://agentastic.dev) | Native macOS (Swift + libghostty) | Embeds Ghostty as rendering engine. Kanban for worktrees. Closed source, macOS-only. | +| [agtx](https://github.com/fynnfluegge/agtx) | Rust TUI | Closest architectural prior art — kanban board, TOML plugin system, MCP server, per-phase agent assignment. Could work with OAT via a plugin but lacks native state awareness. | +| [claude-squad](https://github.com/smtg-ai/claude-squad) | Go TUI | Multi-agent dashboard with preview/diff tabs. tmux-based, daemon mode. | +| [ntm](https://github.com/Dicklesworthstone/ntm) | Go control plane | Pipelines with dependencies, safety policies, REST/WebSocket API. Most "control plane"-like of the external tools. | +| [tmux-agent-status](https://github.com/samleeney/tmux-agent-status) | Bash tmux plugin | Validates filesystem-as-message-bus pattern. Hook-driven agent status. | +| [opensessions](https://github.com/ataraxy-labs/opensessions) | TypeScript sidebar | HTTP-driven metadata push. Lightest tool — just a monitoring sidebar. | +| [dmux](https://github.com/standardagents/dmux) | TypeScript | Worktree isolation, 11+ agent support, built-in file browser. | +| [cmux](https://github.com/craigsc/cmux) | Bash | Simplest — just worktree + branch + Claude launch. | + +**Common patterns:** Git worktrees for isolation, filesystem-based state, tmux as universal multiplexer, hook-driven notifications, TUI dashboards. + +**Why build our own:** None of these tools understand OAT's state machine — phases, HiLL checkpoints, skill routing, boundary tier detection, spec-driven vs quick modes. They can orchestrate agents but can't tell you _what to do next_ based on where your project is. + +--- + +## Architecture: Three Layers + +This is unchanged from March 15 and confirmed by the April 3 brainstorming: + +``` +┌────────────────────────────────────────────┐ +│ UI Layer (one or more of these) │ +│ ├── Ink TUI (sidebar in Zellij/tmux) │ +│ ├── Web Dashboard (React + Tailwind) │ +│ ├── Zellij WASM Plugin (future) │ +│ └── Tauri/Electron desktop app (future) │ +└──────────────┬─────────────────────────────┘ + │ +┌──────────────▼─────────────────────────────┐ +│ Dashboard Server (optional, for web UI) │ +│ (Node.js, HTTP + WebSocket via Hono) │ +└──────────────┬─────────────────────────────┘ + │ +┌──────────────▼─────────────────────────────┐ +│ Control Plane (@open-agent-toolkit/ │ +│ control-plane) │ +│ Pure TypeScript library │ +│ ├── Project discovery + state parsing │ +│ ├── Skill registry + recommender │ +│ ├── Session detection + dispatch │ +│ ├── Worktree management │ +│ └── File watching + event emission │ +└────────────────────────────────────────────┘ +``` + +The control plane has **no UI or server dependencies**. Any consumer (CLI, TUI, web app, extension) imports it and calls typed functions. The dashboard server is only needed if serving a web UI. + +--- + +## Control Plane: What It Does + +### Project Discovery + +- Takes a list of repo root paths (from config file) +- For each repo, runs `git worktree list` to find all worktrees +- Scans each checkout/worktree for `.oat/projects/` directories +- Returns unified list of all discovered projects with repo/worktree context +- Deduplicates: same project across worktrees appears as one entry with multiple locations + +### State Parsing + +Reads OAT markdown artifacts into structured data: + +```typescript +interface ProjectState { + id: string; // "repo:scope:name" + name: string; + scope: string; + repo: string; + locations: ProjectLocation[]; + phase: Phase; + phaseStatus: PhaseStatus; // NOW includes 'pr_open' + workflowMode: WorkflowMode; + executionMode: ExecutionMode; + progress: { completed: number; total: number; currentTask?: string }; + pendingActions: PendingAction[]; + artifacts: Map; // NOW includes 'summary' + blockers: string[]; + prStatus: string | null; // NEW: null | open | merged | closed + prUrl: string | null; // NEW: PR URL + hillCheckpoints: string[]; + hillCompleted: string[]; + timestamps: { + created: string; + completed?: string; + stateUpdated: string; + }; +} + +type Phase = 'discovery' | 'spec' | 'design' | 'plan' | 'implement'; +type PhaseStatus = 'in_progress' | 'complete' | 'pr_open'; +type WorkflowMode = 'spec-driven' | 'quick' | 'import'; +type ExecutionMode = 'single-thread' | 'subagent-driven'; +type ArtifactType = + | 'discovery' + | 'spec' + | 'design' + | 'plan' + | 'implementation' + | 'reviews' + | 'summary'; +``` + +### Skill Recommender + +Given a project state, returns ordered skill suggestions with pre-filled arguments. This is the core "what should I do next?" logic. It implements: + +1. **Early phase routing** (discovery → plan): well-defined table based on phase + status + workflow mode +2. **Post-implementation router**: 6-step state machine checking revision tasks, unprocessed reviews, code review status, summary generation, PR creation, and PR completion +3. **Boundary tier detection**: reads artifact frontmatter to classify readiness (Tier 1/1b/2/3) +4. **HiLL gate checking**: flags phases requiring human approval before advancing + +### Session Detection + +- Uses Zellij CLI API to list panes and their processes +- Fallback: scans running processes for `claude`, `codex` binaries +- Matches to repo/worktree by inspecting process cwd + +### Dispatch + +Two modes: + +- **Send to existing session**: `zellij action write-chars` to type `/` into a pane +- **Launch new session**: `zellij run -- claude --prompt "..."` with worktree setup pipeline + +### File Watching + +- chokidar on `.oat/projects/` directories +- Emits typed events: `project-updated`, `project-created`, `project-removed` +- Consumers subscribe and react (re-render UI, push WebSocket events) + +--- + +## UI Options + +### Option A: Web Dashboard (March 15 design) + +**What:** React + Tailwind web app served by a Hono HTTP + WebSocket server. + +**Layout:** Three-panel — project list (left), project detail with skill recommender (center), active sessions (right). Dispatch modal for sending skills to new or existing sessions. + +**Pros:** + +- Richer UI (dispatch modal with editable arguments, artifact viewer, drag-and-drop) +- Works over SSH (browser connects to `localhost:7400` — key differentiator vs all surveyed alternatives) +- Familiar React/Tailwind DX +- Full design spec and TDD implementation plan already exist + +**Cons:** + +- Requires a running server process +- More dev effort (server + frontend packages) +- Not embedded in terminal workflow — requires switching to a browser + +**Status:** Fully designed and planned (March 15). Needs type updates per audit. + +### Option B: Ink TUI (April 3 brainstorming) + +**What:** React-for-CLI app using [Ink](https://github.com/vadimdemedes/ink), running as a standalone process in a Zellij/tmux pane. + +**Layout:** Three modes responsive to available width: + +1. **Compact sidebar** (~20-30%) — workspace list with one-line status per project, action keybind hints, agent activity indicators +2. **Expanded panel** — project detail with phase progress, action menu, tool launchers +3. **Full-screen kanban** — multi-project board view (toggle into it, not the primary interface) + +**Pros:** + +- Stays in terminal flow — no browser context switch +- Faster to build for MVP (no server, no separate frontend build) +- Same React mental model as web dashboard +- Production-proven (Claude Code, Gemini CLI, Wrangler all use Ink) +- Drops into monorepo with zero friction (TypeScript, same build system) + +**Cons:** + +- No mouse support — keyboard-only navigation +- Simpler widget primitives than a web UI +- Can't do rich dispatch modal with editable fields as easily +- Single-pane rendering (splits handled by Zellij/tmux, not Ink) + +**Status:** Brainstormed and shaped through 5 rounds of discovery. No implementation plan yet. + +### Option C: Both (long-term play) + +Control plane serves both. TUI for day-to-day sidebar, web dashboard for full orchestration view. This is the natural end state but doubles frontend work initially. + +### Option D: Tauri / Rust + React Desktop App (future) + +Full desktop app with native performance. Could embed terminal (like agentastic embeds libghostty). Maximum capability but maximum effort. Worth considering after validating the concept with simpler options. + +**Current recommendation:** Start with the control plane (Phase 1), then build Option B (Ink TUI) as the fastest path to a working MVP. This validates whether the control plane abstraction is useful and whether the workflow panel concept saves time. If it proves valuable, build Option A (web dashboard) or Option D (desktop app) for richer experiences. + +--- + +## Discovery Decisions (April 3 Brainstorming) + +Five rounds of Q&A shaped the product vision. All answers are captured here for future sessions. + +### Product Model + +- **Workspace manager first, OAT tracker second** — the primary interface is a workspace/worktree navigator (like superset.sh), not a project phase viewer. OAT state is an additive layer when `.oat/` exists. +- **Kanban is NOT the primary view** — it's a full-screen mode you toggle into for the big picture. The day-to-day interface is the sidebar. +- **Both sidebar and full-screen** — sidebar (20-30% width) as default for ambient awareness, expand to full-screen with a keybinding. + +### Workspace Model + +``` +┌─ Workspace Navigator (always visible) ──┬─ Main Content Area ──────────────┐ +│ │ │ +│ ▼ repo-1 (main) │ [Agent session / editor / etc] │ +│ ├─ worktree: feat-auth ▸ working │ │ +│ ├─ worktree: fix-api ● done │ │ +│ └─ worktree: docs ○ idle │ │ +│ │ │ +│ ▼ repo-2 (main) │ │ +│ └─ worktree: v2-migrate ▸ working │ │ +│ │ │ +│ ▶ repo-3 (collapsed) │ │ +│ │ │ +├─ OAT Layer (toggle) ────────────────────┤ │ +│ Project: auth-refactor │ │ +│ Phase: Plan → Implement │ │ +│ Tasks: ████████░░ 4/6 │ │ +│ [i] Implement [r] Review [l] Log │ │ +│ HiLL: ✓ discovery ✓ spec ✓ design │ │ +└──────────────────────────────────────────┴──────────────────────────────────┘ +``` + +### Scale & Concurrency + +- 2-5 repos at a time, 1-5 OAT projects per repo +- Sometimes focused on one repo with multiple projects, other times spread across repos +- Panel tracks **one project at a time**, switches as user cycles through workspaces + +### Repo Registration + +- **Explicit add, not auto-discover** — run `oat panel add /path/to/repo`, stored in config file +- No filesystem scanning magic — simple, predictable, user-controlled + +### Multiplexer Strategy + +- **Zellij-first**, tmux via adapter +- Auto-detect via `$ZELLIJ`/`$TMUX` env vars +- Ship a default `oat-panel.kdl` layout file for Zellij +- Adapter interface is ~50 lines per multiplexer — just pane spawning commands + +```typescript +interface MuxAdapter { + splitPane( + command: string, + opts?: { name?: string; cwd?: string; floating?: boolean }, + ): void; + listPanes(): PaneInfo[]; + detectMultiplexer(): 'zellij' | 'tmux' | 'none'; +} +``` + +### Action Triggers + +- **Contextual action menu, not single button** — show all valid transitions based on state +- After plan completes: `[i] Implement [r] Review [p] Pause` +- **Notifications always on** — badge/alert when next step is ready +- No separate orchestrator — OAT's execution continuation already handles autonomous implementation + +### Agent Tracking + +- **Aggregate progress only for subagent mode** — show "3/7 tasks done", don't expose individual subagent worktrees +- Agent status per worktree (working/done/idle) via Claude Code hooks + +### HiLL Checkpoint Flow + +- Ideal: **inline file viewer** — show the artifact (spec, design, plan) directly using yazi or delta diff view +- User wants to _read_ before approving, without leaving the panel context + +### Session Resume + +- **Context-aware** — panel knows where the agent stopped (e.g., task p2-t3), queues the next action with context pre-loaded + +### Review Flow + +- **Auto-trigger review-receive** — when review feedback lands in `reviews/`, panel automatically spawns `oat-project-review-receive` +- Should have a config toggle for manual control + +### Quick Actions (all wanted) + +1. **Tool launcher** — open lazygit/yazi/editor scoped to selected worktree +2. **New workspace wizard** — create worktree + branch + agent session in one flow +3. **Smart "Next Step"** — run `oat-project-next` to auto-detect and launch the right phase +4. **Workspace cleanup** — merge branch, clean up worktree, archive OAT project + +### Diff/Preview + +- Integrate with existing tools: **yazi** (file browser), **lazygit** (git TUI), **delta** (diff viewer) +- Panel should **launch** these tools in panes rather than reimplementing diff viewing + +### Project End-of-Lifecycle + +- **Manual archive** — completed projects persist showing state details, PR link, etc. +- User explicitly archives when ready. No auto-cleanup. + +### Sidebar Information Density + +Everything at a glance per project line: + +- Project name + current phase + phase status +- Next action keybind hints +- Agent activity (working/waiting/done) +- Blocker alerts (slightly lower priority but still shown) + +### Superset/Agentastic Competitive Analysis + +**Why NOT superset.sh or agentastic.dev:** + +1. Vendor lock-in — tied to their decisions on features and direction +2. Electron/native overhead — heavy runtime for a terminal-adjacent tool +3. No OAT awareness — no concept of project phases, HiLL checkpoints, skill routing +4. Closed ecosystem — can't fork and customize deeply + +**What to borrow from their designs:** + +- Workspace sidebar as always-on navigation (repos → worktrees → agent status) +- Toggle-able changes/diff panel +- Keyboard-driven workflow (numbered shortcuts for workspace switching) +- Workspace presets/config for setup/teardown scripts +- One-click tool launching (editor, terminal, etc.) + +**What we add that they can't:** + +- OAT workflow state awareness (phases, tasks, checkpoints, skill routing) +- Contextual action menus based on project state machine +- Auto-triggered review-receive flow +- Context-aware session resume +- Terminal-native (runs in Zellij/tmux, no Electron) +- User-owned and extensible + +--- + +## Phasing Plan + +### Phase 1: Control Plane (`@open-agent-toolkit/control-plane`) + +**Scope:** Pure TypeScript library, no UI. The March 15 plan's Chunks 1-2 (Tasks 1-13), updated per the audit. + +**Modules:** + +- Project discovery (scan repos + worktrees, find `.oat/projects/`) +- State parsing (`state.md` frontmatter → typed `ProjectState`, including new `pr_open`, `oat_pr_status`, `oat_pr_url`, `summary` artifact) +- Task progress (parse `plan.md`/`implementation.md` for completion counts) +- Skill recommender (early phase routing + 6-step post-implementation router + boundary tier detection) +- Session detection (find running claude/codex processes, match to worktrees) +- Dispatch (launch agent sessions in Zellij/tmux panes with skills pre-loaded) +- Worktree management (create/list/remove) +- File watching (chokidar on `.oat/projects/`, emit typed events) + +**Deliverable:** `packages/control-plane/` that the CLI can already consume (e.g., `oat status` using richer state). Ships as `@open-agent-toolkit/control-plane`. + +**What needs updating from March 15:** See `2026-04-03-march15-design-audit.md` for the full delta. Main items: add `pr_open` status, PR tracking fields, `summary` artifact type, rewrite post-implementation recommender, add boundary tier detection, update package namespace. + +### Phase 2: Ink TUI MVP (Option B) + +**Scope:** Thin vertical slice through all three product layers — basic workspace nav + basic OAT state + basic actions. + +**MVP feature set:** + +Layer 1 — Workspace Navigator (minimal): + +- List registered repos (from config) and their worktrees (`git worktree list`) +- Agent status indicator per worktree (working/done/idle via Claude Code hooks) +- Select workspace to focus → switch context + +Layer 2 — Agent Session Manager (minimal): + +- Spawn new agent session in a new pane for selected workspace +- Launch lazygit/yazi scoped to selected worktree (tool launcher) + +Layer 3 — OAT Workflow (minimal): + +- Show current phase + phase status for active workspace (reads `state.md`) +- Aggregate task progress for implement phase +- "Next Step" action that runs `oat-project-next` +- File watcher on `state.md` for live updates + +**Deliverable:** `packages/panel/` with `oat panel` CLI subcommand. New Ink/React package in the monorepo. + +**Tech:** Ink (React for CLI), chokidar for file watching, multiplexer adapter for pane spawning. + +**Purpose:** Validate the concept. Is this useful? Does it save time? Is the control plane abstraction working? Inform whether to invest in a richer UI (web dashboard, desktop app). + +### Phase 2 Deferred Features (v2+) + +- Full kanban board view (full-screen mode) +- Diff/preview pane +- HiLL checkpoint approval flow with inline file viewer +- Context-aware session resume +- Auto-triggered review-receive +- New workspace wizard +- Workspace cleanup/archival +- Git branch overview +- Activity log feed +- Notifications (audio/desktop) +- Multi-project dashboard with progress bars + +### Phase 3: Richer UI (TBD) + +Options after validating with the Ink TUI: + +- **Web Dashboard** (Option A) — March 15 design, Hono server + React + Tailwind. Works over SSH. +- **Tauri Desktop App** (Option D) — Rust + React, native performance, could embed terminal. +- **Both** — control plane serves multiple UIs. + +Decision deferred until Phase 2 validates the concept. + +--- + +## Visual Design — Next Steps + +Before implementing Phase 2 (Ink TUI), more visual design work is needed to think through the actual UI and UX. This will likely happen in a separate session. Key questions for that session: + +- Exact layout for compact sidebar mode vs expanded mode vs full-screen kanban +- Keybinding scheme (vim-style? superset-style? custom?) +- Color palette and status indicator design +- How the OAT layer toggles in/out of the workspace view +- How the action menu presents multiple options +- How the tool launcher (lazygit/yazi) integration feels +- Responsive behavior as Zellij pane width changes +- What information density feels right in the sidebar + +The more detail captured in mockups, the faster implementation will go. + +--- + +## IPC Architecture + +### State Flow + +| Layer | Mechanism | Purpose | +| ----------------------- | ----------------------------------------- | --------------------------------- | +| **State persistence** | `state.md` frontmatter | Source of truth, survives crashes | +| **State observation** | chokidar on `state.md` | Panel detects changes | +| **Agent → Panel** | Claude Code `Stop`/`PostToolUse` hooks | Agent signals completion | +| **Panel → Multiplexer** | `zellij run` / `tmux split-window` | Spawn new agent panes | +| **Panel → OAT** | Direct CLI invocation (`oat project ...`) | Trigger state transitions | + +### Workflow Example: User Clicks "Implement" + +1. Panel reads `state.md`, skill recommender determines `implement` is the primary recommendation +2. User selects "Implement" from action menu +3. Panel runs: `zellij run --name "implement" --cwd -- claude --prompt "Run /oat-project-implement for project "` +4. Claude Code's `Stop` hook fires: `oat panel notify --event=agent-stopped` +5. Panel's file watcher detects `state.md` change, re-reads, updates UI + +### Claude Code Hook Configuration + +```json +{ + "hooks": { + "Stop": [ + { + "matcher": "", + "command": "oat panel notify --event=agent-stopped --session=$SESSION_ID" + } + ] + } +} +``` + +--- + +## TUI Framework: Ink + +The panel TUI uses [Ink](https://github.com/vadimdemedes/ink) — React for the terminal. + +**Core capabilities:** + +- React component model with JSX, hooks, state management +- Flexbox layout via `` (same as CSS but measured in terminal columns/rows) +- Styled text via `` (color, bold, dim, underline, inverse) +- Keyboard input via `useInput` hook (arrow keys, letters, enter, escape) +- Focus management via `useFocus`/`useFocusManager` for tab navigation +- Real-time updates: `useState` + `useEffect` with file watchers drive re-renders + +**Available components:** + +- Built-in: ``, ``, ``, ``, ``, `` +- ink-ui: ``, ``, ``, `` +- Community: `ink-progress-bar`, `ink-table`, `ink-tab`, `ink-link` + +**Limitations:** + +- No mouse support — keyboard only +- No WASM compilation — can't become a Zellij plugin +- No built-in scrolling — needs custom viewport component +- Single-pane rendering — splits handled by Zellij/tmux + +**Who uses it:** Claude Code, Gemini CLI, GitHub Copilot CLI, Cloudflare Wrangler, Prisma CLI, Shopify CLI. + +**Why it fits:** TypeScript-native, React mental model, production-proven, drops into the monorepo with zero friction. For a sidebar + action menu + status display, it has everything needed. + +--- + +## Multiplexer Comparison + +| Capability | tmux | Zellij | +| -------------------- | ---------------------------- | ----------------------------------- | +| Plugin system | None (shell scripts) | Native WASM plugins | +| Layout definition | Custom format, limited | KDL files with plugin panes | +| Pane spawning | `split-window`, `new-window` | `zellij run`, `open_terminal()` API | +| IPC | `send-keys`, env vars | Pipe system (CLI-to-plugin) | +| Claude Code support | Official (Agent Teams) | No official support yet | +| Ecosystem for agents | All 7 surveyed tools use it | 1/7 experimental support | +| UX | Steep learning curve | Built-in keybinding hints | + +**Decision:** Zellij-first (developer's environment, better layout system, future plugin potential), tmux supported via adapter. Both are first-class from day one — the adapter abstraction is trivial. + +--- + +## Configuration + +### Global — `~/.oat/panel.json` (or extending `~/.oat/dashboard.json` from March 15 design) + +```json +{ + "repos": ["~/Code/open-agent-toolkit", "~/Code/api-service"], + "multiplexer": "auto", + "tools": { + "git": "lazygit", + "files": "yazi", + "diff": "delta", + "editor": "$EDITOR" + }, + "defaults": { + "runtime": "claude", + "isolation": "worktree", + "baseBranch": "main" + } +} +``` + +### Per-repo — `.oat/config.json` (committed, shared) + +```json +{ + "dashboard": { + "setupScript": "pnpm run worktree:init", + "branchPrefix": "oat/" + } +} +``` + +--- + +## Files in This Research Series + +| File | Purpose | +| ------------------------------------------- | ----------------------------------------------------------------------------- | +| `2026-03-15-oat-dashboard-design.md` | March 15 full design spec (control plane + server + web UI) | +| `2026-03-15-oat-dashboard.md` | March 15 TDD implementation plan (26 tasks, 5 chunks) | +| `2026-04-03-march15-design-audit.md` | Delta analysis: what changed in OAT since March 15 | +| `2026-04-03-control-plane-current-state.md` | **This file** — consolidated current state and options | +| `oat-workflow-panel-opus-4-6.md` | April 3 deep research: landscape analysis, TUI frameworks, IPC, brainstorming | diff --git a/.oat/repo/research/2026-04-03-march15-design-audit.md b/.oat/repo/research/2026-04-03-march15-design-audit.md new file mode 100644 index 000000000..f8c1530b6 --- /dev/null +++ b/.oat/repo/research/2026-04-03-march15-design-audit.md @@ -0,0 +1,279 @@ +--- +skill: deep-research +schema: analysis +topic: 'March 15 OAT Dashboard Design Audit — Delta Analysis' +model: opus-4-6 +generated_at: 2026-04-03 +context: .oat/repo/research/2026-03-15-oat-dashboard-design.md +--- + +# March 15 OAT Dashboard Design — What's Changed + +An audit of the March 15, 2026 design spec (`2026-03-15-oat-dashboard-design.md`) and implementation plan (`2026-03-15-oat-dashboard.md`) against the current OAT codebase as of April 3, 2026. **39 commits** have landed on main since March 15, introducing new skills, state fields, config systems, and a more sophisticated post-implementation routing state machine. + +## Summary + +The March 15 design is **structurally sound** — the three-layer architecture (control plane → server → UI) and core types are mostly accurate. However, several areas need updates before implementation can begin. The most significant changes are: (1) a new `pr_open` phase status and PR tracking fields, (2) a much more complex post-implementation routing state machine, (3) new lifecycle skills (summary, revise, complete, pr-final), (4) dispatch mechanism clarification, and (5) new config schema additions. + +Nothing in the March 15 design needs to be _thrown away_ — it needs to be _extended_. + +--- + +## Type Accuracy + +### Phase — ACCURATE + +```typescript +type Phase = 'discovery' | 'spec' | 'design' | 'plan' | 'implement'; +``` + +All five phases remain the only values. Confirmed in `oat-project-next/SKILL.md`, `templates/state.md`, and `scaffold.ts`. No new phases added. + +### PhaseStatus — NEEDS UPDATE + +```typescript +// March 15: +type PhaseStatus = 'in_progress' | 'complete'; + +// Current (April 3): +type PhaseStatus = 'in_progress' | 'complete' | 'pr_open'; +``` + +`pr_open` was added in commit `6a80c6a` (March 30). It's set after `oat-project-pr-final` creates the PR but before `oat-project-complete` marks the project done. This is used in the post-implementation router (Step 5.6 of `oat-project-next`). + +**Impact on control plane:** The `ProjectState` type and any status-dependent rendering logic need to handle `pr_open`. The skill recommender table needs a new row for `implement` + `pr_open`. + +### WorkflowMode — ACCURATE + +```typescript +type WorkflowMode = 'spec-driven' | 'quick' | 'import'; +``` + +Unchanged. Confirmed in `scaffold.ts` and `templates/state.md`. + +### ExecutionMode — ACCURATE + +```typescript +type ExecutionMode = 'single-thread' | 'subagent-driven'; +``` + +Unchanged. Routing logic correctly switches between `oat-project-implement` and `oat-project-subagent-implement`. + +--- + +## New State Fields + +Two new frontmatter fields added to `state.md` (commit `6a80c6a`, March 30): + +| Field | Type | Purpose | +| --------------- | -------------- | ---------------------------------------------- | +| `oat_pr_status` | string \| null | PR state tracking (null, open, merged, closed) | +| `oat_pr_url` | string \| null | URL of the created PR | + +**Impact on control plane:** `ProjectState` interface needs these fields. The `parseStateFrontmatter` function (Task 2 in the plan) must extract them. The UI should display PR status and link when available. + +--- + +## New Artifact: summary.md + +A new project artifact template was added (commit `85eccb6`, March 27): + +**File:** `.oat/templates/summary.md` + +**Frontmatter fields:** + +- `oat_summary_last_task` — last task completed when summary was generated +- `oat_summary_revision_count` — number of post-PR revision cycles +- `oat_summary_includes_revisions` — whether revisions are captured + +**Impact on control plane:** + +- `ArtifactType` union needs `'summary'` added +- `checkArtifacts` function (Task 2, Step 8) must check for `summary.md` +- The skill recommender must account for summary generation as a post-implementation step + +--- + +## Skill Recommender — Major Changes + +### Early Phase Routing — ACCURATE + +The March 15 recommendation table for phases `discovery` through `plan` is still correct: + +| Phase | Status | Mode | Recommendation | +| --------- | -------- | ------------ | -------------------------- | +| discovery | complete | spec-driven | oat-project-spec | +| discovery | complete | quick/import | oat-project-plan | +| spec | complete | — | oat-project-review-provide | +| design | complete | — | oat-project-review-provide | +| plan | complete | — | oat-project-implement | + +### Post-Implementation Routing — SIGNIFICANTLY EXPANDED + +The March 15 design assumed a simple flow: + +``` +implement complete → oat-project-review-provide +``` + +**The current implementation has a 6-step post-implementation state machine** (from `oat-project-next/SKILL.md`, Step 5): + +``` +implement complete/pr_open → Post-Implementation Router: + 5.1: Incomplete revision tasks? → oat-project-implement (continue revisions) + 5.2: Unprocessed reviews in reviews/? → oat-project-review-receive + 5.3: Final code review not passed? → oat-project-review-provide OR review-receive + 5.4: Summary not generated? → oat-project-summary (NEW) + 5.5: PR not created? → oat-project-pr-final (NEW) + 5.6: PR is open? → oat-project-complete (NEW) +``` + +**Impact on control plane:** The `SkillRecommendation` logic (Task 8 in the plan) needs to be substantially rewritten for the implement phase. Instead of a single recommendation, it must evaluate 6 conditions in order and recommend the first matching skill. + +### New Skills Not in March 15 Design + +| Skill | Purpose | When Called | +| --------------------------------- | -------------------------------------------------------------- | ------------------------------- | +| `oat-project-next` | Lifecycle router — determines next skill based on state | Entry point for "what's next?" | +| `oat-project-summary` | Generates durable summary artifact from project artifacts | After implementation, before PR | +| `oat-project-pr-final` | Creates/updates the final PR with artifact-derived description | After summary, before complete | +| `oat-project-complete` | Marks project complete, triggers archive if configured | After PR is open | +| `oat-project-revise` | Handles post-PR feedback, creates revision tasks | When PR review feedback arrives | +| `oat-project-reconcile` | Merge reconciliation for worktrees | Post-merge cleanup | +| `oat-project-promote-spec-driven` | Promote quick/import projects to spec-driven | On-demand workflow upgrade | +| `oat-project-document` | Documentation sync with docs site | Post-completion or on-demand | +| `oat-project-import-plan` | Import external plans into OAT format | Import workflow entry | + +**Total oat-project-\* skills: 27** (up from ~18 in March 15 design) + +### Boundary Tier Detection — NEW CONCEPT + +The March 15 design had no concept of boundary tier detection. The current `oat-project-next` skill uses a **3-tier system** to classify artifact readiness: + +| Tier | Condition | Action | +| ------- | --------------------------------------------------------- | ----------------------------- | +| Tier 1 | `oat_status == "complete"` AND has `oat_ready_for` target | Advance to target skill | +| Tier 1b | `oat_status == "complete"` WITHOUT target | Advance to default next phase | +| Tier 2 | `oat_status == "in_progress"` + substantive content | Advance to next phase | +| Tier 3 | `oat_template == true` OR empty artifact | Resume current phase | + +**Impact on control plane:** The skill recommender should implement this tiered detection for more accurate recommendations. Reading just `oat_phase` and `oat_phase_status` from `state.md` is no longer sufficient — the recommender should also check artifact frontmatter (`oat_status`, `oat_ready_for`, `oat_template`) for precise routing. + +--- + +## Dispatch Mechanism — Clarification Needed + +### March 15 Assumption + +``` +claude -p "/ " # Launch agent with skill pre-invoked +codex "" # Launch Codex with prompt +``` + +### Current Reality + +Skills are invoked via **Claude Code's internal Skill tool**, not CLI flags. When `oat-project-next` determines the next skill, it calls the Skill tool to load and execute the next skill within the current conversation. + +**For the control plane, dispatch needs two paths:** + +1. **Send to existing session** — Use `zellij action write-chars` to type `/` into a pane with a running Claude Code session. This works because Claude Code interprets `/` as a skill invocation. + +2. **Launch new session** — Use `zellij run -- claude --prompt "Run / for project "` to start a fresh agent session. The `--prompt` flag sends an initial message. + +The March 15 design's dispatch architecture is conceptually correct but the CLI flag syntax needs verification against current Claude Code CLI. + +--- + +## Config Schema Changes + +### New Config Fields + +```typescript +// Added to OatConfig interface since March 15: +interface OatConfig { + // ... existing fields ... + git?: { + defaultBranch?: string; // NEW — auto-detected during oat init + }; + archive?: { + s3Uri?: string; // NEW — S3 bucket for project archives + s3SyncOnComplete?: boolean; // NEW — auto-sync on project completion + summaryExportPath?: string; // NEW — where to export summaries + }; +} +``` + +### New CLI Command: `oat config` + +Full configuration management: `oat config get `, `oat config set `, `oat config list`. The control plane should use the same config reading logic rather than duplicating it. + +--- + +## Package Namespace Change + +The CLI package moved from `@tkstang/cli` to `@open-agent-toolkit/cli` (commit April 1). All publishable packages now use the `@open-agent-toolkit` scope. + +**Impact:** The control plane package should be `@open-agent-toolkit/control-plane` (not `@oat/control-plane` as in the March 15 design). + +--- + +## Implementation Plan Task-by-Task Impact + +### Chunk 1: Control Plane — Types and Project State + +| Task | Status | Changes Needed | +| -------------------------------- | ------------ | ----------------------------------------------------------------------------- | +| Task 1: Package scaffold | Minor update | Use `@open-agent-toolkit/control-plane` scope | +| Task 2: State parser | Update | Add `oat_pr_status`, `oat_pr_url` fields; add `summary.md` to artifact checks | +| Task 3: Project scanner | Unchanged | Still accurate | +| Task 4: Pending action detection | Update | Add PR-related pending actions (PR open, PR review feedback, revision tasks) | +| Task 5: File watcher | Unchanged | Still accurate | +| Task 6: Exports | Unchanged | Just re-exports | + +### Chunk 2: Control Plane — Skills and Sessions + +| Task | Status | Changes Needed | +| --------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- | +| Task 7: Skill registry | Update | Account for ~27 skills (up from ~18); add new categories | +| Task 8: Skill recommender | **Major rewrite** | Post-implementation routing is now a 6-step state machine; add boundary tier detection; add `pr_open` status handling | +| Task 9: Session detector | Unchanged | Still accurate | +| Task 10: Zellij integration | Minor update | Verify CLI flag syntax (`--prompt` vs `-p`) | +| Task 11: Session launcher | Minor update | Verify dispatch command format | +| Task 12: Worktree manager | Unchanged | Still accurate | +| Task 13: Integration test | Update | Test cases need new fields and routing paths | + +### Chunk 3: Dashboard Server + +| Task | Status | Changes Needed | +| ------------------------- | ------------ | ----------------------------------- | +| Task 14: Package scaffold | Unchanged | Still accurate | +| Task 15a: Read-only API | Minor update | Add PR status to project responses | +| Task 15b: Dispatch API | Minor update | Verify dispatch command syntax | +| Task 16: WebSocket | Unchanged | Still accurate | +| Task 17: Config loading | Update | Add git and archive config sections | + +### Chunk 4: Web UI + +| Task | Status | Changes Needed | +| ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Tasks 18-23 | Update | UI needs to display PR status/link, summary artifact, revision state. Dispatch modal needs updated skill list. Phase progress should show post-implementation sub-steps. | + +### Chunk 5: CLI Integration + +| Task | Status | Changes Needed | +| ----------- | ------------ | ------------------------------------------------------ | +| Tasks 24-26 | Minor update | `oat dashboard` command is still the right entry point | + +--- + +## Recommendations + +1. **Update types first** — Add `pr_open` to `PhaseStatus`, add `oat_pr_status`/`oat_pr_url` to `ProjectState`, add `'summary'` to `ArtifactType`. + +2. **Rewrite the skill recommender** — Port the 6-step post-implementation router from `oat-project-next/SKILL.md` into the recommender, plus add boundary tier detection. + +3. **Import config reading from CLI** — Don't reimplement config parsing. Share the `OatConfig` interface and reader from `@open-agent-toolkit/cli`. + +4. **Consider sharing `oat-project-next` logic** — Rather than reimplementing routing, the control plane could share the routing module with the skill. This avoids drift. + +5. **Verify dispatch CLI syntax** — Before implementing the session launcher, test current Claude Code CLI flags in a real terminal session. diff --git a/.oat/repo/research/oat-workflow-panel-opus-4-6.md b/.oat/repo/research/oat-workflow-panel-opus-4-6.md new file mode 100644 index 000000000..0f5f58523 --- /dev/null +++ b/.oat/repo/research/oat-workflow-panel-opus-4-6.md @@ -0,0 +1,749 @@ +--- +skill: deep-research +schema: architectural +topic: 'OAT Workflow Panel — TUI interface with interactive workflow triggers' +model: opus-4-6 +generated_at: 2026-04-03 +depth: standard +--- + +# OAT Workflow Panel: Feasibility Research + +## Executive Summary + +Building a TUI panel that displays OAT project state and provides interactive controls to trigger workflow steps is **highly feasible**. The recommended approach is a **TypeScript package using Ink (React for CLI)** running as a standalone process in a Zellij or tmux pane, integrated into the existing OAT monorepo as `packages/panel/`. + +**Zellij is the primary multiplexer target**, with tmux supported via adapter. While every existing agent orchestrator builds on tmux, the panel itself is multiplexer-agnostic — it's an Ink TUI process running in a pane, identical in either environment. The only multiplexer-specific code is pane spawning, which is a thin abstraction (~50 lines per adapter). Zellij is primary because: (1) the developer is actively setting it up and will dogfood this path, (2) Zellij layouts (KDL files) let us ship a default `oat-panel.kdl` that auto-configures the sidebar — tmux has no equivalent, (3) Zellij's plugin system leaves the door open for a future native status bar widget, and (4) `zellij run` is a cleaner pane-spawning API than tmux's `split-window`. tmux support costs almost nothing to add and should be first-class from day one. + +A landscape analysis of seven existing tools — [tmux-agent-status](https://github.com/samleeney/tmux-agent-status), [claude-squad](https://github.com/smtg-ai/claude-squad), [agtx](https://github.com/fynnfluegge/agtx), [cmux](https://github.com/craigsc/cmux), [opensessions](https://github.com/ataraxy-labs/opensessions), [ntm](https://github.com/Dicklesworthstone/ntm), and [dmux](https://github.com/standardagents/dmux) — reveals converging patterns: filesystem-as-message-bus for state, git worktrees for isolation, and tmux for session management. **agtx stands out** as the closest prior art: a Rust kanban TUI with per-phase agent assignment, TOML plugin system, and MCP-based orchestration. Its phase model (Backlog/Planning/Running/Review/Done) maps naturally to OAT's lifecycle, and its plugin system is generic enough that an OAT plugin could likely work today — but agtx doesn't provide OAT-specific features like HiLL checkpoint gates, skill routing, frontmatter-driven state, or the spec-driven/quick workflow modes. The panel should draw heavily from agtx's kanban UI model while adding native OAT state management. + +**The kanban board is the core UI metaphor.** OAT's 5-phase lifecycle (discovery → spec → design → plan → implement) maps naturally to kanban columns, with tasks flowing left-to-right as work progresses. Each column shows phase status, active tasks, blockers, and available actions. HiLL checkpoints appear as gates between columns. This provides an at-a-glance view of where everything is across all active projects. OAT already has a richer state model (`state.md` frontmatter with phase tracking, HiLL checkpoints, blockers, and task progress) that maps naturally to this panel UI. The main gap is the absence of a real-time notification mechanism, which can be bridged with Claude Code hooks and file watching. + +**Ghostty** (the user's terminal) has native splits and tabs but no plugin system or general-purpose IPC, so it cannot replace a multiplexer for programmatic pane management. The recommended stack is **Ghostty (terminal) + tmux or Zellij (multiplexer) + OAT panel (TUI in a pane)**. + +## Methodology + +Twelve research angles were explored in parallel across two waves: + +**Wave 1 (6 angles):** +1. **Reference implementation** — Analyzed tmux-agent-status architecture via GitHub +2. **OAT project state model** — Explored `.oat/` structure, `state.md` schema, workflow skills, and CLI commands in the local codebase +3. **Zellij vs tmux** — Researched Zellij plugin API, pipe system, layout format, and community plugins +4. **TUI frameworks** — Compared Ink, ratatui, bubbletea, blessed, Textual, and crossterm +5. **IPC and workflow triggers** — Researched file-based IPC, Unix sockets, Zellij pipes, Claude Code hooks, and pane spawning +6. **Architectural feasibility** — Explored the OAT monorepo structure (Turborepo, pnpm workspaces, build system) + +**Wave 2 (6 angles — landscape analysis):** +7. **claude-squad** — Go-based multi-agent tmux orchestrator with unified dashboard +8. **agtx** — Rust kanban TUI with per-phase agent assignment and MCP-based orchestration +9. **cmux** — Bash git-worktree isolator for parallel Claude sessions +10. **opensessions** — TypeScript tmux sidebar with agent status monitoring +11. **ntm** — Go control plane with pipelines, safety policies, and REST/WebSocket API +12. **dmux** — TypeScript worktree-based multi-agent manager with file browser +13. **Ghostty terminal** — Feature assessment for native integration potential + +Sources included GitHub repositories, Zellij/tmux documentation, framework docs, and the local OAT codebase. + +## Findings + +### Decision Framework + +**Decision drivers:** +- Integrate with OAT's existing TypeScript ESM monorepo without introducing heavy cross-language build complexity +- Display real-time OAT project state (phase, task progress, blockers, HiLL checkpoints) +- Provide interactive controls to trigger workflow steps (implement, review, next phase) +- Support spawning new terminal panes for agent sessions +- Work with Zellij (primary, developer's environment) and tmux (supported) via a multiplexer adapter + +**Constraints:** +- Must not require users to learn a new programming language to contribute +- Must leverage OAT's existing state model (`state.md` frontmatter) +- Should be installable as part of the OAT CLI (`oat panel`) + +**Quality attributes:** +- Maintainability (single-language preference) +- User experience (interactive, real-time updates) +- Integration depth (with both OAT state and terminal multiplexer) +- Installation simplicity + +### Reference: tmux-agent-status Architecture + +The [tmux-agent-status](https://github.com/samleeney/tmux-agent-status) project provides a validated architectural pattern: + +- **Pure Bash** — TPM plugin with no compiled dependencies +- **Three-layer architecture**: (1) Agent hooks write status files to `~/.cache/tmux-agent-status/`, (2) a sidebar-collector daemon aggregates state, (3) display layers (sidebar pane, status line, fzf switcher) read aggregated state +- **Filesystem as message bus** — decouples agents from display; any process writing the right file format becomes trackable +- **Hook-driven input** — Claude Code hooks (`UserPromptSubmit`, `PreToolUse`, `Stop`, `Notification`) fire shell scripts that write status files +- **Status values**: `working`, `done`, `wait`, `parked` with precedence rules +- **Features**: per-pane and per-session status, fzf popup switcher, audio completion notifications, multi-agent deploy scripts + +**Key takeaway**: The filesystem-as-IPC pattern is proven and simple. OAT can adopt this same pattern but with richer state (phases, tasks, checkpoints) and a more interactive panel. + +### OAT Project State Model + +OAT projects live in `.oat/projects///` with a well-defined state model: + +**Phase lifecycle** (stored in `state.md` frontmatter `oat_phase`): +``` +discovery → spec → design → plan → implement +``` + +Quick mode skips spec/design: `discovery → plan → implement` + +**Key state fields for panel display:** + +| Field | Type | Purpose | +|-------|------|---------| +| `oat_phase` | string | Current phase (discovery\|spec\|design\|plan\|implement) | +| `oat_phase_status` | string | Phase state (in_progress\|complete\|pr_open) | +| `oat_workflow_mode` | string | Execution path (spec-driven\|quick\|import) | +| `oat_execution_mode` | string | Implementation variant (single-thread\|subagent-driven) | +| `oat_current_task` | string | Currently executing task ID (e.g., "pNN-tNN") | +| `oat_blockers` | array | Active blocker descriptions | +| `oat_hill_checkpoints` | array | Phases requiring human-in-the-loop approval | +| `oat_hill_completed` | array | Completed checkpoint approvals | +| `oat_lifecycle` | string | Project status (active\|paused) | + +**Transition triggers** come from the `oat-project-next` skill, which reads state and routes to the appropriate phase skill. The panel would need to detect which transitions are available and surface them as actionable buttons. + +**No built-in file watching** — OAT state transitions are explicit (skills read/write `state.md` directly) and pull-based (user runs `oat-project-next`). A panel must poll `state.md` or use external notifications (Claude Code hooks). + +### Landscape Analysis: Existing Agent Orchestrators + +Seven existing tools were analyzed. All are tmux-based. Key patterns and differentiation: + +| Tool | Language | Multiplexer | Primary Pattern | Key Differentiator | +|------|----------|-------------|-----------------|-------------------| +| [tmux-agent-status](https://github.com/samleeney/tmux-agent-status) | Bash | tmux | Status monitoring | Filesystem-as-message-bus, hook-driven, sidebar + status bar | +| [claude-squad](https://github.com/smtg-ai/claude-squad) | Go | tmux | Multi-agent dashboard | Unified TUI with preview/diff tabs, git worktree isolation, daemon mode | +| [agtx](https://github.com/fynnfluegge/agtx) | Rust | tmux | Kanban orchestration | Per-phase agent assignment, MCP server, plugin system (TOML), SQLite state | +| [cmux](https://github.com/craigsc/cmux) | Bash | None (worktrees only) | Workspace isolation | Simplest tool — just worktree + branch + Claude launch | +| [opensessions](https://github.com/ataraxy-labs/opensessions) | TypeScript | tmux (Zellij experimental) | Sidebar companion | HTTP-driven metadata push, Solid UI, multi-agent-type watching | +| [ntm](https://github.com/Dicklesworthstone/ntm) | Go | tmux | Control plane | Pipelines with dependencies, safety policies, approval workflows, REST/WebSocket API | +| [dmux](https://github.com/standardagents/dmux) | TypeScript | tmux | Worktree manager | 11+ agent support, built-in file browser, lifecycle hooks | + +**Converging patterns across the ecosystem:** +- **Git worktrees** for agent isolation (claude-squad, cmux, agtx, dmux) +- **tmux as the universal multiplexer** — every tool uses it; none have production Zellij support +- **Filesystem-based state** — status files, SQLite, or markdown frontmatter +- **Hook-driven notifications** — Claude Code hooks for status transitions +- **TUI dashboard** — most provide a unified view of all agent sessions + +**agtx is the strongest prior art for OAT integration:** +- Its 5-column kanban (Backlog → Planning → Running → Review → Done) maps directly to OAT's 5-phase lifecycle (discovery → spec → design → plan → implement) +- Its TOML plugin system defines per-phase commands, prompts, and artifact gates — similar to OAT skills +- Its MCP server (`agtx serve`) exposes board state as JSON-RPC tools, enabling an orchestrator agent to drive the workflow +- Per-phase agent assignment (e.g., Gemini for research, Claude for implementation) aligns with OAT's execution modes +- Cyclic workflows (Review → Planning with phase counter) support OAT's review-receive pattern + +**Key insight**: Rather than building from scratch, OAT could integrate with or draw heavily from agtx's architecture — particularly its kanban model, MCP-based orchestration, and plugin system — while adding OAT-specific state management (frontmatter, HiLL checkpoints, skill routing). + +### Ghostty Terminal Assessment + +Ghostty is the user's terminal emulator. Key findings for integration potential: + +- **Built-in multiplexer**: Native splits and tabs with session restoration, but **no scriptable API** for programmatic pane management. The developers have explicitly declined arbitrary command execution from keybindings. +- **No plugin system**: No WASM, no extensions, no custom widgets. +- **Limited IPC**: Platform-native only (D-Bus on Linux, AppleScript on macOS). No general-purpose CLI for controlling splits/tabs programmatically. `ghostty +new-window` exists but is narrow. +- **Good terminal host**: GPU-accelerated, Kitty graphics protocol, shell integration — works well *hosting* tmux/Zellij sessions but cannot *replace* them for automation. + +**Verdict**: Ghostty is a great terminal to run OAT inside, but the panel must rely on tmux or Zellij for programmatic pane management. Recommended stack: **Ghostty (terminal) + tmux/Zellij (multiplexer) + OAT panel (Ink TUI in a pane)**. + +### Kanban Board as Core UI + +The panel's primary view should be a **kanban board** showing OAT project state across lifecycle phases. This is inspired by agtx's approach but tailored to OAT's richer state model. + +**Conceptual layout (single project view):** +``` +┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐ +│ Discovery │ Spec │ Design │ Plan │ Implement │ +│ ✓ complete │ ✓ complete │ → in_prog │ pending │ pending │ +├─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤ +│ │ │ ▸ API layer │ │ │ +│ │ │ ▸ Data model│ │ │ +│ │ │ │ │ │ +│ │ │ ⊘ HiLL │ │ │ +│ │ │ checkpoint │ │ │ +└─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘ + Blockers: none Actions: [Approve Design ↵] + Workflow: spec-driven [Open Review Pane] [Pause] +``` + +**Multi-project dashboard view:** +``` +┌──────────────────────────────────────────────────────────────────────┐ +│ OAT Projects ↻ 2s ago │ +├──────────────────────────────────────────────────────────────────────┤ +│ auth-refactor ██████████░░░░ Plan (3/7 tasks) ▸ working │ +│ api-v2-migration ████████████░░ Implement (5/6) ▸ working │ +│ docs-overhaul ██░░░░░░░░░░░░ Discovery ● waiting │ +├──────────────────────────────────────────────────────────────────────┤ +│ [n] New Project [↵] Open [i] Implement [r] Review [?] Help │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +**Key UI elements:** +- **Phase columns** with status indicators (✓ complete, → in progress, pending) +- **Task cards** within active phase showing current task ID and description +- **HiLL checkpoint gates** shown as barriers between phases requiring approval +- **Blocker alerts** surfaced prominently when `oat_blockers` is non-empty +- **Action buttons** contextually showing valid next steps based on current state +- **Progress bars** for implementation phase (tasks completed / total) +- **Agent status** indicators (working, waiting, done) fed by Claude Code hooks + +**OAT phase → kanban column mapping:** + +| OAT Phase | Kanban Column | agtx Equivalent | Available Actions | +|-----------|---------------|-----------------|-------------------| +| discovery | Discovery | Backlog | "Start Discovery", "Skip to Plan" (quick mode) | +| spec | Specification | Planning | "Write Spec", "Approve Spec" (HiLL) | +| design | Design | Planning | "Create Design", "Approve Design" (HiLL) | +| plan | Plan | Planning | "Generate Plan", "Approve Plan" (HiLL) | +| implement | Implement | Running → Review → Done | "Implement", "Open Review Pane", "Pause" | + +### agtx Plugin Compatibility Analysis + +agtx's TOML plugin system is generic enough to configure for any agent workflow. An OAT plugin would look approximately like: + +```toml +[plugin] +name = "oat" +description = "OAT project lifecycle" + +[phases.backlog] +display_name = "Discovery" + +[phases.planning] +display_name = "Spec → Design → Plan" +command = "claude" +prompt_template = "Run /oat-project-next for project {task_name}" +artifacts = ["plan.md"] # gate: file must exist to advance + +[phases.running] +display_name = "Implement" +command = "claude" +prompt_template = "Run /oat-project-implement for project {task_name}" + +[phases.review] +display_name = "Review" +command = "claude" +prompt_template = "Run /oat-project-review for project {task_name}" +``` + +**What agtx gives us today:** +- Kanban TUI with task lifecycle +- Per-task git worktree isolation +- tmux pane management per task +- MCP server for orchestrator-driven automation +- Multi-agent support (different agents per phase) + +**What agtx doesn't give us (and why we still need our own panel):** +- **OAT state awareness** — agtx uses SQLite; OAT uses `state.md` frontmatter. No native parsing of `oat_phase`, `oat_hill_checkpoints`, etc. +- **HiLL checkpoint gates** — agtx has no concept of human-in-the-loop approval barriers between phases +- **Workflow mode switching** — no spec-driven vs quick vs import mode distinction +- **OAT skill routing** — agtx can run commands but doesn't understand `oat-project-next`'s routing logic (boundary tier detection, review safety checks) +- **Multi-phase compression** — agtx maps 1:1 between kanban columns and phases; OAT's spec/design/plan might compress to a single "Planning" column in quick mode +- **Multiplexer flexibility** — agtx is tmux-only; OAT panel targets Zellij-first +- **Monorepo integration** — agtx is a standalone Rust binary; OAT panel should share code with the CLI + +**Verdict**: agtx could be used as a lightweight OAT orchestrator *today* with a TOML plugin, but it would be a "dumb" wrapper — triggering commands without understanding OAT's state machine. The value of building our own panel is native state awareness: the panel *understands* what phase you're in, what transitions are valid, which checkpoints need approval, and can present contextual actions accordingly. + +### Options Analyzed + +#### Option A: TypeScript Ink Package in Monorepo + +- **Description**: New package at `packages/panel/` using [Ink](https://github.com/vadimdemedes/ink) (React for CLI). Runs as a standalone process in a Zellij/tmux pane via `oat panel` CLI subcommand. Watches `state.md` for changes, spawns panes via `zellij run` / `tmux split-window`. +- **Tradeoffs**: + - **Pros**: Native TypeScript/ESM fit; zero additional toolchain; shared imports with CLI (config, manifest modules); React component model for UI; proven in production (Claude Code, Gemini CLI, Wrangler all use Ink); Turborepo auto-detects new package + - **Cons**: No native Zellij plugin integration (runs in a pane, not as a plugin); Ink's widget ecosystem is narrower than ratatui's; cannot be compiled to WASM +- **Constraints it satisfies**: All — single language, monorepo integration, interactive UI, real-time updates via React state +- **Constraints it violates**: None +- **Fit assessment**: Excellent. Lowest integration complexity, fastest iteration, best maintainability +- **Precedent**: Ink is used by Claude Code's own TUI, Gemini CLI, GitHub Copilot CLI, Cloudflare Wrangler + +**Conceptual panel UI (Ink/React):** +```tsx +function WorkflowPanel({ project }: { project: OatProject }) { + const [state, setState] = useState(readState(project)); + + // Watch state.md for changes + useEffect(() => { + const watcher = fs.watch(project.statePath, () => { + setState(readState(project)); + }); + return () => watcher.close(); + }, [project]); + + return ( + + 📋 {project.name} + + + + spawnPane(action)} + /> + + ); +} +``` + +#### Option B: Rust Zellij WASM Plugin + +- **Description**: Native Zellij plugin written in Rust using `zellij-tile` crate. Renders directly in a Zellij pane via the plugin API. Uses Zellij's pipe system for bidirectional communication. +- **Tradeoffs**: + - **Pros**: Deepest Zellij integration; can spawn panes via `open_terminal()` API; receives pipe messages natively; WASM sandboxing; can use `HighlightClicked` for interactive regions + - **Cons**: Requires Rust toolchain in CI/CD; cannot share code with TypeScript CLI; duplicates state parsing logic; Zellij-only (no tmux fallback); steeper learning curve; `wasmi` interpreter is slower than native +- **Constraints it satisfies**: Integration depth, interactive UI +- **Constraints it violates**: Single-language preference, maintainability, multiplexer portability +- **Fit assessment**: High integration quality but high cost. Justified only if Zellij-native behavior is a hard requirement +- **Precedent**: zjstatus (community status bar plugin), zellij-forgot (command reference plugin) + +**Zellij plugin API example:** +```rust +impl ZellijPlugin for OatPanel { + fn load(&mut self, config: BTreeMap) { + subscribe(&[EventType::Timer, EventType::Key, EventType::Mouse]); + set_timeout(1.0); // poll state every second + } + + fn update(&mut self, event: Event) -> bool { + match event { + Event::Timer(_) => { self.refresh_state(); true } + Event::Key(Key::Char('i')) => { + open_terminal_floating( + &PathBuf::from("."), + None, // default shell + ); + false + } + _ => false + } + } + + fn render(&mut self, rows: usize, cols: usize) { + // Write ANSI-styled text to stdout + println!("Phase: {} [{}]", self.phase, self.status); + } +} +``` + +#### Option C: Hybrid — TypeScript CLI + Thin Zellij Plugin + +- **Description**: Core logic in TypeScript (Path A), plus a thin Rust Zellij plugin that acts as a status bar indicator. The plugin communicates with the TypeScript process via Zellij pipes or Unix socket. +- **Tradeoffs**: + - **Pros**: Best of both worlds — rich Ink UI + native Zellij status bar; TypeScript handles heavy lifting + - **Cons**: Two integration points to maintain; added build complexity; marginal UX improvement over Path A alone +- **Constraints it satisfies**: Most, with enhanced Zellij integration +- **Constraints it violates**: Adds Rust build dependency +- **Fit assessment**: Medium. Over-engineered for initial release; viable as a Phase 2 enhancement +- **Precedent**: No direct precedent for this hybrid pattern + +### Tradeoff Matrix + +| Dimension | A: Ink (TypeScript) | B: Zellij WASM (Rust) | C: Hybrid | +|---|---|---|---| +| **Monorepo fit** | ★★★★★ Native | ★★☆☆☆ Separate toolchain | ★★★☆☆ Mixed | +| **Development speed** | ★★★★★ 2-3 weeks | ★★☆☆☆ 4-6 weeks | ★★★☆☆ 3-4 weeks | +| **Zellij integration** | ★★★☆☆ Via CLI commands | ★★★★★ Native plugin API | ★★★★☆ Plugin + CLI | +| **tmux support** | ★★★★☆ Via CLI commands | ☆☆☆☆☆ None | ★★★★☆ Via TypeScript layer | +| **Interactive UI** | ★★★★☆ Ink components | ★★★☆☆ Custom ANSI rendering | ★★★★☆ Ink + plugin | +| **Maintainability** | ★★★★★ Single language | ★★☆☆☆ Rust + TS | ★★★☆☆ Two languages | +| **Real-time updates** | ★★★★☆ File watch + hooks | ★★★★★ Pipes + timers | ★★★★★ Both channels | +| **Code sharing with CLI** | ★★★★★ Direct imports | ☆☆☆☆☆ None | ★★★★☆ TypeScript layer | + +### Zellij vs tmux Comparison + +| Capability | tmux | Zellij | +|---|---|---| +| **Plugin system** | None (shell scripts + hooks) | Native WASM plugins via `zellij-tile` | +| **Layout definition** | Custom format, limited | KDL files with plugin panes, floating panes | +| **Pane spawning** | `split-window`, `new-window` | `zellij run`, `open_terminal()` API | +| **IPC** | `send-keys`, environment vars | Pipe system (CLI-to-plugin, plugin-to-plugin) | +| **Interactive UI** | Status bar only (text) | Full pane rendering with mouse/key events | +| **Plugin isolation** | None | WASM sandboxing | +| **Session management** | Mature, ubiquitous | Session-manager plugin, resurrection | +| **Claude Code support** | Official (Agent Teams) | No official support (community request #31901) | +| **Ecosystem for agents** | 7/7 tools use it | 1/7 experimental support (opensessions) | +| **Scriptability** | Battle-tested, decades of tooling | Good CLI, less proven for automation | +| **UX/Discoverability** | Steep learning curve | Built-in keybinding hints per mode | + +**Verdict**: **Zellij-first, tmux-supported.** While every existing agent orchestrator builds on tmux, the panel itself is multiplexer-agnostic — it's an Ink process in a pane. The only multiplexer-specific code is pane spawning, abstracted behind a simple adapter. Zellij is primary because: (1) developer is actively using it and will dogfood this path, (2) KDL layout files let us ship a default `oat-panel.kdl` for automatic sidebar setup, (3) Zellij's plugin system keeps the door open for a future native status bar widget, (4) `zellij run` is a cleaner pane-spawning API. tmux support costs ~50 lines and should be first-class from day one. + +```typescript +interface MuxAdapter { + splitPane(command: string, opts?: { name?: string; cwd?: string; floating?: boolean }): void; + listPanes(): PaneInfo[]; + detectMultiplexer(): 'zellij' | 'tmux' | 'none'; +} + +// Auto-detect: check $ZELLIJ env var, then $TMUX, then fallback +// zellij: zellij run --name "name" -- command +// tmux: tmux split-window -h -t $session "command" +``` + +**Recommendation**: Target Zellij first (developer's environment, better layout system), support tmux via adapter, design the abstraction layer from day one so both are first-class. + +### IPC Architecture + +The recommended communication architecture layers multiple mechanisms: + +| Layer | Mechanism | Purpose | +|-------|-----------|---------| +| **State persistence** | `state.md` frontmatter | Source of truth, survives crashes | +| **State observation** | `fs.watch()` on `state.md` | Panel detects changes | +| **Agent → Panel notifications** | Claude Code `Stop`/`PostToolUse` hooks | Agent signals completion | +| **Panel → Multiplexer** | `zellij run` / `tmux split-window` | Spawn new agent panes | +| **Panel → OAT** | Direct CLI invocation (`oat project ...`) | Trigger state transitions | + +**Workflow example — user clicks "Implement":** +1. Panel reads `state.md`, determines `implement` is the next valid phase +2. User selects "Implement" button in the panel +3. Panel runs: `zellij run --name "implement" --cwd -- claude --prompt "Run oat-project-implement for "` +4. Claude Code's `Stop` hook fires: `oat panel notify --event=agent-stopped` +5. Panel's `fs.watch()` detects `state.md` change, re-reads and updates UI + +**Claude Code hook configuration** (`.claude/settings.json`): +```json +{ + "hooks": { + "Stop": [{ + "matcher": "", + "command": "oat panel notify --event=agent-stopped --session=$SESSION_ID" + }] + } +} +``` + +### TUI Framework Assessment + +| Framework | Language | Monorepo Fit | Widget Richness | WASM/Zellij | Maintenance | +|---|---|---|---|---|---| +| **Ink** | TypeScript | ★★★★★ | ★★★★☆ | No | Very active | +| **ratatui** | Rust | ★★☆☆☆ | ★★★★★ | Not directly* | Very active | +| **bubbletea** | Go | ★★☆☆☆ | ★★★★☆ | No | Very active | +| **blessed** | Node.js | ★★★☆☆ | ★★★★☆ | No | Unmaintained | +| **Textual** | Python | ★☆☆☆☆ | ★★★★★ | No | Active | + +*ratatui cannot be used inside Zellij WASM plugins — Zellij plugins use their own rendering API (`zellij-tile`), not a terminal backend. ratatui works only as a standalone process in a pane. + +**Ink is the clear winner** for OAT: native TypeScript, React component model, production-proven, drops into the monorepo with zero friction. + +### Risk Considerations + +| Risk | Severity | Likelihood | Mitigation | +|---|---|---|---| +| Ink widget gaps (no built-in progress bar, tree view) | Low | Medium | Use `ink-progress-bar`, `ink-select-input` community packages; custom components are straightforward in React | +| `fs.watch()` reliability across OS | Low | Low | Use `chokidar` for robust cross-platform file watching | +| Zellij not installed on user's system | Medium | Medium | Graceful degradation — panel works standalone, pane spawning requires Zellij/tmux | +| State file race conditions (concurrent agent writes) | Low | Low | OAT state writes are serialized through CLI commands; panel is read-only | +| Zellij API breaking changes | Low | Low | Panel communicates via `zellij` CLI (stable), not plugin API | + +### Recommendation + +- **Recommended option**: **Option A — TypeScript Ink Package** +- **Rationale**: Best monorepo fit, fastest development, single-language maintenance, proven framework (Ink), and sufficient integration depth via Zellij CLI commands. The marginal benefit of native Zellij plugin integration (Option B) does not justify the Rust toolchain overhead and code duplication. +- **Conditions**: Ink must remain maintained (high confidence — used by Claude Code, Gemini CLI). Zellij/tmux CLIs must remain stable for pane spawning (high confidence — part of their public APIs). +- **Fallback**: If deeper Zellij integration becomes necessary, pursue Option C (hybrid) as a Phase 2 enhancement — add a thin Rust status-bar plugin (using ratatui + zellij_widgets adapter) while keeping the core logic in TypeScript. +- **Next steps**: + 1. Scaffold `packages/panel/` with Ink, React, and TypeScript + 2. Implement `state.md` frontmatter parser and file watcher (chokidar) + 3. Build kanban board view — phase columns, task cards, HiLL checkpoint gates, progress bars + 4. Build multi-project dashboard view — project list with progress summaries + 5. Implement multiplexer adapter abstraction (Zellij primary, tmux secondary, auto-detect via `$ZELLIJ`/`$TMUX` env vars) + 6. Add `oat panel` CLI subcommand + 7. Implement pane spawning for workflow triggers (`zellij run` / `tmux split-window`) + 8. Configure Claude Code hooks for agent-to-panel notifications + 9. Create default Zellij layout (`layouts/oat-panel.kdl`) with sidebar + main pane + 10. Explore agtx's MCP server pattern for potential orchestrator-to-panel communication + +## Brainstorming & Discovery + +_This section captures open questions, feature ideas, and design explorations that are still being shaped. Separate from the research findings above._ + +### Feature Ideas Under Consideration + +**Core kanban panel:** +- Kanban board showing OAT project phases as columns with task flow +- Multi-project dashboard with progress bars and agent status +- Contextual action buttons based on current state (Implement, Review, Approve, Pause) +- HiLL checkpoint gates as visual barriers requiring explicit approval +- Blocker alerts surfaced prominently + +**Pane management:** +- Spawn new Claude sessions for specific workflow steps in adjacent panes +- Open independent code review in a separate pane +- Attach/detach from running agent sessions +- Auto-layout: `oat panel` configures Zellij layout with sidebar panel + main workspace + +**Notifications & awareness:** +- Agent completion notifications (via Claude Code hooks) +- Review feedback alerts (watch `reviews/` directory) +- Idle agent detection (agent waiting for input) +- Optional audio/desktop notifications (like tmux-agent-status) + +**Orchestration:** +- MCP server for programmatic board control (inspired by agtx) +- Multi-agent coordination across projects +- Per-phase agent assignment (e.g., different models for research vs implementation) + +### Discovery Answers (Round 1) + +**Scale & concurrency:** +- 2-5 different repositories at a time, 1-5 OAT projects per repo +- Sometimes focused on one repo with multiple projects, other times spread across repos with 1-2 each +- Implication: **multi-repo, multi-project dashboard is essential**. Single-project kanban is a drill-down view, not the default. Need a repo-aware project hierarchy. + +**Automation & triggers:** +- One-click trigger is primary, but actions are **contextual and multi-option** — e.g., after plan completes, user might want to "Implement" OR "Open Review Pane" OR "Pause" +- Notifications always on — badge/alert when next step is ready +- Orchestrator not needed as a separate system — OAT's execution continuation already handles autonomous implementation when desired +- Implication: **action menu, not single button**. Show all valid transitions as selectable options. Notification layer runs independently. + +**Views & features (prioritized):** +1. **Diff/preview pane** — see agent changes without switching panes (like claude-squad) +2. **Git branch overview** — branches, worktrees, merge status across projects +3. **Log/activity feed** — scrollable feed of agent actions, state transitions, notifications + +**Layout:** +- **Both: sidebar + full-screen toggle**. Sidebar (20-30% width) as default for ambient awareness, expand to full-screen dashboard with a keybinding for detailed interaction. +- **Kanban is NOT the primary view** — it's a drill-down/full-screen view for when you want to see the big picture. The primary interface is a compact sidebar or tab showing project summaries, agent status, and action options. Kanban board is available as a full-screen mode (Ghostty tab or expanded pane). +- Implication: need three rendering modes — (1) **compact sidebar** (project list with status indicators and action shortcuts), (2) **expanded panel** (project detail with phase progress, logs, diff preview), (3) **full-screen kanban** (multi-project board view). Ink components should be responsive to available width. + +### Discovery Answers (Round 2) + +**Sidebar information density:** +- All of: project + phase + status, next action keybind hints, agent activity, and blockers +- Blockers slightly lower priority but still shown +- Implication: sidebar is **information-dense**, not minimal. Each project line should pack status, progress, agent state, and action hints. Think of it like a compact htop for OAT projects. + +**Multi-repo model — THIS IS A KEY REFRAME:** +- The panel is fundamentally a **workspace manager** first, OAT project tracker second +- Left-side panel always shows **workspaces** (repos) and **worktrees** within each — like superset.sh, cmux, codex desktop app, conductor.build +- That workspace panel is the always-on primary navigation surface +- OAT project state is an **additive layer** on top of the workspace view — easy to show/hide +- Implication: **the core product is a workspace/worktree navigator with agent session management.** OAT integration is a feature, not the whole product. This changes the architecture — the base layer is repo/worktree awareness, and OAT state enriches it when `.oat/` exists. + +**Diff/preview:** +- Integrate with existing tools: **yazi** (file browser), **lazygit** (git TUI) +- Or use diff viewer patterns from the landscape tools (claude-squad's inline diff) +- Implication: panel should **launch** these tools in panes rather than reimplementing diff viewing. Spawning a lazygit pane for a worktree is more powerful than a built-in diff widget. + +**Agent tracking scope:** +- Panel tracks **one project at a time**, switches as user cycles through workspaces/projects +- Not a multi-project simultaneous monitoring dashboard +- Implication: simplifies the design significantly — no need for multiplexed state watching. The active workspace determines what OAT state is displayed. Switching workspace = switching OAT context. + +### Revised Architecture Vision + +Based on discovery, the product is better described as: + +**`oat panel` = Workspace Manager + Agent Session Manager + OAT Workflow Layer** + +``` +┌─ Workspace Navigator (always visible) ──┬─ Main Content Area ──────────────┐ +│ │ │ +│ ▼ repo-1 (main) │ [Agent session / editor / etc] │ +│ ├─ worktree: feat-auth ▸ working │ │ +│ ├─ worktree: fix-api ● done │ │ +│ └─ worktree: docs ○ idle │ │ +│ │ │ +│ ▼ repo-2 (main) │ │ +│ └─ worktree: v2-migrate ▸ working │ │ +│ │ │ +│ ▶ repo-3 (collapsed) │ │ +│ │ │ +├─ OAT Layer (toggle) ────────────────────┤ │ +│ Project: auth-refactor │ │ +│ Phase: Plan → Implement │ │ +│ Tasks: ████████░░ 4/6 │ │ +│ [i] Implement [r] Review [l] Log │ │ +│ HiLL: ✓ discovery ✓ spec ✓ design │ │ +└──────────────────────────────────────────┴──────────────────────────────────┘ +``` + +**Layer 1 — Workspace Navigator** (core, always visible): +- List repos and their worktrees (git worktree list) +- Agent status per worktree (working/done/idle via hooks) +- Click/select to focus a workspace → switch multiplexer pane +- Create new worktree + branch + agent session +- Merge worktree back, clean up + +**Layer 2 — Agent Session Manager** (core): +- Track which agents are running in which panes +- Spawn new agent sessions (claude, codex, etc.) in new panes +- Attach/detach from sessions +- Launch lazygit, yazi in worktree context + +**Layer 3 — OAT Workflow Layer** (additive, toggleable): +- Only appears when selected workspace has `.oat/` +- Shows OAT project state for active workspace +- Phase progress, task tracking, HiLL checkpoints +- Action menu with valid next steps +- Full-screen kanban available as a view mode + +### Discovery Answers (Round 3) + +**HiLL checkpoint approval flow:** +- Ideal: **file viewer inline** — show the artifact (spec, design, plan) directly in the panel using something like yazi or delta diff view +- Not just a button — user wants to *read* the artifact before approving, without leaving the panel context +- Implication: panel needs an embedded file viewer or tight integration with a pager/viewer tool. Could render markdown in the expanded panel view, or spawn delta/bat in a preview pane. + +**Session resume:** +- **Context-aware resume** — panel knows where the agent stopped (e.g., "implement task p2-t3"), queues the next action, and offers to launch it with the right context pre-loaded +- Implication: panel tracks not just OAT phase but the specific `oat_current_task` value, and can construct a resume prompt. This means reading `state.md` for `oat_current_task` and generating the appropriate `claude --resume` or fresh `claude --prompt "continue oat-project-implement from p2-t3"` command. + +**Review flow:** +- **Auto-trigger review-receive** — when review feedback lands in `reviews/`, panel automatically spawns `oat-project-review-receive` without waiting for user action +- Implication: file watcher on `reviews/` directory triggers automated agent spawn. This is the first *autonomous* behavior — panel acts without user clicking anything. Should have a config toggle for users who prefer manual control. + +**Quick actions (all selected):** +1. **Tool launcher** — open lazygit/yazi/editor scoped to selected workspace's worktree +2. **New workspace wizard** — create worktree + branch + agent session in one flow +3. **Smart "Next Step"** — run `oat-project-next` to auto-detect and launch the right phase +4. **Workspace cleanup** — merge worktree branch, clean up worktree, archive OAT project + +**Superset.sh as design reference (and why not just use it):** +- [Superset.sh](https://superset.sh) is the closest UX reference — Electron + React desktop app for orchestrating parallel CLI agents across git worktrees +- Its layout matches what we want: workspaces sidebar (Cmd+B), changes/diff panel (Cmd+L), central terminal with splits/tabs, editor launcher (Cmd+O) +- Features: worktree-per-agent isolation, agent-agnostic (Claude/Codex/Gemini/etc.), built-in diff viewer, status monitoring, workspace presets via `.superset/config.json` +- Architecture: Electron + React + TailwindCSS + tRPC + Drizzle ORM, built with Vite/Turborepo/Bun + +**Why NOT superset:** +1. **Vendor lock-in** — tied to their decisions on features, direction, and priorities. Can't extend it for OAT-specific workflows. +2. **Electron overhead** — heavy runtime for what's fundamentally a terminal-adjacent tool. User already has Ghostty + Zellij. +3. **No OAT awareness** — no concept of project phases, HiLL checkpoints, skill routing, spec-driven workflows. Would need to be bolted on externally. +4. **Closed ecosystem** — ELv2 license, can't fork and customize deeply. + +**Agentastic.dev as additional reference:** +- Native macOS app (Swift) that embeds **libghostty** for GPU-accelerated terminal rendering — not a Ghostty plugin, a standalone app linking Ghostty's library +- Features: 30+ agent support, worktree-per-agent isolation, built-in diff viewer, code editor, browser panel, **kanban board** for organizing worktrees by label with drag-and-drop, fuzzy finder, multi-agent code review +- Layout: panel-based IDE with vertical tabs for worktrees, terminal panes, splits, integrated tooling +- Free but **closed source**, macOS-only — same vendor lock-in concern as superset +- Interesting technical approach: proving that libghostty can be embedded as a terminal rendering component, though this doesn't help us for a cross-platform TUI + +**What to steal from superset + agentastic's designs:** +- Workspace sidebar as always-on navigation (repos → worktrees → agent status) +- Toggle-able changes/diff panel +- Keyboard-driven workflow (Cmd+1-9 for workspace switching, splits, etc.) +- Workspace presets/config for setup/teardown scripts +- One-click tool launching (editor, terminal, etc.) + +**What we add that superset can't:** +- OAT workflow state awareness (phases, tasks, checkpoints) +- Contextual action menus based on project state machine +- Auto-triggered review-receive flow +- Context-aware session resume +- Terminal-native (runs in Zellij/tmux, no Electron) +- User-owned and extensible + +### Discovery Answers (Round 4) + +**Subagent visualization:** +- **Aggregate progress only** — show "3/7 tasks done" in the panel, don't expose individual subagent worktrees +- Subagent internals stay within OAT's execution engine; panel is a high-level dashboard, not a process manager +- Implication: panel reads `oat_current_task` and task completion counts from `implementation.md`, doesn't need to track subagent panes or worktrees + +**MVP scope:** +- **Thin vertical slice** — basic workspace nav + basic OAT state + basic actions, ship fast and iterate +- Not a workspace-manager-first or OAT-first approach — do a thin cut through all three layers simultaneously +- Implication: MVP includes (1) workspace sidebar with repo/worktree listing, (2) OAT phase indicator for active workspace, (3) one or two action triggers (Next Step, tool launcher), (4) basic agent status from hooks. Iterate from there based on what feels most useful in practice. + +### MVP Feature Set (Draft) + +Based on all discovery rounds, the thin vertical slice MVP would include: + +**Layer 1 — Workspace Navigator (minimal):** +- List repos (from config) and their worktrees (`git worktree list`) +- Agent status indicator per worktree (working/done/idle via Claude Code hooks) +- Select workspace to focus → switch context + +**Layer 2 — Agent Session Manager (minimal):** +- Spawn new agent session in a new pane for selected workspace +- Launch lazygit/yazi scoped to selected worktree (tool launcher) + +**Layer 3 — OAT Workflow (minimal):** +- Show current phase + phase status for active workspace (reads `state.md`) +- Aggregate task progress for implement phase +- "Next Step" action that runs `oat-project-next` +- File watcher on `state.md` for live updates + +**Deferred to v2:** +- Full kanban board view +- Diff/preview pane +- HiLL checkpoint approval flow with inline file viewer +- Context-aware session resume +- Auto-triggered review-receive +- New workspace wizard +- Workspace cleanup/archival +- Git branch overview +- Activity log feed +- Notifications (audio/desktop) +- Multi-project dashboard with progress bars + +### Discovery Answers (Round 5) + +**Repo registration model:** +- **Explicit add, not auto-discover** — you run something like `oat panel add /path/to/repo` and it appears in the panel. Repos don't show up until added. +- Stored in a config file (e.g., `~/.oat/panel.json`) +- Implication: panel needs an `add`/`remove` command and reads from a persistent config. No filesystem scanning magic. Simple, predictable, user-controlled. + +**Subagent visualization:** +- **Task progress only** — same display whether single-thread or subagent-driven. Just show task completion counts. +- Panel doesn't need to know about subagent internals, worktrees, or pane assignments. + +**Project end-of-lifecycle:** +- **Manual archive** — completed projects stay visible showing state details, PR link, etc. User explicitly archives when ready. +- No auto-cleanup. Completed state is informational, not a trigger for automation. +- Similar to how claude-squad, agtx, and superset handle it — done items persist until dismissed. + +**MVP scope confirmation:** +- **Thin vertical slice of both layers** — basic workspace sidebar + basic OAT state, ship fast, iterate together. + +### Open Questions (Resolved) + +All discovery questions have been answered. The artifact is ready to inform a project plan. + +_If new questions emerge during implementation, they'll be captured here._ + + **TBD** + **TBD** + **TBD** + +--- + +## Sources & References + +### Agent Orchestrator Landscape +1. [tmux-agent-status](https://github.com/samleeney/tmux-agent-status) — Bash tmux plugin; filesystem-as-message-bus pattern for agent status +2. [claude-squad](https://github.com/smtg-ai/claude-squad) — Go multi-agent dashboard with preview/diff tabs and git worktree isolation +3. [agtx](https://github.com/fynnfluegge/agtx) — Rust kanban TUI with per-phase agent assignment, MCP server, and TOML plugin system +4. [cmux](https://github.com/craigsc/cmux) — Bash git-worktree isolator for parallel Claude sessions +5. [opensessions](https://github.com/ataraxy-labs/opensessions) — TypeScript tmux sidebar with HTTP-driven agent status monitoring +6. [ntm](https://github.com/Dicklesworthstone/ntm) — Go control plane with pipelines, safety policies, and REST/WebSocket API +7. [dmux](https://github.com/standardagents/dmux) — TypeScript worktree-based multi-agent manager with built-in file browser + +### Terminal & Multiplexer +8. [Zellij Plugin System](https://zellij.dev/documentation/plugins.html) — WASM plugin API, pipe system, layout format +9. [Zellij Pipe System](https://zellij.dev/documentation/plugin-pipes.html) — CLI-to-plugin and plugin-to-plugin messaging +10. [Zellij CLI Actions](https://zellij.dev/documentation/cli-actions) — Pane management commands +11. [zjstatus](https://github.com/dj95/zjstatus) — Community Zellij status bar plugin +12. [Ghostty](https://ghostty.org/docs/features) — Terminal emulator features and IPC limitations +13. [Ghostty Scripting Discussion](https://github.com/ghostty-org/ghostty/discussions/2353) — Developer stance on programmatic control +14. [Zellij support request for Claude Code](https://github.com/anthropics/claude-code/issues/31901) + +### TUI Frameworks +15. [Ink](https://github.com/vadimdemedes/ink) — React for interactive command-line apps (TypeScript) +16. [ratatui](https://github.com/ratatui/ratatui) — Rust TUI framework +17. [zellij-tile crate](https://docs.rs/zellij-tile) — Rust SDK for Zellij plugins +18. [bubbletea](https://github.com/charmbracelet/bubbletea) — Go Elm-architecture TUI framework + +### OAT Codebase +19. `packages/cli/src/commands/state/generate.ts` — State aggregation logic +20. `.agents/skills/oat-project-next/SKILL.md` — Workflow routing algorithm +21. `.oat/projects/shared/remote-project-management/state.md` — Example project state + +### Integration +22. [Claude Code Hooks](https://docs.anthropic.com/en/docs/claude-code/hooks) — Hook system for agent lifecycle events +23. [Node.js fs.watch](https://nodejs.org/api/fs.html#fswatchfilename-options-listener) — File watching API