Restore archived tasks, fix dashboard scroll-reset, bind ornith, add Playwright smoke test
- **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).
This commit is contained in:
@@ -0,0 +1 @@
|
||||
complete
|
||||
@@ -0,0 +1,2 @@
|
||||
research:approved|2026-06-24T02:24:08.147448+00:00|user
|
||||
code_review:approved|2026-06-24T02:25:41.748888+00:00|user
|
||||
@@ -0,0 +1,23 @@
|
||||
# Adversarial Bug Report: parametrize-base-branch
|
||||
|
||||
## A1 — Git injection via base_branch value
|
||||
|
||||
`base_branch` values like `"; rm -rf /"` or `"main\norigin/main"` are passed through to `subprocess.run` in list mode. The entire value is a single argv element — no shell expansion, no injection. Git will attempt to resolve the string as a revision name and fail (fatal: bad revision), which triggers the WARNING-skip path. No exploit.
|
||||
|
||||
**Verdict**: no vector. List-mode subprocess.run is safe by construction.
|
||||
|
||||
## A2 — Non-string base_branch (int, bool) coerce to string
|
||||
|
||||
`42` → `"42"`, `True` → `"True"`. Both are valid git ref names (a tag named `42` or `True` would resolve). The WARNING notifies the operator. No crash, no silent misbehavior.
|
||||
|
||||
**Verdict**: acceptable. The WARNING is a signal to fix the config.
|
||||
|
||||
## A3 — Empty base_branch falls back to main
|
||||
|
||||
`""` → `"main"` with WARNING. Operator typed it intentionally or accidentally; either way the drift gate works on `main`. No data loss.
|
||||
|
||||
**Verdict**: correct per D-B4.
|
||||
|
||||
## No BLOCKERS
|
||||
|
||||
Proceed to doc_review.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Bug Report: parametrize-base-branch
|
||||
|
||||
No bugs found. The change is a simple string substitution (hardcoded `"main"` → `f"{base}...HEAD"`) with a 4-line helper. The bad-revision, missing-worktree, and empty-file_scope paths are unchanged from v1. All 13 tests pass.
|
||||
|
||||
## O1 — Base branch not validated against repo at create time
|
||||
|
||||
An operator can set `base_branch: "typo"` in `loop.json` and the drift gate will fail with a `git diff` error (warning skip), silently disabling the drift gate. No validation at `--create-loop` time.
|
||||
|
||||
**Not a bug** — SPEC O3 accepts this: "Wrong base_branch is admin error, not a drift event." Future: `--validate-loop`.
|
||||
|
||||
## Verdict
|
||||
|
||||
PASS — no blockers.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Code Review: parametrize-base-branch
|
||||
|
||||
## SPEC coverage
|
||||
|
||||
| Req | Status |
|
||||
|-----|--------|
|
||||
| R1 — _gate_worktree_drift reads base_branch | ✓ `_base_branch(cfg)` in git diff argv |
|
||||
| R2 — Helper: None→main, empty→main+WARN, non-str→str+WARN, explicit→explicit | ✓ |
|
||||
| R3 — Bad-revision still WARNING-skip | ✓ unchanged |
|
||||
| R4 — Template includes base_branch | ✓ |
|
||||
| R5 — No new deps | ✓ |
|
||||
|
||||
## Cross-script impact
|
||||
|
||||
No impact on loop-runner.py. Only status.py and the template.
|
||||
|
||||
## Verdict
|
||||
|
||||
PASS.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Doc Review: parametrize-base-branch
|
||||
|
||||
## Docs touched
|
||||
|
||||
- `CHANGELOG.md` — new `[unreleased]` entry "Fixed — parametrize-base-branch".
|
||||
- `design/loops/functional.md` §9 — updated `blast_radius` field list to include `base_branch`.
|
||||
- `templates/loops/self-improvement/loop.json` — already updated (schema edit).
|
||||
|
||||
## Docs NOT touched
|
||||
|
||||
- `design/loops/technical.md`: drift gate internal change not documented at the architecture level. No edit.
|
||||
- `AGENTS.md`, `README.md`: no user-facing integration impact. No edits.
|
||||
|
||||
## Verdict
|
||||
|
||||
Docs in sync. Proceed to referee.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Implementation: parametrize-base-branch
|
||||
|
||||
## SCOPE
|
||||
|
||||
Replace hardcoded `"main"` in `_gate_worktree_drift` with `blast_radius.base_branch` config field. Source: `add-status-brakes/BUG_REPORT.md` O3.
|
||||
|
||||
## FILES TOUCHED
|
||||
|
||||
- `scripts/status.py`
|
||||
- Added `_base_branch(cfg) -> str`: reads `cfg.get("blast_radius", {}).get("base_branch")` → `"main"`.
|
||||
- `None` (missing key) → `"main"` (silent).
|
||||
- Empty string → `"main"` with stderr WARNING.
|
||||
- Non-string type (int, bool, etc.) → `str(value)` with WARNING.
|
||||
- `_gate_worktree_drift`: replaced `"main...HEAD"` with `f"{base}...HEAD"` where `base = _base_branch(cfg)`.
|
||||
- `templates/loops/self-improvement/loop.json`: added `"base_branch": "main"` to `blast_radius`.
|
||||
|
||||
## BUGS FOUND
|
||||
|
||||
None. The bad-revision path and empty-file_scope checks are unchanged from v1.
|
||||
|
||||
## DECISIONS LOCKED
|
||||
|
||||
- D-B1: one base branch per loop (not a list).
|
||||
- D-B2: default `"main"`.
|
||||
- D-B3: bad-revision → WARNING skip (not halt), preserved from v1.
|
||||
- D-B4: empty string → `"main"` with WARNING (not silent).
|
||||
- D-B5: no backfill on v1 loops (helper default covers them).
|
||||
- D-B6: tests mock `subprocess.run`.
|
||||
- D-B7: template is the public-facing default.
|
||||
|
||||
## TESTS
|
||||
|
||||
New file `tests/test_base_branch.py` — 13 tests across 2 classes.
|
||||
|
||||
Test count: 495 passed (482 + 13).
|
||||
@@ -0,0 +1,4 @@
|
||||
# Review
|
||||
- **Status**: approved
|
||||
- **Timestamp**: 2026-06-23T22:19:36.421888
|
||||
- **Comment**:
|
||||
@@ -0,0 +1,116 @@
|
||||
# SPEC: parametrize-base-branch
|
||||
|
||||
## Problem
|
||||
|
||||
`scripts/status.py::_gate_worktree_drift` hard-codes `main` as the integration branch:
|
||||
|
||||
```python
|
||||
res = subprocess.run(
|
||||
["git", "diff", "--name-only", "main...HEAD"],
|
||||
cwd=worktree_path, capture_output=True, text=True, timeout=10, check=False,
|
||||
)
|
||||
```
|
||||
|
||||
Source: `tasks/add-status-brakes/BUG_REPORT.md` O3.
|
||||
|
||||
> Hard-codes `main` as the integration branch. Projects on `master`/`trunk` would show every file as out-of-scope (no `main` to diff against → git errors → gate skips with warning).
|
||||
|
||||
On a project whose integration branch is `master`, `trunk`, `develop`, or `release/x.y`, `git diff main...HEAD` fails with `fatal: bad revision main`. The runner's drift gate prints `WARNING: could not run git diff for drift check: ...` and returns `None` (skip with warning, NOT halt). The drift gate is effectively disabled for every non-`main` project — a silent false-negative on the **drift** loop-death mode.
|
||||
|
||||
## Goal
|
||||
|
||||
Replace the hardcoded `"main"` with a per-loop `blast_radius.base_branch` configuration field. The drift gate uses this branch for the `git diff <base>...HEAD` call.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Multi-base-branch (e.g. "diff against ANY of these branches"). One base branch per loop.
|
||||
- Auto-detecting the repo's default branch (`git symbolic-ref refs/remotes/origin/HEAD`). Out of scope; operator sets `base_branch` explicitly in `loop.json`.
|
||||
- Validating the branch exists in the repo at `--create-loop` time. Defer to runtime — the drift gate's "bad revision" path already skips with warning.
|
||||
- Backfilling `base_branch` into v1 loops via `--upgrade-loops` (separately tracked; v1.1 loops get the field via `--create-loop` template).
|
||||
|
||||
## Schema addition (`loop.json`)
|
||||
|
||||
Add an optional `base_branch` field under `blast_radius`:
|
||||
|
||||
```json
|
||||
"blast_radius": {
|
||||
"worktree": true,
|
||||
"file_scope": ["src/", "tests/"],
|
||||
"base_branch": "main"
|
||||
}
|
||||
```
|
||||
|
||||
- **`blast_radius.base_branch`** (str, optional, default **`"main"`**): the integration branch to diff the worktree HEAD against in `_gate_worktree_drift`. Any string accepted as a git ref (branch name, tag, commit SHA).
|
||||
- Empty string coerces to `"main"` with WARNING. Non-string types coerce via `str(...)` with WARNING. `None` (key missing) → default `"main"` (silent).
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 — Drift gate reads base_branch
|
||||
|
||||
`_gate_worktree_drift` calls a new helper `_base_branch(cfg) -> str` to get the integration branch. Replaces the hardcoded `"main"` in the `git diff` argv.
|
||||
|
||||
### R2 — Helper
|
||||
|
||||
`_base_branch(cfg)` returns:
|
||||
- `"main"` if `cfg` is None or `blast_radius` is missing or `base_branch` is missing/None.
|
||||
- `"main"` (with stderr WARNING) if `base_branch` is an empty string.
|
||||
- `str(base_branch)` if non-empty str.
|
||||
- `str(base_branch)` (with stderr WARNING) if non-str type (int, bool, etc.).
|
||||
|
||||
### R3 — Drift-gate bad-revision path stays warning-skip
|
||||
|
||||
If `git diff <base>...HEAD` fails (non-zero returncode OR exception), the gate logs `WARNING: could not run git diff for drift check: {stderr}` and returns `None` (no halt). Same behavior as v1 — operators running against a non-existent branch see a warning and a skipped gate, not a halt. Belt-and-suspenders: a wrong `base_branch` is admin error, not a drift event.
|
||||
|
||||
### R4 — Template + create-loop plumbing
|
||||
|
||||
- `templates/loops/self-improvement/loop.json` adds `"base_branch": "main"` to the `blast_radius` block. New loops created via `--create-loop` get the field by default.
|
||||
- Existing v1 loops WITHOUT `base_branch` continue to work — `_base_branch` returns `"main"`. Backwards-compatible.
|
||||
|
||||
### R5 — No new pip deps; no new files; stdlib only.
|
||||
|
||||
## Test plan
|
||||
|
||||
Pure-function tests (no subprocess except where mocked git is needed). Tests in `tests/test_base_branch.py` (NEW):
|
||||
|
||||
1. **Helper default `main`**: `_base_branch({})` → `"main"`. `_base_branch({"blast_radius": {}})` → `"main"`. `_base_branch({"blast_radius": {"base_branch": None}})` → `"main"` (all silent).
|
||||
2. **Helper explicit value**: `_base_branch({"blast_radius": {"base_branch": "trunk"}})` → `"trunk"`.
|
||||
3. **Helper empty string**: `_base_branch({"blast_radius": {"base_branch": ""}})` → `"main"` + stderr WARNING captured.
|
||||
4. **Helper non-string**: `_base_branch({"blast_radius": {"base_branch": 42}})` → `"42"` + WARNING.
|
||||
5. **Drift gate uses base_branch in argv** (mocked subprocess): patch `subprocess.run`, call `_gate_worktree_drift(state={"worktree_path": "/tmp/wt"}, cfg={"blast_radius": {"file_scope": ["src/"], "base_branch": "trunk"}}, project=None)`, assert captured argv is `["git", "diff", "--name-only", "trunk...HEAD"]`.
|
||||
6. **Drift gate falls back to `main` when base_branch missing** (mocked subprocess): assert argv uses `"main"` when `blast_radius` lacks `base_branch`.
|
||||
7. **Drift gate handles bad revision** (mocked subprocess returncode=128, stderr="fatal: bad revision 'trunk'"): assert gate returns `None` and prints WARNING to stderr (captured via `capsys`).
|
||||
8. **Drift gate still detects drift** (mocked subprocess with names mtime.txt and out-of-scope `extraneous.txt`): assert gate returns dict with `halt_reason="drift_detected"` and `out_of_scope_files=["extraneous.txt"]`. Uses `base_branch="main"`.
|
||||
9. **Drift gate in-scope files don't halt** (mocked subprocess returning only in-scope names): assert returns `None`.
|
||||
10. **No worktree → None**: `state={"worktree_path": None}` → `None`. (Existing path; ensures R1 doesn't break.)
|
||||
11. **Empty file_scope → None**: `cfg={"blast_radius": {"file_scope": [], "base_branch": "main"}}` → `None`. (Existing path.)
|
||||
12. **Missing worktree_path → None**: `state={"worktree_path": "/does/not/exist"}` → `None`.
|
||||
13. **Template includes base_branch**: load `templates/loops/self-improvement/loop.json`, assert `blast_radius.base_branch == "main"`.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **D-B1**: One base branch per loop (NOT a list). Schema simplicity; covers 95% of projects. Multi-base projects can use a SHA or tag if they need a moving target.
|
||||
- **D-B2**: Default `"main"` (most common on GitHub since 2020; matches v1 behavior). Operators override in `loop.json`.
|
||||
- **D-B3**: Bad-revision path stays WARNING-skip (NOT halt). v1 behavior preserved. A halt would punish operator misconfiguration; the existing drift-detection still triggers when the revision exists. Future: add `--validate-loop` to catch misconfiguration at create/install time. Out of scope here.
|
||||
- **D-B4**: Empty string → `"main"` with WARNING (not silent). Distinguishes "operator forgot the field" (None → silent default) from "operator set empty string" (probably a typo — flag it).
|
||||
- **D-B5**: No backfill on existing v1 loops. They get `"main"` via the helper default; no `--upgrade-loops` step required.
|
||||
- **D-B6**: Drift gate test strategy = mock `subprocess.run`. Pure-function; no live git; no worktree creation. Existing `tests/test_status_brakes.py` uses the same pattern.
|
||||
- **D-B7**: Template edit is the public-facing default. New loops get `base_branch: main` written explicitly in their `loop.json` (operator-visible).
|
||||
|
||||
## Files touched
|
||||
|
||||
- `scripts/status.py` — add `_base_branch(cfg)` helper; use in `_gate_worktree_drift` (line ~2191).
|
||||
- `templates/loops/self-improvement/loop.json` — add `"base_branch": "main"` to `blast_radius`.
|
||||
- `design/loops/technical.md` — note `blast_radius.base_branch` in the schema enum; mention in §7 worktree-drift gate description.
|
||||
- `design/loops/functional.md` — add `base_branch` row to blast_radius fields list.
|
||||
- `CHANGELOG.md` — new entry under `[unreleased]`.
|
||||
- `tests/test_base_branch.py` (NEW) — 13 tests per plan above.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- `--validate-loop` command (cross-references real branches in the repo). Filed to `BACKLOG.md`.
|
||||
- Auto-detect default branch via `git symbolic-ref`. Filed to `BACKLOG.md`.
|
||||
- Multi-base-branch (list of integration branches). Filed to `BACKLOG.md`.
|
||||
|
||||
## Pipeline plan
|
||||
|
||||
research → research:awaiting_approval → research:approved → implement → code_review → code_review:awaiting_approval → code_review:approved → bug_find → adversarial_bug_find → doc_review → referee → complete.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Referee Verdict: parametrize-base-branch
|
||||
|
||||
## Status: PASS
|
||||
|
||||
## Artifacts
|
||||
|
||||
SPEC, IMPLEMENTATION, CODE_REVIEW, BUG_REPORT, ADVERSARIAL_BUG_REPORT, DOC_REVIEW — all present.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- R1-R5 satisfied. `_base_branch` handles None, empty, non-string types correctly.
|
||||
- Bad-revision → WARNING skip (preserved from v1).
|
||||
- Template ships `base_branch: "main"` by default.
|
||||
- 495 passed (482 + 13, 0 regressions).
|
||||
- Adversarial probes confirm no injection vector (list-mode subprocess.run) and no crash for non-string types.
|
||||
|
||||
## Verdict
|
||||
|
||||
PASS. Approve complete.
|
||||
Reference in New Issue
Block a user