Files
hqt/.hq/2026-06-09-plan-hq-terminal.md
felixm 90944948f5 Initial commit: hqt — HQ Terminal TUI
TUI for orchestrating AI coding harness sessions (Claude Code, Codex,
Kiro, etc.) via tmux. Click CLI bootstraps a Textual TUI over
ProjectService/SessionService backed by SQLite, spawning harness
sessions as tmux windows through TmuxManager.

Includes recent fixes:
- Visible Tab focus highlight on dialog OK/Cancel buttons
- Auto-select first project on launch
- Auto-select first session + per-project session-selection memory
- tmux new-window targets an explicit free index, fixing
  "index N in use" failures (broken spawn/attach in attached sessions)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:06:57 -04:00

248 lines
9.5 KiB
Markdown

# HQ Terminal — Implementation Plan
Reference spec: `.hq/2026-06-09-spec-hq-terminal.md`
## Phase 1: Project Scaffold
Set up the new workspace with tooling, dependencies, and basic structure.
### Steps
1. **Create workspace** at `~/dev/hqt/` with `uv init --lib --name hqt`
2. **Configure pyproject.toml**:
- Python ≥ 3.12
- Dependencies: `textual>=2.0`, `sqlalchemy>=2.0`, `click>=8.0`
- Dev dependencies: `pytest`, `pytest-asyncio`, `ruff`
- Entry point: `[project.scripts] hqt = "hqt.cli:main"`
3. **Create package structure** per spec: `src/hqt/` with all subdirectories
4. **Create `.hq/PROJECT.md`** for the new project
5. **Verify**: `uv run hqt --help` produces output
## Phase 2: Database Layer
SQLite models, engine, and schema management.
### Steps
1. **`src/hqt/config.py`** — Settings dataclass with paths:
- `db_path`: `~/.local/share/hqt/hqt.db`
- `config_dir`: `~/.config/hqt/`
- `skills_dir`: `~/.config/hqt/skills/`
- `tmux_path`: `tmux`
- `tui_session_name`: `hqt-main`
2. **`src/hqt/db/models.py`** — SQLAlchemy models: `Project`, `Harness`, `Session`, `McpServer`, `ProjectMcpServer`, `ProjectSkill`
3. **`src/hqt/db/session.py`** — Engine factory, session factory, `ensure_db()` (creates dir + tables)
4. **`src/hqt/db/migrate.py`** — Additive migration helper (add missing columns)
5. **Tests**: model creation, ensure_db on fresh + existing databases
6. **Verify**: `uv run pytest tests/test_db.py -q` passes
## Phase 3: TmuxRunner
The async tmux command abstraction. Largely ported from HQ.
### Steps
1. **`src/hqt/tmux/runner.py`** — Port `TmuxRunner` from HQ:
- `new_session(name, cwd, command, width, height)`
- `has_session(name) -> bool`
- `kill_session(name)`
- `send_text(name, text)`
- `send_enter(name)`
- `capture_pane(name) -> str`
- `switch_client(target)` ← NEW (not in HQ)
- `list_sessions() -> list[str]` ← NEW
2. **`src/hqt/tmux/manager.py`** — Higher-level session lifecycle:
- `spawn_session(session: Session, config: SpawnConfig) -> None`
- `resume_session(session: Session, config: SpawnConfig) -> None`
- `kill_session(session: Session) -> None`
- `attach_session(session: Session) -> None` (switch-client)
- `is_alive(session: Session) -> bool`
- `poll_all_sessions(sessions: list[Session]) -> dict[int, bool]`
3. **Tests**: mock `_exec` function, verify command construction
4. **Verify**: `uv run pytest tests/test_tmux.py -q` passes
## Phase 4: Harness Abstraction Layer
The configurator protocol and concrete implementations.
### Steps
1. **`src/hqt/harnesses/base.py`** — `HarnessConfigurator` protocol, `SpawnConfig`, `HarnessIdentity` dataclasses
2. **`src/hqt/harnesses/claude.py`** — Claude Code configurator:
- UUID generation via `uuid5`
- Spawn: `claude --session-id {uuid} --model {model}`
- Resume: `claude --resume {uuid}`
- MCP: write project `.mcp.json`
- Skills: write/append `CLAUDE.md`
- Ready/busy regexes from terminal profiles
3. **`src/hqt/harnesses/kiro.py`** — Kiro CLI configurator (stub for now):
- Basic spawn command
- MCP: Kiro-managed (no injection in v1)
4. **`src/hqt/harnesses/aider.py`** — Aider configurator:
- Spawn: `aider --model {model} --restore-chat-history`
- Skills: `--read {skill_file}` flags
- No MCP support
5. **`src/hqt/harnesses/generic.py`** — Fallback for unknown harnesses:
- Env-var based config only
6. **`src/hqt/harnesses/registry.py`** — Discover available harnesses (`shutil.which`), return configurator instances
7. **Tests**: each configurator produces correct SpawnConfig for given inputs
8. **Verify**: `uv run pytest tests/test_harnesses.py -q` passes
## Phase 5: Skills & MCP Services
Skill registry and MCP server management.
### Steps
1. **`src/hqt/skills/registry.py`** — Port from HQ: scan `skills_dir`, parse frontmatter, return `Skill` objects
2. **`src/hqt/skills/injection.py`** — Port from HQ: `wrap_prompt_with_skills()`
3. **`src/hqt/mcp/service.py`** — CRUD for MCP servers and project bindings (DB operations)
4. **`src/hqt/projects/service.py`** — Project CRUD: add, list, archive, git status reading
5. **Tests**: skill discovery with fixture directory, MCP CRUD, project CRUD
6. **Verify**: `uv run pytest tests/test_skills.py tests/test_mcp.py tests/test_projects.py -q` passes
## Phase 6: Session Service (Orchestration Core)
The service that ties together DB, tmux, and harness configurators.
### Steps
1. **`src/hqt/sessions/service.py`** — Session lifecycle:
- `create_session(project_id, harness_name, nickname, model, skill_names, mcp_ids) -> Session`
- Generates harness UUID via configurator
- Builds SpawnConfig
- Calls tmux manager to spawn
- Persists session row
- `resume_session(session_id) -> None`
- Loads session from DB
- Builds resume SpawnConfig with stored UUID
- Calls tmux manager to spawn
- `stop_session(session_id) -> None`
- Kills tmux session
- Updates DB (last_activity_at)
- `delete_session(session_id) -> None`
- Kills tmux if alive
- Removes DB row
- `attach_session(session_id) -> None`
- Calls tmux switch-client
- `list_sessions(project_id) -> list[SessionInfo]`
- Joins DB data with tmux liveness
2. **Tests**: mock tmux manager, verify session lifecycle
3. **Verify**: `uv run pytest tests/test_sessions.py -q` passes
## Phase 7: Textual TUI
The user interface.
### Steps
1. **`src/hqt/tui/app.py`** — Textual `App` subclass:
- Bindings: q=quit, n=new session, a=add project, /=filter
- Compose: Header, ProjectList, SessionList, Footer
- Timer: poll tmux liveness every 3s
2. **`src/hqt/tui/widgets/project_list.py`** — ListView of projects:
- Shows name, path, branch
- Selection updates session list
- Keybindings: Enter=select, a=add, d=archive
3. **`src/hqt/tui/widgets/session_list.py`** — ListView of sessions for selected project:
- Shows harness icon, nickname, alive/dead status
- Keybindings: Enter=attach, n=new, d=delete, r=rename, R=resume (if dead)
4. **`src/hqt/tui/screens/new_session.py`** — Modal screen:
- Harness selector (dropdown)
- Nickname input
- Model selector (populated from configurator.supported_models)
- Skills multi-select
- MCP servers multi-select
- Create / Cancel buttons
5. **`src/hqt/tui/screens/add_project.py`** — Modal:
- Path input (with directory completion if feasible, or paste)
- Name override (optional)
6. **`src/hqt/tui/styles.tcss`** — Textual CSS for layout
7. **Integration test**: App mounts, projects display, key bindings respond
8. **Verify**: `uv run pytest tests/test_tui.py -q` passes (headless Textual testing)
## Phase 8: CLI Entry Point & tmux Bootstrap
Wire everything together.
### Steps
1. **`src/hqt/cli.py`** — Click CLI:
- `hqt` (default) — bootstrap into tmux + launch TUI
- `hqt list` — print projects/sessions table
- `hqt attach <id>` — switch to session
- `hqt new <project> <harness>` — create session non-interactively
- `hqt doctor` — check tmux, harness availability
2. **`src/hqt/__main__.py`** — `python -m hqt` support
3. **tmux bootstrap logic**:
```python
def bootstrap():
if not in_tmux():
# Create hqt-main session with TUI as the command
exec tmux new-session -s hqt-main "hqt --inside-tmux"
else:
# Already in tmux, just run the TUI
run_tui()
```
4. **First-run setup**: suggest tmux keybinding, create config dir/skills dir
5. **Verify**: `hqt doctor` works, `hqt` launches TUI inside tmux
## Phase 9: Integration Testing & Polish
### Steps
1. **End-to-end test**: create project → create session → verify tmux session exists → attach → kill → verify dead
2. **Handle edge cases**:
- tmux not installed → clear error
- Harness not found → show in UI, disable create
- DB locked → retry pattern
- Session name collision → fail gracefully
3. **README.md** with quick start
4. **Verify**: full test suite passes, manual smoke test with claude-code
## Dependency Graph
```
Phase 1 (scaffold)
├── Phase 2 (database)
│ │
│ ├── Phase 5 (skills + MCP)
│ │ │
│ │ └──────┐
│ │ │
│ └── Phase 6 (session service) ◄── Phase 4 (harness abstraction)
│ │
├── Phase 3 (tmux runner) ──────────────┘
│ │
│ ▼
└── Phase 7 (TUI) ◄──────────── Phase 6
Phase 8 (CLI + bootstrap)
Phase 9 (integration + polish)
```
## Estimated Effort
| Phase | Complexity | Approx. LOC |
|-------|-----------|-------------|
| 1. Scaffold | Low | ~50 (config) |
| 2. Database | Low | ~150 |
| 3. TmuxRunner | Low | ~120 (mostly port) |
| 4. Harness abstraction | Medium | ~300 |
| 5. Skills + MCP | Low | ~150 (mostly port) |
| 6. Session service | Medium | ~200 |
| 7. TUI | Medium-High | ~400 |
| 8. CLI + bootstrap | Low | ~100 |
| 9. Integration | Medium | ~150 |
| **Total** | | **~1,620** |
## Implementation Order (sequential for a single agent)
1. Phase 1 → 2. Phase 2 → 3. Phase 3 → 4. Phase 4 → 5. Phase 5 → 6. Phase 6 → 7. Phase 7 → 8. Phase 8 → 9. Phase 9
Each phase produces a working, testable increment. The TUI (Phase 7) is the largest single piece but depends on everything below it being solid.