# AGENTS.md

## Subagent iteration workflow

When delegating work to subagents, follow this iteration loop:

1. **Specify** — define the task clearly: what files to change, what the
   output should look like, what constraints apply (read-only vs. build)
2. **Receive** — review the subagent's output for correctness, edge cases,
   style, and consistency with the existing codebase
3. **Critique** — send back specific issues: "Block 3 has an off-by-one
   error", "The README doesn't explain the --package flag", etc.
4. **Iterate** — repeat steps 2-3 until zero issues remain. Do not accept
   work that still has bugs, documentation gaps, or untested edge cases
5. **Accept** — only when you cannot find any more issues. **DONE means:**
   - All unit tests pass (`(fiveam:run-all-tests)` or equivalent)
   - Integration tests pass (if applicable)
   - The project runs and the changed feature works end-to-end (start the runtime,
     exercise the feature, verify output)
   - No regressions introduced (pre-existing tests still pass, unchanged features
     still work)
   - `check-parens` reports zero issues on modified org files
   - If the project has a runnable entry point (daemon, TUI, demo), verify it
     starts and the change is visible
   - **Test with the user's command, not a substitute.** If the user runs
     `passepartout tui`, test with `passepartout tui`. Loading a stale fasl
     or bypassing the normal entry point will miss compilation errors that
     only appear on a fresh build.

Key principles:
- The subagent implements, the architect reviews. Do not switch roles.
- When testing reveals a bug, the architect writes the spec and deploys a
  subagent to fix it. Do not fix bugs discovered during testing yourself.
- Every review pass must find fewer issues than the previous pass
- If a subagent introduces new bugs while fixing reported ones, reject
- Zero issues means zero: no style nits, no missing edge cases, no
  unclear prose, no dead code
- This applies to code, documentation, tests, and config files equally

## Development Cycle (every change)

0. **Start the runtime** — boot the Lisp image that loads your project.
   For passepartout: `passepartout daemon` (loads the entire project into one SBCL image).
   For standalone CL projects: SBCL with `(ql:quickload :your-project)`.
   The running image IS the development environment. The REPL is mandatory.
   The SBCL fallback below exists only for bootstrapping (when the runtime cannot
   start) and CI.

1. **Read the next TODO** — find the next unreached `*** TODO` item in
   `docs/ROADMAP.org` (search `*** TODO`). Read its prose, `:PROPERTIES:`,
   and estimated line budget. That item is the target for this change cycle.

2. **Create a branch** — `git checkout -b feature/<version>-<name>` from main.
   Every feature develops in its own branch. Branches are cheap, disposable,
   and keep abandoned work off main. Name the branch after the version and
   a short slug: `feature/v0.1.0-layout-engine`, `feature/v0.9.0-eval-harness`.
   Complex features that span multiple phases may use a single branch with
   multiple commits rather than one branch per phase.

3. **Think in org** — write your reasoning, goals, and approach in the .org file first.

4. **Write contract** — define a `** Contract` section listing each function's behavior:
   `(fn-name args)`: description. Returns/guarantees ...

5. **TDD in REPL** — the inner loop runs entirely in the running image:

   a. **Write tests in org** — add `fiveam:test` forms to the `* Test Suite` section
      of the .org source file. Tests are definitions, not explorations — write them
      in the file first.

   b. **Send tests to REPL → RED** — evaluate the test forms in the running image.
      Run the suite. It must FAIL — the implementation doesn't exist yet.
      Record the failure output in the .org file under the test.

   c. **Develop implementation in REPL** — redefine functions directly in the
      running image. Explore. Discover the real argument shapes, edge cases, and
      helper functions through interaction, not speculation. Each `defun` in the
      REPL is immediate — no tangle, no reload, sub-second feedback.

   d. **Run tests → GREEN** — after each change, re-run the suite from the REPL.
      When all tests pass, the implementation is complete. If still RED, return to
      step c. Record the passing output in the .org file under the test.

   e. **Copy code to org** — copy each finished function from the REPL into its
      own `#+begin_src lisp` block in the .org file. The code is already working;
      the file is now its permanent home. One function per block. Never write a
      function in a file that hasn't been proven in the image.

