# CritMatic Patch Watcher (Phase 1)

Local-only macOS bot that polls Wago.tools for new WoW builds, diffs the WoW global-function surface, greps CritMatic source for at-risk symbols, and posts a macOS notification linking to a Markdown report. **Detect + analyze + notify only.** No code changes, no git operations, no releases.

## Install

```bash
REPO="$(pwd)"
PY="$(which python3)"

sed "s|__REPO_PATH__|${REPO}|g; s|__PYTHON3__|${PY}|g" \
  bot/plist/com.critmatic.bot.plist \
  > ~/Library/LaunchAgents/com.critmatic.bot.plist

launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.critmatic.bot.plist
```

The first run fires immediately (because `RunAtLoad=true`). Subsequent runs every 15 min (Phase 3 cadence).

## Pause / resume

```bash
# Pause: any future scheduled run exits immediately.
touch bot/state/disabled

# Resume:
rm bot/state/disabled
```

## Uninstall

```bash
launchctl bootout gui/$UID ~/Library/LaunchAgents/com.critmatic.bot.plist
rm ~/Library/LaunchAgents/com.critmatic.bot.plist
```

## Where things live

- Reports: `bot/reports/<date>-build-<n>-<version>.md` (gitignored).
- Last-seen state: `bot/state/last-seen-builds.json` (gitignored).
- Logs: `bot/state/launchd.log` (gitignored).
- Notifications log: `bot/state/notifications.log` (gitignored).
- Kill switch: `bot/state/disabled` (gitignored).

## Reading a report

A report is a single Markdown file with these sections:

- **At-risk call sites:** every place in CritMatic source that uses a WoW global function that disappeared between the previous build and the new one.
- **Possibly renamed:** heuristic pairs (Levenshtein <= 3) where a removed name and an added name look similar. Verify before acting.
- **Raw diff stats:** counts.

The analyzer pulls Blizzard's auto-generated API docs from Gethe/wow-ui-source under `Interface/AddOns/Blizzard_APIDocumentationGenerated/`. It captures function names (`Type = "Function"`) and event LiteralNames (`Type = "Event"`). Tree-SHA diffing means only files that actually changed between two builds get re-fetched. CVars and other surfaces are out of scope.

## Run on demand

```bash
PYTHONPATH=. python3 bot/detect.py
```

## Auto-fix proposals (Phase 2)

When the analyzer finds **1-8 at-risk call sites** in CritMatic, the bot:
1. Creates a worktree at `<repo_parent>/CritMatic-bot/<branch_name>`.
2. Runs `claude -p` headlessly in that worktree to propose a fix (tools restricted to read/write/grep, no Bash, no MCP, 5-minute timeout, max 30 turns).
3. Commits whatever Claude edited on the new branch.
4. Runs five objective verify checks: `luac -p`, packager dry-run (`-d -z`), zero-removed-symbol grep, path allowlist, and a 200-line diff cap.
5. Labels the fix `auto-ship-eligible` (verify all-pass + Claude self-rated >= 75) or `held-for-review` (anything else).
6. Notifies via macOS Notification Center; appends a line to `bot/state/notifications.log`; records the fix in `bot/state/pending-fixes.json`.

If 9+ sites are at risk, the bot skips Claude entirely and notifies `manual-review-needed`.

Phase 2 NEVER auto-merges, NEVER pushes, NEVER releases. Use the approval CLI:

```bash
# List pending fixes:
PYTHONPATH=. python3 bot/approve.py

# Inspect one (shows verify rows + diff):
PYTHONPATH=. python3 bot/approve.py show bot/patch-wow-12_0_5

# Merge into local master (re-runs verify, ff-only, no push):
PYTHONPATH=. python3 bot/approve.py merge bot/patch-wow-12_0_5

# Discard:
PYTHONPATH=. python3 bot/approve.py reject bot/patch-wow-12_0_5
```

`merge` re-runs the verify gate before touching master; if checks now fail, the merge aborts and the entry stays pending. The merge is fast-forward only; if master moved, rebase the worktree manually.

### Where Phase 2 things live

- Pending fixes: `bot/state/pending-fixes.json` (gitignored).
- Auto-fix worktrees: `<repo_parent>/CritMatic-bot/<branch>/` (sibling directory; not inside the repo).
- Inspect manually with `git worktree list`.

## Auto-release (Phase 3)

When the analyzer produces an `auto-ship-eligible` fix, the bot:

1. Auto-merges the fix branch into local `master` (re-runs the 5-check verify gate; ff-only).
2. Bumps the version in `CritMatic.toc`, `versions.json`, and `CHANGE_LOG.md` (minor if the fix touched >3 files OR >50 diff lines, else patch).
3. Creates an annotated tag `vX.Y.Z` (with `-1`, `-2`, ... suffix on collision).
4. Schedules the release for a millisecond-jittered timestamp inside the next Tue 9-11am US Pacific OR Wed 9-11am CET window.

On every poll (every 15 min), the bot checks `bot/state/pending-releases.json` for any release whose `scheduled_at <= now`. For each due release it `git push origin master <tag>` and runs `bash .release/release.sh` with `CF_API_KEY` sourced from `.env`. On success the entry is removed; on failure the `retries` counter increments (cap 3) and the next poll retries.

### Auto-release kill switch

```bash
# Pause auto-releases (eligible fixes go to manual approval queue instead;
# already-scheduled releases are held but NOT cancelled until you cancel them):
touch bot/state/auto-release-disabled

# Resume:
rm bot/state/auto-release-disabled
```

### Cancel a scheduled release before it fires

```bash
PYTHONPATH=. python3 bot/approve.py cancel v5.5.8
```

### Tighter polling cadence

Phase 3 changes the launchd `StartInterval` from 4h to 15 min so scheduled releases land within ±15 min of their target. After updating from a Phase 1/2 install, re-bootstrap the agent:

```bash
launchctl bootout gui/$UID ~/Library/LaunchAgents/com.critmatic.bot.plist
sed "s|__REPO_PATH__|$(pwd)|g; s|__PYTHON3__|$(which python3)|g" \
  bot/plist/com.critmatic.bot.plist \
  > ~/Library/LaunchAgents/com.critmatic.bot.plist
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.critmatic.bot.plist
```

## Run the test suite

```bash
PYTHONPATH=. python3 -m unittest discover bot/tests
```
