Repository navigation
automation | Draft Docs PRs For Previous Day Dev PRs #1
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: automation | Draft Docs PRs For Previous Day Dev PRs | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| lookback_days: | |
| description: Number of UTC calendar days to look back when scanning merged PRs | |
| required: false | |
| default: "1" | |
| anchor_date: | |
| description: Optional UTC date to scan, in YYYY-MM-DD format | |
| required: false | |
| default: "" | |
| schedule: | |
| - cron: "59 23 * * *" | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| env: | |
| lookback_days: ${{ github.event.inputs.lookback_days || vars.lookback_days || '1' }} | |
| jobs: | |
| prepare-merged-branches: | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 10 | |
| outputs: | |
| start_date: ${{ steps.prepare.outputs.start_date }} | |
| end_date: ${{ steps.prepare.outputs.end_date }} | |
| has_merges: ${{ steps.prepare.outputs.has_merges }} | |
| merge_summary: ${{ steps.prepare.outputs.merge_summary }} | |
| matrix: ${{ steps.prepare.outputs.matrix }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Fetch latest dev branch | |
| run: git fetch origin dev:refs/remotes/origin/dev --no-tags | |
| - name: Prepare merged PRs | |
| id: prepare | |
| shell: bash | |
| env: | |
| INPUT_ANCHOR_DATE: ${{ github.event.inputs.anchor_date || '' }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| args=( | |
| --branch origin/dev | |
| --lookback-days "${lookback_days}" | |
| ) | |
| anchor_date="${INPUT_ANCHOR_DATE}" | |
| if [ "${{ github.event_name }}" = "schedule" ]; then | |
| anchor_date="$(date -u -d 'yesterday' +%F)" | |
| fi | |
| if [ -n "${anchor_date}" ]; then | |
| echo "Using anchor date ${anchor_date}" | |
| args+=(--anchor-date "${anchor_date}") | |
| fi | |
| python3 tools/prepare_merged_branches.py "${args[@]}" | |
| create-docs-prs: | |
| needs: | |
| - prepare-merged-branches | |
| if: ${{ needs.prepare-merged-branches.outputs.has_merges == 'true' }} | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.prepare-merged-branches.outputs.matrix) }} | |
| steps: | |
| - name: Check out core repository | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 | |
| with: | |
| python-version: "3.10" | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 | |
| - name: Install workflow dependencies | |
| run: uv sync --locked | |
| - name: Fetch latest dev branch | |
| run: git fetch origin dev:refs/remotes/origin/dev --no-tags | |
| - name: Prepare branch note paths | |
| id: branch_paths | |
| run: | | |
| OUTPUT_DIR="branch-dev-notes/${{ matrix.safe_branch }}-${{ matrix.short_sha }}" | |
| mkdir -p "${OUTPUT_DIR}" | |
| echo "output_dir=${OUTPUT_DIR}" >> "${GITHUB_OUTPUT}" | |
| echo "notes_json=${OUTPUT_DIR}/branch_notes.json" >> "${GITHUB_OUTPUT}" | |
| echo "notes_markdown=${OUTPUT_DIR}/branch_notes.md" >> "${GITHUB_OUTPUT}" | |
| echo "assessment_json=${OUTPUT_DIR}/docs_assessment.json" >> "${GITHUB_OUTPUT}" | |
| echo "assessment_markdown=${OUTPUT_DIR}/docs_assessment.md" >> "${GITHUB_OUTPUT}" | |
| echo "docs_edit_scope_json=${OUTPUT_DIR}/docs_edit_scope.json" >> "${GITHUB_OUTPUT}" | |
| echo "docs_edit_scope_markdown=${OUTPUT_DIR}/docs_edit_scope.md" >> "${GITHUB_OUTPUT}" | |
| echo "docs_scope_plan=${OUTPUT_DIR}/docs_scope_plan.md" >> "${GITHUB_OUTPUT}" | |
| - name: Generate branch notes | |
| env: | |
| LLM_API_KEY: ${{ secrets.OPENAI_API_KEY || secrets.LLM_API_KEY }} | |
| LLM_ARGS: ${{ secrets.LLM_ARGS }} | |
| LLM_MODEL: ${{ vars.LLM_MODEL || secrets.LLM_MODEL || 'openai/gpt-4o-mini' }} | |
| NOTES_JSON: ${{ steps.branch_paths.outputs.notes_json }} | |
| NOTES_MARKDOWN: ${{ steps.branch_paths.outputs.notes_markdown }} | |
| PR_NUMBER: ${{ matrix.pr_number }} | |
| PR_TITLE: ${{ matrix.pr_title }} | |
| PR_BODY_B64: ${{ matrix.pr_body_b64 }} | |
| PR_URL: ${{ matrix.pr_url }} | |
| run: | | |
| uv run python tools/generate_branch_notes.py \ | |
| --branch-name "${{ matrix.branch_name }}" \ | |
| --merge-sha "${{ matrix.merge_sha }}" \ | |
| --first-parent "${{ matrix.first_parent }}" \ | |
| --second-parent "${{ matrix.second_parent }}" \ | |
| --pr-number "${PR_NUMBER}" \ | |
| --pr-title "${PR_TITLE}" \ | |
| --pr-body-base64 "${PR_BODY_B64}" \ | |
| --pr-url "${PR_URL}" \ | |
| --json-output "${NOTES_JSON}" \ | |
| --markdown-output "${NOTES_MARKDOWN}" | |
| - name: Assess documentation impact for branch | |
| id: assessment | |
| env: | |
| LLM_API_KEY: ${{ secrets.OPENAI_API_KEY || secrets.LLM_API_KEY }} | |
| LLM_ARGS: ${{ secrets.LLM_ARGS }} | |
| LLM_MODEL: ${{ vars.LLM_MODEL || secrets.LLM_MODEL || 'openai/gpt-4o-mini' }} | |
| NOTES_JSON: ${{ steps.branch_paths.outputs.notes_json }} | |
| NOTES_MARKDOWN: ${{ steps.branch_paths.outputs.notes_markdown }} | |
| ASSESSMENT_JSON: ${{ steps.branch_paths.outputs.assessment_json }} | |
| ASSESSMENT_MARKDOWN: ${{ steps.branch_paths.outputs.assessment_markdown }} | |
| run: | | |
| uv run python tools/assess_branch_notes.py \ | |
| --notes-json "${NOTES_JSON}" \ | |
| --notes-markdown "${NOTES_MARKDOWN}" \ | |
| --json-output "${ASSESSMENT_JSON}" \ | |
| --markdown-output "${ASSESSMENT_MARKDOWN}" | |
| python3 tools/write_docs_assessment_outputs.py \ | |
| --assessment-json "${ASSESSMENT_JSON}" \ | |
| --branch-slug "${{ matrix.safe_branch }}" \ | |
| --short-sha "${{ matrix.short_sha }}" \ | |
| --pr-number "${{ matrix.pr_number }}" | |
| - name: Check out docs repository | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' }} | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 | |
| with: | |
| repository: topoteretes/cognee-docs | |
| token: ${{ secrets.REPO_DISPATCH_PAT_TOKEN }} | |
| ref: main | |
| path: docs-repo | |
| - name: Prepare docs branch | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' }} | |
| working-directory: docs-repo | |
| run: | | |
| git fetch origin "${{ steps.assessment.outputs.docs_branch }}" || true | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| if git show-ref --verify --quiet "refs/remotes/origin/${{ steps.assessment.outputs.docs_branch }}"; then | |
| git checkout -B "${{ steps.assessment.outputs.docs_branch }}" "origin/${{ steps.assessment.outputs.docs_branch }}" | |
| else | |
| git checkout -B "${{ steps.assessment.outputs.docs_branch }}" | |
| fi | |
| - name: Prepare docs edit scope | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' }} | |
| id: docs_scope | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| python3 tools/prepare_docs_edit_scope.py \ | |
| --repo "${{ github.repository }}" \ | |
| --pr-number "${{ matrix.pr_number }}" \ | |
| --docs-root docs-repo \ | |
| --notes-json "${{ steps.branch_paths.outputs.notes_json }}" \ | |
| --assessment-json "${{ steps.branch_paths.outputs.assessment_json }}" \ | |
| --scope-json-output "${{ steps.branch_paths.outputs.docs_edit_scope_json }}" \ | |
| --scope-markdown-output "${{ steps.branch_paths.outputs.docs_edit_scope_markdown }}" | |
| - name: Plan docs changes with Claude | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' }} | |
| id: claude_scope | |
| continue-on-error: true | |
| uses: anthropics/claude-code-action@c81e3bc69d1b18badbb63ba39581218f02421678 # v1.0.201 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| prompt: | | |
| Read and follow `.github/prompts/docs_scope_plan.md`. | |
| - Branch: `${{ matrix.branch_name }}` | |
| - PR: `#${{ matrix.pr_number }} ${{ matrix.pr_title }}` | |
| - Merge SHA: `${{ matrix.merge_sha }}` | |
| - Short SHA: `${{ matrix.short_sha }}` | |
| - First parent: `${{ matrix.first_parent }}` | |
| - Second parent: `${{ matrix.second_parent }}` | |
| - Scope plan output: `./${{ steps.branch_paths.outputs.docs_scope_plan }}` | |
| - **Prepared documentation edit scope** (`./${{ steps.branch_paths.outputs.docs_edit_scope_markdown }}`): Curated source files, docs candidates, assessment summary, and out-of-scope files. | |
| - **Prepared documentation edit scope JSON** (`./${{ steps.branch_paths.outputs.docs_edit_scope_json }}`): Machine-readable copy of the prepared scope. | |
| claude_args: "--allowed-tools Read,Write,Glob,Grep --max-turns 35" | |
| # The action fails a step whose reported turn count overshoots the cap even | |
| # when the agent finished cleanly, which throws away completed work. So judge | |
| # from the agent's own result: a clean finish carries on, a genuinely | |
| # truncated run (error_max_turns) still fails here rather than opening a PR | |
| # from a half-written plan. | |
| - name: Check the planning agent finished | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' }} | |
| env: | |
| CLAUDE_CONCLUSION: ${{ steps.claude_scope.outputs.conclusion }} | |
| EXECUTION_FILE: ${{ steps.claude_scope.outputs.execution_file }} | |
| run: | | |
| set -euo pipefail | |
| if [ "${CLAUDE_CONCLUSION:-}" != "success" ]; then | |
| if [ -z "${EXECUTION_FILE:-}" ] || [ ! -s "${EXECUTION_FILE}" ] || \ | |
| ! jq -e '[.. | objects | select(has("is_error"))] | last | .is_error == false' \ | |
| "${EXECUTION_FILE}" >/dev/null 2>&1; then | |
| echo "The planning agent did not finish (conclusion=${CLAUDE_CONCLUSION:-unknown})." | |
| exit 1 | |
| fi | |
| echo "The action failed on the turn count but the planning agent finished cleanly - keeping the work." | |
| fi | |
| - name: Validate docs scope plan | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' }} | |
| run: | | |
| test -s "${{ steps.branch_paths.outputs.docs_scope_plan }}" | |
| if [ -n "$(git -C docs-repo status --short)" ]; then | |
| echo "The docs planning step modified docs-repo, but planning must not edit documentation." >&2 | |
| git -C docs-repo status --short >&2 | |
| exit 1 | |
| fi | |
| - name: Read docs scope plan outputs | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' }} | |
| id: scope_plan | |
| run: | | |
| python3 tools/write_docs_scope_plan_outputs.py \ | |
| --scope-plan "${{ steps.branch_paths.outputs.docs_scope_plan }}" | |
| - name: Print generated planning markdown | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' }} | |
| run: | | |
| for file in \ | |
| "${{ steps.branch_paths.outputs.notes_markdown }}" \ | |
| "${{ steps.branch_paths.outputs.assessment_markdown }}" \ | |
| "${{ steps.branch_paths.outputs.docs_edit_scope_markdown }}" \ | |
| "${{ steps.branch_paths.outputs.docs_scope_plan }}" | |
| do | |
| if [ -s "$file" ]; then | |
| echo "## ${file}" | |
| cat "$file" | |
| echo | |
| else | |
| echo "## ${file} (missing or empty)" | |
| fi | |
| done | |
| - name: Generate docs changes with Claude | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' && steps.scope_plan.outputs.docs_needed == 'true' }} | |
| id: claude | |
| continue-on-error: true | |
| uses: anthropics/claude-code-action@c81e3bc69d1b18badbb63ba39581218f02421678 # v1.0.201 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| prompt: | | |
| Read and follow `.github/prompts/docs_edit.md`. | |
| - Branch: `${{ matrix.branch_name }}` | |
| - PR: `#${{ matrix.pr_number }} ${{ matrix.pr_title }}` | |
| - Merge SHA: `${{ matrix.merge_sha }}` | |
| - Short SHA: `${{ matrix.short_sha }}` | |
| - First parent: `${{ matrix.first_parent }}` | |
| - Second parent: `${{ matrix.second_parent }}` | |
| ## Required inputs | |
| Read these first: | |
| - **Documentation scope plan** (`./${{ steps.branch_paths.outputs.docs_scope_plan }}`) | |
| - **Branch notes** (`./${{ steps.branch_paths.outputs.notes_markdown }}`) | |
| - **Documentation assessment** (`./${{ steps.branch_paths.outputs.assessment_markdown }}`) | |
| claude_args: "--allowed-tools Read,Edit,Write,Glob,Grep,Bash --max-turns 50" | |
| # The action fails a step whose reported turn count overshoots the cap even | |
| # when the agent finished cleanly, which throws away completed work. So judge | |
| # from the agent's own result: a clean finish carries on, a genuinely | |
| # truncated run (error_max_turns) still fails here rather than opening a PR | |
| # containing a half-written page. | |
| - name: Check the editing agent finished | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' && steps.scope_plan.outputs.docs_needed == 'true' }} | |
| env: | |
| CLAUDE_CONCLUSION: ${{ steps.claude.outputs.conclusion }} | |
| EXECUTION_FILE: ${{ steps.claude.outputs.execution_file }} | |
| run: | | |
| set -euo pipefail | |
| if [ "${CLAUDE_CONCLUSION:-}" != "success" ]; then | |
| if [ -z "${EXECUTION_FILE:-}" ] || [ ! -s "${EXECUTION_FILE}" ] || \ | |
| ! jq -e '[.. | objects | select(has("is_error"))] | last | .is_error == false' \ | |
| "${EXECUTION_FILE}" >/dev/null 2>&1; then | |
| echo "The editing agent did not finish (conclusion=${CLAUDE_CONCLUSION:-unknown})." | |
| exit 1 | |
| fi | |
| echo "The action failed on the turn count but the editing agent finished cleanly - keeping the work." | |
| fi | |
| - name: Prepare docs PR content | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' && steps.scope_plan.outputs.docs_needed == 'true' }} | |
| id: pr_content | |
| env: | |
| NOTES_JSON: ${{ steps.branch_paths.outputs.notes_json }} | |
| ASSESSMENT_JSON: ${{ steps.branch_paths.outputs.assessment_json }} | |
| BRANCH_NAME: ${{ matrix.branch_name }} | |
| SHORT_SHA: ${{ matrix.short_sha }} | |
| DEFAULT_PR_TITLE: ${{ steps.assessment.outputs.pr_title }} | |
| run: | | |
| python3 tools/prepare_docs_pr_content.py \ | |
| --notes-json "${NOTES_JSON}" \ | |
| --assessment-json "${ASSESSMENT_JSON}" \ | |
| --branch-name "${BRANCH_NAME}" \ | |
| --short-sha "${SHORT_SHA}" \ | |
| --default-pr-title "${DEFAULT_PR_TITLE}" \ | |
| --docs-root docs-repo | |
| - name: Create docs draft commit | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' && steps.scope_plan.outputs.docs_needed == 'true' }} | |
| id: commit_docs | |
| working-directory: docs-repo | |
| run: | | |
| git add -A | |
| if git diff --cached --quiet; then | |
| echo "changes_made=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git commit -m "${{ steps.pr_content.outputs.pr_title }}" | |
| echo "changes_made=true" >> "$GITHUB_OUTPUT" | |
| - name: Push docs draft branch | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' && steps.scope_plan.outputs.docs_needed == 'true' && steps.commit_docs.outputs.changes_made == 'true' }} | |
| working-directory: docs-repo | |
| run: git push --force-with-lease origin "${{ steps.assessment.outputs.docs_branch }}" | |
| - name: Create or update docs pull request | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' && steps.scope_plan.outputs.docs_needed == 'true' }} | |
| id: manage_pr | |
| env: | |
| GH_TOKEN: ${{ secrets.REPO_DISPATCH_PAT_TOKEN }} | |
| HEAD_BRANCH: ${{ steps.assessment.outputs.docs_branch }} | |
| PR_TITLE: ${{ steps.pr_content.outputs.pr_title }} | |
| PR_BODY: ${{ steps.pr_content.outputs.pr_body }} | |
| CHANGES_MADE: ${{ steps.commit_docs.outputs.changes_made }} | |
| run: | | |
| python3 tools/manage_docs_pr.py \ | |
| --target-repo topoteretes/cognee-docs \ | |
| --head-branch "${HEAD_BRANCH}" \ | |
| --pr-title "${PR_TITLE}" \ | |
| --pr-body "${PR_BODY}" \ | |
| --changes-made "${CHANGES_MADE}" | |
| - name: Summarize docs PR result | |
| if: ${{ steps.assessment.outputs.needs_update == 'true' && steps.scope_plan.outputs.docs_needed == 'true' }} | |
| env: | |
| CHANGED_FILES: ${{ steps.pr_content.outputs.changed_files }} | |
| run: | | |
| { | |
| echo "## PR docs review: #${{ matrix.pr_number }} ${{ matrix.pr_title }}" | |
| echo | |
| echo "- Branch: \`${{ matrix.branch_name }}\`" | |
| echo "- Merge commit: \`${{ matrix.merge_sha }}\`" | |
| echo "- Needs documentation update: \`${{ steps.assessment.outputs.needs_update }}\`" | |
| if [ "${{ steps.assessment.outputs.needs_update }}" = "true" ]; then | |
| echo "- Docs branch: \`${{ steps.assessment.outputs.docs_branch }}\`" | |
| if [ -n "${CHANGED_FILES}" ]; then | |
| echo "- Updated docs files:" | |
| printf '%s\n' "${CHANGED_FILES}" | while IFS= read -r changed_file; do | |
| [ -n "${changed_file}" ] || continue | |
| echo " - \`${changed_file}\`" | |
| done | |
| else | |
| echo "- Updated docs files: none" | |
| fi | |
| if [ -n "${{ steps.manage_pr.outputs.pr_url }}" ]; then | |
| echo "- Pull request: ${{ steps.manage_pr.outputs.pr_url }}" | |
| else | |
| echo "- Pull request: not created" | |
| fi | |
| fi | |
| echo | |
| echo "### Documentation assessment" | |
| echo | |
| cat "${{ steps.branch_paths.outputs.assessment_markdown }}" | |
| } >> "${GITHUB_STEP_SUMMARY}" | |
| notify-failure: | |
| name: Notify failure | |
| needs: | |
| - prepare-merged-branches | |
| - create-docs-prs | |
| if: ${{ failure() }} | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 | |
| - name: Send failure email | |
| env: | |
| SMTP_SERVER: ${{ secrets.SMTP_SERVER }} | |
| SMTP_PORT: ${{ secrets.SMTP_PORT || '587' }} | |
| SMTP_USERNAME: ${{ secrets.MILENKO_SMTP_USERNAME || vars.DEV_PREVIOUS_DAY_COMMITS_NOTIFICATION_EMAIL }} | |
| SMTP_PASSWORD: ${{ secrets.MILENKO_SMTP_PASSWORD }} | |
| SMTP_USE_TLS: ${{ vars.SMTP_USE_TLS || 'true' }} | |
| NOTIFICATION_EMAIL_SENDER: ${{ secrets.NOTIFICATION_EMAIL_SENDER }} | |
| NOTIFICATION_EMAIL_RECEIVER: ${{ secrets.NOTIFICATION_EMAIL_RECEIVER }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| PREPARE_RESULT: ${{ needs.prepare-merged-branches.result }} | |
| CREATE_DOCS_PRS_RESULT: ${{ needs.create-docs-prs.result }} | |
| run: | | |
| if [ -z "${SMTP_SERVER}" ] || [ -z "${SMTP_USERNAME}" ] || [ -z "${SMTP_PASSWORD}" ] || [ -z "${NOTIFICATION_EMAIL_SENDER}" ] || [ -z "${NOTIFICATION_EMAIL_RECEIVER}" ]; then | |
| echo "SMTP secrets or DEV_PREVIOUS_DAY_COMMITS_NOTIFICATION_EMAIL variable are not configured; skipping failure email." | |
| exit 0 | |
| fi | |
| python3 tools/send_failure_email.py |