---
phase: 38-doctor-self-heal
plan: 01
type: execute
wave: 1
depends_on: ["35-01", "36-01", "37-01"]
files_modified:
  - plugins/autonomous-loop/scripts/doctor-lib.sh
  - plugins/autonomous-loop/scripts/heal-self.sh
  - plugins/autonomous-loop/skills/doctor/SKILL.md
  - plugins/autonomous-loop/hooks/session-bind.sh
  - plugins/autonomous-loop/tests/test-doctor.sh
  - plugins/autonomous-loop/tests/test-heal-self.sh
autonomous: true
requirements:
  - DOC-01
  - DOC-02
  - DOC-03
  - DOC-04

must_haves:
  truths:
    - "DOC-01: /autonomous-loop:doctor skill produces per-loop GREEN/YELLOW/RED report; --json mode emits structured output; --fix mode auto-remediates SAFE ops only (unload orphan plists, archive corrupted entries) — never spawns claude or auto-reclaims"
    - "DOC-02: doctor-lib.sh cross-references registry, heartbeats, launchctl list, plist files; detects: zombie launchctl (no registry), orphan registry (no plist), label collisions, multi-cwd JSONL contamination via ~/.claude/projects scan, missing heartbeats, stale pending-bind"
    - "DOC-03: heal-self.sh is idempotent — archives entries with owner_session_id ∈ {unknown, unknown-session, '', pending-bind} older than 1 hour to ~/.claude/loops/registry.archive.jsonl; gated by content hash so it runs at most once per registry-version on SessionStart"
    - "DOC-04 partial: status skill remains as-is (deferred enhancement); doctor verdict is accessible via /autonomous-loop:doctor explicitly"
    - "session-bind.sh invokes heal-self.sh once per session start (gated by hash) so cleanup happens automatically over time"
    - "Phases 35+36+37 tests still pass; new tests for doctor + heal-self pass; shellcheck clean; bun validate-plugins.mjs PASS"
  artifacts:
    - path: "plugins/autonomous-loop/scripts/doctor-lib.sh"
      provides: "loop_doctor_report, loop_doctor_fix functions"
      contains: "GREEN|YELLOW|RED"
    - path: "plugins/autonomous-loop/scripts/heal-self.sh"
      provides: "Self-migration archiving stale unknown-owner entries"
      contains: "registry.archive.jsonl"
    - path: "plugins/autonomous-loop/skills/doctor/SKILL.md"
      provides: "/autonomous-loop:doctor user-invocable skill"
      contains: "GREEN/YELLOW/RED"
---

<objective>
Self-diagnostic operator-facing surface: a `/autonomous-loop:doctor` skill that produces a per-loop GREEN/YELLOW/RED report; an idempotent `heal-self.sh` migration that auto-cleans stale registry entries on every fresh SessionStart.

Purpose: Make the system self-diagnostic. User can ask "is everything OK?" inside Claude Code and get a structured answer, instead of reverse-engineering launchctl + JSONL state.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
</execution_context>

<context>
@.planning/phases/38-doctor-self-heal/38-CONTEXT.md
@plugins/autonomous-loop/CLAUDE.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: scripts/doctor-lib.sh</name>
  <files>plugins/autonomous-loop/scripts/doctor-lib.sh</files>
  <action>
Create `doctor-lib.sh` exporting:

- `loop_doctor_report` — outputs per-loop verdict (GREEN/YELLOW/RED) with reasons. Iterates registry; for each loop runs the detection catalog from CONTEXT.md. Supports `--json` flag.
- `loop_doctor_fix` — same scan but applies safe remediations: unload orphan launchctl entries (those with no registry), archive corrupted registry entries to `registry.archive.jsonl`, prune `/var/folders/*` test entries. NEVER spawns claude. NEVER auto-reclaims.
- `_doctor_check_<area>` private helpers for each detection: `registry_vs_launchctl`, `heartbeat_freshness`, `jsonl_multi_cwd`, `label_uniqueness`, `pending_bind_staleness`.

Severity model:

- RED: known-broken state requiring action (zombie launchctl, multi-cwd JSONL, label collision)
- YELLOW: probably-OK but worth attention (pending-bind <1h, dead pid + recent heartbeat race)
- GREEN: healthy

