name: Sisyphus Agent

on:
  workflow_dispatch:
    inputs:
      prompt:
        description: "Custom prompt"
        required: false
  # Only issue_comment works for fork PRs (secrets available)
  # pull_request_review/pull_request_review_comment do NOT get secrets for fork PRs
  issue_comment:
    types: [created]

jobs:
  agent:
    runs-on: ubuntu-latest
    # @sisyphus-dev-ai mention only (maintainers, exclude self)
    if: >-
      github.event_name == 'workflow_dispatch' ||
      (github.event_name == 'issue_comment' &&
       contains(github.event.comment.body || '', '@sisyphus-dev-ai') &&
       (github.event.comment.user.login || '') != 'sisyphus-dev-ai' &&
       contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association || ''))

    permissions:
      contents: read

    steps:
      # Checkout with sisyphus-dev-ai's PAT
      - uses: actions/checkout@v5
        with:
          token: ${{ secrets.GH_PAT }}
          fetch-depth: 0

      # Git config - commits as sisyphus-dev-ai
      - name: Configure Git as sisyphus-dev-ai
        run: |
          git config user.name "sisyphus-dev-ai"
          git config user.email "sisyphus-dev-ai@users.noreply.github.com"

      # gh CLI auth as sisyphus-dev-ai
      - name: Authenticate gh CLI as sisyphus-dev-ai
        run: |
          echo "${{ secrets.GH_PAT }}" | gh auth login --with-token
          gh auth status

      - name: Ensure tmux is available (Linux)
        if: runner.os == 'Linux'
        run: |
          set -euo pipefail
          if ! command -v tmux >/dev/null 2>&1; then
            sudo apt-get update
            sudo apt-get install -y --no-install-recommends tmux
          fi
          tmux -V

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest

      - name: Cache Bun dependencies
        uses: actions/cache@v4
        with:
          path: |
            ~/.bun/install/cache
            node_modules
          key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
          restore-keys: |
            ${{ runner.os }}-bun-

      # Build local oh-my-opencode
      - name: Build oh-my-opencode
        run: |
          bun install
          bun run build

      # Install OpenCode + configure local plugin + auth in single step
      - name: Setup OpenCode with oh-my-opencode
        env:
          OPENCODE_AUTH_JSON: ${{ secrets.OPENCODE_AUTH_JSON }}
          ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          export PATH="$HOME/.opencode/bin:$PATH"

          # Install OpenCode (skip if cached)
          if ! command -v opencode &>/dev/null; then
            echo "Installing OpenCode..."
            curl -fsSL https://opencode.ai/install -o /tmp/opencode-install.sh
            
            # Try default installer first, fallback to pinned version if it fails
            if file /tmp/opencode-install.sh | grep -q "shell script\|text"; then
              if ! bash /tmp/opencode-install.sh 2>&1; then
                echo "Default installer failed, trying with pinned version..."
                bash /tmp/opencode-install.sh --version 1.0.204
              fi
            else
              echo "Download corrupted, trying direct install with pinned version..."
              bash <(curl -fsSL https://opencode.ai/install) --version 1.0.204
            fi
          fi
          opencode --version

          # Run local oh-my-opencode install (uses built dist)
          bun run dist/cli/index.js install --no-tui --claude=max20 --chatgpt=no --gemini=no

          # Override plugin to use local file reference
          OPENCODE_JSON=~/.config/opencode/opencode.json
          REPO_PATH=$(pwd)
          jq --arg path "file://$REPO_PATH/src/index.ts" '
            .plugin = [.plugin[] | select(. != "oh-my-opencode")] + [$path]
          ' "$OPENCODE_JSON" > /tmp/oc.json && mv /tmp/oc.json "$OPENCODE_JSON"

          OPENCODE_JSON=~/.config/opencode/opencode.json
          jq --arg baseURL "$ANTHROPIC_BASE_URL" --arg apiKey "$ANTHROPIC_API_KEY" '
            .provider.anthropic = {
              "name": "Anthropic",
              "npm": "@ai-sdk/anthropic",
              "options": {
                "baseURL": $baseURL,
                "apiKey": $apiKey
              },
              "models": {
                "claude-opus-4-5": {
                  "id": "claude-opus-4-5-20251101",
                  "name": "Opus 4.5",
                  "limit": { "context": 190000, "output": 64000 },
                  "options": { "effort": "high" }
                },
                "claude-opus-4-5-high": {
                  "id": "claude-opus-4-5-20251101",
                  "name": "Opus 4.5 High",
                  "limit": { "context": 190000, "output": 128000 },
                  "options": { "effort": "high", "thinking": { "type": "enabled", "budgetTokens": 64000 } }
                },
                "claude-sonnet-4-5": {
                  "id": "claude-sonnet-4-5-20250929",
                  "name": "Sonnet 4.5",
                  "limit": { "context": 200000, "output": 64000 }
                },
                "claude-sonnet-4-5-high": {
                  "id": "claude-sonnet-4-5-20250929",
                  "name": "Sonnet 4.5 High",
                  "limit": { "context": 200000, "output": 128000 },
                  "options": { "thinking": { "type": "enabled", "budgetTokens": 64000 } }
                },
                "claude-haiku-4-5": {
                  "id": "claude-haiku-4-5-20251001",
                  "name": "Haiku 4.5",
                  "limit": { "context": 200000, "output": 64000 }
                }
              }
            }
          ' "$OPENCODE_JSON" > /tmp/oc.json && mv /tmp/oc.json "$OPENCODE_JSON"

          OMO_JSON=~/.config/opencode/oh-my-opencode.json
          PROMPT_APPEND=$(cat << 'PROMPT_EOF'
          <ultrawork-mode>
          [CODE RED] Maximum precision required. Ultrathink before acting.

          YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
          TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.

          ## AGENT UTILIZATION PRINCIPLES (by capability, not by name)
          - **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
          - **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
          - **Planning & Strategy**: For implementation tasks, spawn a dedicated planning agent for work breakdown (not needed for simple questions/investigations)
          - **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
          - **Frontend/UI Tasks**: Delegate to UI-specialized agents for design and implementation

          ## EXECUTION RULES
          - **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
          - **PARALLEL**: Fire independent agent calls simultaneously via background_task - NEVER wait sequentially.
          - **BACKGROUND FIRST**: Use background_task for exploration/research agents (10+ concurrent if needed).
          - **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
          - **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.

          ## WORKFLOW
          1. Analyze the request and identify required capabilities
          2. Spawn exploration/librarian agents via background_task in PARALLEL (10+ if needed)
          3. Always Use Plan agent with gathered context to create detailed work breakdown
          4. Execute with continuous verification against original requirements

          ## TDD (if test infrastructure exists)

          1. Write spec (requirements)
          2. Write tests (failing)
          3. RED: tests fail
          4. Implement minimal code
          5. GREEN: tests pass
          6. Refactor if needed (must stay green)
          7. Next feature, repeat

          ## ZERO TOLERANCE FAILURES
          - **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions - deliver FULL implementation
          - **NO MockUp Work**: When user asked you to do "port A", you must "port A", fully, 100%. No Extra feature, No reduced feature, no mock data, fully working 100% port.
          - **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." - finish 100%
          - **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later"
          - **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified
          - **NO TEST DELETION**: Never delete or skip failing tests to make the build pass. Fix the code, not the tests.

          THE USER ASKED FOR X. DELIVER EXACTLY X. NOT A SUBSET. NOT A DEMO. NOT A STARTING POINT.

          </ultrawork-mode>

          ---


          [analyze-mode]
          ANALYSIS MODE. Gather context before diving deep:

          CONTEXT GATHERING (parallel):
          - 1-2 explore agents (codebase patterns, implementations)
          - 1-2 librarian agents (if external library involved)
          - Direct tools: Grep, AST-grep, LSP for targeted searches

          IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
          - Consult oracle for strategic guidance

          SYNTHESIZE findings before proceeding.

          ---

          ## GitHub Actions Environment

          You are `sisyphus-dev-ai` in GitHub Actions.

          ### CRITICAL: GitHub Comments = Your ONLY Output

          User CANNOT see console. Post everything via `gh issue comment` or `gh pr comment`.

          ### Comment Formatting (CRITICAL)

          **ALWAYS use heredoc syntax for comments containing code references, backticks, or multiline content:**

          ```bash
          gh issue comment <number> --body "$(cat <<'EOF'
          Your comment with `backticks` and code references preserved here.
          Multiple lines work perfectly.
          EOF
          )"
          ```

          **NEVER use direct quotes with backticks** (shell will interpret them as command substitution):
          ```bash
          # WRONG - backticks disappear:
          gh issue comment 123 --body "text with `code`"
          
          # CORRECT - backticks preserved:
          gh issue comment 123 --body "$(cat <<'EOF'
          text with `code`
          EOF
          )"
          ```

          ### GitHub Markdown Rules (MUST FOLLOW)

          **Code blocks MUST have EXACTLY 3 backticks and language identifier:**
          - CORRECT: ` ```bash ` ... ` ``` `
          - WRONG: ` ``` ` (no language), ` ```` ` (4 backticks), ` `` ` (2 backticks)
          
          **Every opening ` ``` ` MUST have a closing ` ``` ` on its own line:**
          ```
          ```bash
          code here
          ```
          ```
          
          **NO trailing backticks or spaces after closing ` ``` `**
          
          **For inline code, use SINGLE backticks:** `code` not ```code```
          
          **Lists inside code blocks break rendering - avoid them or use plain text**

          ### Rules
          - EVERY response = GitHub comment (use heredoc for proper escaping)
          - Code changes = PR (never push main/master)
          - Setup: bun install first
          - Acknowledge immediately, report when done

          ### Git Config
          - user.name: sisyphus-dev-ai
          - user.email: sisyphus-dev-ai@users.noreply.github.com
          PROMPT_EOF
          )
          jq --arg append "$PROMPT_APPEND" '.agents.Sisyphus.prompt_append = $append' "$OMO_JSON" > /tmp/omo.json && mv /tmp/omo.json "$OMO_JSON"

          mkdir -p ~/.local/share/opencode
          echo "$OPENCODE_AUTH_JSON" > ~/.local/share/opencode/auth.json
          chmod 600 ~/.local/share/opencode/auth.json

          cat "$OPENCODE_JSON"

      # Collect context
      - name: Collect Context
        id: context
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
          EVENT_NAME: ${{ github.event_name }}
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          COMMENT_BODY: ${{ github.event.comment.body }}
          COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
          COMMENT_ID_VAL: ${{ github.event.comment.id }}
          REPO: ${{ github.repository }}
        run: |
          if [[ "$EVENT_NAME" == "issue_comment" ]]; then
            ISSUE_NUM="$ISSUE_NUMBER"
            AUTHOR="$COMMENT_AUTHOR"
            COMMENT_ID="$COMMENT_ID_VAL"

            # Check if PR or Issue and get title
            ISSUE_DATA=$(gh api "repos/$REPO/issues/${ISSUE_NUM}")
            TITLE=$(echo "$ISSUE_DATA" | jq -r '.title')
            if echo "$ISSUE_DATA" | jq -e '.pull_request' > /dev/null; then
              echo "type=pr" >> $GITHUB_OUTPUT
              echo "number=${ISSUE_NUM}" >> $GITHUB_OUTPUT
            else
              echo "type=issue" >> $GITHUB_OUTPUT
              echo "number=${ISSUE_NUM}" >> $GITHUB_OUTPUT
            fi
            echo "title=${TITLE}" >> $GITHUB_OUTPUT
          fi

          echo "comment<<EOF" >> $GITHUB_OUTPUT
          echo "$COMMENT_BODY" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT
          echo "author=$AUTHOR" >> $GITHUB_OUTPUT
          echo "comment_id=$COMMENT_ID" >> $GITHUB_OUTPUT

      # Add :eyes: reaction (as sisyphus-dev-ai)
      - name: Add eyes reaction
        if: steps.context.outputs.comment_id != ''
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          gh api "/repos/${{ github.repository }}/issues/comments/${{ steps.context.outputs.comment_id }}/reactions" \
            -X POST -f content="eyes" || true

      - name: Add working label
        if: steps.context.outputs.number != ''
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          gh label create "sisyphus: working" \
            --repo "${{ github.repository }}" \
            --color "fcf2e1" \
            --description "Sisyphus is currently working on this" \
            --force || true
          
          if [[ "${{ steps.context.outputs.type }}" == "pr" ]]; then
            gh pr edit "${{ steps.context.outputs.number }}" \
              --repo "${{ github.repository }}" \
              --add-label "sisyphus: working" || true
          else
            gh issue edit "${{ steps.context.outputs.number }}" \
              --repo "${{ github.repository }}" \
              --add-label "sisyphus: working" || true
          fi

      - name: Run oh-my-opencode
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
          USER_COMMENT: ${{ steps.context.outputs.comment }}
          COMMENT_AUTHOR: ${{ steps.context.outputs.author }}
          CONTEXT_TYPE: ${{ steps.context.outputs.type }}
          CONTEXT_NUMBER: ${{ steps.context.outputs.number }}
          CONTEXT_TITLE: ${{ steps.context.outputs.title }}
          REPO_NAME: ${{ github.repository }}
          DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
        run: |
          export PATH="$HOME/.opencode/bin:$PATH"

          PROMPT=$(cat <<'PROMPT_EOF'
          [analyze-mode]
          ANALYSIS MODE. Gather context before diving deep:

          CONTEXT GATHERING (parallel):
          - 1-2 explore agents (codebase patterns, implementations)
          - 1-2 librarian agents (if external library involved)
          - Direct tools: Grep, AST-grep, LSP for targeted searches

          IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
          - Consult oracle for strategic guidance

          SYNTHESIZE findings before proceeding.

          ---

          Your username is @sisyphus-dev-ai, mentioned by @AUTHOR_PLACEHOLDER in REPO_PLACEHOLDER.

          ## Context
          - Title: TITLE_PLACEHOLDER
          - Type: TYPE_PLACEHOLDER
          - Number: #NUMBER_PLACEHOLDER
          - Repository: REPO_PLACEHOLDER
          - Default Branch: BRANCH_PLACEHOLDER

          ## User's Request
          COMMENT_PLACEHOLDER

          ---

          ## CRITICAL: First Steps (MUST DO BEFORE ANYTHING ELSE)

          ### [CODE RED] MANDATORY CONTEXT READING - ZERO EXCEPTIONS

          **YOU MUST READ ALL CONTENT. NOT SOME. NOT MOST. ALL.**

          1. **READ FULL CONVERSATION** - Execute ALL commands below before ANY other action:
             - **Issues**: `gh issue view NUMBER_PLACEHOLDER --comments`
             - **PRs**: Use ALL THREE commands to get COMPLETE context:
               ```bash
               gh pr view NUMBER_PLACEHOLDER --comments
               gh api repos/REPO_PLACEHOLDER/pulls/NUMBER_PLACEHOLDER/comments
               gh api repos/REPO_PLACEHOLDER/pulls/NUMBER_PLACEHOLDER/reviews
               ```
             
             **WHAT TO EXTRACT FROM THE CONVERSATION:**
             - The ORIGINAL issue/PR description (first message) - this is often the TRUE requirement
             - ALL previous attempts and their outcomes
             - ALL decisions made and their reasoning
             - ALL feedback, criticism, and rejection reasons
             - ANY linked issues, PRs, or external references
             - The EXACT ask from the user who mentioned you
             
             **FAILURE TO READ EVERYTHING = GUARANTEED FAILURE**
             You WILL make wrong assumptions. You WILL repeat past mistakes. You WILL miss critical context.

          2. **CREATE TODOS IMMEDIATELY**: Right after reading, create your todo list using todo tools.
             - First todo: "Summarize issue/PR context and requirements"
             - Break down ALL work into atomic, verifiable steps
             - Plan everything BEFORE starting any work

          ---


          Plan everything using todo tools.
          Then investigate and satisfy the request. Only if user requested to you to work explicitly, then use plan agent to plan, todo obsessively then create a PR to `BRANCH_PLACEHOLDER` branch.
          When done, report the result to the issue/PR with `gh issue comment NUMBER_PLACEHOLDER` or `gh pr comment NUMBER_PLACEHOLDER`.
          PROMPT_EOF
          )

          PROMPT="${PROMPT//AUTHOR_PLACEHOLDER/$COMMENT_AUTHOR}"
          PROMPT="${PROMPT//REPO_PLACEHOLDER/$REPO_NAME}"
          PROMPT="${PROMPT//TYPE_PLACEHOLDER/$CONTEXT_TYPE}"
          PROMPT="${PROMPT//NUMBER_PLACEHOLDER/$CONTEXT_NUMBER}"
          PROMPT="${PROMPT//TITLE_PLACEHOLDER/$CONTEXT_TITLE}"
          PROMPT="${PROMPT//BRANCH_PLACEHOLDER/$DEFAULT_BRANCH}"
          PROMPT="${PROMPT//COMMENT_PLACEHOLDER/$USER_COMMENT}"

          stdbuf -oL -eL bun run dist/cli/index.js run "$PROMPT"

      # Push changes (as sisyphus-dev-ai)
      - name: Push changes
        if: always()
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          if [[ -n "$(git status --porcelain)" ]]; then
            git add -A
            git commit -m "chore: changes by sisyphus-dev-ai" || true
          fi

          BRANCH=$(git branch --show-current)
          if [[ "$BRANCH" != "main" && "$BRANCH" != "master" ]]; then
            git push origin "$BRANCH" || true
          fi

      - name: Update reaction and remove label
        if: always()
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          if [[ -n "${{ steps.context.outputs.comment_id }}" ]]; then
            REACTION_ID=$(gh api "/repos/${{ github.repository }}/issues/comments/${{ steps.context.outputs.comment_id }}/reactions" \
              --jq '.[] | select(.content == "eyes" and .user.login == "sisyphus-dev-ai") | .id' | head -1)
            if [[ -n "$REACTION_ID" ]]; then
              gh api -X DELETE "/repos/${{ github.repository }}/reactions/${REACTION_ID}" || true
            fi

            gh api "/repos/${{ github.repository }}/issues/comments/${{ steps.context.outputs.comment_id }}/reactions" \
              -X POST -f content="+1" || true
          fi

          if [[ -n "${{ steps.context.outputs.number }}" ]]; then
            if [[ "${{ steps.context.outputs.type }}" == "pr" ]]; then
              gh pr edit "${{ steps.context.outputs.number }}" \
                --repo "${{ github.repository }}" \
                --remove-label "sisyphus: working" || true
            else
              gh issue edit "${{ steps.context.outputs.number }}" \
                --repo "${{ github.repository }}" \
                --remove-label "sisyphus: working" || true
            fi
          fi
