docs: add status maintenance workflow documentation (#729)

covers:
- how the workflow determines time windows
- archival rules and line count targets
- audio generation (pronunciation, terminology, tone)
- troubleshooting common issues (reverted PRs, wrong terminology)

also fixes: tag format from '["ai"]' to "ai"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

authored by zzstoatzz.io Claude Opus 4.5 and committed by GitHub ea8d67d8 4c74e12e

Changed files
+144 -1
.github
docs
+1 -1
.github/workflows/status-maintenance.yml
··· 299 299 fi 300 300 301 301 echo "Uploading as: $TITLE" 302 - uv run --with plyrfm -- plyrfm upload update.wav "$TITLE" --album "$YEAR" -t '["ai"]' 302 + uv run --with plyrfm -- plyrfm upload update.wav "$TITLE" --album "$YEAR" -t "ai" 303 303 env: 304 304 PLYR_TOKEN: ${{ secrets.PLYR_BOT_TOKEN }}
+143
docs/tools/status-maintenance.md
··· 1 + # status maintenance workflow 2 + 3 + automated workflow that archives old STATUS.md content and generates audio updates. 4 + 5 + ## what it does 6 + 7 + 1. **archives old content**: moves previous month's sections from STATUS.md to `.status_history/YYYY-MM.md` 8 + 2. **generates audio**: creates a podcast-style audio update covering recent work 9 + 3. **opens PR**: commits changes and opens a PR for review 10 + 4. **uploads audio**: after PR merge, uploads the audio to plyr.fm 11 + 12 + ## workflow file 13 + 14 + `.github/workflows/status-maintenance.yml` 15 + 16 + ## triggers 17 + 18 + - **manual**: `workflow_dispatch` (run from Actions tab) 19 + - **on PR merge**: uploads audio after status-maintenance PR is merged 20 + 21 + schedule is currently disabled but can be enabled for weekly runs. 22 + 23 + ## how it determines the time window 24 + 25 + the workflow finds the most recently merged PR with a branch starting with `status-maintenance-`: 26 + 27 + ```bash 28 + gh pr list --state merged --search "status-maintenance" --limit 20 \ 29 + --json number,title,mergedAt,headRefName | \ 30 + jq '[.[] | select(.headRefName | startswith("status-maintenance-"))] | sort_by(.mergedAt) | reverse | .[0]' 31 + ``` 32 + 33 + everything merged since that date is considered "new work" for the audio script. 34 + 35 + ### handling reverted PRs 36 + 37 + if a status-maintenance PR is merged then reverted, it still appears as "merged" in GitHub's API. this can cause the workflow to think there's no new content. 38 + 39 + **workaround**: temporarily add an exclusion to the jq filter: 40 + 41 + ```bash 42 + | select(.number != 724) # exclude reverted PR 43 + ``` 44 + 45 + remove the exclusion after the next successful run. 46 + 47 + ## archival rules 48 + 49 + **line count targets**: 50 + - ideal: ~200 lines 51 + - acceptable: 300-450 lines 52 + - maximum: 500 lines (must not exceed) 53 + 54 + **what gets archived**: 55 + - content from months BEFORE the current month 56 + - if today is January 2026, December 2025 sections move to `.status_history/2025-12.md` 57 + 58 + **how archival works**: 59 + 1. CUT the full section from STATUS.md (headers, bullets, everything) 60 + 2. APPEND to the appropriate `.status_history/YYYY-MM.md` file 61 + 3. REPLACE in STATUS.md with a brief cross-reference 62 + 63 + archival means MOVING content, not summarizing. the detailed write-ups are preserved in the archive. 64 + 65 + ## audio generation 66 + 67 + ### pronunciation 68 + 69 + the project name is pronounced "player FM". in scripts, write: 70 + - "player FM" or "player dot FM" 71 + - never "plyr.fm" or "plyr" (TTS mispronounces it) 72 + 73 + ### terminology 74 + 75 + plyr.fm operates at the ATProto protocol layer: 76 + - say "ATProto identities" or "identities" 77 + - never "Bluesky accounts" 78 + 79 + Bluesky is one application on ATProto, like plyr.fm is another. 80 + 81 + ### tone 82 + 83 + dry, matter-of-fact, slightly sardonic. avoid: 84 + - "exciting", "amazing", "incredible" 85 + - over-congratulating or sensationalizing 86 + 87 + ### script structure 88 + 89 + 1. opening (10s): date range, focus 90 + 2. main story (60-90s): biggest feature, design decisions 91 + 3. secondary feature (30-45s): if applicable 92 + 4. rapid fire (20-30s): smaller changes 93 + 5. closing (10s): wrap up 94 + 95 + ## inputs 96 + 97 + | input | type | default | description | 98 + |-------|------|---------|-------------| 99 + | `skip_audio` | boolean | false | skip audio generation | 100 + 101 + ## secrets required 102 + 103 + | secret | purpose | 104 + |--------|---------| 105 + | `ANTHROPIC_API_KEY` | claude code | 106 + | `GOOGLE_API_KEY` | gemini TTS | 107 + | `PLYR_BOT_TOKEN` | plyr.fm upload | 108 + 109 + ## manual run 110 + 111 + ```bash 112 + gh workflow run "status maintenance" --ref main 113 + ``` 114 + 115 + with skip_audio: 116 + ```bash 117 + gh workflow run "status maintenance" --ref main -f skip_audio=true 118 + ``` 119 + 120 + ## troubleshooting 121 + 122 + ### workflow sees wrong time window 123 + 124 + check which PR it's using as the baseline: 125 + 126 + ```bash 127 + gh pr list --state merged --search "status-maintenance" --limit 5 \ 128 + --json number,title,mergedAt,headRefName 129 + ``` 130 + 131 + if a reverted PR is polluting the results, add a temporary exclusion. 132 + 133 + ### audio has wrong terminology 134 + 135 + check the terminology section in the workflow prompt. common mistakes: 136 + - "Bluesky accounts" should be "ATProto identities" 137 + - "plyr" should be "player FM" (phonetic) 138 + 139 + ### STATUS.md over 500 lines 140 + 141 + the archival step should handle this, but verify: 142 + - december content should be in `.status_history/2025-12.md` 143 + - only current month content stays in STATUS.md