Commit Graph
32 Commits
Author SHA1 Message Date
istos 661dd89af4 An archive chip on the card, where the card is
Every card in backlog/, to-do/ and done/ gets a ⌸ chip at the end of its
footer row — the tray's own glyph — arming on the first click and
archiving on the second, through the routes and the ARCHIVE_FROM guard
that already existed. In-progress and review cards do not get one: the
board offers what it will do.

Committed by hand after the run that wrote it ended mid-turn without
committing. The work is the agent's; the suite passes on it (875 tests)
and it is unchanged from what the run left staged.
2026-08-01 19:57:03 +02:00
istosandClaude Opus 5 463b35bdaf A card joins a phase without the file being opened
Phases are meant to arrive whole — members listed, dependencies filled in, readable in a diff before any of it runs. This is the other case: the card you decide belongs after all.

⟶ phase sits on backlog/ and to-do/ cards that are not already in a phase and are not phase cards themselves, and opens a sheet naming the phase cards waiting in to-do/ with what each already holds. Picking one appends `- <n> — <title>` to the end of that phase's ## Cards — the way a person writes it, because the section is authored and read by hand and a machine-shaped line is how a format stops being pleasant.

Only to-do/. A phase in in-progress/ is running: its branch exists and its members are being worked in the order the list had when it started, so appending mid-flight is a different feature with different questions. Offer it and someone finds that out the hard way. No phase waiting there and the action is absent rather than present and empty.

Nothing else moves. One line into the phase card, nothing at all into the card added — membership runs one direction and joining a phase is not a commitment to start it. The append goes out through append_to_section, the same door the phase log uses, so it commits itself under BOARD_COMMIT_MOVES, reaches the other boards, and reads the phase card off the disk rather than off a render: two boards adding to one phase produce two lines, not a lost one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 11:21:27 +02:00
istosandClaude Opus 5 c49f5bd014 The board shows a phase running, and shows it stopping
A phase card inherits the PR chip, CI, the review actions and the
merge-and-clean-up sheet by being a card. Three things it does not
inherit, and this is them.

The runner learns to stop. `stopped — held by <name>` joins `halted` and
`run started` in the phase log, and one reader (`run_state`) answers
where a phase is from the last line that says: running, halted, stopped
or never run. `stop_phase` writes that line — so the next beat stands
down — and holds the member agent in flight, because a phase that said
it had stopped while its agent kept working would be lying about the one
thing it was asked. Nothing is unwound: the branch, every card merged
into it and every worktree are left exactly as they were. A halted phase
can be held too, which is the other half of the halt's promise that it
holds until the phase is run again or stopped.

The halt is now told three times, as a dead run is: the state, the
ticker line it already had, and a toast — rare, actionable, and the
whole argument for starting a phase and walking away. Every advance is
narrated: the member that came up green, the merge, the next start.

