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>
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>
CLAUDE.md and the task template recited pytest / lint-imports /
frontend as *the* definition of done in core-owned text; both now say
'the project's configured checks' and point at the checks file. Bench's
own local/CLAUDE.md records that its definition of done is the stdlib
suite, kept in the checks file beside it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What counts as a definition-of-done check was the origin project's
stack (pytest / lint-imports / frontend) frozen into core: emit.py
classified against inline literals and the Focus panel carried three
fixed rows with duplicated regexes. Per the three-layer law that is
project knowledge, so it now lives in one file: core/checks ships the
old rows as the default, and a checks file in manager/local/ replaces
it wholesale — the same filename-wins resolution as prompts.
- core/checks: '<label>: <command regex>' per line, self-documenting;
read fresh on every use.
- emit.py classifies Bash commands against the resolved file (kind
'check', the label carried into the summary) and judges pass/fail
generically — counted results, broken totals, OK/FAILED verdict
lines — since the hook payload carries no exit status. The runtime
moved under a __main__ guard so the classifier is importable.
- config.checks() mirrors the parser (the bridge stays standalone) and
httpd serves it in /api/state; the Focus panel renders one row per
served entry, matching events with the same patterns — no fixed
rows, no duplicated regexes.
- manager/local/checks gives bench its real definition (unittest), so
the self-hosted board shows a check that can actually run.
- The default BOARD_AGENT_COMMANDS drops its pytest prefix: core no
longer names any stack outside the shipped checks default, and a
test walks manager/core to keep it that way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Twelve task cards from the first self-hosted dogfooding sessions: 02,
05, 06, 07 done; 08 in review; 09 queued; the rest in backlog. Review
launches no longer pass the retired MultiEdit tool name as a deny rule
(it killed every review/relevance launch at flag parsing — task 10
keeps the follow-up test work). Gitignore .worktrees/ and personal
.claude/settings.local.json; commit the portable .claude/settings.json
that task 02's wiring fix now generates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Focus 'Right now' well advertised a report with the › chevron but was
a static div. When the rendered event carries detail it is now a real
disclosure — a details.fold whose summary is the well itself, keyed into
S.openFolds like the Sessions timeline's folds so the open state survives
the SSE-driven re-renders, with the report in the fold's mono <pre>,
scroll-bounded when long.
Wells where clicking does nothing no longer wear the chevron: the board
cards' live line and the empty-focus well lead with · instead (the drive
wells' ✳ already meant something else). One glyph, one meaning.
tests/test_focus_well.py encodes the invariants as source-level checks:
› only inside the wellfold summary, detail rendered preformatted, fold
state keyed rather than DOM-only, Sessions folds untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All four core prompt templates now end with the same reader-first
report contract: name the audience (a teammate deciding the card's
fate in under a minute), lead with the state of the work, put action
items in their own list, complete sentences and repo-relative paths,
and 'short but self-sufficient' in place of 'concise'. Machine-parsed
marker lines (NOT READY, RELEVANCE REVIEW, PR REVIEW, ADDRESSED) are
untouched, and a new test locks the block identical across templates,
keeps the markers verbatim, proves str.format still renders, and
checks local/prompts/ overrides still win.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three layout defects broke ./start.sh in the self-hosted repo, one of
which (missing local/state/) broke every fresh vendored install too:
- install.py resolved the project as TM.parent, a hardcoded vendored-
layout assumption. It now asks git for the toplevel from the manager's
directory (same resolution as config._repo_root), falling back to the
parent when git is unavailable — vendored installs still find the host
repo, self-hosted bench finds itself instead of its parent.
- adapters/claude/wire hardcoded ".task-manager/" into the emit hook
command and plansDirectory. Both are now derived from the manager's
path relative to the project root, so vendored installs keep the
.task-manager/ prefix and self-hosted bench gets prefix-free paths.
_is_ours also recognises the emit.py suffix, so settings wired with
the old literal path count as stale and are repaired idempotently.
- board.py and state.py created the sessions/agent dirs without
parents=True; local/state/ is gitignored and ships empty, so a virgin
checkout died with FileNotFoundError before serving. Boot now creates
the whole chain, wiring or no wiring.
tests/test_self_hosting.py (stdlib unittest) covers both layouts' wiring,
stale-path repair, idempotent re-runs, refusal without .claude/, root
resolution with and without git, and an integration boot of board.py
from a scratch checkout with no local/state/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Work agents were launched under acceptEdits with no Bash allowlist, so
headless runs could edit files but never run tests or commit — the exact
contradiction card 05 documents. Each launch intent (work, act-pr,
review) now carries a permissions.allow list granting exactly what its
prompt demands, delivered through the same generated settings JSON as
the event hooks. Project test/check commands arrive as neutral prefixes
via BOARD_AGENT_COMMANDS; a new opencode adapter renders the same three
stances in its config language as the portability proof. A clean agent
exit with an empty branch no longer advances the card to review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>