- **Restore 82 completed tasks** from tasks/complete/ back to tasks/ top level (all <7 days old per the cleanup policy; premature bulk archive was fixed). - **Dashboard: fix scroll-reset on auto-refresh** — renderBoard rebuilds the board via innerHTML every 2s, destroying each column-body's scrollTop. Now snapshots column-body scrollTop + board.scrollLeft + view.scrollTop before rebuild and restores after (matched by PHASE_GROUPS index). - **Dashboard UI additions** (pre-existing unstaged work): approval section cards, transition buttons, inline artifact editor (textarea for writing missing SPEC/VERDICT/etc from the detail modal). - **Bind ornith as Implement model** — config.md: Model explicit to omlx/Ornith-1.0-35B-4bit-mlx, context window 32768. Interactive autopilot already used ornith via opencode default; now explicit. - **Fix cleanup stub** — automaton-cleanup.sh had a stale --project arg pointing at a pytest temp dir (test isolation leak). Rewired to point at ~/.automaton. - **Fix plist-isolation test** — test asserted host plist doesn't exist, but a real install creates it. Now snapshots mtime before run, asserts unchanged after (only a write during the test counts as bleed). - **New Playwright smoke test** (tests/test_dashboard_ui.py) — 2 tests: board renders tasks, column scroll survives auto-refresh tick. Verified the test fails without the scroll fix (scrollTop resets to 0). Skipped via importorskip when playwright is absent (main CI stays green). - **Clarify SI loop scope in README** — new-project onboarding section documents the framework-scoped self-improvement loop and options (leave/pause/create project loop). - **CHANGELOG** documents all changes including the known model-divergence gap (mde tasks marked complete but per-role model binding was never implemented).
50 lines
3.2 KiB
Markdown
50 lines
3.2 KiB
Markdown
# 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/<name>`; 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 `<loop_path>/worktree` (matches `LOOP_WORKTREE_DIR` in status.py).
|
|
- Branch name is `loop/<loop_name>` (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
|