The page: run phase (take over on someone else's) in the slot start work
occupies, hold while it runs, and a header chip beside the agents chip —
breathing accent with the phase, its progress and the card in flight,
alarm and holding when it halts, absent entirely when there is nothing
to say. One chip per phase, because two phases could in principle run at
once and a chip that showed one of them silently would be worse than
none. Opening a phase card lists its members in run order with each
one's stage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:32:32 +02:00
istosandClaude Opus 5 35c83f5f70 Say what "the board never merges" always meant
It meant main. A phase branch is the board's own, and merging into it
is bookkeeping in the same family as committing a move — so the
absolute in AGENTS.md, and the same claim on the landing page, both
become "never merges into main". Nothing about the human gate changes;
the sentence just stops overstating it.

The rest is the section the runner needs: how a phase is started, why
the branch exists at all, what makes the beat stateless, what green
means and what halts, and who runs it when there are replicas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:44:02 +02:00
istosandClaude Opus 5 9621437479 A phase is a card that lists its cards
`**Type:** Phase` plus a `## Cards` section is the whole model. The phase
card is the only authority on membership: it lists its members in run
order, and a member's phase and position are derived from that list
rather than stored on the member — one direction, one place to edit.

- taskfiles.py reads the section (numbers only; `31`, `#31` and `031` are
  one card, indented lines are a member's own continuation) and parses
  `Depends on` at last, into the numbers it names. Nothing acts on it:
  the list says what runs next, the line says whether it may.
- collect() weaves the board: members resolved in document order, each
  member given its phase and 3/5 position. What will not resolve is
  flagged rather than skipped — a number no card has, a card two phases
  both list (both flagged), a card listed twice, a line naming no number,
  a phase listed by a phase.
- board.html gives a member card a `⟶ <phase> 3/5` chip in the footer
  row beside CI and PR ↗, opening the phase card, and wears phase drift
  as a pill next to status drift.

Nothing runs yet — no branch, no launches, no advancing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:01:37 +02:00
Ronald AshriandGitHub 5d3f22717f Merge pull request #39 from 12vectors/task/37-sync-resolves-its-remote-and-says-when-it-cannot
37 — Sync resolves its remote like the rest of the board, and says so when it cannot
2026-08-01 08:47:15 +02:00
istosandClaude Opus 5 58b658389b sync: ride the resolved remote, and say when there is none
REMOTE/UPSTREAM were module constants, so a team-mode board whose remote
is named anything but origin synced nothing at all — silently, with a
healthy header. The remote is now resolved per use (config's answer, the
one PRs already honour) and threaded through fetch, publish, replay and
integrate; every message names whatever was resolved.

The silence is the other half. Where _converge()/push_now() returned a
bare 'no-origin' that both callers discarded, _remote() now _note()s at
stalled level first: no remote at all names both fixes (add one, or set
BOARD_GIT_REMOTE), and a BOARD_GIT_REMOTE naming a remote this checkout
does not have stalls naming it rather than quietly using another. It
clears with a recovery line like the offline path, and install() asks at
startup so the chip is there from first paint, not the second beat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 07:33:39 +02:00
istosandClaude Opus 5 6829136f42 start: a pinned port stays pinned across a restart
The free-port probe bound without SO_REUSEADDR while the board's own
ThreadingHTTPServer sets it, so the socket a just-stopped board left in
TIME_WAIT read as "taken by something else": a routine stop/start walked
the board to the next port and wrote that over the user's BOARD_PORT pin.
The probe now binds exactly as the server does, which is the whole race.

Behind it, a held port gets a few seconds (BOARD_PORT_WAIT, 5s) to clear
before the walk, re-asking is_our_board each beat — a restart races its
own predecessor far more often than a stranger takes the port. Walking
off a pinned port still persists, since the hooks and agents read
BOARD_PORT and must reach the live board, but it now says so in full:
the right file (manager/local/.env, not manager/.env), old → new, and
how to reclaim the pin.

Tested end to end against a scratch host with a stub board.py, over real
sockets: a genuine TIME_WAIT remnant, a listener that lets go mid-wait,
a listener that does not, and our own board answering.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 07:32:54 +02:00
Ronald AshriandGitHub 385acae35a Merge pull request #36 from 12vectors/task/44-archiving-a-card-reaches-git
44 — Archiving a card reaches git, and so does every other write the board makes to a task file
2026-07-31 17:22:25 +02:00
Ronald AshriandGitHub 97f6343275 Merge pull request #37 from 12vectors/task/38-a-card-being-completed-looks-like-it
38 — A card being merged and cleaned up looks like it, and holds still
2026-07-31 17:22:16 +02:00
istos 5736126142 …and the card wears it, and holds still
The card takes the accent border and a breathing `completing` pill in
the slot the status pill already owns — the working vocabulary, because
that is what is happening — and carries the latest narrated step on its
activity line, so "parking the drive" and "cleaned up: worktree and
local branch removed" land on the card and not only in the ticker.

While it is claimed the card offers nothing: no hover actions, no drive
or command chips (both run against the worktree being removed), and not
draggable, so the sheet cannot be reopened behind the run. Suppressed,
not ignored on click — an action that looks available and does nothing
is the same lie in a different place.
2026-07-31 16:57:16 +02:00
istosandClaude Opus 5 b462b83a7f Archiving a card reaches git, like every other task-file write
An archive renamed the file on disk and stopped there, leaving an
uncommitted deletion of a tracked file — exactly what sync._clean()
refuses to run over, so one drag onto the tray stalled every later board
commit, kept the archive off the other boards, and left the card in a
single working tree. Appended agent reports had the same gap.

taskfiles now has one door out of a stage directory, _relocate(): it
registers the expectation, writes, moves, and commits under the same
COMMIT_MOVES gate a move uses — so archive, unarchive and move all commit
by construction rather than by each caller remembering. The messages read
like the others (`board: <n> -> archived (<name>)`, and the stage slug on
the way back) and carry the `board: ` prefix sync's piggyback guard
requires; routing through _commit means state.task_committed() fires, so
the event-driven push publishes it.

The second door is append_to_task(), which writes into a card where it
stands and commits via commit_edit(); agents._file_report() uses it
instead of touching config.TASKS itself, so a run's closing report is
recorded rather than left modified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 16:57:09 +02:00
istos d3a43ef1ae install: detect the test command instead of asking for it
The third setup question wanted an answer about a repo the person may
have just cloned, thirty seconds in, before anything had explained why
the board needed one. The file that names a project's ecosystem usually
names its runner too, so it is read off the project: package.json → npm
test, Cargo.toml → cargo test, go.mod → go test ./..., a
pyproject/setup.py/tests/ → python3 -m unittest.

Nothing recognisable writes BOARD_AGENT_COMMANDS empty rather than the
example's Python default, which would be quietly wrong in most repos —
and a wrong prefix denies an agent exactly the way an empty one does, so
absent beats wrong here.

Two details the first attempt got wrong, both now covered:

- Detection runs *before* the questions. Placed after them, a Ctrl-D
  part-way through skipped it and left the example's default.
- "Keep what the project already said" reads the existing .env, not the
  example-seeded defaults dict — which already carries the example's
  answer, so every first run looked like it had one and detection never
  ran.

The landing page's terminal, README.md and AGENTS.md follow: two
questions now, and AGENTS.md documents the detection table.
2026-07-31 16:47:30 +02:00
Ronald AshriandGitHub 028635e3fc Merge pull request #35 from 12vectors/task/45-a-past-agent-session-is-not-you
45 — A past agent session is labelled "You", because "You" is what the board says when it does not know
2026-07-31 16:40:50 +02:00
Ronald AshriandGitHub 09cf11e73c Merge pull request #34 from 12vectors/task/42-the-report-keeps-its-headline
42 — A long closing report loses its head, which is where it says what happened
2026-07-31 16:40:40 +02:00
istosandClaude Opus 5 f428ee0468 sessions: persist who a session was, so a replayed run is not "You"
"You" was the else-branch of session_label: anything the board could not
attribute to an agent it attributed to the person. Every session read back
from disk was one of those, because the agent id lived only in the session
registry and never reached the persisted events — so past agent runs came
back wearing the human's label, carrying their own closing reports
underneath it.

Identity is now a small whole file beside each event log
(state/sessions/<id>.who.json): agent id, the agent's name, the model it
rode, and the task. A file rather than a key on the events, because the
logs are append-only JSONL whose first line every reader takes for an
event — and because the name and the model are nowhere in the stream, so
this is the only thing a restart can read them back from. It is rewritten
only when what the board knows changes, which also covers an agent id that
arrives on a later event.

load_disk_sessions() reads it back, and the label now has three registers
instead of two: the agent's name (persisted, so a restart no longer costs
it), "You" only for a session positively recorded as carrying no agent,
and a neutral "Session · <id>" for a log written before any of this was
recorded. Old logs are not retro-attributed in either direction.

agentFor() in board.html falls back to the persisted identity when this
board no longer holds the live record, so a replayed agent session wears
its model chip from what was written rather than from what happens to be
in memory. What depends on liveness (Hold, the worktree branch) finds
nothing there and stays silent, as before.

tests/test_session_identity.py drives the real ingest → persist → reload
path and the page's own chip functions in node.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 16:35:19 +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
istos 72a0d4195f board: put the running-agent count in the tab title
A running agent was visible on the board and nowhere else, so switching
tabs — the normal thing to do while one works for minutes — left bench
saying the same string whether three agents ran or none.

The count now leads the title (`2◌ · bench · bench`), ahead of even the
project: the reasoning that put the project first (truncation eats the
tail) applies harder to the one state you want from a tab you are not
looking at. The mark is the in-flight glyph the CI and copilot chips
already wear, not an emoji, and a quiet board keeps the plain title byte
for byte.

Both the tab and the header chip now count through one runningAgents(),
so they cannot disagree, and the title is written only when the string
actually changed — render() runs on every SSE frame.

Behaviour is covered in node (skipped where node is absent); the wiring
that keeps one filter and one writer is a source-level invariant, as
elsewhere for board.html.
2026-07-31 16:12:47 +02:00
istosandClaude Opus 5 5ac3547821 docs: fix the sections the site cuts its pages from
The site's page bodies are heading slices of these two files, so a
sentence that only makes sense to an agent inside an installed project
becomes a sentence on bench.12vectors.com. Fixed at the source rather
than forked into the site:

- "The flow is linear" and its diagram sat at the end of "Pull requests",
  which put the whole stage flow on the PR page and left "Stages" with no
  opening. Moved under "## Stages", where it introduces what follows.
- "Agents working the board" ended in a stray sentence about worktrees
  and one-agent-per-task; folded into the start-work paragraph it
  belongs to.
- Start work no longer claims to launch `claude -p`: it launches through
  the configured adapter, which is the whole point of adapters.
- "see below" and "the PR guard above" name their sections instead,
  since above and below are other pages on the site.
- The tool-chips sentence had grown into the middle of the verdict it
  interrupted ("The card wears it in the design system's colours", with
  no nearby "it"). Split into two paragraphs.
- "Task file format" gains a table of every header field, what it may
  contain and who writes it — including PR, which the board writes and
  nothing documented until now.
- Paths are written from the repo root, the reference-directory example
  no longer names another project's regulator, and README's three-layer
  law links the two documents it points at rather than naming them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 14:29:36 +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
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 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
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
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 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 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
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 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
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