7 Commits
Author SHA1 Message Date
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
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 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 e800cf1a91 Address PR review: symlink-safe removal, guard-gated stamp, hermetic tests
The first-boot guard is now its own predicate gating both the clean and
the local/state/ stamp, so a disarmed run (pre-existing local/.env, or
self-hosted) touches nothing at all — before, it still created the stamp
directory on any vendored run. Removal unlinks symlinks instead of
following them into rmtree. Tests filter BOARD_* out of the subprocess
environment, assert the stamp stays absent when .env disarms the guard,
and cover the symlink case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:22:33 +02:00
istosandClaude Fable 5 44537ae653 Ship a pristine board on install: first boot clears distribution cards
A vendored clone of bench arrives carrying bench's own task cards, plans
and reference documents, so a new user's first board opened pre-loaded
with our backlog. install.py now detects first boot — a vendored install
(project root != manager root) whose manager/local/ has never been
populated (no .env, no state/) — and only then clears the stage
directories, tasks/archive/, plans/ and reference/, keeping
task-template.md and the .gitkeep files and printing every removal.
--dry-run lists instead of removing. After cleaning it stamps
manager/local/state/, so the guard is permanently false on every later
run even if the adapter wire fails, and a host's own cards are never
touched. Self-hosted repos (bench itself, including fresh dev clones)
are never cleaned: their tasks/ is the repo's history.

Covered end-to-end in tests/test_install_first_boot.py by running
install.py as a subprocess against scratch host layouts; README notes
the first-boot behaviour under "Install into a repo".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:09:46 +02:00
istosandClaude Fable 5 cc931a9174 Start cleanly when bench is the project itself
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>
2026-07-29 19:55:57 +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