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>
53 lines
1.8 KiB
Plaintext
53 lines
1.8 KiB
Plaintext
# What a bench release ships — the whole list, one place. release.sh
|
|
# builds the artifact from it; update.sh reads the copy of this file
|
|
# inside the downloaded artifact to know which top-level files to
|
|
# replace; the artifact test asserts the tarball matches it exactly.
|
|
#
|
|
# One entry per line, `<class> <path>`:
|
|
#
|
|
# copy <file> core-owned file from the repo — shipped, and replaced
|
|
# by every update
|
|
# tree <dir> directory shipped recursively from the repo, replaced
|
|
# WHOLESALE by every update (__pycache__/.DS_Store
|
|
# excluded at build)
|
|
# once <file> shipped from the repo on install, then yours — updates
|
|
# never touch it
|
|
# keep <dir> shipped as an empty directory holding a .gitkeep —
|
|
# updates never touch it
|
|
# seed <file> starter content generated by release.sh (never present
|
|
# in bench's own repo, which has its real one) — updates
|
|
# never touch it
|
|
#
|
|
# Anything not listed here does not ship: bench's own task cards, its
|
|
# manager/local/ content, local/state/, .claude/, tests/, release.sh.
|
|
#
|
|
# Modes ship too, and one invariant is absolute: any shipped file whose
|
|
# first two bytes are `#!` carries the executable bit in the tarball.
|
|
# The artifact test enforces it with no exception list — a shebang is a
|
|
# promise the file can be run.
|
|
copy AGENTS.md
|
|
copy CLAUDE.md
|
|
copy README.md
|
|
copy LICENSE
|
|
copy install.py
|
|
copy start.sh
|
|
copy stop.sh
|
|
copy update.sh
|
|
tree manager/core
|
|
once .gitignore
|
|
once tasks/task-template.md
|
|
keep tasks/backlog
|
|
keep tasks/to-do
|
|
keep tasks/in-progress
|
|
keep tasks/review
|
|
keep tasks/done
|
|
keep tasks/archive
|
|
keep plans
|
|
keep reference
|
|
keep manager/local/adapters
|
|
keep manager/local/commands
|
|
keep manager/local/driver
|
|
keep manager/local/prompts
|
|
seed manager/local/AGENTS.md
|
|
seed manager/local/CLAUDE.md
|