CI / build (push) Has been cancelled
- Archive 79 completed framework-dev tasks from tasks/ -> tasks/complete/ - status.py: add --cleanup-done and --install-cleanup-schedule commands - Add scripts/automaton-cleanup.sh for periodic task archiving - Dashboard: rename 'Background' tab -> 'Agent', 'Cleanup' agent -> 'Completed Task Archiver', remove redundant group headers and pill badges, dim inactive agent placeholders - .rules.md: add Self-Documenting UI Names rule - New tests: test_cleanup_done.py, expanded test_app.py and test_task.py
7.3 KiB
7.3 KiB
SPEC: add-blast-radius-scheduler
Context
Task 2 (add-status-brakes) shipped --can-edit --loop [--loop-worktree], the _gate_worktree_drift brake gate, and platform.system() dispatch for scheduler generation. Task 3 (add-loop-runner) shipped the runner with a stub at step 4: # worktree plumbing lands in task add-blast-radius-scheduler. The runner currently uses state["worktree_path"] if set, else falls back to project_root -- but never creates the worktree. This task closes that gap: the runner ensures a per-loop git worktree exists before spawning the Implement role, per technical.md section 7 step 4 and D2.
Non-Goals (deferred)
--no-worktreeCLI flag for--create-loop-> v1.1 (theblast_radius.use_worktree: falsefield inloop.jsonis the v1 opt-out mechanism; a CLI flag is convenience sugar).- Worktree garbage collection / pruning -> v1.1 (BACKLOG
worktree-gc). blast_radius.base_branchparameterization -> v1.1 (hardening item; v1 hardcodesmainas the base).--claim-loop-taskatomic ownership -> v1.1.- Fcntl lock on worktree creation -> v1.1 (same TOCTOU item as
add-status-brakesA6).
Requirements
R1 -- _ensure_worktree helper in loop-runner.py
- New function
_ensure_worktree(state, cfg, loop_path, project_dir) -> strthat returns the cwd to use for harness invocations. - Reads
blast_radius.use_worktreefromloop.json(default:Truewhen the field is missing, matching D2 "default is worktree-on"). - When
use_worktreeisFalse: returnstr(project_dir)immediately. No git calls. No state mutation. - When
use_worktreeisTrueandstate["worktree_path"]is already set and the path exists: return the existing worktree path. No state mutation. - When
use_worktreeisTrueandstate["worktree_path"]is null or the path no longer exists:- Determine the worktree path:
<loop_path>/worktree(usingLOOP_WORKTREE_DIR = "worktree"). - Determine the branch name:
loop/<name>where<name>isstate["name"]or the loop dir name. - Run
git rev-parse --is-inside-work-treefromproject_dirto verify it is a git repo. If not, fall back to R2. - Run
git worktree add <worktree_path> -b loop/<name>fromproject_dir. If the branch already exists, usegit worktree add <worktree_path> loop/<name>(checkout existing branch, no-b). - On success: update
state["worktree_path"]andstate["worktree_branch"], write state atomically, return the worktree path. - On failure: fall back to R2.
- Determine the worktree path:
- Tests:
test_ensure_worktree_creates_worktree,test_ensure_worktree_reuses_existing,test_ensure_worktree_use_worktree_false_returns_project_root,test_ensure_worktree_missing_field_defaults_true.
R2 -- Graceful degradation (no git / not a repo / worktree creation fails)
- If
gitis not found (FileNotFoundError), orgit rev-parse --is-inside-work-treefails (non-zero exit), orgit worktree addfails (non-zero exit): log a WARNING to.state.logand returnstr(project_dir)as cwd. - The loop does NOT halt. The tick proceeds with
cwd = project_dir. The drift gate (_gate_worktree_drift) will skip itself becauseworktree_pathremains null. - This makes worktree creation best-effort: a loop configured with
use_worktree: trueon a non-git project simply edits the primary checkout. The operator is responsible for understanding this trade-off (documented infunctional.md). - Tests:
test_ensure_worktree_falls_back_when_not_git_repo,test_ensure_worktree_falls_back_when_git_missing,test_ensure_worktree_falls_back_when_worktree_add_fails,test_ensure_worktree_logs_warning_on_fallback.
R3 -- Integration into cmd_tick
- Replace the current step 4 block in
cmd_tick(lines ~493-498 ofloop-runner.py) with a call to_ensure_worktree(state, cfg, loop_path, project_dir). - The returned cwd is used for all three role invocations (Implement, Verify, Orchestrate).
- The state mutation (setting
worktree_path/worktree_branch) happens inside_ensure_worktreevia_write_state_loop. This is safe because it occurs before any harness subprocess; a crash after this point but before step 10 leaves the worktree path recorded (which is correct -- the worktree exists on disk). - Tests:
test_tick_creates_worktree_on_first_tick,test_tick_reuses_worktree_on_second_tick,test_tick_falls_back_to_project_root_when_no_git.
R4 -- Worktree branch already exists
- When
git worktree add <path> -b loop/<name>fails because the branch already exists (exit code 128, stderr containsalready exists), retry withgit worktree add <path> loop/<name>(checkout existing branch without-b). - If the retry also fails, fall back to R2.
- This handles the case where a loop was previously created, the worktree was deleted, but the branch remains in the repo.
- Tests:
test_ensure_worktree_reuses_existing_branch,test_ensure_worktree_falls_back_when_branch_checkout_fails.
R5 -- State consistency
_ensure_worktreewritesworktree_pathandworktree_branchto.state.loopatomically via_write_state_loop(same tmp+rename pattern).- If the worktree path was previously set but the directory no longer exists (e.g. manually deleted), clear
worktree_pathandworktree_branchin state before attempting recreation. If recreation fails, leave them cleared (R2 fallback). - Tests:
test_ensure_worktree_clears_stale_worktree_path,test_ensure_worktree_recreates_after_deletion.
R6 -- Platform path handling
- Use
pathlib.Pathfor all path construction. On Windows,Pathhandles backslash separators automatically. - The
git worktree addcommand receives the worktree path as a string; git handles OS-specific path normalization on its own. - No
platform.system()calls needed in the runner for worktree creation (unlike--install-schedulewhich generates OS-native scheduler units). The runner's worktree creation is platform-agnostic viaPath. - Tests:
test_worktree_path_uses_pathlib(verify the path is constructed viaPathnot string concatenation; checked by examining the argv passed tosubprocess.run).
R7 -- Doc updates
- Update
design/loops/technical.mdsection 7 step 4 to note the runner now creates the worktree (remove the "deferred" language if present). - Update
CHANGELOG.mdunder[unreleased]. - Tests: none (doc-only).
R8 -- New test file tests/test_blast_radius.py
- Mirrors
test_loop_runner.py's stubbing pattern (monkeypatch.setattr(subprocess, "run", fake_run)). - Covers R1-R6 as itemized above; target 12-16 tests.
- All subprocess calls stubbed; no live git operations in CI. For tests that need a real git repo, use
tmp_path+subprocess.run(["git", "init"])in a fixture (these are integration tests that hit the real git binary but are fast and deterministic). - Add one regression test:
test_existing_loop_with_worktree_path_ticks_unchanged-- a loop withworktree_pathset and the path existing still ticks without callinggit worktree add. - Tests: self-referential (the file IS the test).
Verification
python3 -m py_compile scripts/loop-runner.pypython3 -m pytest tests/test_blast_radius.py -vpython3 -m pytest tests/ -q-- full suite must remain green; expected total approx 370 (354 + 12-16 new).bash -n scripts/*.sh(no shell changes; safety check).