JSONL multi-cwd check: open `~/.claude/projects/<sanitized-cwd>/<sid>.jsonl`, read first 50 + last 50 lines, jq for distinct `.cwd` values. >1 distinct cwd = RED.

`set -euo pipefail`. Source registry-lib.sh + provenance-lib.sh. Set `_PROV_AGENT=doctor-lib.sh`.
</action>
</task>

<task type="auto">
  <name>Task 2: scripts/heal-self.sh</name>
  <files>plugins/autonomous-loop/scripts/heal-self.sh</files>
  <action>
Create `heal-self.sh`. On invocation:
1. Compute SHA256 of `~/.claude/loops/registry.json`.
2. Read `~/.claude/loops/.last-healed-hash`. If matches current hash, exit 0 (already-healed; idempotent gate).
3. Iterate registry; for each entry where `owner_session_id ∈ {unknown, unknown-session, '', pending-bind}` AND `(now_us - owner_start_time_us) / 1e6 > 3600`:
   - Append the entry as JSONL to `~/.claude/loops/registry.archive.jsonl` (with `archived_ts_iso` field).
   - Remove from registry.json via `update_loop_field`-style atomic write.
   - Emit `heal_archived` provenance event.
4. Update `.last-healed-hash` to current hash AFTER archival.
5. Returns 0 always (graceful — must not block SessionStart).

Make executable. shellcheck clean.
</action>
</task>

<task type="auto">
  <name>Task 3: skills/doctor/SKILL.md</name>
  <files>plugins/autonomous-loop/skills/doctor/SKILL.md</files>
  <action>
Create `skills/doctor/SKILL.md` (mirror style of skills/status/SKILL.md):

```yaml
---
name: doctor
description: "Diagnose autonomous-loop fleet health. Reports GREEN/YELLOW/RED per loop with remediation hints. TRIGGERS - autonomous-loop doctor, fleet diagnose, loop health check, find zombie loops."
allowed-tools: Bash
argument-hint: "[--json] [--fix]"
disable-model-invocation: false
---
```

Skill body:

1. Source doctor-lib.sh.
2. Parse args for `--json` and `--fix`.
3. Call `loop_doctor_report` (or `loop_doctor_fix` if `--fix`).
4. Format output with severity colors when stdout is a TTY; plain text otherwise.

Include a "What gets fixed automatically" section in the SKILL.md body listing the safe ops.
</action>
</task>

<task type="auto">
  <name>Task 4: hooks/session-bind.sh — invoke heal-self.sh</name>
  <files>plugins/autonomous-loop/hooks/session-bind.sh</files>
  <action>
At the END of session-bind.sh main logic (before `exit 0`), invoke heal-self.sh as a best-effort background-or-inline call:
```bash
HEAL_SCRIPT="$SCRIPT_DIR/../scripts/heal-self.sh"
if [ -x "$HEAL_SCRIPT" ]; then
  "$HEAL_SCRIPT" 2>/dev/null || true
fi
```
This makes self-healing automatic on every fresh SessionStart, gated by content-hash so repeat work is avoided.
  </action>
</task>

<task type="auto">
  <name>Task 5: tests/test-doctor.sh + test-heal-self.sh</name>
  <files>plugins/autonomous-loop/tests/test-doctor.sh, plugins/autonomous-loop/tests/test-heal-self.sh</files>
  <action>
test-doctor.sh — 4 cases:
1. Clean state → GREEN for all loops
2. Zombie launchctl entry → RED, mentions bootout
3. Pending-bind > 1h → YELLOW
4. --json output is valid JSON with `loops[].verdict` array

test-heal-self.sh — 3 cases:

1. Stale pending-bind > 1h → archived to registry.archive.jsonl, removed from registry.json
2. Recent pending-bind < 1h → preserved (not archived)
3. Idempotent: second invocation with same hash → no-op (verified by archive count unchanged)

Use PATH-shimmed `launchctl` like test-plist-collision.sh.
</action>
</task>

<task type="auto">
  <name>Task 6: shellcheck + tests + commit</name>
  <action>
1. chmod +x new scripts and tests.
2. shellcheck on all new + modified .sh files.
3. Run new tests: test-doctor.sh + test-heal-self.sh — both pass.
4. Run regression: phases 35/36/37 tests all green.
5. bun validate-plugins.mjs — PASS.
6. Commit with full body referencing DOC-01..04.
  </action>
</task>

</tasks>
