{
  "skill_name": "muggle-pr-followup",
  "notes": "These evals test the TICK DECISION behavior plus the recovery/stop substrate. A real tick needs live GitHub state and the Cron tools, so each prompt hands the model a concrete slot + provider state and asks for the step-by-step plan it would execute; assertions check the plan takes the branch the contract mandates. Cases 0–10 cover per-tick dispatch/idle decisions; cases 11–17 cover cron-delivered ticks handing back to the token-free monitor, the owner stop switch, and the stopped-slot / kill-file absorb that makes an unreachable orphaned cron inert; cases 8 and 18–19 cover the monitor's own wake signals (settled-red CI, branch-behind/conflicting, and blocked-resume on any CI-digest move); cases 20–22 cover session ownership — reconcile re-arms only slots the running session armed, fails closed when it cannot identify itself, and a dead session's slot changes hands only through an explicit single-slug adopt. Run via skill-creator's eval runner — CI does not execute these (ci.yml gates on vitest/check-skill-deps; skill-eval.yml runs gate scenarios sourced from muggle-ai-brain and the routing eval).",
  "evals": [
    {
      "id": 0,
      "eval_name": "actionable-threads-dispatch-address-reviews",
      "prompt": "Tick muggle-ai-works-pr500 500. The slot is open. The PR has two unresolved, not-outdated review threads whose newest comments are from a human (no muggle-do:bot marker), CI is fully green, and the branch is level with master. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "computes_actionable_from_live_thread_state", "text": "Plan derives the actionable set from current unresolved-thread state, not from a stored review-id cursor." },
        { "name": "classifies_by_loop_marker", "text": "Plan decides a thread is actionable by the absence of the muggle-do:bot marker on the newest comment, never by author login." },
        { "name": "cancels_own_cron_before_dispatch", "text": "Plan cancels this watcher's own cron before dispatching, so no tick overlaps the running cycle." },
        { "name": "dispatches_address_reviews", "text": "Plan dispatches /muggle-do with an address-reviews directive carrying the PR URL, slug, and owning review ids, then exits." },
        { "name": "stays_a_dumb_pipe", "text": "Plan does NOT classify reviews, post replies, resolve threads, or escalate — those belong to /muggle-do." }
      ]
    },
    {
      "id": 1,
      "eval_name": "reviews-preempt-ci",
      "prompt": "Tick muggle-ai-works-pr501 501. The slot is open. There is one unresolved human review thread AND two red required checks on the head SHA. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "dispatches_address_reviews_only", "text": "Plan dispatches address-reviews for the review thread." },
        { "name": "does_not_poll_or_dispatch_ci", "text": "Plan does NOT dispatch fix-ci on this tick — actionable feedback preempts the CI branch entirely." },
        { "name": "single_dispatch_then_exit", "text": "Plan performs exactly one dispatch and exits rather than handling both concerns in one tick." }
      ]
    },
    {
      "id": 2,
      "eval_name": "behind-branch-dispatches-rebase",
      "prompt": "Tick muggle-ai-works-pr502 502. The slot is open, there are no unresolved review threads, CI is green. GitHub reports mergeStateStatus BLOCKED and mergeable MERGEABLE, and the compare call shows behind_by 4. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "reads_staleness_from_compare", "text": "Plan determines the branch is out of date from behind_by on the compare call (commit ancestry)." },
        { "name": "does_not_trust_mergeStateStatus", "text": "Plan does NOT conclude the branch is current just because mergeStateStatus reads BLOCKED rather than BEHIND." },
        { "name": "dispatches_rebase", "text": "Plan dispatches /muggle-do with a rebase directive (PR URL + slug, no review ids, no check names)." },
        { "name": "keys_dedup_on_head_and_base_pair", "text": "Plan keys the rebase attempt/escalation lookup on the head..base_tip pair, not the head SHA alone." }
      ]
    },
    {
      "id": 3,
      "eval_name": "red-ci-within-budget-dispatches-fix-ci",
      "prompt": "Tick muggle-ai-works-pr503 503. The slot is open, no unresolved review threads, branch is level with its base. Two checks are red on the head SHA. ci_fix_attempts for that SHA is 1 and the SHA is not in ci_escalated_shas. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "checks_attempt_budget", "text": "Plan verifies ci_fix_attempts for the head SHA is under the cap of 3 before dispatching." },
        { "name": "checks_escalated_set", "text": "Plan verifies the head SHA is not in ci_escalated_shas." },
        { "name": "dispatches_fix_ci_with_check_names", "text": "Plan dispatches /muggle-do with a fix-ci directive carrying the red check names." },
        { "name": "cancels_own_cron_before_dispatch", "text": "Plan cancels this watcher's cron before dispatching." }
      ]
    },
    {
      "id": 4,
      "eval_name": "red-ci-over-budget-idles",
      "prompt": "Tick muggle-ai-works-pr504 504. The slot is open, no unresolved review threads, branch is level with its base. Three checks are red on the head SHA, ci_fix_attempts for that SHA is 3, and the SHA is in ci_escalated_shas. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "does_not_dispatch", "text": "Plan does NOT dispatch fix-ci — the budget is spent and the SHA is already escalated." },
        { "name": "treats_as_durable_block", "text": "Plan classifies this idle as blocked pending a human (reason ci_escalated), not as a transient idle." },
        { "name": "reminds_owner_at_1m", "text": "Plan emits a one-line owner reminder and keeps the normal 1m cadence — it does not back off, slow down, or stop polling." },
        { "name": "no_pr_side_post", "text": "Plan does NOT post anything to the PR for a blocked tick; the reminder goes to the loop owner in-session." }
      ]
    },
    {
      "id": 5,
      "eval_name": "merged-pr-finalizes-without-respawn",
      "prompt": "Tick muggle-ai-works-pr505 505. Refreshing the PR shows state MERGED. The slot on disk still says open. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "finalizes_the_slot", "text": "Plan finalizes the slot: marks it terminal, writes result.md, logs, and unschedules this watcher's cron." },
        { "name": "does_not_respawn", "text": "Plan does NOT respawn or re-arm a watcher — a terminal PR needs none." },
        { "name": "hands_off_terminal_wrapup", "text": "Plan hands the terminal wrap-up to /muggle-do post-merge cleanup as the turn's last action." },
        { "name": "unschedules_recorded_id_first", "text": "Plan cancels the cron by the id recorded in cron.json first, falling back to a CronList match — so teardown works even after CronList has gone blind." }
      ]
    },
    {
      "id": 6,
      "eval_name": "stale-queued-fire-does-not-refinalize",
      "prompt": "Tick muggle-ai-works-pr506 506. Reading the slot, prs.json already records state merged and result.md exists — this fire was queued before the cron was cancelled. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "detects_stale_fire", "text": "Plan recognises this as a stale queued fire from the on-disk terminal state before fetching anything." },
        { "name": "does_not_refetch_or_refinalize", "text": "Plan does NOT re-fetch the PR, re-write result.md, or re-run the terminal handoff." },
        { "name": "defensively_cancels_and_logs", "text": "Plan defensively cancels any lingering cron for the slug and appends a stale-tick line to followup.log, then exits." }
      ]
    },
    {
      "id": 7,
      "eval_name": "repeated-stale-fires-escalate-orphan-once",
      "prompt": "Tick muggle-ai-works-pr507 507. prs.json records state merged and result.md exists. The watcher's session was continued, so neither the recorded cron id nor CronList can reach this slug's cron. followup.log shows finalize recorded cancel=not-found and one earlier stale-tick line also recorded cancel=not-found — there is no cancel=found line anywhere for this slot — and no stale-orphan-escalated line. This fire's defensive cancel per cancel-cron.md again reports not-found. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "detects_orphaned_cron", "text": "Plan concludes the cron is orphaned because no cancel has ever reached it — finalize's teardown cancel and every stale fire's defensive cancel all report not-found — and the stale fires persist (a prior stale-tick line already exists). It reaches this from the reachability signal, not from counting stale fires to a threshold." },
        { "name": "requires_persistence_not_a_lone_fire", "text": "Plan escalates because this is not the first stale fire (a prior stale-tick line exists); it would treat a single not-found fire as a possible manually-run tick or just-expired cron, not an orphan." },
        { "name": "would_not_escalate_a_drained_backlog", "text": "Plan would NOT escalate had any cancel reported found — finalize's teardown cancel or an earlier stale fire's defensive cancel — because a reachable cron's later fires are a finite queued-drain backlog, not an orphan." },
        { "name": "escalates_to_owner_once", "text": "Plan surfaces a one-line owner notice that only a session restart clears the orphan, and appends a stale-orphan-escalated marker line so the notice never repeats." },
        { "name": "later_fires_absorb_silently", "text": "Plan treats any stale fire after the marker exists as a silent absorb — log stale-tick and exit, no repeated notice." },
        { "name": "never_guess_deletes_other_ids", "text": "Plan does NOT delete cron ids that CronList surfaces for other slugs — those belong to other live watchers." }
      ]
    },
    {
      "id": 8,
      "eval_name": "arm-monitor-wakes-on-settled-red-ci",
      "prompt": "Arm the watch for muggle-ai-works-pr508 508 after a clean drain (no reviews, no unresolved threads, checks green at seed time). Describe the watch.sh monitor loop you would write and what makes it wake the session.",
      "files": [],
      "assertions": [
        { "name": "polls_check_run_rollup", "text": "The loop polls the head SHA's check-run rollup each iteration, not just reviews and threads." },
        { "name": "wakes_only_on_settled_red", "text": "The loop wakes the session when the checks have settled with a failure (no check pending and one or more in the fail bucket) — it does NOT wake while any check is still pending." },
        { "name": "dedups_ci_on_head_sha", "text": "The loop dedups the CI wake on the head SHA (fires once per red head) rather than on a monotonic id, because the check rollup is non-monotonic and resets on each push." },
        { "name": "stays_a_dumb_pipe", "text": "The loop only signals that CI went red and hands off to a tick; it does NOT apply the ci_fix_attempts budget, classify the failure, or dispatch fix-ci itself — those belong to the tick and /muggle-do." },
        { "name": "self_terminates_not_unbounded", "text": "The loop is self-terminating, not a bare while-true: it claims the slot by writing its PID to watch.pid, and each iteration exits when superseded by a newer arm (watch.pid changed) or past the lifetime cap — so it cannot keep polling forever after its session ends." }
      ]
    },
    {
      "id": 9,
      "eval_name": "outdated-thread-with-unanswered-question-still-actionable",
      "prompt": "Tick muggle-ai-works-pr509 509. The slot is open. The PR has one unresolved review thread whose newest comment is a human question with no muggle-do:bot marker, but GitHub reports isOutdated=true on the thread because a later push moved the line it was anchored to. CI is green and the branch is level with master. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "treats_outdated_thread_as_actionable", "text": "Plan marks the thread actionable even though isOutdated=true — the gates are resolution state and the loop marker, never outdatedness." },
        { "name": "does_not_skip_on_isOutdated", "text": "Plan does NOT skip or idle the thread on the grounds that its anchored line moved; the unanswered question survives the line move and must be surfaced." },
        { "name": "dispatches_address_reviews", "text": "Plan dispatches /muggle-do with an address-reviews directive for the thread's owning review id, rather than idling." },
        { "name": "leaves_mootness_to_mdo", "text": "Plan leaves the judgment of whether the moved code already mooted the concern to /muggle-do's reply — it is not a reason for the watcher to filter the thread out." }
      ]
    },
    {
      "id": 10,
      "eval_name": "outdated-thread-loop-already-replied-stays-quiet",
      "prompt": "Tick muggle-ai-works-pr510 510. The slot is open. The PR has one unresolved review thread with isOutdated=true whose newest comment carries the muggle-do:bot marker — the loop has already replied. CI is green and the branch is level with master. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "marker_makes_it_non_actionable", "text": "Plan treats the thread as non-actionable because its newest comment carries the muggle-do:bot marker — the marker rule holds regardless of outdatedness." },
        { "name": "no_dispatch_no_echo", "text": "Plan does NOT dispatch address-reviews; re-dispatching would echo the loop's own reply." },
        { "name": "idles_transiently", "text": "Plan idles this tick as a transient idle (green, branch current, awaiting the next human comment) and keeps the 1m cadence — it is not a durable human-block." }
      ]
    },
    {
      "id": 11,
      "eval_name": "cron-delivered-idle-with-live-monitor-cancels-duplicate",
      "prompt": "Tick muggle-ai-works-pr520 520 — this fire was delivered by a /loop recovery cron, not a monitor wake. The slot is open, watch-heartbeat was touched 40 seconds ago, there are no unresolved review threads, CI is green, and the branch is level with master. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "recognises_cron_delivered_fire", "text": "Plan recognises this tick was delivered by a /loop recovery cron and therefore runs the hand-back-to-monitor step — a monitor wake would skip it." },
        { "name": "reads_heartbeat_as_live_monitor", "text": "Plan reads watch-heartbeat mtime, sees it is fresh (within 3 minutes), and concludes a live monitor already owns the 1m cadence." },
        { "name": "cancels_duplicate_cron", "text": "Plan cancels this cron per cancel-cron.md (recorded-id first) because it is a duplicate poller — there is nothing to arm." },
        { "name": "never_recreates_cron_or_double_arms", "text": "Plan does NOT CronCreate a recurring cron and does NOT start a second monitor over the live one; exactly one poller (the monitor) remains." }
      ]
    },
    {
      "id": 12,
      "eval_name": "cron-delivered-idle-with-dead-monitor-rearms-monitor",
      "prompt": "Tick muggle-ai-works-pr521 521 — this fire was delivered by a /loop recovery cron. The slot is open, watch-heartbeat is missing (the last monitor died), there are no unresolved review threads, CI is green, and the branch is level with master. Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "detects_dead_monitor_from_beacon", "text": "Plan reads watch-heartbeat, finds it missing or older than 3 minutes, and concludes the monitor is dead and this cron has become the primary poller." },
        { "name": "finishes_arming_a_monitor", "text": "Plan finishes the arm-watcher sequence — seed the watermark to the ids this tick itself read (its own observed max, not a fresh post-tick fetch), start the persistent labeled monitor — using this tick as the drain." },
        { "name": "cancels_cron_after_arming", "text": "Plan then cancels this cron per cancel-cron.md and appends a re-armed (monitor restored) line to followup.log, so the 1m cadence lives in the token-free monitor rather than a per-minute model turn." },
        { "name": "never_rearms_with_a_cron", "text": "Plan does NOT restore the poller by creating a recurring /loop cron; the cron is only ever a one-shot recovery delivery." }
      ]
    },
    {
      "id": 13,
      "eval_name": "reconcile-rearms-silent-open-watcher-with-monitor",
      "prompt": "Reconcile the slots. muggle-ai-works-pr522 522 is open (no result.md), but its watch-heartbeat is 40 minutes stale and the newest followup.log line is 40 minutes old — the watcher stopped silently (a crashed cycle that never respawned). The global kill file is absent. Walk me through the plan for this slot.",
      "files": [],
      "assertions": [
        { "name": "classifies_silent_watcher_from_freshest_beacon", "text": "Plan judges the watcher dead because the freshest beacon (watch-heartbeat mtime or newest followup.log line) is older than the staleness window." },
        { "name": "rearms_via_arm_watcher_monitor", "text": "Plan re-arms per arm-watcher.md — drain tick, watermark seed, persistent monitor — restoring a token-free poller." },
        { "name": "never_rearms_with_cron", "text": "Plan does NOT re-arm by creating a recurring /loop cron; the 1m cadence belongs in the monitor and a cron fire would be a full model turn." },
        { "name": "would_skip_under_killfile", "text": "Plan notes it would skip re-arming entirely if polling.disabled existed — here it is absent, so it proceeds." }
      ]
    },
    {
      "id": 14,
      "eval_name": "stop-single-slot-teardown-and-stopped-rename",
      "prompt": "The owner runs stop for muggle-ai-works-pr523 523 (one slot). The slot has a live monitor, a recorded cron id in cron.json, and on Windows an inner bash watch.sh grandchild. Walk me through the plan.",
      "files": [],
      "assertions": [
        { "name": "stops_monitor_including_windows_grandchild", "text": "Plan TaskStops the monitor AND kills the surviving bash <slug>/watch.sh grandchild (enumerated by command line) that outlives TaskStop on Windows — otherwise it keeps touching watch-heartbeat and its open handles block the rename." },
        { "name": "cancels_cron_recorded_id_first", "text": "Plan cancels the cron per cancel-cron.md, recorded-id first with a CronList-match fallback, and admits a cron neither lookup reaches is orphaned but inert." },
        { "name": "renames_slot_to_stopped", "text": "Plan renames the slot dir to <slug>.stopped — the load-bearing act the tick absorb, reconcile, and auto-track all key on to stay away." },
        { "name": "no_global_killfile_for_single_slot", "text": "Plan does NOT write the global polling.disabled kill file for a scoped single-slot stop, and writes no result.md — stopping is resumable, not finalizing." }
      ]
    },
    {
      "id": 15,
      "eval_name": "stop-everything-writes-global-killfile",
      "prompt": "The owner runs stop with no slug — take every watcher down. Several slots are open with live monitors and recorded crons. Walk me through the plan.",
      "files": [],
      "assertions": [
        { "name": "stops_and_renames_every_open_slot", "text": "Plan stops each open slot (monitor + Windows grandchild + cron) and renames each to <slug>.stopped." },
        { "name": "writes_global_killfile", "text": "Plan writes the global kill file ~/.muggle-ai/muggle-do/polling.disabled so any surviving orphaned cron fire absorbs itself before resolving a slot, and reconcile refuses to re-arm while it exists." },
        { "name": "stopping_is_not_finalizing", "text": "Plan writes no result.md — a stopped slot stays open on the provider and is resumable by renaming <slug>.stopped back and re-arming." },
        { "name": "honest_about_orphan_floor", "text": "Plan reports an orphaned cron honestly — it may keep firing one-line absorbs until its host session ends, which is the harness's floor, not a teardown failure." }
      ]
    },
    {
      "id": 16,
      "eval_name": "stopped-slot-absorb-before-any-io",
      "prompt": "Tick agent-pin-conversions 137 — this fire was delivered by an orphaned /loop cron whose handle a compaction severed. On disk the live slot is gone and only agent-pin-conversions.stopped exists (the owner ran stop). Walk me through the plan for this tick.",
      "files": [],
      "assertions": [
        { "name": "absorbs_before_any_fetch_or_write", "text": "Plan checks for the .stopped slot (and the polling.disabled kill file) at the very top of Step 0, before any GitHub fetch, state write, or log line." },
        { "name": "one_line_absorb_then_exit", "text": "Plan outputs a single line (stopped: <slug> — absorbed) and exits — no fetch, no state write, no followup.log line, no dispatch." },
        { "name": "does_not_resurrect_or_guess_delete", "text": "Plan does NOT recreate the slot, re-arm a monitor, or delete cron ids CronList surfaces for other slugs — those belong to other live watchers." },
        { "name": "absorb_caps_the_orphan", "text": "Plan recognises this absorb gate is exactly what caps an unreachable orphaned cron at one harmless line per fire until its session dies." }
      ]
    },
    {
      "id": 17,
      "eval_name": "bootstrap-clears-killfile-on-explicit-url",
      "prompt": "Bootstrap a watcher from the URL https://github.com/multiplex-ai/muggle-ai-works/pull/524. The global polling.disabled kill file is present from an earlier stop-everything. Walk me through the plan.",
      "files": [],
      "assertions": [
        { "name": "clears_killfile_as_first_act", "text": "Plan deletes the polling.disabled kill file during bootstrap, treating a pasted PR URL as an explicit request to watch that overrides a prior stop-everything." },
        { "name": "surfaces_the_clear_in_one_line", "text": "Plan says in one line that it cleared the kill file, so the state change is visible." },
        { "name": "then_seeds_and_arms", "text": "Plan then seeds the slot and arms the watch normally — leaving the kill file in place would make every tick of the new watch absorb itself stillborn." },
        { "name": "only_bootstrap_clears_it", "text": "Plan notes only bootstrap (an explicit URL) clears the kill file; auto-track and reconcile never do." }
      ]
    },
    {
      "id": 18,
      "eval_name": "arm-monitor-wakes-on-branch-behind",
      "prompt": "Arm the watch for muggle-ai-works-pr525 525 after a clean drain (no reviews, no unresolved threads, CI green, branch level with master at seed time). Describe the watch.sh monitor loop you would write and specifically what makes it wake the session when the base branch later advances and the branch falls behind.",
      "files": [],
      "assertions": [
        { "name": "polls_branch_standing", "text": "The loop polls the branch's standing against its base each iteration (behind_by via the compare call and the conflict signal per branch-standing.md), not just reviews, threads, and CI." },
        { "name": "wakes_on_behind_or_conflict", "text": "The loop wakes the session when behind_by > 0 or the branch conflicts with its base, so a silently-stale branch triggers a tick without waiting for an unrelated wake." },
        { "name": "dedups_on_rebase_key_pair", "text": "The loop dedups the rebase wake on the rebase_key pair (head_sha..base_tip_sha), firing once per newly-due pair and re-arming when the head or base moves — not on a head-only key, which would wedge permanently once the base advances." },
        { "name": "not_a_wake_when_unknown_and_level", "text": "The loop does NOT wake when behind_by == 0 and mergeable is UNKNOWN (conflict state still computing) — it waits, matching the tick's Step 5." },
        { "name": "stays_a_dumb_pipe", "text": "The loop only signals a rebase is due and hands off to a tick; it does NOT apply the conflict_resolve_attempts budget, resolve conflicts, or dispatch the rebase itself — those belong to the tick and /muggle-do." }
      ]
    },
    {
      "id": 19,
      "eval_name": "arm-monitor-wakes-on-cidigest-change-while-blocked",
      "prompt": "Arm the watch for muggle-ai-works-pr526 526 while it is blocked pending a human (last_seen.blocked present, reason ci_escalated). Describe the watch.sh monitor loop and specifically what makes it resume the block when the head's CI later moves to green, or a rerun/external deploy check posts — i.e. a non-red CI change.",
      "files": [],
      "assertions": [
        { "name": "blocked_probe_is_conditional", "text": "The loop runs the blocked-resume CI probe only while the watch is blocked — it keys off BLOCKED_CIDIGEST being non-empty, and the probe stays dormant when the watch is not blocked." },
        { "name": "wakes_on_any_digest_move_not_just_red", "text": "While blocked, the loop wakes on ANY change to the head's CI digest away from BLOCKED_CIDIGEST — a green pass, a rerun, or an external deploy check — not only on a settled-red transition." },
        { "name": "digest_matches_fingerprint_definition", "text": "The digest the loop computes is the same bucket-plus-sorted-name/conclusion signature the blocked fingerprint's ci_digest uses, so the loop and the tick's Step 2.5 resume gate agree by construction." },
        { "name": "resume_is_the_tick_job", "text": "The loop only wakes on the digest move; clearing last_seen.blocked and re-evaluating belongs to the tick's Step 2.5 fingerprint gate, and the blocked-tick flag/resume path is what maintains BLOCKED_CIDIGEST — the loop never writes it." }
      ]
    },
    {
      "id": 20,
      "eval_name": "reconcile-never-rearms-another-sessions-slot",
      "prompt": "Run reconcile. $CLAUDE_CODE_SESSION_ID is session-AAA. On disk: muggle-ai-works-pr600 is open, owner.json says session-AAA, heartbeat 3 hours old. muggle-ai-ui-pr601 is open, owner.json says session-BBB, heartbeat 3 hours old. muggle-ai-brain-pr602 is open with no owner.json at all, heartbeat 3 hours old. muggle-ai-works-pr603 has owner.json session-BBB and its PR is now MERGED. Walk me through the plan for this sweep.",
      "files": [],
      "assertions": [
        { "name": "partitions_by_owner_session_id", "text": "Plan splits the slots by owner.json session_id against $CLAUDE_CODE_SESSION_ID before deciding anything — pr600 owned, pr601/pr602/pr603 foreign." },
        { "name": "rearms_only_the_owned_slot", "text": "Plan re-arms ONLY muggle-ai-works-pr600. It does not re-arm pr601 or pr602 despite their stale heartbeats." },
        { "name": "missing_owner_is_foreign_not_adoptable", "text": "Plan treats muggle-ai-brain-pr602's absent owner.json as foreign and never re-arms or backfills an owner for it." },
        { "name": "finalizes_terminal_regardless_of_owner", "text": "Plan finalizes muggle-ai-works-pr603 even though session-BBB owns it, because finalizing needs no context — it writes result.md and cancels the cron." },
        { "name": "reports_orphans_without_offering", "text": "Plan reports pr601 and pr602 as orphaned with the adopt command, and does NOT offer to adopt them, prompt about them, or pick one to take." }
      ]
    },
    {
      "id": 21,
      "eval_name": "reconcile-fails-closed-without-a-session-id",
      "prompt": "Run reconcile in an environment where $CLAUDE_CODE_SESSION_ID is unset. Three open slots exist, all with stale heartbeats: two carry owner.json with different session ids, one carries none. Walk me through the plan.",
      "files": [],
      "assertions": [
        { "name": "treats_every_slot_as_foreign", "text": "Plan treats ALL three slots as foreign because the running session cannot identify itself — it does not match the unset id against anything." },
        { "name": "rearms_nothing", "text": "Plan re-arms zero watchers." },
        { "name": "still_finalizes_and_reports", "text": "Plan still refreshes live state, finalizes any slot whose PR went terminal, and reports the open ones as orphans — it degrades to finalize-and-report rather than aborting." }
      ]
    },
    {
      "id": 22,
      "eval_name": "adopt-is-explicit-and-single-slot",
      "prompt": "The user runs: /muggle:muggle-pr-followup adopt muggle-ai-ui-pr601. That slot is open, its owner.json names a different session, and its PR is still open on GitHub. Walk me through the plan.",
      "files": [],
      "assertions": [
        { "name": "claims_owner_before_arming", "text": "Plan overwrites owner.json with this session's $CLAUDE_CODE_SESSION_ID and a fresh claimed_at BEFORE arming the monitor." },
        { "name": "warns_about_the_context_gap", "text": "Plan states that this session lacks the original session's context for the PR, so cycles work from the PR and diff alone." },
        { "name": "arms_with_a_drain_tick", "text": "Plan arms per arm-watcher — a drain tick first, then the persistent monitor — rather than resuming from the previous owner's watermark alone." },
        { "name": "adopts_only_the_named_slot", "text": "Plan adopts exactly muggle-ai-ui-pr601 and does not sweep, list-and-take, or adopt any other orphaned slot in the same run." },
        { "name": "refuses_a_stopped_slot", "text": "Plan would refuse the adoption outright if the slot were named <slug>.stopped, since stop is the owner's kill switch and adoption is not a way around it." }
      ]
    },
    {
      "id": 23,
      "eval_name": "arm-runs-the-shipped-loop-never-authors-one",
      "prompt": "Arm the watch for muggle-ai-works-pr600 600 after a clean drain. The slot has no watch.pid. Walk me through exactly how you start the monitor, including the command you run.",
      "files": [],
      "assertions": [
        { "name": "runs_the_shipped_loop", "text": "Plan starts the shipped scripts/pr-watch-loop.sh, passing --slot, --repo and --pr, with the script path resolved to an absolute path under CLAUDE_PLUGIN_ROOT." },
        { "name": "does_not_author_a_watch_sh", "text": "Plan does NOT write, generate, or hand-author a per-slot watch.sh, and does NOT copy one from another slot." },
        { "name": "persistent_labeled_monitor", "text": "Plan starts it as a persistent background monitor labeled 'PR #<n> — <title>', one per PR." },
        { "name": "claims_owner_first", "text": "Plan writes owner.json with this session's $CLAUDE_CODE_SESSION_ID before starting the monitor." }
      ]
    },
    {
      "id": 24,
      "eval_name": "monitor-wakes-on-branch-merely-behind-base",
      "prompt": "A watch is running on muggle-ai-works-pr383 383. Its base branch advances by two commits. The PR reports mergeable=MERGEABLE (no conflict) and mergeStateStatus=BLOCKED because a review is still required. The base requires branches to be up to date before merging. Should the watch loop wake, and how does it decide?",
      "files": [],
      "assertions": [
        { "name": "wakes_on_behind_not_only_conflict", "text": "The loop DOES wake. A branch that is merely behind reports MERGEABLE, so the conflict signal alone never fires for it — behind_by > 0 is its own wake condition." },
        { "name": "does_not_rely_on_mergestatestatus", "text": "The decision does NOT come from mergeStateStatus: it is a single enum with precedence, and BLOCKED (review required) masks BEHIND on a PR still awaiting approval." },
        { "name": "measures_behind_via_compare", "text": "Behind-ness is measured with a compare call (behind_by), not inferred from the PR's mergeable/mergeStateStatus fields." },
        { "name": "keys_on_the_head_base_pair", "text": "The floor is the rebase_key <head_sha>..<base_tip_sha>, so the wake fires once per newly-due pair and re-arms when the base advances again under an unchanged head." },
        { "name": "condition_lives_in_shipped_file", "text": "The wake condition is defined in the shipped scripts/pr-watch-events.sh (watch_wake_rebase), not re-derived per arm." }
      ]
    }
  ]
}