6. **Update literate prose** — write/update the explanatory text around the code:
   what it does, why it exists, how it connects to the rest of the system.

7. **Tangle** — generate the .lisp file from the .org source:
   ```
   emacs --batch --eval "(progn (require 'org) (find-file \"org/FILE.org\") (org-babel-tangle) (kill-buffer))"
   ```
   Tangling is a finalization step, not part of the inner loop. The inner loop
   (steps 5a–5e) happens entirely in the REPL. Tangle once, when the file is
   ready to commit.

8. **Run full test suite** — from the REPL, run every test suite in the project:
   ```
   (fiveam:run-all-tests)
   ```
   This catches regressions across the entire system. A function that passes its
   own tests but breaks another module is not done.

9. **Validate block balance** — check that every `#+begin_src lisp` block in the
   modified .org files has balanced parentheses. Use your project's equivalent
   function or the SBCL fallback below.

10. **Plan the test before running it.** When testing a runnable entry point
    (daemon, TUI, demo), write down three things first:
    - **Environment** — use `tmux`, not a PTY shell. tmux provides real
      `FD-STREAM`s identical to a user's terminal. A plain PTY or subprocess
      may inherit `SYNONYM-STREAM`s and miss bugs.
    - **Command** — the exact command the user runs (`passepartout tui`),
      not a substitute (`load "file.fasl"`, `compile-file`, etc.)
    - **Pass/fail criteria** — specific checks written before the test runs:
      "check for COMPILE-FILE-ERROR in output", "check for NOT is not of
      type SB-SYS:FD-STREAM", "check Goodbye after /quit", "check daemon
      responds to repl command". Do not accept "it seems to work" as a pass.

11. **Commit on the branch** — include the RED and GREEN test output recorded
    in the .org file as part of the commit message evidence:
    ```
    git add org/ lisp/ docs/
    git commit -m "v0.9.0: eval harness — 10 tasks, regression detection

    RED: 0/10 pass (tasks not yet defined)
    GREEN: 10/10 pass"
    ```

11. **Mark the origin TODO DONE** — in `docs/ROADMAP.org`, change the
    `*** TODO` item to `*** DONE` and add a `:LOGBOOK:` entry with the
    completion date. This is a separate commit on the branch:
    #+begin_src org
    :LOGBOOK:
    - State "DONE"  from "TODO"  [YYYY-MM-DD Day]
    :END:
    #+end_src

12. **Merge to main** — the merge IS the release. Rebase onto main first
    to keep history linear, then fast-forward merge:
    ```
    git checkout main
    git merge feature/v0.9.0-eval-harness
    ```

13. **Bump the submodule** — if the project is a submodule in the parent
    `memex` repo (e.g., passepartout), stage the submodule pointer and commit:
    ```
    git add projects/passepartout
    git commit -m "bump passepartout → v0.9.0"
    ```
    Standalone projects skip this step.

14. **Delete the branch** — `git branch -d feature/v0.9.0-eval-harness`.
    Abandoned branches can be deleted before merge with no cleanup needed.

## Branch Policy

- Every feature starts on a branch from main. Branch names: `feature/<version>-<slug>`.
- ROADMAP.org changes (DONE markers, LOGBOOK entries) happen on the branch, not
  on main directly. They merge to main with the feature.
- If a feature fails or is abandoned, delete the branch. No revert commits, no
  dead code on main, no `;; OBSOLETE` comments. Git history preserves the
  experiment if you need to reference it later.
- Rebase onto main before merging. Keep history linear. No merge commits.
- Complex features that span multiple roadmap versions may live on one branch
  with multiple commits, merging to main when the entire chain is stable.
