# Zindex - PR architecture diff. Surfaces architectural impact in every PR
# review by deriving the architecture from the PR head, comparing against
# main's persisted scene, and posting before-and-after diagrams as a PR comment.
# Unlike the living-architecture workflow, this never modifies main's revision
# history - it creates transient PR-tagged revisions for the diff comparison
# only.
#
# Drop into .github/workflows/zindex-pr-arch-diff.yml.

name: Zindex - PR architecture diff

on:
  pull_request:
    types: [opened, synchronize, reopened]
  workflow_dispatch:

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

env:
  ZINDEX_API_BASE: https://api.zindex.ai

jobs:
  pr-architecture-diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # need merge-base for the comparison

      # 1. Capture main's revision - the "before" baseline. Don't modify it.
      - name: Capture main revision
        id: main_rev
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
        run: |
          echo "rev=$(curl -fsSL -H "Authorization: Bearer $ZINDEX_API_KEY" \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID" | jq -r '.revision')" >> "$GITHUB_OUTPUT"

      # 2. Scan the PR head's tree (NOT main). Same convention map as the
      #    living-architecture workflow - but applied to the PR's source.
      - name: Scan PR head for architectural state
        run: |
          mkdir -p out
          node scripts/scan-architecture.mjs > out/architecture-after.json

      # 3. Compute the "would-be" applyOps batch. Tag the revision message
      #    with the PR number so transient PR revisions are easy to spot in
      #    the audit log.
      - name: Compute hypothetical applyOps batch
        run: |
          node scripts/architecture-to-ops.mjs \
            --revision-message "pr-${{ github.event.pull_request.number }}: hypothetical post-merge state" \
            out/architecture-after.json > out/ops.json

      # 4. Apply ops - produces a new revision representing the PR's "after"
      #    state. This is intentional: the persisted scene now temporarily
      #    holds main + this PR's hypothetical changes. The next scheduled
      #    main-branch run will overwrite this when the PR merges (or you
      #    can leave PR revisions in place as a record of architectural
      #    proposals).
      - name: Apply ops as PR-tagged revision
        id: pr_apply
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
        run: |
          RESP=$(curl -fsSL -X POST \
            -H "Authorization: Bearer $ZINDEX_API_KEY" \
            -H "Content-Type: application/json" \
            --data-binary @out/ops.json \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/ops")
          echo "rev=$(echo "$RESP" | jq -r '.revision')" >> "$GITHUB_OUTPUT"

      # 5. Diff main → PR. This is the canonical answer to "what does this
      #    PR change architecturally?" - surface this in the comment.
      - name: Diff main → PR
        id: diff
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
          MAIN: ${{ steps.main_rev.outputs.rev }}
          PR: ${{ steps.pr_apply.outputs.rev }}
        run: |
          curl -fsSL -H "Authorization: Bearer $ZINDEX_API_KEY" \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/diff?from=$MAIN&to=$PR" \
            > out/diff.json
          ADDED=$(jq -r '.summary.added' out/diff.json)
          REMOVED=$(jq -r '.summary.removed' out/diff.json)
          MODIFIED=$(jq -r '.summary.modified' out/diff.json)
          echo "added=$ADDED" >> "$GITHUB_OUTPUT"
          echo "removed=$REMOVED" >> "$GITHUB_OUTPUT"
          echo "modified=$MODIFIED" >> "$GITHUB_OUTPUT"
          TOTAL=$((ADDED + REMOVED + MODIFIED))
          echo "total=$TOTAL" >> "$GITHUB_OUTPUT"

      # 6. Render BOTH revisions for the side-by-side comparison.
      - name: Render before (main)
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
          REV: ${{ steps.main_rev.outputs.rev }}
        run: |
          curl -fsSL -X POST \
            -H "Authorization: Bearer $ZINDEX_API_KEY" \
            -H "Content-Type: application/json" \
            -d "{\"format\":\"svg\",\"theme\":\"clean\",\"revision\":$REV}" \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/render" \
            | jq -r '.output.content' > out/before.svg

      - name: Render after (PR)
        env:
          ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
          ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
          REV: ${{ steps.pr_apply.outputs.rev }}
        run: |
          curl -fsSL -X POST \
            -H "Authorization: Bearer $ZINDEX_API_KEY" \
            -H "Content-Type: application/json" \
            -d "{\"format\":\"svg\",\"theme\":\"clean\",\"revision\":$REV}" \
            "$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/render" \
            | jq -r '.output.content' > out/after.svg

      - uses: actions/upload-artifact@v4
        id: upload
        with:
          name: pr-architecture-diff
          path: |
            out/before.svg
            out/after.svg
            out/diff.json
          retention-days: 30

      - uses: peter-evans/find-comment@v3
        id: find_comment
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: "github-actions[bot]"
          body-includes: "<!-- zindex-bot:pr-architecture-diff -->"

      # 7. Post the diff comment. If the PR has no architectural impact
      #    (total = 0), be honest about it - agents shouldn't generate
      #    noise about non-architectural PRs.
      - uses: peter-evans/create-or-update-comment@v4
        with:
          comment-id: ${{ steps.find_comment.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          edit-mode: replace
          body: |
            <!-- zindex-bot:pr-architecture-diff -->
            ${{ steps.diff.outputs.total == '0' && format('### No architectural changes detected
            This PR does not add, remove, or rewire any service, database, queue, or external dependency relative to `main` (revision {0}).
            ', steps.main_rev.outputs.rev) || format('### Architecture diff · main {0} → pr-{1}

            | | |
            |---|---|
            | Architectural changes | +{2} / -{3} / ~{4} |
            | Main revision | `{0}` |
            | PR revision | `{5}` |

            <details><summary>Download before / after / diff (SVGs + JSON)</summary>

            {6}

            </details>
            ', steps.main_rev.outputs.rev, github.event.pull_request.number, steps.diff.outputs.added, steps.diff.outputs.removed, steps.diff.outputs.modified, steps.pr_apply.outputs.rev, steps.upload.outputs.artifact-url) }}

            <sub>Rendered by Zindex · scene `${{ secrets.ZINDEX_SCENE_ID }}`</sub>
