Commit Graph
100 Commits
Author SHA1 Message Date
istos eafd1ad546 board: 31 → in-progress (istos) 2026-07-31 11:12:41 +02:00
istos 66c6ce0165 board: 30 → done (istos) 2026-07-31 11:12:41 +02:00
Ronald AshriandGitHub ebe33eef87 Merge pull request #25 from 12vectors/task/30-docs-design-on-true-bench-content
30 — Re-cut the docs design on content bench actually has
2026-07-31 11:12:33 +02:00
istos 7dc336d33f board: 30 PR opened (istos) 2026-07-31 11:10:18 +02:00
istos e304829a26 board: 30 → review (istos) 2026-07-31 11:10:09 +02:00
istos 66f06b96b9 docs design: turn 2 of Bench Docs on real bench content
Turn 1 of the docs design wrapped three good layouts around invented
content — a bench.toml, a brew-installed CLI, "lanes", a handoff rule
engine, port 7331, PAS-411 cards. None of it exists. Turn 2 holds all
three layouts and replaces every word, label and code block with the
product AGENTS.md, README.md and manager/core/.env.example describe.

  1b Dockside — hero terminal is the real install one-liner, the real
    first-run questions and the real board.py startup lines on 26071.
    The six doors are install, the five stages, agents on the board,
    PRs & review, team mode, the three-layer law.
  1a Harbour — the article is "Claiming a card": what each move writes
    to **Assignee:**, the two local/.env keys that turn it on, and the
    board: <n> → <stage> (<name>) commit it makes.
  1c Logbook — the CLI reference is now a settings reference, with
    BOARD_SYNC as the shown key and the board's own activity log (real
    event strings from sync.py, agents.py and github.py) in the pinned
    console.

The design project's write path needs an interactive permission grant,
which a headless run cannot answer, so the finished file lands here as
reference/bench-docs-design-turn2.dc.html for a human to upload as
"Bench Docs.dc.html". The card scoped itself to the design project with
no repo files changed; this one file is the deviation, and a git rm
undoes it once the upload has happened.
2026-07-31 11:09:42 +02:00
istos 163e705e45 board: 30 → in-progress (istos) 2026-07-31 10:59:20 +02:00
istos 1161552f8b board: 29 → done (istos) 2026-07-31 10:27:52 +02:00
Ronald AshriandGitHub 098f83fc3b Merge pull request #24 from 12vectors/task/29-first-run-writes-local-env
29 — A first run writes local/.env, asking the questions it can't guess
2026-07-31 10:27:44 +02:00
istos 086f1bd76f board: 29 PR opened (istos) 2026-07-31 07:56:33 +02:00
istos 6cb7fc7eee board: 29 → review (istos) 2026-07-31 07:56:24 +02:00
istosandClaude Opus 5 635205486b A first run writes local/.env, asking what it cannot guess
A project could run bench for months without a manager/local/.env:
everything fell back to core/.env.example, so the two settings that
change what bench is — claim-on-move and syncing through origin/main —
were invisible to anyone who had not read that file.