- **Bug fixes, typos, docs-only edits, and single-session jobs do not get a
  branch.** Commit them directly to main. The heuristic: if it can be finished
  in one session and has no plausible alternative that could replace it, it
  goes to main. If it spans sessions or might be abandoned for a better
  approach, it gets a branch.

## Tools

All tools are standalone scripts in `projects/<tool>/`, committed to the memex.
They survive upgrades, reinstalls, and new clones.

| Tool | What | Usage |
|------|------|-------|
| `check-parens` | Validate paren balance in org lisp blocks (uses SBCL reader) | `projects/check-parens/check-parens org/file.org` |
| `repl-block` | Extract and send an org lisp block to the daemon REPL | `projects/repl-block/repl-block org/file.org --function foo` |
| `check-tangle` | Tangle an org file and compile the result in SBCL | `projects/check-tangle/check-tangle org/file.org` |
| `repl` | Send lisp code to the running daemon | `projects/repl-tool/repl "(+ 1 2)"` |
| `tangle` | Tangle an org file via Emacs batch | `projects/tangle-tool/tangle org/file.org` |
| `org-eval` | Evaluate an org src block via Emacs | `projects/org-eval-tool/org-eval org/file.org` |
| `verify-repl` | Compliance: check defuns have REPL-VERIFIED comments | `projects/verify-repl-tool/verify-repl org/` |

## Package reference

When sending code to the REPL, use the correct `(in-package ...)` form first.

### Passepartout

| Package | When to use |
|---------|-------------|
| `:passepartout` | Core system — skills, gates, memory, dispatcher |
| `:passepartout.channel-tui` | TUI — event handlers, view, state |
| `<project>-tests` | Test suites — run with `(fiveam:run (intern ...))` |

### cl-tty

| Package | When to use |
|---------|-------------|
| `:cl-tty.backend` | Backend protocol — terminal init, draw-text, read-event |
| `:cl-tty.rendering` | Framebuffer — make-framebuffer, flush-framebuffer |
| `:cl-tty.input` | Input — key-event, defkeymap, dispatch-key-event, text-input, textarea |
| `:cl-tty.layout` | Layout — vbox, hbox, spacer, layout-calculate |
| `:cl-tty.box` | Components — box, text, word-wrap, char-width, dirty-mixin, render protocol |
| `:cl-tty.theme` | Theme — theme class, define-preset, load-preset, theme-color |
| `:cl-tty.dialog` | Dialog — dialog stack, select, select-dialog, alert-dialog, toast |
| `:cl-tty.slot` | Slot/plugin system |
| `:cl-tty.markdown` | Markdown — parse-blocks, parse-inline, highlight-code, render-md |

## Setup

Before working on CL projects in this monorepo:

- **Quicklisp local-projects** — ensure `~/quicklisp/local-projects/` is a symlink
  to `~/memex/projects/`. This eliminates stale copies and ensures cl-tty,
  passepartout, and other projects are loaded from the same location being edited:
  ```
  ln -sf ~/memex/projects/cl-tty ~/quicklisp/local-projects/cl-tty
  ```

- **No .lisp files** — projects contain only `.org` source files. `.lisp` files
  are generated by tangling and are never edited directly. After cloning or
  pulling, tangle all org files before loading:
  ```
  cd projects/passepartout && for f in org/*.org; do projects/tangle-tool/tangle "$f"; done
  ```

## Commands

Tangle a single file:
  projects/tangle-tool/tangle org/FILE.org

Or:
  emacs --batch --eval "(progn (require 'org) (find-file \"org/FILE.org\") (org-babel-tangle) (kill-buffer))"

Run tests (from REPL):
  (fiveam:run (intern "SUITE-NAME" :project-TESTS))
  (fiveam:run-all-tests)

Run tests (SBCL fallback — only when the runtime cannot start):
  sbcl --noinform \
    --eval '(ql:quickload :your-project :silent t)' \
    --eval '(load "lisp/FILE.lisp")' \
    --eval '(fiveam:run (intern "SUITE-NAME" :project-TESTS))' --quit

