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-23T17:42:56.270146+00:00|user
|
||||
code_review:approved|2026-06-23T17:45:50.448814+00:00|user
|
||||
@@ -0,0 +1,74 @@
|
||||
# ADVERSARIAL_BUG_REPORT: move-completed-tasks-to-complete-folder
|
||||
|
||||
## Methodology
|
||||
|
||||
Targeted attack on:
|
||||
1. Race condition during directory rename
|
||||
2. Symlink escape in task name
|
||||
3. `tasks/complete/` already exists with wrong permissions
|
||||
4. Concurrent completion of the same task
|
||||
5. In-flight operations after directory move
|
||||
|
||||
## Findings
|
||||
|
||||
### Attack 1: Race condition during directory rename -- NOT EXPLOITABLE
|
||||
|
||||
If two processes call `--transition complete` on the same task simultaneously, the race is:
|
||||
- Process A: writes `.state` to `complete`, checks `dest.exists()` (False), renames
|
||||
- Process B: writes `.state` to `complete`, checks `dest.exists()` -- but the rename has already happened
|
||||
|
||||
Process B would not operate on the same `task_path` because `_task_dir` with the `_require_state` state check determines the current location. Actually, Process B's `_write_state` happens after Process A's rename... wait, let me think.
|
||||
|
||||
Both processes call `_task_dir` before any writes, so both get the same `task_path` (the regular location). Process A writes the state, renames the dir. Process B's `_write_state` tries to write `.state` to `task_path` which no longer exists. `_write_state` uses `task_path.write_text(...)` or similar, which would create a NEW directory at the old location! This is a bug.
|
||||
|
||||
Wait, let me check `_write_state`:
|
||||
|
||||
```python
|
||||
def _write_state(task_path: Path, phase: str) -> None:
|
||||
state_file = task_path / ".state"
|
||||
task_path.mkdir(parents=True, exist_ok=True)
|
||||
state_file.write_text(phase.strip() + "\n")
|
||||
```
|
||||
|
||||
It calls `task_path.mkdir(parents=True, exist_ok=True)`! So if Process A renames the directory, Process B's `_write_state` would create a new `tasks/<name>/` directory with `.state` = `"complete"`, but no other artifacts. This is a stale task directory.
|
||||
|
||||
However, this is a theoretical race condition. In practice:
|
||||
- `--transition complete` is called by the orchestrator role (a single process per tick)
|
||||
- Human interaction with `--transition` is serial (one shell command at a time)
|
||||
- Only CI or concurrent users would trigger this, which is extremely rare
|
||||
|
||||
The fix would be to write state AFTER the rename, but the rename needs to happen in `cmd_transition` while the state write is at the end. This is a v1 issue.
|
||||
|
||||
**Verdict:** ACCEPTED RISK (theoretical race condition, rare in practice, mitigated by ordering: rename before state write; second process fails with `FileNotFoundError` instead of creating stale directory)
|
||||
|
||||
*(Note: after review, the implementation was changed to rename BEFORE `_write_state`, so the state is written at the new location. This eliminates the stale-directory race entirely for the `complete` case.)*
|
||||
|
||||
### Attack 2: Symlink escape in task name -- NOT VULNERABLE
|
||||
|
||||
`task_path.rename` operates on Path objects. If `task_path` is a symlink, `rename` follows the symlink and moves the target. However, `task_path` is constructed from the task name which is validated as kebab-case by `--create-task`. Completed task names are the same as the original task name.
|
||||
|
||||
**Verdict:** NOT VULNERABLE
|
||||
|
||||
### Attack 3: `tasks/complete/` exists with wrong permissions -- NOT VULNERABLE
|
||||
|
||||
`mkdir(parents=True, exist_ok=True)` does not change permissions of an existing directory. If `tasks/complete/` exists but is not writable, `rename` will fail with `PermissionError`. `set -e` in shell scripts would catch this. In the Python function, the error propagates to the caller.
|
||||
|
||||
**Verdict:** NOT VULNERABLE (fails loudly)
|
||||
|
||||
### Attack 4: Concurrent completion of the same task -- ACCEPTED
|
||||
|
||||
Same as Attack 1. If two processes complete the same task concurrently, one will succeed and the other will create a stale directory at the original location. The stale directory would contain only `.state` with `"complete"` but no other artifacts. The `_task_dir` fallback might return this stale directory instead of the real completed one.
|
||||
|
||||
**Verdict:** ACCEPTED RISK (concurrent starts are rare; stale directory with only `.state` is benign)
|
||||
|
||||
### Attack 5: In-flight operations after directory move -- HANDLED
|
||||
|
||||
After the rename, the `cmd_transition` function continues to line 613 (the `print` statement). No further file operations on `task_path` occur. The print uses only the task name string, not the path.
|
||||
|
||||
**Verdict:** HANDLED
|
||||
|
||||
## Summary
|
||||
|
||||
Two accepted risks (theoretical race conditions on concurrent completion) and no exploitable vulnerabilities.
|
||||
|
||||
**Verdict: CLEAN** (with accepted race condition risks)
|
||||
@@ -0,0 +1,23 @@
|
||||
# BUG_REPORT: move-completed-tasks-to-complete-folder
|
||||
|
||||
## Findings
|
||||
|
||||
### Bug 1 (LOW): `cmd_create_task` error message references old path
|
||||
|
||||
When creating a task with the same name as a completed task, the error message uses `task_path` which is now the completed task path (via `_task_dir` fallback). The message says "already exists at {task_path}" which shows the `tasks/complete/<name>/` path instead of `tasks/<name>/`. This is correct behavior but could be confusing to the user.
|
||||
|
||||
**Severity:** LOW (accurate but surprising path)
|
||||
**Fix:** None needed for v1. The error message is factually correct.
|
||||
|
||||
### Bug 2 (INFO): Subtask paths not covered by fallback
|
||||
|
||||
The `_task_dir` fallback only applies to non-subtask paths (no "/" in the name). If a subtask is completed, `_task_dir` won't find it in `tasks/complete/<parent>/subtasks/<name>/`. However, subtasks are never independently transitioned to `complete` -- they are part of their parent task's lifecycle.
|
||||
|
||||
**Severity:** INFO (by design)
|
||||
**Fix:** None needed.
|
||||
|
||||
## Summary
|
||||
|
||||
No correctness bugs found. One LOW (cosmetic error message) and one INFO (by design).
|
||||
|
||||
**Verdict: CLEAN**
|
||||
@@ -0,0 +1,51 @@
|
||||
# CODE_REVIEW: move-completed-tasks-to-complete-folder
|
||||
|
||||
## Reviewed Files
|
||||
|
||||
1. `scripts/status.py` -- `_task_dir` fallback (lines 246-249), `cmd_transition` move (lines 601-610)
|
||||
2. `tests/test_move_completed.py` -- 9 tests
|
||||
3. `CHANGELOG.md` -- task 9 entry
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. `_task_dir` fallback
|
||||
|
||||
The fallback checks `base / "complete" / task_name` when `base / task_name` doesn't exist. This is correct. The regular path takes priority over the completed path, so active tasks are always found first. Subtask paths (with "/") are not checked against the completed dir -- this is acceptable because subtasks are always parented to active tasks and are never completed independently.
|
||||
|
||||
**Verdict:** PASS
|
||||
|
||||
### 2. `cmd_transition` move
|
||||
|
||||
The move logic:
|
||||
1. Computes `tasks_root = task_path.parent` -- this is `tasks/` for a regular task
|
||||
2. Creates `complete_dir = tasks_root / "complete"` -- creates if missing
|
||||
3. Refuses if `dest` already exists
|
||||
4. Renames `task_path` to `dest`
|
||||
|
||||
One edge case: if a task is `human_intervention` → `complete`, `_auto_update_verdict_on_complete` modifies `VERDICT.md` in `task_path` before the rename. The modified file is then moved to the completed location. Correct.
|
||||
|
||||
**Verdict:** PASS
|
||||
|
||||
### 3. Test coverage
|
||||
|
||||
9 tests cover:
|
||||
- `_task_dir` regular, fallback, and preference (3 tests)
|
||||
- `_all_task_dirs` exclusion (1 test)
|
||||
- Directory move, creation, create-task refusal, transition refusal, state read (5 tests)
|
||||
|
||||
**Verdict:** PASS
|
||||
|
||||
### 4. Edge cases
|
||||
|
||||
- **`--audit` on completed tasks**: Not affected because `_all_task_dirs` doesn't scan `tasks/complete/`.
|
||||
- **Loop-owned tasks**: If a loop's `current_task` points to a completed task, the audit at line 954 checks `_task_dir(ltask, args.project).exists()` which will find the completed task via fallback. Correct.
|
||||
- **`--transition` from complete**: `LEGAL_TRANSITIONS.get("complete", [])` returns `[]`, so any transition is refused.
|
||||
- **`--create-task` with completed name**: `_task_dir` finds the completed path, `task_path.exists()` returns True, and the error is printed. Correct.
|
||||
|
||||
**Verdict:** PASS
|
||||
|
||||
## Summary
|
||||
|
||||
All 4 review areas pass. The implementation is minimal, correct, and well-tested. 9 new tests. Full suite: 433 passed.
|
||||
|
||||
**Overall verdict: APPROVED**
|
||||
@@ -0,0 +1,25 @@
|
||||
# DOC_REVIEW: move-completed-tasks-to-complete-folder
|
||||
|
||||
## Reviewed Documentation
|
||||
|
||||
1. `CHANGELOG.md` -- task 9 entry
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. CHANGELOG.md
|
||||
|
||||
Entry accurately describes the change: `--transition complete` moves task directory from `tasks/<name>/` to `tasks/complete/<name>/`. Notes the `_task_dir` fallback, `--list`/`--audit` exclusion, and 9 new tests.
|
||||
|
||||
**Verdict:** PASS
|
||||
|
||||
### 2. Cross-reference check
|
||||
|
||||
- `AGENTS.md` references `tasks/` as the task directory -- no mention of `tasks/complete/`. Needs no update because `tasks/complete/` is an implementation detail (tasks are moved there automatically).
|
||||
- `README.md` mentions `--transition complete` -- no change needed (users don't need to know about the directory move).
|
||||
- `design/loops/README.md` references the 8 bootstrap tasks -- task 9 was added separately. No loop integration docs reference task paths.
|
||||
|
||||
## Summary
|
||||
|
||||
All documentation is accurate. No doc gaps found.
|
||||
|
||||
**Verdict: APPROVED**
|
||||
@@ -0,0 +1,40 @@
|
||||
# IMPLEMENTATION: move-completed-tasks-to-complete-folder
|
||||
|
||||
## Summary
|
||||
|
||||
When `--transition complete` is called, the task directory is now moved from `tasks/<name>/` to `tasks/complete/<name>/`. The `_task_dir` function has a fallback to find completed tasks. `--list` and `--audit` exclude completed tasks.
|
||||
|
||||
## Changes
|
||||
|
||||
### R1 -- `_task_dir` fallback (scripts/status.py:236-249)
|
||||
|
||||
Added a fallback check: if `tasks/<name>/` doesn't exist, check `tasks/complete/<name>/`. This ensures `--task <name>`, `--transition`, `--approve`, and all other commands that call `_task_dir` still find completed tasks.
|
||||
|
||||
### R2 -- `cmd_transition` moves task directory (scripts/status.py:601-610)
|
||||
|
||||
After `_write_state(task_path, target)`, if `target == "complete"`, the function:
|
||||
1. Computes the tasks root directory (`task_path.parent`)
|
||||
2. Creates `tasks/complete/` if it doesn't exist
|
||||
3. Renames `task_path` to `tasks/complete/<name>/`
|
||||
4. Refuses if a completed task with the same name already exists
|
||||
|
||||
### R3 -- `_all_task_dirs` unchanged
|
||||
|
||||
`_all_task_dirs` does NOT scan `tasks/complete/`. Only `--task <name>` with fallback can find completed tasks.
|
||||
|
||||
### R4 -- Tests
|
||||
|
||||
`tests/test_move_completed.py`: 9 tests across 3 classes:
|
||||
- `TestTaskDirFallback` (3 tests): regular path, completed fallback, regular preference
|
||||
- `TestAllTaskDirsExcludesCompleted` (1 test): completed tasks excluded from listing
|
||||
- `TestCompleteMovesDir` (5 tests): move, dir creation, create-task refusal, transition refusal, state read after move
|
||||
|
||||
### R5 -- Documentation
|
||||
|
||||
- `CHANGELOG.md`: task 9 entry
|
||||
|
||||
## Verification
|
||||
|
||||
- `python3 -m py_compile scripts/status.py` -- OK
|
||||
- `python3 -m pytest tests/test_move_completed.py -v` -- 9 passed
|
||||
- `python3 -m pytest tests/ -q` -- 433 passed (424 + 9 new)
|
||||
@@ -0,0 +1,33 @@
|
||||
# RESEARCH: move-completed-tasks-to-complete-folder
|
||||
|
||||
## Objective
|
||||
|
||||
When `--transition complete` is called, move the task directory from `tasks/<name>/` to `tasks/complete/<name>/`. Keep `--list` and `--audit` showing only active tasks. Allow `--task <name>` to find completed tasks by fallback.
|
||||
|
||||
## Current Behavior
|
||||
|
||||
`--transition complete` only writes the `.state` file to `"complete"`. The task directory stays in `tasks/<name>/` alongside active tasks, cluttering the listing and audit.
|
||||
|
||||
## Design
|
||||
|
||||
### _task_dir fallback
|
||||
|
||||
Add a fallback in `_task_dir`: if `tasks/<name>/` doesn't exist, check `tasks/complete/<name>/`. This ensures `--task <name>` and `--transition` still work for completed tasks.
|
||||
|
||||
### cmd_transition move
|
||||
|
||||
After `_write_state(task_path, target)`, if `target == "complete"`, compute the tasks root and move `task_path` to `tasks/complete/<name>/`. Create `tasks/complete/` if it doesn't exist. Refuse if a completed task with the same name already exists.
|
||||
|
||||
### _all_task_dirs unchanged
|
||||
|
||||
Do NOT scan `tasks/complete/` in `_all_task_dirs`. Completed tasks are out of sight from `--list` and `--audit`. The fallback in `_task_dir` is sufficient for targeted lookups.
|
||||
|
||||
### cmd_create_task
|
||||
|
||||
`_task_dir` already returns the completed path via fallback, so `cmd_create_task` will see `task_path.exists()` and refuse with "already exists". No separate check needed.
|
||||
|
||||
## Risks
|
||||
|
||||
- **Audit:** `--audit` uses `_all_task_dirs` which doesn't scan `tasks/complete/`, so completed tasks are invisible to audit. This is the desired behavior.
|
||||
- **Loop references:** If a loop's `current_task` points to a completed task, the audit at line 954 checks `_task_dir(ltask, args.project).exists()` which will find the completed task via fallback. Correct.
|
||||
- **`--transition` on completed tasks:** `_task_dir` finds the completed task, `_require_state` reads `"complete"`, and `LEGAL_TRANSITIONS.get("complete", [])` returns `[]`, so any transition is refused. Correct.
|
||||
@@ -0,0 +1,50 @@
|
||||
# SPEC: move-completed-tasks-to-complete-folder
|
||||
|
||||
## Context
|
||||
|
||||
When a task transitions to `complete`, its directory remains in `tasks/<name>/` alongside active tasks. This clutters `--list` and `--audit`. The fix: move completed task directories to `tasks/complete/<name>/` when `--transition complete` is called.
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 -- `_task_dir` fallback
|
||||
|
||||
Modify `_task_dir(task_name, project)` in `scripts/status.py` to check `tasks/complete/<name>` as a fallback when `tasks/<name>` doesn't exist:
|
||||
|
||||
```python
|
||||
task_path = base / task_name
|
||||
if not task_path.exists():
|
||||
completed = base / "complete" / task_name
|
||||
if completed.exists():
|
||||
return completed
|
||||
return task_path
|
||||
```
|
||||
|
||||
This ensures `--task <name>`, `--transition`, `--approve`, and other commands that call `_task_dir` still work for completed tasks.
|
||||
|
||||
### R2 -- `cmd_transition` moves task directory
|
||||
|
||||
After `_write_state(task_path, target)` in `cmd_transition`, add: if `target == "complete"`, compute the tasks root directory and move `task_path` to `tasks/complete/<name>/`. Create `tasks/complete/` if it doesn't exist. Print a message confirming the move.
|
||||
|
||||
### R3 -- `_all_task_dirs` unchanged
|
||||
|
||||
Do NOT scan `tasks/complete/` in `_all_task_dirs`. Completed tasks are out of sight from `--list` and `--audit`.
|
||||
|
||||
### R4 -- Tests
|
||||
|
||||
Write `tests/test_move_completed.py` covering:
|
||||
1. `test_complete_moves_dir` -- simulate `--transition complete` on a task, verify the dir moves to `tasks/complete/<name>/`.
|
||||
2. `test_task_dir_fallback` -- verify `_task_dir` returns the completed path when task is in `tasks/complete/`.
|
||||
3. `test_all_task_dirs_excludes_completed` -- verify `_all_task_dirs` does NOT include completed tasks.
|
||||
4. `test_create_task_refuses_completed` -- verify `--create-task` with the same name as a completed task is refused.
|
||||
5. `test_transition_refuses_from_complete` -- verify `--transition` from `complete` is refused.
|
||||
6. `test_complete_dir_created_on_first_move` -- verify `tasks/complete/` is created if it doesn't exist.
|
||||
|
||||
### R5 -- Documentation
|
||||
|
||||
- `CHANGELOG.md` under `[unreleased]`
|
||||
|
||||
## Verification
|
||||
|
||||
- `python3 -m py_compile scripts/status.py`
|
||||
- `python3 -m pytest tests/test_move_completed.py -v`
|
||||
- `python3 -m pytest tests/ -q` -- full suite green
|
||||
@@ -0,0 +1,28 @@
|
||||
# VERDICT: move-completed-tasks-to-complete-folder
|
||||
|
||||
## Task
|
||||
|
||||
On `--transition complete`, move the task directory from `tasks/<name>/` to `tasks/complete/<name>/`. Add `_task_dir` fallback. Keep `--list` and `--audit` excluding completed tasks.
|
||||
|
||||
## Deliverables Review
|
||||
|
||||
| Requirement | Status | Evidence |
|
||||
|---|---|---|
|
||||
| R1: `_task_dir` fallback | DONE | `scripts/status.py` lines 246-249, 3 tests |
|
||||
| R2: `cmd_transition` moves on complete | DONE | `scripts/status.py` lines 601-612, 5 tests |
|
||||
| R3: `_all_task_dirs` unchanged | DONE | 1 test confirms exclusion |
|
||||
| R4: Tests | DONE | 9 tests in `tests/test_move_completed.py`, all passing |
|
||||
| R5: Documentation | DONE | CHANGELOG updated |
|
||||
|
||||
## Quality Assessment
|
||||
|
||||
- **Test coverage:** 9 new tests, all passing. Full suite 433 passed (was 424). No regressions.
|
||||
- **Code quality:** Minimal change (15 lines added to status.py). Cleanly separates `complete` path from regular transition path.
|
||||
- **Race condition fix:** Rename before state write ensures no stale directory creation on concurrent completion.
|
||||
- **Security:** Adversarial review found no exploitable vulnerabilities.
|
||||
|
||||
## Verdict
|
||||
|
||||
**APPROVED -- ready for complete.**
|
||||
|
||||
All 5 requirements fully implemented, tested, and documented. This completes all 9 bootstrap tasks for loop engineering v1.
|
||||
Reference in New Issue
Block a user