install.py now writes it on a first run. It asks three questions no
default can be right about (solo or team, which agent adapter, the
project's test command) and writes core/.env.example with the answers
substituted into their lines, comments and all keys intact — so the
written file is where the project reads what else it can change.

- Runs after first_boot_clean: .env is one of the two things the
  first-boot guard reads, so writing it earlier would skip the clean.
- Never asks without a terminal on stdin. install.py sits on the path
  of start.sh, update.sh and every hook, so no TTY prints one line and
  carries on rather than blocking a board start on an invisible prompt.
  --dry-run reports the questions and writes nothing.
- An existing .env is never touched; --setup is the only way back to
  the questions, pre-filling from the current file and rewriting it in
  place, so start.sh's fallback BOARD_PORT line survives.
- Bare Enter takes every default (the result is the example verbatim,
  i.e. today's behaviour exactly); Ctrl-D skips the rest.

start.sh needed no change — it already calls install.py before the port
dance, which is the right order. Tests drive the interactive runs over a
real pty and the non-interactive ones with /dev/null on stdin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 07:55:57 +02:00
istos 96fcae7d8c board: 29 → in-progress (istos) 2026-07-31 07:45:15 +02:00
istos 8ab97398a5 board: 10 → done (istos) 2026-07-30 15:10:59 +02:00
Ronald AshriandGitHub b06d030fd3 Merge pull request #23 from 12vectors/task/10-review-launches-die-on-multiedit-deny-rule
10 — Review launches die at startup: MultiEdit deny rule names a tool that no longer exists
2026-07-30 15:10:55 +02:00
istos c9fbf17775 board: 10 PR opened (istos) 2026-07-30 15:10:23 +02:00
istos 152bbdd7f3 board: 10 → review (istos) 2026-07-30 14:34:51 +02:00
istosandClaude Opus 5 47779a35bc Assert the review launch's flag list whole, and say why it moves
The MultiEdit deny rule was already dropped in the hotfix; this is task
10's follow-up half — the guard that catches the next fossil.

The claude adapter's review branch spells "cannot edit files" in the
vendor's own tool names, and that roster moves: a rule naming a tool the
installed CLI does not have is refused at startup, so the launch dies
before the agent speaks. Membership assertions could not see that
happening, so the stub-binary test now asserts each mode's flag list
literally (settings payload elided) — a deny name added or renamed shows
up as a diff a reviewer must re-verify against the installed CLI. The
run script carries the same warning where the list actually lives.

The helper drops a leaked AGENT_MODEL, since --model would otherwise
appear in an argv now compared whole; test_agent_model.py owns that flag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 14:34:19 +02:00
istos 3fed71246c board: 25 → to-do (istos) 2026-07-30 14:26:50 +02:00
istos 1f1cf3cc74 board: 28 → to-do (istos) 2026-07-30 14:26:45 +02:00
istos 63ddfe4b95 board: 27 → to-do (istos) 2026-07-30 14:26:43 +02:00
istos f2bd570f59 board: 10 → in-progress (istos) 2026-07-30 14:26:41 +02:00
istosandClaude Fable 5 ae9f10e3d0 Cards 27, 28: update offers to commit itself; a pinned port stays pinned
Both from the first downstream 0.2 update report: the vendored diff
left as homework in a clean-main workflow, and the TIME_WAIT port hop
that rewrote an explicit BOARD_PORT pin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 14:25:14 +02:00
istosandClaude Fable 5 a5dd0ac565 Version 0.2-alpha
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 14:15:14 +02:00
istos f64afcfd92 board: 11 → done (istos) 2026-07-30 14:13:20 +02:00
Ronald AshriandGitHub 8d1e84819d Merge pull request #22 from 12vectors/task/11-failed-agent-runs-must-be-visible
11 — A failed agent run must leave a visible trace on the card
2026-07-30 14:13:11 +02:00
istosandClaude Fable 5 c2cf1e92c0 Card 26: forge adapters — core stops speaking GitHub
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 14:12:06 +02:00
istos 780cdf97f4 board: 11 PR opened (istos) 2026-07-30 13:11:03 +02:00
istos 55a5d2a781 board: 11 → review (istos) 2026-07-30 13:10:55 +02:00
istosandClaude Opus 5 d94b6ee423 A failed run is a state the card wears
An agent that exits non-zero was the least-handled outcome on the board:
one ticker line that scrolled away, a card that looked exactly as it did
before the launch, and the log's contents — usually the whole story — left
on disk. Three launches died in an API outage and the board said nothing a
person would notice.

So the outcome is recorded on the run: exit code, ended-at, and the
cleaned tail of its log as the excerpt (a launch that died before the
agent spoke says so rather than showing blank). From that the card takes
the alarm border and a `run failed` pill, with the excerpt on hover and in
full in the card sheet; a toast fires, because failures are rare and
actionable; and the ticker line stays, now naming what the log ended on.

Every headless kind lands in _finish, so work, act-pr, PR review and the
relevance check are all covered. The state is scoped to the run and the
stage: the next launch supersedes it, and the watcher drops it when the
card moves, so nothing follows a card into review/.

The way is cleared for the relaunch too: a failed run with no commits has
its worktree and empty branch removed — the reasoning a decline already
uses — so ▸ start work works without a hand `git worktree remove`. A
failed run with commits keeps its worktree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 13:10:26 +02:00
istos 3693b66574 board: 11 → in-progress (istos) 2026-07-30 12:54:59 +02:00
istos 829a764870 board: 21 → done (istos) 2026-07-30 12:51:35 +02:00
Ronald AshriandGitHub 64ad311267 Merge pull request #20 from 12vectors/task/21-shebang-means-executable
21 — A shebang means executable: fix the shipped modes and test the invariant
2026-07-30 12:51:27 +02:00
istos eb29af4977 board: 24 → done (istos) 2026-07-30 12:51:17 +02:00
Ronald AshriandGitHub 5eed46d963 Merge pull request #21 from 12vectors/task/24-model-chip-beside-the-agent-name
24 — A model chip beside every agent name
2026-07-30 12:51:12 +02:00
istos 663552f5db board: 24 PR opened (istos) 2026-07-30 12:05:02 +02:00
istos fa0be43fe6 board: 24 → review (istos) 2026-07-30 12:04:54 +02:00
istos 053e1d7d71 A model chip beside every agent name
Task 12 recorded which model each launch rode, but it surfaced only at
the tail of the session-detail metadata line, where nobody's eye lands.
With work and review models diverging per intent, "which brain did this"
is a routine review question, so the answer now sits beside the name that
did it.

One component, modelChip(), rendered wherever a name identifies a run:
the sessions list row, the session-detail header, the Focus header and
the working card's agent line. It borrows the session-id hash's register
— small, mono, dim — and takes no colour, because a model is not a state.
The vendor prefix a name repeats is dropped for the chip (claude-opus-4-8
→ opus-4-8, anthropic/x → x) with the whole string on hover; an
unfamiliar name is shown as recorded rather than guessed at.

A launch that never knew its model wears no chip at all — an inherited
default, or a session replayed from disk after a restart. The two
metadata lines that used to carry the model keep only what the chip
cannot say: "model inherited", and only when it genuinely is.

Tests lift the two functions out of the page and run them in node (skipped
where node is absent) for the shortening, escaping and silence; placement
and register stay source-level invariants, as board.html has no frontend
test runner.
2026-07-30 12:04:36 +02:00
istos 8794061db3 board: 21 PR opened (istos) 2026-07-30 12:00:29 +02:00
istos 5dd154cef7 board: 21 → review (istos) 2026-07-30 12:00:21 +02:00
istosandClaude Opus 5 eec69ba575 A shebang means executable: test the invariant, cut the patch
install.py shipped mode 644 in v0.1-alpha, so the README one-liner's
./install.py was permission-denied on every install. The repo modes and
update.sh's repair line were hotfixed already (all 14 shebang'd tracked
files are 100755; update.sh:165 names install.py) — this is the guard
that keeps them that way, and the patch release that heals the field.

- tests/test_release_artifact.py: the invariant, read from the tar
  header rather than the repo — every member whose content starts `#!`
  must carry the exec bit, failing by name. No exception list: there is
  no shipped file that legitimately may not be run, and gaining one
  means editing the test with a reason. Two tests keep it honest: the
  guard is proven to bite by repacking the real artifact with
  install.py's mode stripped, and an unpacked release must run
  ./install.py as a program, not via python3.
- tests/test_update_from_release.py: an install whose install.py is
  mode 644 — the shape v0.1-alpha left in the field — is executable
  again after any update. `cp` onto an existing file keeps the
  destination's mode, so the chmod line is the only thing healing it;
  removing that line fails this test.
- manager/core/VERSION → 0.1-alpha.1: cutting the patch is the honest
  move over a release note telling users to work around it.
- manager/core/release-manifest: the invariant, stated where the
  shipping list lives.

Verified: python3 -m unittest discover -s tests (267 tests, OK). Both
new assertions were watched failing first — a build-side `chmod -x` on
the staged install.py, and update.sh with install.py dropped from its
chmod list — then restored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 12:00:01 +02:00
istos 39ab96d81b board: 24 → in-progress (istos) 2026-07-30 11:52:54 +02:00
istos 9e5e44d48f board: 21 → in-progress (istos) 2026-07-30 11:52:51 +02:00
istos de81ce653e board: 20 → done (istos) 2026-07-30 11:51:41 +02:00
istos 13af1d0a60 Merge branch 'task/20-replicas-render-actors-act' 2026-07-30 11:51:36 +02:00
istos f4e01377c5 board: 23 → done (istos) 2026-07-30 11:49:50 +02:00
istos dd00a77238 Merge branch 'task/23-updated-bench-logo-from-design' 2026-07-30 11:49:45 +02:00
istos f241fc5968 board: 20 → review (istos) 2026-07-30 11:19:57 +02:00
istosandClaude Opus 5 96ec71ff33 docs: state syncs, reactions don't
AGENTS.md gets the "State syncs; reactions don't" section under Syncing
boards — the actor-only rule, the file-carried backstops behind it, and
the two consequences you can see (a half-done side effect is nobody's to
finish automatically; ownership gates work launches). The Pull requests
section says which board opens one and how ↑ open PR asks afterwards, the
card-actions list gains ▸ take over and ↑ open PR, "Claiming a card" stops
saying the claim refuses nothing, and the merge & clean up paragraph
documents the origin-side merge — including the constraint the local path
hid: whoever clicks needs merge rights on the repo, not just push rights.

.env.example: BOARD_COMMIT_MOVES documents the claim as a launch gate,
BOARD_SYNC the actor-only reactions and the origin-side merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:18:24 +02:00
istosandClaude Opus 5 755d40dd33 etiquette: the actor's board acts, every other replica renders
Task 19 gave every board the same truth; this makes exactly one of them
react to it. State syncs; reactions don't.

- watch.py: attribution is now the trigger gate. _actor returns (who,
  remote), and a move a pull applied — the arrivals sync files — renders
  and narrates but opens no PR. A plain mv on this disk still acts: inert
  means "happened elsewhere", not "unattributed".
- github.py: the file-carried gates behind that rule, so the rare double
  is harmless rather than loud. The **PR:** line commits itself in team
  mode (taskfiles.commit_edit, sharing the move's pathspec-scoped commit),
  so it reaches the other boards instead of sitting in one working tree —
  where it would also stall sync outright; and a `gh pr create` that races
  anyway adopts the PR GitHub already holds.
- No board finishes the actor's half-done side effect on its own: the
  startup reconcile stands down in team mode and a review card with a
  branch and no PR carries ↑ open PR (POST /api/pr/open), which is a
  person deciding rather than N boards guessing. _open_pr raises its
  reasons now, so the automatic path narrates them and the explicit one
  toasts them.
- agents.py: the claim gates work launches. A card someone else holds
  refuses, naming them; ▸ take over is the deliberate second path (armed
  like everything that costs tokens) and reassigns via taskfiles'
  set_assignee; an unheld card claims itself on launch. Only in team mode
  — with BOARD_COMMIT_MOVES off nothing writes an assignee, so nothing
  reads one as a lock.
- github.complete_task: with BOARD_SYNC on, merge & clean up runs
  `gh pr merge` and lets the beat deliver the result, so local main only
  ever fast-forwards and no board makes a merge commit of its own. A
  branch without a PR is refused with a pointer to ↑ open PR. Sync off
  keeps the local merge path exactly as it was.

Verified with tests/test_actor_acts.py: two real clones of a real bare
upstream and a stub gh — the replica that only renders, the PR line that
travels, the double that adopts, the takeover that reassigns, and both
merge paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:18:17 +02:00
istos 3d427670ef board: 23 → review (istos) 2026-07-30 11:16:52 +02:00
istosandClaude Opus 5 3efa0f6aef The header wears the design's wordmark
The design's answer to "what is the bench logo" is that there isn't a drawn
one: the word is the logo — "bench", lowercase, set in Zilla Slab SemiBold
and tracked -.015em, with the b lifted out of the same face as the icon.
The board still showed the old "Bench" in Plex Sans and a dot in the tab.

Bring both in line. The board may not fetch a font for a logo, so the
wordmark ships as outlines: one path per letter on a 1000-unit em, filled
with currentColor so Night and Daylight each get their own ink and the mark
never wears a colour that means state. --logo-h is the single number that
sizes it. The tab icon is the wordmark's own b on the accent tile it
already had, reusing the b's outline string character for character.

Nothing beside it moves: the mono path line still sits on the wordmark's
baseline, at the design's 11px, and the header's padding, switcher and chips
are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:16:25 +02:00
istos 865b4eeb3b board: 23 → in-progress (istos) 2026-07-30 10:59:35 +02:00
istos eaa2d3f781 board: 20 → in-progress (istos) 2026-07-30 10:56:57 +02:00
istos 0e37d01b64 board: 20 → to-do (istos) 2026-07-30 10:56:35 +02:00
istos c812314706 board: 20 → backlog (istos) 2026-07-30 10:52:36 +02:00
istos e80ff6adc2 board: 20 → to-do (istos) 2026-07-30 10:52:30 +02:00
istos 3916b0942f board: 19 → done (istos) 2026-07-30 10:49:16 +02:00
istos 0bd9279284 Merge branch 'task/19-boards-sync-through-origin-main' 2026-07-30 10:49:11 +02:00
istos 3e3612916c board: 19 → review (istos) 2026-07-30 09:48:46 +02:00
istos ee8469d009 docs: team mode through origin/main
AGENTS.md gets the "Syncing boards" section — what pushes, what pulls,
what a lost race looks like, and the two disciplines that make it safe:
local main advances only through the board and origin, and sync never
merges. The module map gains sync.py, the settings paragraph and the
Board view's description gain the sync chip and the author attribution,
and .env.example documents BOARD_SYNC / BOARD_SYNC_INTERVAL, including
the fetch-rate line for rate-limited remotes.
2026-07-30 09:47:49 +02:00
istos 9d98e2c55a sync: origin/main as the shared board, behind BOARD_SYNC
Team mode's second half. A board-made move already commits itself (18);
now that commit publishes, every board pulls on a beat, and a card two
boards move at once resolves the way git resolves everything else — the
push race is the concurrency control.

- core/sync.py: push is event-driven (a new state.COMMIT_HOOKS registry
  fires it from taskfiles, so taskfiles stays left of everything that
  reacts to it); pull is a beat that fast-forwards, or replays this
  board's own commits on top when the two diverged. A replay that
  conflicts on a task file drops the local move — origin is the
  linearizer — and toasts who took the card.
- The piggyback guard stands in front of every push and every replay:
  each local-ahead commit on main must be `board: `-prefixed, so a
  human's unpushed work is never published as a side effect of a card
  moving. Uncommitted changes, a checkout off main and an unreachable
  origin all stall or degrade rather than risk anything, each narrated
  once instead of once per beat.
- watch.py names the commit author instead of "disk" for moves a pull
  brought, via the arrivals sync files for it; its narration moved out
  of the loop into narrate() so it can be tested directly.
- The board grows a sync chip that appears only when sync stops
  converging, and the SSE stream grows a toast type so the server can
  say something to the person, not just to the ticker.

Gate off (the default) means no fetch, no push, no thread, no change.

Verified with tests/test_boards_sync.py: two real clones of a real bare
upstream race each other through every case above.
2026-07-30 09:47:38 +02:00
istos ed5bf81a4d board: 19 → in-progress (istos) — complete the half-committed move
The claim commit e31de61 was hand-made after the board (gate still off)
had already moved the file on disk: it added the in-progress copy but
missed staging the backlog deletion. Nothing was lost — same content,
both paths, now one.
2026-07-30 09:24:44 +02:00
istos 0af24ccbc4 board: 20 → in-progress (istos) 2026-07-30 09:23:14 +02:00
istos e31de617b7 board: 19 → in-progress (istos) 2026-07-30 09:22:13 +02:00
istosandClaude Fable 5 c6be5a6298 Board state: 04, 18, 22 done; 24 queued to to-do
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 09:18:44 +02:00
istos 48a8d6aa9c Merge branch 'task/18-cards-claimed-on-move' 2026-07-30 09:17:19 +02:00
istosandClaude Opus 4.8 2e84227407 Clarify gate-off docs: an existing assignee is still read and shown
Copilot review on PR #15: the 'gate off' wording in AGENTS.md and
.env.example read as 'no assignee', but read_task() always parses the
Assignee line and the card face always renders it. The gate governs only
whether a move writes/clears the line and commits. Reword both to say so.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 09:16:06 +02:00
istosandClaude Opus 5 de5cedb31f Card 22: the tab names its project
Every board tab read "Bench — task board", so the moment a second bench
existed the tab bar stopped saying which was which. The title now leads
with the project — "<project> · bench" — because tab truncation eats the
tail and the tail is the same in every bench tab.

The project is config.PROJECT: the repo directory's name, or BOARD_TITLE
from local/.env for people whose checkouts are all called "app". The
server renders it into the served page's <title>, so the tab is right on
first paint with no flicker from generic to named; /api/state carries it
too, and renderTitle() keeps it in step when the view switcher swaps the
tail (sessions, focus). The project stays the first word regardless, and
nothing else writes document.title.

Tests: tests/test_board_title.py covers the server half in fresh
interpreters (BOARD_TITLE resolution, the rendered title, escaping, the
rest of the page untouched) and the browser half as source invariants,
the same way the other board.html tests work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:11:58 +02:00
istosandClaude Opus 5 3629116afa Document the claim: a "Claiming a card" section and the Assignee field
AGENTS.md gains the convention — claiming is moving, the assignee is who
launches agents on the card, hand-moves bypass the claim and should update
the line by hand, and git identities collide the way git's do — plus the
**Assignee:** header field beside Status/Priority/Type and the
BOARD_COMMIT_MOVES gate with its unpushed-main consequence.
.env.example documents the setting with its default (off).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:06:00 +02:00
istosandClaude Opus 5 22255f2842 Cards are claimed on move: the assignee written, the move committed
Moving a card out of backlog/ or to-do/ now claims it. taskfiles.move_task
writes an **Assignee:** line from `git config user.name` — first claim only,
an existing assignee is never overwritten — and clears it when a card is
walked all the way back to backlog/. The same move then commits itself: one
commit, the move and the claim together, staged by pathspec so a developer's
unrelated staged work is neither committed nor unstaged, messaged
`board: <number> → <stage> (<name>)`. Hooks are skipped (bookkeeping, not
code) and nothing is pushed — that is task 19's job. A commit that fails is
narrated in the ticker; the card has already moved, and disk is the truth.

All of it sits behind BOARD_COMMIT_MOVES, off by default, so a
single-player board moves cards byte-identically to before.

The card face shows the owner instead of "nobody yet" in every stage — on
done/ cards the line reads as history — and the who row now escapes what
the file said.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:05:48 +02:00
istosandClaude Fable 5 a647b849e8 Card 25: in-lane ordering via a board-managed Rank line
Rank rides in the card header so every reorder is a single-file write
that merges one card at a time — the shape the directory-is-truth law
and task 19's sync model both demand. Sparse integer ranks, midpoint
drops, one sanctioned multi-file renumber when gaps exhaust.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 09:03:25 +02:00
istosandClaude Fable 5 5bc297d859 Card 24: a model chip beside every agent name
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:59:26 +02:00
istosandClaude Fable 5 6dff0d3f84 Card 23: bring the updated bench logo in from the design project
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:58:24 +02:00
istos 374003f5d7 Merge branch 'task/04-activity-log-resize-grip-does-nothing' 2026-07-30 08:51:54 +02:00
istosandClaude Fable 5 de4ff8fe92 Card 22: the board's tab names its project
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:50:51 +02:00
istosandClaude Fable 5 ead190ac03 Shebang'd files ship executable; update.sh heals existing installs (task 21 hotfix)
First field bug of v0.1-alpha: install.py was committed 100644, so
every install shipped it permission-denied for direct ./install.py use
(start.sh's python3 invocation masked it). Exec bit set on all four
shebang'd files (install.py, board.py, hook_settings.py,
permission_config.py) and install.py added to update.sh's post-update
chmod line so already-broken installs heal on their next update. The
artifact-side invariant test — every shipped #! file executable — is
card 21.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:50:08 +02:00
istosandClaude Fable 5 e12d5b1a85 Cards 18-20: the multi-user arc — claim on move, sync through origin, replica etiquette
From the 2026-07-30 design discussion: origin/main becomes the truth
and every checkout a replica. 18 makes moving a card claim it (assignee
written and committed atomically); 19 pushes board moves and pulls on a
beat, with git as the lock server and take-over toasts for lost races;
20 keeps side effects on the actor's board only, gates launches on
claims, and routes team-mode merges through origin. All gated off by
default; single-player bench is untouched until opted in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:45:58 +02:00
istosandClaude Fable 5 0aeba69b01 CI: run the test suite on every PR and push to main
Ubuntu runner, Python 3.11 and 3.13 (stdlib-only project: oldest
supported and newest current). Git identity and init.defaultBranch=main
configured for the tests that build scratch repos; everything else the
suite needs (bash, tar, rsync, stub binaries) is on the runner already.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:30:55 +02:00
istosandClaude Fable 5 84d260318d Card 04 to review; gitignore .DS_Store
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:29:53 +02:00
istosandClaude Fable 5 a0eb3d6bb6 Version 0.1-alpha — the first release
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:29:14 +02:00
istosandClaude Fable 5 f9d55efa15 Make the activity log's resize grip actually resize
The grip's handlers write #logbody's inline height, but the CSS said
flex:1 — inside the column-flex #logpanel the flex algorithm sized the
element from basis 0% + grow and never consulted the height property,
so dragging did nothing. Worse, mouseup persisted a re-read
offsetHeight (the flex-computed value), overwriting the remembered
size with the status quo on every attempt.

Re-couple the two: #logbody becomes flex:none so the written height is
authoritative again, with max-height:60vh guarding overflow. The
handlers track the clamped height they compute and mouseup persists
that value; the load-time restore runs through the same clamp and
writes the same property.

The drawer grip writes width on the position:fixed #drawer itself, so
no flex competes there — covered by a non-regression test alongside
the source-level invariants in tests/test_log_resize.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:22:36 +02:00
istosandClaude Fable 5 9cf6edd3a9 Board sweep: 15, 16, 17 landed; done pile archived
The first full cycle closes: all seventeen cards from two days of
self-hosted dogfooding are done, and the earlier done pile moved to
tasks/archive/ — out of every column, never deleted. The board starts
its next cycle empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:18:19 +02:00
istosandClaude Fable 5 74adc09566 Merge origin/main into task/15: releases meet the AGENTS.md world and the LICENSE
Resolutions honouring both sides:
- CLAUDE.md: main's pointer form wins; this branch's release-era wording
  (tree diagram line, Updating section) ported into AGENTS.md where the
  brief now lives.
- update.sh: this branch's release-based rewrite wins; main's additions
  to the old file list (AGENTS.md from task 13, LICENSE) are carried by
  the release-manifest instead: added copy AGENTS.md and copy LICENSE.
- Starter local/: manifest and release.sh now seed the split pair
  (AGENTS.md notes + pointer CLAUDE.md) mirroring task 13's layout;
  artifact test updated to match.
- tests/test_update_round_trip.py (main's, aimed at the removed
  git-clone mechanism) retired; its two 13-specific intents ported into
  test_update_from_release.py as
  test_agents_brief_replaces_an_old_vendor_named_copy, its third already
  covered there. 147 tests green.
- release.sh's lost executable bit restored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:15:28 +02:00
istos 035ee87d4b Merge branch 'task/16-conflicted-prs-first-class-with-an-agent-path' 2026-07-30 08:08:13 +02:00
istosandClaude Fable 5 580ff879ac Make card actions click-solid: stable targets, visible windows, instant feedback
The hover actions were easy to miss and mute when hit: a ~20px target
that slid in on hover and reshaped when arming swapped the label, then
zero feedback between the second click and the SSE redraw seconds later.

board.html, one state machine for every action button (hover actions and
$-command chips alike), its truth in S.acts so SSE re-renders mid-window
redraw the same picture:

- Stable geometry: rest/confirm/busy labels stack in one grid cell so the
  button is born at its widest state's width; the slot fades in place
  (opacity only); buttons are 24px tall and the toprow reserves that
  height, so nothing moves or grows under the pointer.
- Visible armed window: alarm colour plus a draining underline over the
  disarm timer, lengthened to 5s; a rebuilt button rejoins the drain
  partway via a negative animation delay.
- Instant busy state: firing disables the button, breathes the glyph and
  shows a participle (starting…, holding…) until the redraw replaces the
  card, the runner reports failure, or a 15s timeout restores it with a
  toast naming the action — never a silent revert.
- Misses fall harmlessly: the slot pads its hitbox (padding + negative
  margin) and swallows clicks itself, so gap clicks neither fire an
  action nor open the card sheet.

tests/test_card_actions.py holds the source-level invariants; the full
suite (119 tests) passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 08:02:46 +02:00
istosandClaude Fable 5 e515c8289c Address PR review: path-safety guards in update.sh, no bare asserts in setUpClass
Refuse assets whose tar members or manifest copy paths reach outside
their root, checked before anything is replaced; create parent dirs for
future nested copy entries. Test both refusals hermetically. Replace
setUpClass bare asserts with explicit checks that survive python -O.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:57:35 +02:00
istosandClaude Fable 5 8d08470530 Give act-pr agents a conflict-resolution path, additive only
Both adapters' act-pr stances gain git fetch and git merge so a
conflicted PR can be resolved by merging main into the branch. History
rewriting is denied outright rather than merely unlisted — the plain
'git push' allow would otherwise cover the force spellings — via deny
rules in the claude settings and last-match-wins deny globs in the
opencode config. git rebase stays absent and denied in both.

act-pr.md teaches the flow: merge origin/main, honour both sides'
intent, tests green, the resolution in its own commit naming files and
choices, covered explicitly in the report — and refuse semantic
conflicts (main made the branch's premise false) by aborting the merge
and naming the collision for a human. The adapters README records the
widened contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:57:27 +02:00
istosandClaude Fable 5 43e4319817 Surface PR merge conflicts on the review card
The poller now reads GitHub's mergeable field alongside reviews and
checks. A CONFLICTING PR drops any approved-green verdict (as
changes-needed-by-you, not a CI failure), wears an alarm-coloured
conflicts chip in the card's footer row, and narrates the flip in the
ticker. GitHub computes mergeability lazily, so UNKNOWN keeps the
previous reading instead of flapping the chip.

The poll fold is now a pure function (_fold), so the verdict logic is
testable without gh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:57:19 +02:00
istosandClaude Fable 5 7a7ad39d0a Test release.sh's publish refusals: dirty tree, existing tag
Both stops precede any gh call, so they run hermetically in a scratch
git repo carrying only release.sh and a VERSION.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:49:41 +02:00
istosandClaude Fable 5 370fdee833 Docs: install is the release one-liner, a clone is dev mode
README's first instruction becomes the tokenless curl | tar against
the stable latest-release URL; the clone path moves to a "working on
bench itself" note. CLAUDE.md's Updating section and .env.example's
BENCH_SOURCE comment describe release-based updates: the source is
stamped at build time, BENCH_SOURCE is an override rather than a
prerequisite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:48:11 +02:00
istosandClaude Fable 5 d6fb478466 Build bench releases from a manifest; update.sh consumes them
The repo is two things at once — bench-the-project and bench-the-
distribution — and install/update conflated them by cloning the repo
and subtracting what should not have come along. Invert it: one
curated artifact, correct by construction.

- manager/core/release-manifest: the whole shipping list in one place
  (copy/tree/once/keep/seed classes). update.sh's hardcoded top-level
  file list, promoted.
- release.sh: builds bench.tar.gz from the manifest (contents at the
  tarball root, stable asset name — the tokenless latest-release URL
  depends on both), stamps the source repo into the shipped update.sh,
  refuses on dirty tree or existing tag, tags v<VERSION> and publishes
  via gh release create. Never ships in the artifact.
- update.sh: downloads the latest release (BENCH_REF pins a tag) via
  gh with an anonymous curl fallback; refuses when the asset's VERSION
  disagrees with its tag; replaces manager/core/ wholesale plus the
  artifact manifest's `copy` files; touches nothing else. No release
  published -> says so and changes nothing; no silent git fallback.
- tests: the tarball equals exactly the manifest (no cards, no local/
  content beyond the generated starter, no state/tests/.claude), the
  artifact installs pristine and boots the board, and update.sh's
  replace/survive/refuse paths run hermetically against PATH-stubbed
  gh and curl.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:48:03 +02:00
istosandClaude Fable 5 1bd2cf533b Merge origin/main into task/14: CLAUDE.md became the AGENTS.md pointer
Rename-vs-edit conflict: task 13 (landed) moved the workflow brief to
AGENTS.md leaving CLAUDE.md as a pointer, while this branch had edited
the brief's work-launch section in place. Resolution: CLAUDE.md keeps
main's pointer form; this branch's documentation of the
branch-from-origin behaviour moved verbatim into AGENTS.md where that
text now lives. agents.py and the rest auto-merged; 103 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:46:26 +02:00
istos 4f3170378f Merge branch 'task/12-choose-agent-models-per-intent' 2026-07-30 07:41:04 +02:00
istosandClaude Fable 5 ac75c8ecd3 Add MIT license (12vectors, 2026)
Ships with the distribution: update.sh's core-owned file list carries
LICENSE so installed projects receive it, and the release manifest
(task 15) inherits the list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:40:17 +02:00
istosandClaude Fable 5 9fed08d174 Let the board choose agent models, per launch intent
BOARD_AGENT_MODEL picks one model for every headless launch;
BOARD_AGENT_MODEL_WORK / _ACT_PR / _REVIEW beat it per intent (review
covers PR reviews and relevance checks). Empty — the default — means
the variable never reaches the adapter and the vendor's own resolution
applies, exactly today's behaviour; a stray AGENT_MODEL in the board's
own environment is stripped rather than inherited silently.

Core carries the name as an opaque string: config resolves intent →
model, _launch passes it as AGENT_MODEL, and each adapter renders it
natively — claude appends --model, opencode sets the generated
config's model key ("provider/model-id", per its docs). The resolved
model (or its absence) is recorded on the agent record, so the
Sessions and Focus views state what a run actually rode instead of
leaving it to whoever's machine the board happens to run on.

Stub-binary tests pin the byte-identical-when-unset guarantee, the
per-intent resolution, the launch-env seam and the record.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:37:39 +02:00
istosandClaude Fable 5 0111a9ffc8 PR #7 review: count 'ahead' against HEAD, not local main
The ticker note says the branch point is N ahead of this checkout, but
the count compared origin/main to the local main branch — misleading
whenever the board runs from another branch or detached HEAD. HEAD is
the fallback base, so HEAD..origin/main is exactly what launching from
it would have missed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:36:51 +02:00
istos 51887a0e73 Merge branch 'task/13-agents-md-as-the-canonical-brief' 2026-07-30 07:36:40 +02:00
istosandClaude Fable 5 e10b5cf1ed Move the workflow brief to AGENTS.md; keep CLAUDE.md as a pointer
AGENTS.md is the cross-vendor name (opencode, Codex, Gemini CLI read it
natively; so does current Claude Code), and the brief goes to every
vendor's agents — an opencode work agent previously launched with no
project brief at all. The content moves verbatim to AGENTS.md at the
root and in manager/local/; each CLAUDE.md becomes a load-bearing
compatibility pointer (@AGENTS.md import) for older Claude Code CLIs.

update.sh's core-owned file list now carries both names, so updating an
old-layout install lands AGENTS.md and replaces the full CLAUDE.md with
the pointer instead of resurrecting it. All four core prompts, README,
the board.py/taskfiles.py docstrings, the task template and the adapter
contract docs now name AGENTS.md; adapters/README.md notes that vendors
reading AGENTS.md from the working tree need no adapter work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:35:28 +02:00
istosandClaude Fable 5 e1b9a8f662 Let kanban columns flex down to a token floor on small laptops
Five fixed 296px columns overflowed 13" MacBooks (~1556px footprint vs
~1440 viewport), clipping the Done column. Columns now grow/shrink from
a zero basis, capped at --col (pixel-identical at >=1556px) and floored
at a new --col-min token (240px: five columns + gaps + board padding =
1276px, fitting 1280px viewports). Below the floor the existing
overflow-x:auto fallback is unchanged.

Sessions, Focus and the header are already fluid at 1280px — checked,
nothing to fix there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:31:41 +02:00