Milestones 4 and 5: project layer, review gate and serial merge, all measured
This commit is contained in:
@@ -390,3 +390,87 @@ status, and our screens should show that second run rather than hide it.
|
||||
under the board identity by mistake". So `authorType: user` is not proof that the person
|
||||
typed it. Screen 4 currently prints `user` as "you"; that label is wrong often enough to
|
||||
matter, and it needs a better rule than the author type alone.
|
||||
|
||||
---
|
||||
|
||||
# Milestones 4 and 5, measured, 2026-11-10
|
||||
|
||||
## The project layer
|
||||
|
||||
`bin/office <folder>` writes exactly one file, `<folder>/.harness/harness.toml`:
|
||||
|
||||
```toml
|
||||
session = "paperclip-harness"
|
||||
watching = "AUD-163"
|
||||
|
||||
[paperclip]
|
||||
company = "d4921013-e5da-4d88-8016-57d5746e2d03"
|
||||
project = "17adbb47-f966-4a82-a8ef-e48c941443ea"
|
||||
|
||||
[machine]
|
||||
host = "nixos-desktop"
|
||||
folder = "/tmp/pc-sandbox-2026-11-10"
|
||||
```
|
||||
|
||||
That is the whole of what we store: which folder, on which machine, in which session,
|
||||
showing which task. Everything else is read from Paperclip.
|
||||
|
||||
Measured, in this order, on one project:
|
||||
|
||||
| Step | Result |
|
||||
|---|---|
|
||||
| `bin/office` | wrote the file, built five panes on workspaces 1–5 |
|
||||
| run again | five panes adopted, none created |
|
||||
| `bin/board --replace` | five closed, five created, then placed 1–5 in order |
|
||||
| `bin/project-reset --dry-run` | listed the panes, three worktrees and three branches |
|
||||
| `bin/project-reset --yes` | removed them; `git worktree list` left only the main checkout |
|
||||
| `bin/office` afterwards | five panes built again from nothing |
|
||||
|
||||
Two failures found by doing it:
|
||||
|
||||
- **A rebuild scatters the workspace order.** Closing panes renumbers the rest, so the
|
||||
pages landed as shell 2, tree 3, reply 4, log 5, new 1. The fix is a final pass that
|
||||
moves each pane once to the workspace it belongs on (`tuios move-window`) and names it
|
||||
again. Without that pass the rail order is meaningless.
|
||||
- **Adopting a project must not invent a session.** The first version defaulted the session
|
||||
to the folder name, wrote that into the file, then failed because no such session
|
||||
existed — and the good session name was already overwritten. Sessions are made by a
|
||||
person, so `bin/office` now reads the recorded session first, checks it exists, and
|
||||
refuses with the exact `tuios new` command when it does not.
|
||||
|
||||
## The merge gate
|
||||
|
||||
`bin/merge` takes a lock (`flock` on `/tmp/paperclip-harness-merge.lock`), rebases the
|
||||
task's branch onto the base, runs the task's own verify command, runs `ocr` over the
|
||||
rebased range, and only then merges — then runs the verify command again on the base
|
||||
branch and rolls the merge back if it fails.
|
||||
|
||||
Every branch of that path was exercised, not reasoned about:
|
||||
|
||||
| Case | What happened |
|
||||
|---|---|
|
||||
| clean task (`AUD-163`, docs only) | merged at `2899981`; `ocr` reported `skipped`, 0 files, and said so instead of pretending to have reviewed |
|
||||
| verify command fails | exit 4, task left alone, comment written |
|
||||
| review finds a critical | `AUD-165` added `cleanup.sh` with `rm -rf "$HOME/tmp"`; exit 5, finding quoted, `cleanup.sh` never reached `main` |
|
||||
| review cannot run | exit 5, refused |
|
||||
| another merge holds the lock | exit 6, refused immediately |
|
||||
|
||||
`ocr`'s JSON has `status`, `summary.files_reviewed` and `comments[]` with `severity`
|
||||
(`critical`, `high`, …), `path`, `start_line` and `content`. Blocking severities are
|
||||
`critical` and `high`; `skipped` with zero files is "nothing to review", recorded
|
||||
plainly, while any other non-`complete` status refuses.
|
||||
|
||||
## Two API behaviours that shape the code
|
||||
|
||||
**A comment on an assigned task wakes its agent.** The merge tool's first refusal comment
|
||||
went onto a task still assigned to `Builder`, and `Builder` immediately woke and took the
|
||||
task back to `in_progress` — paying for a run to read a message meant for a person. So a
|
||||
tool's verdict is written only after the task is handed back:
|
||||
`PATCH {"assigneeAgentId": null, "assigneeUserId": "<the board user>"}`. A person's reply
|
||||
from screen 4 is the opposite case: there the wake is the point.
|
||||
|
||||
**The issue list is a summary.** `GET /api/companies/{id}/issues` carries no
|
||||
`currentExecutionWorkspace` and no blockers; the single-task read has both. Any code that
|
||||
needs a worktree path, a branch or a blocker must do a second `GET /api/issues/{id}`.
|
||||
That is the third time this list-versus-record difference has bitten, after blockers and
|
||||
after the earlier probe.
|
||||
|
||||
Reference in New Issue
Block a user