1# status maintenance via claude code 2# 3# two-phase workflow: 4# 1. workflow_dispatch: archives old STATUS.md sections, generates audio, opens PR 5# 2. on PR merge: uploads audio to plyr.fm 6# 7# required secrets: 8# ANTHROPIC_API_KEY - claude code 9# GOOGLE_API_KEY - gemini TTS (for audio generation) 10# PLYR_BOT_TOKEN - plyr.fm developer token (for audio upload) 11 12name: status maintenance 13 14on: 15 # TODO: restore schedule after testing 16 # schedule: 17 # - cron: "0 9 * * 1" # every monday 9am UTC 18 workflow_dispatch: 19 inputs: 20 skip_audio: 21 description: "skip audio generation" 22 type: boolean 23 default: false 24 pull_request: 25 types: [closed] 26 branches: [main] 27 28jobs: 29 # phase 1: archive + generate audio + open PR 30 maintain: 31 if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' 32 runs-on: ubuntu-latest 33 permissions: 34 contents: write 35 pull-requests: write 36 id-token: write 37 38 steps: 39 - uses: actions/checkout@v4 40 with: 41 fetch-depth: 0 42 43 - uses: astral-sh/setup-uv@v4 44 45 - uses: anthropics/claude-code-action@v1 46 with: 47 anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} 48 claude_args: | 49 --model opus 50 --allowedTools "Read,Write,Edit,Bash,Fetch,Task" 51 prompt: | 52 you are maintaining the plyr.fm (pronounced "player FM") project status file. 53 54 ## critical rules 55 56 1. STATUS.md MUST be kept under 500 lines. this is non-negotiable. 57 2. archive content MUST be moved to .status_history/, not deleted 58 3. podcast tone MUST be dry, matter-of-fact, slightly sardonic - NOT enthusiastic or complimentary 59 60 ## task 1: gather temporal context 61 62 CRITICAL: you must determine the correct time window by finding when the LAST status maintenance PR was MERGED (not opened). 63 64 run these commands: 65 ```bash 66 date 67 # get the most recently merged status-maintenance PR (filter by branch name, sort by merge date) 68 gh pr list --state merged --search "status-maintenance" --limit 20 --json number,title,mergedAt,headRefName | jq '[.[] | select(.headRefName | startswith("status-maintenance-"))] | sort_by(.mergedAt) | reverse | .[0]' 69 git log --oneline -50 70 ls -la .status_history/ 2>/dev/null || echo "no archive directory yet" 71 wc -l STATUS.md 72 ``` 73 74 determine: 75 - what is today's date? 76 - when was the last status-maintenance PR MERGED? (use the mergedAt field from the jq output - it's the most recent PR with a branch starting with "status-maintenance-") 77 - what shipped SINCE that merge date? (this is your focus window - NOT "last week") 78 - does .status_history/ exist? (this implies whether or not this is the first episode) 79 - how many lines is STATUS.md currently? 80 81 IMPORTANT: the time window for this maintenance run is from the last merged status-maintenance PR until now. if the last PR was merged on Dec 2nd and today is Dec 8th, you should focus on everything from Dec 3rd onwards, NOT just "the last week". 82 83 ## task 2: archive old month sections 84 85 **line count targets**: 86 - ideal: ~200 lines (concise overview) 87 - acceptable: 300-450 lines 88 - maximum: 500 lines (MUST NOT exceed) 89 90 **when to archive**: if STATUS.md > 400 lines OR contains detailed sections from previous months 91 92 **what to archive**: content from months BEFORE the current month 93 - if today is January 2026, move December 2025 sections to .status_history/2025-12.md 94 - if today is February 2026, move January 2026 sections to .status_history/2026-01.md 95 - current month content stays in STATUS.md 96 97 **how to archive** (this means MOVING content, not summarizing): 98 1. create .status_history/ directory if it doesn't exist 99 2. identify "### Month Year" sections from previous months in STATUS.md 100 3. CUT the full section content (headers, bullet points, everything) 101 4. PASTE/APPEND to .status_history/YYYY-MM.md 102 - if archive file exists: append to end of file 103 - if archive file doesn't exist: create with header "# plyr.fm Status History - Month Year" 104 5. REPLACE the moved section in STATUS.md with a brief cross-reference: 105 ``` 106 ### December 2025 107 108 See `.status_history/2025-12.md` for detailed history. 109 ``` 110 6. preserve document structure (keep "## recent work", "## priorities", "## technical state" headers) 111 112 CRITICAL: "archiving" = moving actual content to archive files, NOT condensing or summarizing in place. 113 the detailed write-ups must be preserved in .status_history/, not deleted. 114 115 VERIFY: run `wc -l STATUS.md` after archiving. target 300-450 lines, must be under 500. 116 117 ## task 3: generate audio overview (if skip_audio is false) 118 119 skip_audio input: ${{ inputs.skip_audio }} 120 121 if skip_audio is false: 122 123 ### deep investigation phase 124 125 before writing anything, you need to deeply understand what happened in the time window. 126 use subagents liberally to investigate in parallel: 127 128 1. **get the full picture of PRs merged in the time window**: 129 ```bash 130 gh pr list --state merged --search "merged:>={mergedAt date}" --limit 50 --json number,title,body,mergedAt,additions,deletions,files 131 ``` 132 133 2. **for each significant PR, read its body and understand the design decisions**: 134 - what problem was being solved? 135 - what approach was taken and why? 136 - what are the key files changed? 137 138 3. **read the actual code changes** for the top 2-3 most significant PRs: 139 - use `gh pr diff {number}` or read the changed files directly 140 - understand the architecture, not just the commit messages 141 142 4. **read background context**: 143 - STATUS.md (the current state) 144 - docs/deployment/overview.md if it exists 145 - Fetch https://atproto.com/guides/overview to understand ATProto primitives 146 - Fetch https://atproto.com/guides/lexicon to understand NSIDs and lexicons 147 148 ### identify the narrative structure 149 150 after investigating, categorize what shipped: 151 152 **big ticket items** (1-3 major features or architectural changes): 153 - these get the most airtime (60-70% of the script) 154 - explain HOW they were designed, not just WHAT they do 155 - discuss interesting technical decisions or tradeoffs 156 157 **smaller but notable changes** (3-6 fixes, improvements, polish): 158 - these get rapid-fire coverage (20-30% of the script) 159 - one or two sentences each 160 - acknowledge they happened without belaboring them 161 162 ### write the podcast script 163 164 write to podcast_script.txt with "Host: ..." and "Cohost: ..." lines. 165 166 **CHRONOLOGICAL NARRATIVE STRUCTURE** (CRITICAL): 167 168 the script must tell a coherent story of the time period, structured as: 169 170 1. **opening** (10 seconds): set the scene - what's the date range, what was the focus? 171 172 2. **the main story** (60-90 seconds): the biggest thing that shipped 173 - what problem did it solve? 174 - how was it designed? (explain the architecture accessibly) 175 - what's interesting about the implementation? 176 - the hosts should have a back-and-forth discussing the design 177 178 3. **secondary feature** (30-45 seconds, if applicable): another significant change 179 - lighter treatment than the main story 180 - still explain the "why" not just the "what" 181 182 4. **rapid fire** (20-30 seconds): the smaller changes 183 - "we also saw..." or "a few other things landed..." 184 - quick hits: bug fixes, polish, minor improvements 185 - don't dwell, just acknowledge 186 187 5. **closing** (10 seconds): looking ahead or wrapping up 188 189 the narrative should flow like you're telling a friend what happened on the project this week. 190 use transitions: "but before that landed...", "meanwhile...", "and then to tie it together..." 191 192 ### tone requirements (CRITICAL) 193 194 the hosts should sound like two engineers who: 195 - are skeptical, amused and somewhat intrigued by the absurdity of building things 196 - acknowledge problems and limitations honestly 197 - don't over-use superlatives ("amazing", "incredible", "exciting") 198 - explain technical concepts through analogy, not hypey jargon 199 - genuinely find the technical details interesting (not performatively enthusiastic) 200 201 avoid excessive phrasing: 202 - "exciting", "amazing", "incredible", "impressive", "great job" 203 - "the team has done", "they've really", "fantastic work" 204 - any variation of over-congratulating or over-sensationalizing the project 205 206 ### pronunciation (CRITICAL - READ THIS CAREFULLY) 207 208 the project name "plyr.fm" is pronounced "player FM" (like "music player"). 209 210 **in your script, ALWAYS write "player FM" or "player dot FM" - NEVER write "plyr.fm" or "plyr".** 211 212 the TTS engine will mispronounce "plyr" as "plir" or "p-l-y-r" if you write it that way. 213 write phonetically for correct pronunciation: "player FM", "player dot FM". 214 215 ### identifying what actually shipped 216 217 read the commit messages and PR bodies carefully to understand what changed. 218 219 - if something is completely NEW (didn't exist before), say it "shipped" or "launched" 220 - if something existing got improved or fixed, call it what it is: fixes, improvements, polish 221 222 don't rely on commit message prefixes like `feat:` or `fix:` - they're not always accurate. 223 read the actual content to understand the scope of what changed. 224 225 ### time references (CRITICAL) 226 227 NEVER say "last week", "this week", "recently", or vague time references. 228 229 ALWAYS use specific date ranges based on the mergedAt date from task 1: 230 - "since December 2nd" or "from December 3rd to today" 231 - "in the past six days" (if that's accurate) 232 - "since the last update" 233 234 the listener doesn't know when "last week" was - be specific. 235 236 target length: 2-3 minutes spoken (~300-400 words) (it should be 4-5 if its the first episode) 237 238 ### generate audio 239 240 run: uv run scripts/generate_tts.py podcast_script.txt update.wav 241 then: rm podcast_script.txt 242 243 ## task 4: open PR 244 245 if any files changed: 246 1. first, generate a unique branch name: BRANCH="status-maintenance-$(date +%Y%m%d-%H%M%S)" 247 2. git checkout -b $BRANCH 248 3. git add .status_history/ STATUS.md update.wav 249 4. git commit -m "chore: status maintenance" 250 5. git push -u origin $BRANCH 251 6. gh pr create with a title and body you craft: 252 - title should be descriptive of what this status update covers (e.g. "chore: status maintenance - playlist fast-follow fixes" or "chore: status maintenance - December updates") 253 - make it clear this is an automated status maintenance PR from the GitHub Action 254 - body should summarize what changed (archival, audio generation, etc.) 255 256 add a label like "ai-generated" to the PR (create the label if it doesn't exist) 257 if nothing changed, report that no maintenance was needed. 258 259 env: 260 GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} 261 262 # phase 2: upload audio after PR merge 263 upload-audio: 264 if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'status-maintenance-') 265 runs-on: ubuntu-latest 266 267 steps: 268 - uses: actions/checkout@v4 269 270 - uses: astral-sh/setup-uv@v4 271 272 - name: Upload audio to plyr.fm 273 run: | 274 if [ ! -f update.wav ]; then 275 echo "No update.wav found, skipping upload" 276 exit 0 277 fi 278 279 # check existing tracks to determine episode number 280 EXISTING=$(uv run --with plyrfm -- plyrfm my-tracks --limit 50 2>/dev/null || echo "") 281 TODAY=$(date +'%B %d, %Y') 282 YEAR=$(date +%Y) 283 284 # count how many "plyr.fm update - {date}" tracks exist for today 285 TODAY_COUNT=$(echo "$EXISTING" | grep -c "plyr.fm update - $TODAY" || echo "0") 286 287 if [ "$TODAY_COUNT" -gt 0 ]; then 288 # already have one today, add episode number 289 EPISODE=$((TODAY_COUNT + 1)) 290 TITLE="plyr.fm update - $TODAY (#$EPISODE)" 291 else 292 TITLE="plyr.fm update - $TODAY" 293 fi 294 295 echo "Uploading as: $TITLE" 296 uv run --with plyrfm -- plyrfm upload update.wav "$TITLE" --album "$YEAR" -t '["ai"]' 297 env: 298 PLYR_TOKEN: ${{ secrets.PLYR_BOT_TOKEN }}