auto-author #86
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: auto-author | |
| # Daily schedule enabled after draft quality was verified across two batches | |
| # (docs/auto-content-cron-plan.md §7). The cron only DRAFTS + opens a PR — it | |
| # never writes to Firestore. Publishing stays a human step (merge + seed), so a | |
| # scheduled run cannot push unreviewed content into the Google-indexed corpus. | |
| # | |
| # The one thing it DOES write to main directly is content/backlog.ts, flipping | |
| # the topics it drafted `pending` → `drafted`. That is the queue pointer, not | |
| # content, and it has to land immediately: the next run checks out main, so a | |
| # mark that waits for review advances nothing and the drafter re-drafts the same | |
| # head of the queue forever. (It did exactly that from 2026-07-11 to 2026-08-21 | |
| # — 42 daily PRs, all three identical topics, ~250 wasted generations.) | |
| # `content/backlog.ts` is in no workflow's trigger paths, so that push loops | |
| # nothing. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| limit: | |
| description: 'Max topics to draft this run (hard cap 5)' | |
| default: '3' | |
| schedule: | |
| - cron: '17 3 * * *' # 03:17 UTC daily; scheduled runs use the default limit (3) | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # Never let two runs race on the backlog pointer or push to main together. | |
| concurrency: auto-author | |
| env: | |
| # Backpressure: unmerged drafts PRs mean nobody is publishing, so drafting | |
| # more just burns Anthropic credit. Above this many open auto-draft PRs the | |
| # run skips before it makes a single model call. | |
| MAX_OPEN_DRAFT_PRS: '5' | |
| jobs: | |
| draft: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| # Matches CI: --ignore-scripts skips husky's `prepare`, which has no job here. | |
| - run: npm ci --ignore-scripts | |
| - name: Backpressure — skip if drafts PRs are piling up | |
| id: backpressure | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| OPEN=$(gh pr list --state open --limit 200 \ | |
| --json headRefName --jq '[.[]|select(.headRefName|startswith("auto-draft/"))]|length') | |
| echo "open drafts PRs: $OPEN (max ${MAX_OPEN_DRAFT_PRS})" | |
| if [ "$OPEN" -ge "$MAX_OPEN_DRAFT_PRS" ]; then | |
| echo "go=false" >> "$GITHUB_OUTPUT" | |
| echo "::warning::auto-author skipped — $OPEN open auto-draft PRs (>= $MAX_OPEN_DRAFT_PRS). Merge or close them; drafting more would only burn API credit." | |
| else | |
| echo "go=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Generate drafts | |
| if: steps.backpressure.outputs.go == 'true' | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| # Writes content/auto-draft.en.ts (the reviewable carrier) AND advances | |
| # content/backlog.ts. The two are committed to different places below. | |
| run: npx tsx scripts/auto-author.ts --apply --limit=${{ github.event.inputs.limit || '3' }} | |
| - name: Stash the backlog advance | |
| if: steps.backpressure.outputs.go == 'true' | |
| run: | | |
| if git diff --quiet -- content/backlog.ts; then | |
| echo "advanced=false" >> "$GITHUB_ENV" | |
| else | |
| cp content/backlog.ts "$RUNNER_TEMP/backlog.ts" | |
| echo "advanced=true" >> "$GITHUB_ENV" | |
| fi | |
| git checkout -- content/backlog.ts | |
| # CI cannot run on the PR this job opens: GitHub suppresses workflows on | |
| # PRs created with GITHUB_TOKEN (the runs land as `action_required` and | |
| # never execute), which is why 42 drafts PRs sat with zero checks. Running | |
| # the same three gates here is what actually protects main — a malformed | |
| # carrier would otherwise only fail typecheck AFTER the merge. | |
| # Upgrade path: create the PR with a PAT/GitHub App token instead, and | |
| # ci.yml will run on it normally. | |
| - name: Gates (typecheck / lint / test) | |
| if: steps.backpressure.outputs.go == 'true' | |
| run: | | |
| npm run typecheck | |
| npm run lint | |
| npm test | |
| - name: Open PR if drafts changed | |
| id: pr | |
| if: steps.backpressure.outputs.go == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| if git diff --quiet -- content/auto-draft.en.ts; then | |
| echo "No new drafts produced — nothing to open." | |
| echo "opened=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| BRANCH="auto-draft/$(date +%Y%m%d-%H%M%S)" | |
| git config user.name "gptwiki-bot" | |
| git config user.email "bot@gptwiki.net" | |
| git checkout -b "$BRANCH" | |
| git add content/auto-draft.en.ts | |
| # --no-verify: skip the husky lint-staged hook rewriting generated data. | |
| git commit --no-verify -m "auto-author: draft batch (review before seeding)" | |
| git push -u origin "$BRANCH" | |
| gh pr create \ | |
| --title "Auto-author drafts (en+zh) — review before seeding" \ | |
| --body "Generated by \`scripts/auto-author.ts\` — each topic as en+zh with a shared image prompt. Typecheck/lint/test ran in the drafting job (GitHub suppresses CI on \`GITHUB_TOKEN\`-authored PRs). **Review facts + sources**, then merge — \`auto-seed\` publishes en+zh + one hero image per topic automatically. Rationale + guardrails: docs/auto-content-cron-plan.md." | |
| echo "opened=true" >> "$GITHUB_OUTPUT" | |
| # Only after the PR exists, so a failure above leaves the topics `pending` | |
| # to be retried tomorrow rather than silently consumed. | |
| - name: Advance the backlog on main | |
| if: steps.backpressure.outputs.go == 'true' && steps.pr.outputs.opened == 'true' && env.advanced == 'true' | |
| run: | | |
| git config user.name "gptwiki-bot" | |
| git config user.email "bot@gptwiki.net" | |
| git checkout main | |
| git pull --rebase origin main | |
| cp "$RUNNER_TEMP/backlog.ts" content/backlog.ts | |
| if git diff --quiet -- content/backlog.ts; then | |
| echo "backlog already advanced upstream — nothing to push."; exit 0 | |
| fi | |
| git commit --no-verify -m "backlog: mark drafted topics [skip ci]" -- content/backlog.ts | |
| git push origin main |