- 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
5.5 KiB
Implementation: add-status-brakes
Implements SPEC.md R1–R10. All new code lives in scripts/status.py (loop extensions) plus a new test file tests/test_status_brakes.py and a minimal loop template at templates/loops/ci-triage/loop.json.
Surface added (R1–R10)
| Req | CLI surface | Behavior |
|---|---|---|
| R1 | n/a | .state.loop schema v1 with 13 default fields; written atomically via tmp+rename |
| R2 | --create-loop NAME [--from-template T] |
Refuses non-kebab, duplicates, unknown template; patches name into copied loop.json; seeds empty .state.log |
| R3 | --version; --approve --loop NAME |
--version reads ## Framework Version from config.md; --approve --loop is the only way to clear a halt (D4); increments resumed_count |
| R4 | --can-continue NAME [--json] |
Cheap status probe: ok := status == "running" |
| R5 | --check-gate NAME [--json] |
Runs 6 gates in order; first failure halts the loop and emits structured verdict |
| R6 | --install-schedule NAME [--interval S] |
Generates run-tick.sh/.bat; installs launchd plist / crontab block / schtasks unit per platform.system(); --pause-loop best-effort disables the unit |
| R7 | --can-edit --loop NAME [--loop-worktree] --file P |
Checks file against loop's blast_radius.file_scope; refuses files outside project/framework root |
| R8 | --transition extension |
Refuses if a HALTED loop owns the task (_loop_owning_task scan); points user at --approve --loop |
| R9 | --audit Cat-6 block; --loop-list |
Reuses _audit_loops_block; runs even when no tasks exist |
| R10 | .state.log tick trail |
Every state-changing op appends an ISO-timestamped line; tests assert PAUSED/RESUMED/APPROVED/HALT are all logged |
Gate order (R5)
gate_loop_status -> not running -> halt w/ existing halt_reason
gate_iterations -> iteration_count >= max_iterations -> iterations_exhausted
gate_budget -> spent_usd >= max_budget_usd -> budget_exhausted (remote-only, informational)
gate_task_phase -> current_task in human_intervention -> human_intervention
gate_worktree_drift -> changed files outside file_scope -> drift_detected
gate_score_plateau -> score_history flat across window -> verifier_failed
First failure wins. Halt is written atomically; schedule is best-effort disabled.
Helper functions added (scripts/status.py, before def main())
LOOP_*constants (states, halts, schema version, file names)_loops_dir,_loop_dir,_all_loop_dirs_read_state_loop,_write_state_loop,_initial_state_loop,_read_loop_config_append_tick_log,_loop_untracked_hint_halt_loop,_disable_schedule,_enable_schedule_loop_owning_task(R8 ownership scan)_gate_*(6 gate functions)_loop_max_iterations_task_phase_for_loopcmd_create_loop,cmd_install_schedule,cmd_pause_loop,cmd_resume_loopcmd_approve_loop(R3 halt-clear)cmd_check_gate,cmd_can_continuecmd_loop_list,cmd_versioncmd_can_edit_loop(R7 worktree scope)
Existing functions extended
cmd_can_edit— early hook: ifargs.loop, delegate tocmd_can_edit_loop.cmd_transition— R8 halt-refusal inserted after_require_state;_loop_owning_taskscan.cmd_audit—_audit_loops_block(args)helper called twice (early-return empty-tasks path + main path); Cat-6 header always printed.
Argparse additions (main())
--create-loop, --from-template, --install-schedule, --interval, --pause-loop, --resume-loop, --loop, --loop-worktree, --check-gate, --can-continue, --loop-list, --version.
Dispatch order places loop commands before task commands so --approve --loop doesn't fall through to the --task-required cmd_approve.
New file: templates/loops/ci-triage/loop.json
Minimal template used as --create-loop default. Defines brakes.max_iterations=25, score_plateau_window=5, blast_radius.use_worktree=true. Full prompt/template expansion is task 6.
Tests
tests/test_status_brakes.py — 46 tests across 10 classes mirroring R1–R10:
TestStateLoopSchema(R1) — default-schema assertions + tick log file presenceTestCreateLoop(R2) — kebab/dup/template rejection + name-patchingTestVersionAndApprove(R3) — version regex; approve refuses non-halted; clears halted + bumpsresumed_countTestCanContinue(R4) — running ok, halted denied, unknown → exit 2TestCheckGate(R5) — fresh-pass, status-halt, iterations-exhausted, iterations-remaining, budget-exhausted, budget-informational, task-phase-halt, score-plateau, short-history-ok, JSON outputTestInstallSchedule(R6) — stub generation, default interval from config, unknown-loop rejectionTestCanEditLoop(R7) — in-scope allowed, out-of-scope denied, outside-root denied, no-file rejectedTestTransitionHaltRefusal(R8) — refused when halted owner, allowed when running owner, allowed when no ownerTestAuditAndList(R9) — empty list, populated list, Cat-6 header on empty, halted flag, untracked flag, running-passTestTickLog(R10) — PAUSED/RESUMED/APPROVED/HALT all loggedTestPauseResume— pause sets paused; resume only from paused; halted→approve pointer
Verification
python3 -m py_compile scripts/status.py # OK
python3 -m pytest tests/test_status_brakes.py -q # 46 passed
python3 -m pytest tests/ -q # 310 passed (was 264 + 46 new)
No existing tests changed. Full suite green.