Commit Graph
5 Commits
Author SHA1 Message Date
istos 7df6e19d0d completing a card is a state the board holds
Merge & clean up is a minute of destructive work behind one click, and
nothing recorded that it was running: /api/task/complete took a second
request as readily as the first.

state.py grows COMPLETING — claimed before the first step, released in a
finally after the last one, on success, conflict and crash alike. The
steps are already narrated as board events against the file, so
record_board_event folds the latest summary into the claim rather than
asking complete_task to report twice. It is memory, not disk: a board
killed mid-completion leaves no card stuck busy.

/api/state carries the registry and every change publishes it, so the
board renders the busy card from the server's truth rather than from
what one tab happened to click.
2026-07-31 16:57:09 +02:00
istosandClaude Opus 5 d9907fadde core: a long report keeps its head, and says what it cut
The closing report was capped by keeping its last 3000 characters, so a
report longer than that lost exactly the sentence the prompt contract
puts first — the state of the work — and the task file began mid-word.
Card 32's record still shows it.

reports.py is now the one place a report is clipped: it keeps both ends
(the headline and the review-first pointer), cuts the middle on line
boundaries, and leaves one line of prose saying how many characters went
and naming the log under local/state/agent/logs/ that still holds all of
it. One documented cap, CAP = 12000, replaces 3000 in agents.py and 1500
in github.py, so the PR body and the task file carry the same text for
the same run. It sits left of both consumers in the module map and
depends on config alone.

The failure excerpt keeps its tail deliberately — for a crash the end is
the story — and now says so through reports.tail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 16:27:29 +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
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 29cbb11eb5 Bench 1: the task board extracted as its own distribution
Everything core from cicero-pas's .task-manager, with instance data
reduced to skeleton: empty stage directories, the task template, empty
local/ scaffolding, and a README covering install (clone into
.task-manager/, vendored on purpose) and update (update.sh replaces core
wholesale; local/ and tasks/ survive).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 17:57:07 +02:00