For error details: bind fiveam:*on-failure* to :debug

## REPL — mandatory

All development happens in a running Lisp image. Start your runtime:
- Passepartout: `passepartout daemon` — boots the entire project, listens on port 9105
- Standalone CL projects: `sbcl` with `(ql:quickload :your-project)`

Send code from opencode using the `lisp` tool (any SBCL project) or the `repl`
tool (passepartout daemon on port 9105). The inner loop (step 5a–5e) never leaves
the REPL:

1. Send test forms from .org to REPL → RED
2. Redefine functions in REPL → test → iterate
3. Send tests → GREEN
4. Copy working code back to .org

Tangle only when the file is complete and ready to commit. Never batch-compile
outside the image when the runtime is available. Use the SBCL fallback above only
when the runtime itself cannot start.

## Rules

- After copying code from the REPL to the .org file, run ~../check-parens/check-parens <file.org>~ to verify all Lisp blocks have balanced parentheses. This catches mismatched parens before tangling:
  ~projects/check-parens/check-parens org/channel-tui-view.org~
- .org is source of truth; .lisp is generated — never edit .lisp directly
- Every code change starts with a contract and a failing test
- Prove RED before writing implementation
- Implementation is developed in the REPL, then copied to .org — never write
  code in a file that hasn't been proven in the image
- Validate before committing
- If a tool fails, explain why and ask before trying alternatives
- Before shipping a version, run the `** File Update Checklist` in `docs/ROADMAP.org`
- **YOU MAY NOT** push a version tag (e.g., `v0.5.0`), create a GitHub release,
  or run `git push` that triggers CI/CD version workflows without explicit
  permission. Ask first.

## Library/Application Boundary (passepartout + cl-tty)

The line between cl-tty and passepartout must be clear. Violations produce
duplicated code, inconsistent UX, and maintenance burden across both projects.

**The rule: before writing any UI code in passepartout, check cl-tty first.**
If the abstraction already exists in cl-tty, use it. If it doesn't exist, add it
to cl-tty first, then consume it in passepartout.

| This belongs in **cl-tty** (library) | This belongs in **passepartout** (application) |
|---------------------------------------|------------------------------------------------|
| Widget rendering, layout computation, dirty tracking | Which widgets to show and when |
| Theme system (presets, role→hex resolution, load/switch) | Theme choice and application-specific semantic keys |
| Markdown parser, renderer, syntax highlighter | What markdown content to render |
| Input handling (keymaps, text-input, textarea, event dispatch) | What keybindings to register and what actions they trigger |
| Dialog, select, toast rendering and keyboard navigation | Dialog content and on-submit/on-select callbacks |
| Word-wrap, char-width, cursor rendering | Cursor position and text content |
| Box, scrollbox, tabbar, panel containers | Data displayed inside containers |

**Concrete checklist before writing any UI function in a passepartout .org file:**
1. Run `(do-external-symbols (s :cl-tty.box) ...)` or check this table
2. If the function exists in cl-tty, call it — do not reimplement
3. If the function doesn't exist but belongs in a library (generic widget, theme
   mechanism, layout primitive, input handler), add it to cl-tty first
4. If the function is passepartout-specific business logic (command dispatch,
   daemon communication, gate trace data model), write it in passepartout

Common violations found in the codebase (do not repeat):
- Writing a parallel theme system (`*tui-theme*`, `*tui-theme-presets*`,
  `theme-switch`) — use `cl-tty.theme` instead
- Writing a custom word-wrap — use `cl-tty.box:word-wrap` instead
- Writing a custom markdown parser or syntax highlighter — use
  `cl-tty.markdown` instead
- Bypassing `select-handle-key` with manual dialog key dispatch — use
  `select-handle-key` and `render-dialog` instead
- Hardcoding inline hex colors — use `(theme-color :role)` instead
- Manual framebuffer coordinate arithmetic — use `vbox`/`hbox`/`spacer` layout
  from `cl-tty.layout` instead
