# Implementation: add-blast-radius-scheduler Implements per-loop git worktree creation in `scripts/loop-runner.py` per SPEC R1-R6. ## Files changed - `scripts/loop-runner.py` -- added `_git_run`, `_ensure_worktree`, `LOOP_WORKTREE_DIR` constant; replaced step 4 stub with worktree creation; updated module docstring. - `tests/test_blast_radius.py` -- 15 tests covering R1-R6 + regression. ## R-by-R coverage | Req | Code | |-----|------| | R1 _ensure_worktree | `_ensure_worktree(state, cfg, loop_path, project_dir)` -- checks `blast_radius.use_worktree` (default True), reuses existing worktree, creates new via `git worktree add` | | R2 graceful degradation | `_git_run` catches `OSError`/`SubprocessError`; not-a-repo and worktree-add failures log WARNING and return `project_dir` | | R3 cmd_tick integration | Step 4 replaced: `cwd = _ensure_worktree(state, cfg, loop_path, project_dir)` | | R4 branch already exists | First try `-b loop/`; on "already exists" in stderr, retry without `-b` (checkout existing branch) | | R5 state consistency | Stale `worktree_path` (path doesn't exist) is cleared before recreation; state written atomically via `_write_state_loop` | | R6 platform paths | `pathlib.Path` for all path construction; git handles OS-specific normalization | | R7 doc updates | technical.md section 7 step 4 updated; CHANGELOG.md updated | | R8 tests | 15 tests in `tests/test_blast_radius.py` | ## Key design decisions - `use_worktree` defaults to `True` when the field is missing (D2: "default is worktree-on"). - Worktree path is `/worktree` (matches `LOOP_WORKTREE_DIR` in status.py). - Branch name is `loop/` (matches technical.md section 7 step 4). - `_git_run` is a thin wrapper around `subprocess.run(["git", ...])` that returns `(rc, stdout, stderr)` and catches all `OSError`/`SubprocessError`. - State is written inside `_ensure_worktree` (not deferred to step 10) because the worktree exists on disk immediately after creation; recording it in state is correct even if the tick crashes later. - The drift gate (`_gate_worktree_drift` in status.py) already handles the case where `worktree_path` is null (skips the check). So fallback to project root is safe. ## Tests (`tests/test_blast_radius.py`) 15 tests across 6 classes; all `subprocess.run` calls stubbed via monkeypatch. - `TestEnsureWorktree` (4): creates worktree; reuses existing; use_worktree=false returns project root; missing field defaults true. - `TestGracefulDegradation` (3): falls back when not git repo; falls back when git missing; falls back when worktree add fails. - `TestBranchExists` (1): reuses existing branch (retry without -b). - `TestStateConsistency` (2): clears stale worktree path; recreates after deletion. - `TestTickIntegration` (3): first tick creates worktree; second tick reuses; falls back when no git. - `TestPlatformPaths` (1): worktree path constructed via pathlib. - `TestRegression` (1): existing loop with worktree_path ticks unchanged. ## Verification - `python3 -m py_compile scripts/loop-runner.py` -- PASS - `python3 -m pytest tests/test_blast_radius.py -v` -- 15 passed - `python3 -m pytest tests/ -q` -- 369 passed (354 + 15 new) - `bash -n scripts/*.sh` -- no shell changes