Commit Graph
107 Commits
Author SHA1 Message Date
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>
v0.2-alpha
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