From 15ef39c6764c4b14294e08a43dac95349563665a Mon Sep 17 00:00:00 2001 From: twaldin Date: Mon, 7 Sep 2026 21:45:11 -0700 Subject: [PATCH 1/2] docs: align maintained guidance with current CLI --- CLAUDE.md | 21 ++++++++++---------- README.md | 38 ++++++++++++++++++++----------------- docs/testing-tui.md | 23 ++++++++++++++-------- src/adapters/CLAUDE.md | 4 ++-- templates/flt-skill/base.md | 8 ++++---- 5 files changed, 53 insertions(+), 41 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b12be60..a84c6b5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ flt is a **set of primitives a user can compose however they want.** `spawn`, `k - **Worktree-per-agent isolation.** `src/worktree.ts` creates a `flt/` branch and worktree under `tmpdir()` so concurrent agents don't trample each other's git state. `--no-worktree` opts out. - **Per-CLI adapters.** `src/adapters/` defines `CliAdapter` (spawn args, instruction filename, ready-state detection, dialog auto-approval, status detection). Most adapters delegate to `@twaldin/harness-ts` for shared detection logic; flt adds the spawn/lifecycle/dialog-bypass on top. - **Status by polling pane content.** No agent SDKs are wired up. `src/controller/poller.ts` runs `tmux capture-pane`, feeds the buffer to `adapter.detectStatus`, and writes status transitions back to state. Workflow advancement, ephemeral cleanup, and TUI status colors all hang off this single transition signal. -- **Instructions are projected, not owned.** `src/instructions.ts` (delegating to `@twaldin/harness-ts`) prepends an `` block to the project's native instruction file (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, etc.) and restores the original on kill. flt never owns the file outside its markers. +- **Instructions are projected, not owned.** `src/instructions.ts` (delegating to `@twaldin/harness-ts`) prepends a block delimited by `flt:start` / `flt:end` to the adapter's instruction file (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, etc.) and restores the original on kill. flt never owns the file outside its markers. ## Repo map @@ -24,6 +24,7 @@ src/ tmux.ts tmux session lifecycle + key delivery worktree.ts git worktree creation under tmpdir() instructions.ts project instruction file projection (flt:start/end) + flt-skill.ts synthetic flt protocol skill builder presets.ts preset CRUD (~/.flt/presets.json) skills.ts skill discovery + projection per CLI harness.ts @twaldin/harness-ts wrapper bits @@ -40,7 +41,7 @@ src/ adapters/ per-CLI adapters — see src/adapters/CLAUDE.md commands/ one file per `flt ` (init/spawn/send/kill/…) - controller/ daemon: server.ts + client.ts + poller.ts + controller/ daemon: server.ts + client.ts + poller.ts + reaper.ts workflow/ YAML workflow engine — see src/workflow/CLAUDE.md tui/ raw-ANSI TUI — see src/tui/CLAUDE.md pr-adapters/ gh / gt / manual PR creation backends @@ -48,11 +49,11 @@ src/ types/ ambient module declarations (e.g. harness-ts.d.ts) utils/ stripAnsi etc. -templates/ instruction-file templates (system-block-*.md, workflow-block.md) -docs/ design docs (droid-oauth-proxy.md, ssh-sandbox-design.md) +templates/ instruction blocks, flt protocol skill, roles, skills, workflows +docs/ isolated TUI testing guide (testing-tui.md) demo/ demo gif assets and scripts -examples/ example workflows + presets -scripts/ release + smoke-test scripts +examples/ example workflows +scripts/ diagnostic, fixture, and smoke-test scripts tests/ unit/ bun test — pure functions, parsers integration/ bun test — end-to-end flows (real tmux/git) @@ -66,7 +67,7 @@ tests/ - **Bun-only.** Runtime is Bun (≥1.0.0). Tests use `bun test`. Don't introduce node-only APIs without checking; many `Bun.*` calls (`Bun.spawn`, `Bun.file`, fetch-over-unix-socket) appear by design. - **TypeScript with no `any` casts.** No `as any` / `as unknown as` shortcuts. Add narrow types or fix the source type. - **One command per file in `src/commands/`.** Each file exports a `Direct(...)` function (callable in-process by tests and the controller) and a thin Commander wrapper in `cli.ts`. Don't fold multiple subcommands into one file. -- **One adapter per file in `src/adapters/`** following the `CliAdapter` interface (`src/adapters/types.ts`). Register the new adapter in `src/adapters/registry.ts` and add it to the `Record` map. See `src/adapters/CLAUDE.md`. +- **One adapter per file in `src/adapters/`** following the `CliAdapter` interface (`src/adapters/types.ts`). Register the new adapter in `src/adapters/registry.ts`: import it and update `knownAdapters`, `adapterCommands`, and `adapterFactories`. See `src/adapters/CLAUDE.md`. - **Workflow steps are typed by `type` field.** New step kinds add a discriminated union member in `src/workflow/types.ts` and a handler in `src/workflow/engine.ts`. See `src/workflow/CLAUDE.md`. - **`flt:start`/`flt:end` markers are sacred.** Anything inside is regenerated on spawn and cleaned on kill. Never hand-edit content between those markers — edit the templates in `templates/` or the projection logic in `src/instructions.ts` instead. - **Filenames for instructions: CLAUDE.md is canonical, AGENTS.md is a symlink.** Where a CLAUDE.md exists in this repo, an `AGENTS.md` symlink points to it so OpenCode/Codex/etc. pick up the same content. Don't divergently edit AGENTS.md. @@ -74,10 +75,10 @@ tests/ ## Gotchas - **Adapter parity isn't perfect.** Each CLI has its own dialog phrasing, ready signal, and submit-key sequence. When you add behavior, exercise it on at least claude-code + codex + opencode; they cover the three main quirks (multi-line paste, slow startup, custom instruction file). -- **Aider was removed** from the registry — it's REPL-driven (`/run`, `/add`, `/edit`) with no autonomous shell tool, so it doesn't fit the autonomous-agent-with-tools model. The README still mentions it; don't re-add an aider adapter without revisiting that decision. +- **Aider was removed** from the registry — it's REPL-driven (`/run`, `/add`, `/edit`) with no autonomous shell tool, so it doesn't fit the autonomous-agent-with-tools model. Don't re-add an aider adapter without revisiting that decision. - **OpenCode uses a custom agent file** (`.opencode/agents/flt.md`), not the project's `AGENTS.md`. See `src/adapters/opencode.ts`. - **Status polling, not push.** A change in the running agent's pane is observed at most ~1s late. Tests that race against status transitions need to wait, not assume. -- **`flt kill` nukes the worktree.** If you used the helper worktree to stash diffs, capture them before kill. +- **`flt kill` removes the worktree by default.** Capture any needed diffs first, or use `flt kill --preserve-worktree` to retain it for recovery. - **Workflow advancement fires on `running → idle`.** Steps that exit before going idle (instant errors, refusals to start) won't trigger `advanceWorkflow`. Add an explicit failure path if you introduce a new such case. - **TUI is a separate process from the controller.** `flt controller stop && flt controller start` does NOT restart the TUI — the TUI process loaded `panels.ts` and other rendering code ONCE at launch and keeps using that snapshot. After syncing source changes into the install directory (or merging a fix that touches sidebar/render code), you have to quit the TUI (`q` or kill the `flt tui` process) AND restart it for the change to take effect. The controller restart only matters for spawn/kill/poller logic, not for what's painted on screen. - **Two install paths exist for `flt`.** The `flt` binary on `$PATH` is typically `~/.bun/bin/flt`, which symlinks to `~/.bun/install/global/node_modules/@twaldin/flt-cli/src/cli.ts`. There is ALSO an `~/.nvm/versions/node//lib/node_modules/@twaldin/flt-cli/` install if `npm i -g` was used at any point. When syncing local source changes into the install directory for testing, copy into BOTH paths (or at least the one that backs `$(readlink "$(which flt)")`) — copying to only one means the running TUI/controller might still be on the stale code. `md5 ~/flt/src/foo.ts /src/foo.ts` is the quickest verifier. @@ -86,6 +87,6 @@ tests/ - Tests: `bun test`, `bun test:unit`, `bun test:integration`, or `bun test ` for one file. - Local CLI run: `bun src/cli.ts ` (the `flt` bin in `package.json` points at the same). -- Don't `git add -A` blindly — untracked session artifacts (`AUDIT.md`, `HANDOFF.md`, `tree.md`, `plan.json`, `handoffs/`, etc.) live at the root and are gitignored; only `AGENTS.md` is a symlink (→ CLAUDE.md). +- Don't `git add -A` blindly — untracked session artifacts (`AUDIT.md`, `HANDOFF.md`, `tree.md`, `plan.json`, `handoffs/`, etc.) live at the root and are gitignored; preserve them and the `AGENTS.md` → `CLAUDE.md` symlinks. - For a feature that touches adapter behavior, read the corresponding `harness-ts` adapter first; many fields are inherited and shouldn't be overridden in flt. - `scripts/tui-pilot.sh smoke` spins up a fully isolated flt (private HOME + tmux server) and pilots the TUI via tuistory — use it to verify TUI/skill changes without touching the live fleet; see `docs/testing-tui.md`. diff --git a/README.md b/README.md index f76b0a4..179f05a 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ Requires [Bun](https://bun.sh) and [tmux](https://github.com/tmux/tmux). You nee ## Quick start ```bash -flt init # initialize fleet + start controller -flt tui # open the terminal UI +flt init # run inside tmux: initialize fleet, start controller, open TUI +# After quitting the TUI with q, reconnect with: flt tui # spawn agents in any CLI from the same interface flt spawn coder -c claude-code -m sonnet -d ~/project "fix the login bug" @@ -46,7 +46,7 @@ flt presets add coder -c codex -m gpt-5.3-codex flt spawn coder -d ~/project "fix the parser bug" # name == preset, auto-resolved ``` -Presets can store everything — `cli`, `model`, `dir`, `parent`, `worktree`, `persistent`, `soul`. A fully configured preset means spawn is just name + task: +Presets can also store `dir`, `parent`, `worktree`, `persistent`, and `soul` in `~/.flt/presets.json`; `flt presets add` exposes CLI, model, and description. A fully configured preset means spawn is just name + task: ```bash */30 * * * * flt spawn monitor "run health checks" # dir, parent, cli, model all in preset @@ -79,7 +79,8 @@ DEC 2026 synchronized output for zero-flicker rendering on modern terminals (Gho | Normal | `s` | Spawn | | Normal | `r` | Reply to selected agent | | Normal | `m` | Inbox | -| Normal | `t` | Shell | +| Normal | `t` | Metrics | +| Normal | `T` | Shell | | Normal | `K` | Kill agent | | Normal | `q` | Quit | | Log focus | `j/k` | Scroll | @@ -111,7 +112,7 @@ Each adapter handles the messy per-CLI differences so you don't have to: | SWE-agent | `swe-agent` | Prompt template injection, no instruction file | | pi | `pi` | OpenAI subscription OAuth flow, slash-command prompt detection | -Dialog auto-approval means agents spawned from cron never block on permission prompts. This is what makes unattended operation work. +Adapters auto-approve recognized dialogs to support unattended operation. Unknown prompts or missing authentication can still block an agent; inspect its output with `flt logs `. ## Agent identity @@ -119,7 +120,7 @@ Agents get their identity from two sources: **SOUL.md** — who the agent is. Lives at `~/.flt/agents//SOUL.md` or referenced via preset. Defines role, behavior, domain knowledge. Injected into the CLI's native instruction file on spawn. -**Skills** — what the agent can do. Markdown files in `~/.flt/skills/` (global) or `~/.flt/agents//skills/` (per-agent). For Claude Code, skills become slash commands. For other CLIs, skills are embedded in the instruction file. +**Skills** — what the agent can do. Each skill is a directory containing `SKILL.md` under `~/.flt/skills/`. Enable skills with a preset's `skills` list, repeatable `flt spawn --skill `, or `--all-skills`. flt copies selected skill directories into the adapter's project-local skill area; discovery and instruction-file indexing vary by CLI. The project's own instructions (CLAUDE.md, AGENTS.md, GEMINI.md) stay untouched — flt prepends its block with markers and removes it on kill. @@ -146,31 +147,34 @@ Messaging is simple: `flt send parent` routes to whoever spawned you. `flt send ## Workflows -YAML state machines that chain agents together: +YAML state machines that chain agents together. This example uses the `cc-coder` and `cc-reviewer` presets seeded by `flt init` (both use Claude Code): ```yaml # ~/.flt/workflows/code-review.yaml name: code-review steps: - id: implement - preset: coder - task: "Implement {task}" + preset: cc-coder + task: "Implement {task}. Address previous review feedback: {fail_reason}" + max_retries: 2 on_complete: review - id: review - preset: reviewer + preset: cc-reviewer + dir: "{steps.implement.worktree}" + worktree: false task: "Review PR {pr}. Branch: {steps.implement.branch}" on_complete: done on_fail: implement - max_retries: 2 ``` ```bash -flt workflow run code-review -t "add OAuth login" -flt workflow status code-review +flt workflow run code-review -t "add OAuth login" -d ~/project +flt workflow status # show active runs +# For one run, use the ID printed by run/list: flt workflow status ``` -Later steps can reference earlier agents' worktrees, branches, and PRs via template variables. Agents signal transitions with `flt workflow pass` or `flt workflow fail`. +Later steps can reference earlier agents' worktrees and branches; `{pr}` holds the auto-created PR URL when available. The reviewer uses the implementer's worktree rather than a new checkout. `max_retries` belongs on the retry target (`implement` here) to bound the review loop. Agents signal transitions with `flt workflow pass` or `flt workflow fail`. ## Architecture @@ -182,12 +186,12 @@ Later steps can reference earlier agents' worktrees, branches, and PRs via templ config.json # settings, theme inbox.log # agent messages activity.log # JSONL event stream - workflows/ # YAML definitions + run state + workflows/ # YAML definitions + runs// # run.json, results, handoffs, and other run artifacts skills/ # global skills agents// SOUL.md # identity state.md # agent state (compaction/resume) - skills/ # per-agent skills ``` ``` @@ -218,7 +222,7 @@ flt activity # fleet event log flt exit # shut down fleet flt presets list|add|remove # manage presets flt workflow run|status|list|cancel # manage workflows -flt skills list # list available skills +flt skill list # list available skills flt controller start|stop|status # manage controller daemon ``` diff --git a/docs/testing-tui.md b/docs/testing-tui.md index 48eaddf..0e5a6a3 100644 --- a/docs/testing-tui.md +++ b/docs/testing-tui.md @@ -152,23 +152,27 @@ reviewer's job is to judge it. ## Cross-CLI skill discovery verification (plan 007 worked example) -Use the harness to confirm each adapter finds `.flt/skills/flt/SKILL.md` at -spawn time. For each CLI adapter to test (here: `pi` as a cheap proxy): +Use the harness to check projected skill files and confirm that a CLI can +discover them at spawn time. Projection paths vary by adapter; see +`projectSkills` in `src/skills.ts`. The example below uses `pi`, which currently +projects into `.flt/skills/`. Running it invokes a real provider-backed agent; +it is not part of the TUI-chrome smoke test. ```bash ISO=$(bash scripts/tui-pilot.sh up --link ~/.claude --link ~/.codex) export FLT_PILOT_HOME=$ISO +WORKDIR=$(mktemp -d "$ISO/skill-check.XXXXXX") -# Spawn a cheap agent with skill projection enabled. +# Spawn an agent in a private throwaway project directory. # FLT_ALLOW_NO_WORKTREE=1 is an env var (read via process.env), not a CLI arg — # it must be passed as a prefix or via the env command. FLT_ALLOW_NO_WORKTREE=1 \ env -u TMUX HOME=$ISO TMUX_TMPDIR=$ISO/tmux \ - bun src/cli.ts spawn skill-check --cli pi --no-worktree --dir /tmp + bun src/cli.ts spawn skill-check --cli pi --no-worktree --dir "$WORKDIR" # Check that the flt skill was projected into the agent's instruction file area. # The skill projection lands in the project dir supplied via --dir: -ls /tmp/.flt/skills/flt/SKILL.md +ls "$WORKDIR/.flt/skills/flt/SKILL.md" # Or grep the agent logs to confirm skill injection: env -u TMUX HOME=$ISO TMUX_TMPDIR=$ISO/tmux \ @@ -178,6 +182,9 @@ env -u TMUX HOME=$ISO TMUX_TMPDIR=$ISO/tmux \ bash scripts/tui-pilot.sh down --rm ``` -For a full cross-adapter sweep, repeat for each adapter (`claude-code`, `codex`, -`opencode`, etc.) and assert that `.flt/skills/flt/SKILL.md` is present in the -worktree after spawn and absent after kill. +For a cross-adapter sweep, check the destination selected by `projectSkills` +for each adapter, not a universal `.flt/skills/` path. Claude Code uses +`.claude/skills/`, OpenCode uses `.opencode/skills/`, and Droid uses +`.factory/skills/`. Check file projection separately from actual CLI discovery; +the latter requires an installed, authenticated CLI. Use a fresh private +project directory per probe and verify managed files are removed after kill. diff --git a/src/adapters/CLAUDE.md b/src/adapters/CLAUDE.md index 184b04d..a021875 100644 --- a/src/adapters/CLAUDE.md +++ b/src/adapters/CLAUDE.md @@ -57,9 +57,9 @@ When changing detection behavior, **fix it in `harness-ts` first** — flt and t 1. Confirm the CLI has an autonomous shell tool (not REPL-only). aider was removed for failing this test. 2. Add (or reuse) a harness-ts adapter for shared detection. 3. Create `src/adapters/.ts` exporting `Adapter: CliAdapter`. -4. Register it in `registry.ts` (import + add to the `adapters` map). +4. Register it in `registry.ts`: import it and update `knownAdapters`, `adapterCommands`, and `adapterFactories`. 5. Add fixtures under `tests/adapters/` covering ready detection, dialog auto-approval, idle/running transitions. -6. Update `docs/adapters.md` (end-user table) and any README adapter listing. +6. Update the end-user adapter table in `README.md`. ## Currently registered diff --git a/templates/flt-skill/base.md b/templates/flt-skill/base.md index 595a281..bfb03bd 100644 --- a/templates/flt-skill/base.md +++ b/templates/flt-skill/base.md @@ -21,7 +21,7 @@ block is intentionally minimal — full protocol lives here. | Ask the human a structured question | `flt ask human ''` (only when permitted; see comms below) | | List live agents | `flt list` | | Read another agent's pane | `flt logs ` | -| Spawn a sub-agent (depth-limited) | `flt spawn --preset --bootstrap ""` | +| Spawn a sub-agent (depth-limited) | `flt spawn --preset ""` | | Tear down a sub-agent (and its worktree) | `flt kill ` | ## Completion + handoffs @@ -39,9 +39,9 @@ block is intentionally minimal — full protocol lives here. ## Skills -Other skills you have are listed in your CLI's normal skill index -(claude-code: `.claude/skills/`; opencode: `.opencode/skills/`; others: -`.flt/skills/`). Read a skill only when it's relevant to the current task. +Enable additional skills with the preset's `skills` list, `--skill `, +or `--all-skills`. Find projected skills in the adapter's project-local skill +area or instruction-file index. Read a skill when it's relevant to the task. ## Do not From 96d2caf6035ea3f5ad52fd1d7c5ff6461924ae82 Mon Sep 17 00:00:00 2001 From: twaldin Date: Mon, 7 Sep 2026 21:48:02 -0700 Subject: [PATCH 2/2] docs: document isolated Bun checks for contributors --- CONTRIBUTING.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aaaa037..e4745a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,10 +12,29 @@ Thanks for the interest. flt is a solo project; I merge PRs when I have time. ```bash bun install +bunx tsc --noEmit bun test ``` -All tests must pass (`bun test`). If your change needs new tests, add them. +All tests and the typecheck must pass. CI runs `bunx tsc --noEmit`, then the unit and integration suites separately. `bun test` also includes adapter telemetry tests. + +Integration tests create real tmux sessions. On a machine with a live fleet, run tests with a temporary HOME and private tmux socket directory, with `TMUX` unset so it cannot select your live server: + +```bash +( + sandbox=$(mktemp -d /tmp/flt-tests.XXXXXX) + mkdir -p "$sandbox/tmux" + trap 'env -u TMUX TMUX_TMPDIR="$sandbox/tmux" tmux kill-server 2>/dev/null || true; rm -rf "$sandbox"' EXIT + git config --file "$sandbox/.gitconfig" init.defaultBranch main + git config --file "$sandbox/.gitconfig" user.name "flt tests" + git config --file "$sandbox/.gitconfig" user.email "flt-tests@example.invalid" + env -u TMUX -u FLT_AGENT_NAME -u FLT_PARENT_NAME -u FLT_PARENT_SESSION \ + -u FLT_DEPTH -u FLT_SKILLS_DIR -u FLT_ALLOW_NO_WORKTREE \ + HOME="$sandbox" TMUX_TMPDIR="$sandbox/tmux" bun test +) +``` + +Install dependencies before entering the sandbox. The temporary Git configuration is only for fixture repositories; your configured identity is unchanged. The tool-gated TUI pilot test skips when `tctl` is absent from the temporary HOME. See [the TUI testing guide](docs/testing-tui.md) for separate TUI verification. ## Style