diff --git a/.forgejo/kcl-ci/main.k b/.forgejo/kcl-ci/main.k index 4cd9959..f88eac6 100644 --- a/.forgejo/kcl-ci/main.k +++ b/.forgejo/kcl-ci/main.k @@ -3,6 +3,7 @@ import kcl_ci.schemas.config as cfg import kcl_ci.steps.checkout as checkout_mod import kcl_ci.workflows.kcl_ci_build as kcl_build_mod import kcl_ci.workflows.claude as claude_mod +import kcl_ci.versions # ============================================================================= # Check name constants @@ -58,9 +59,7 @@ _bun_deps = lambda -> {str:} { # Claude Code workflow (tag mode — responds to @claude mentions) # Uses the standard claude.k module from kcl-ci # ============================================================================= -_claude_workflow = claude_mod.claude_workflow(claude_mod.ClaudeConfig { - anthropic_api_key_secret = "ANTHROPIC_API_KEY" -}) +_claude_workflow = claude_mod.claude_workflow() # ============================================================================= # KCL CI Build workflow (auto-regenerates workflows from KCL) @@ -181,9 +180,6 @@ _test_action_workflow = { github_token = h.gha_secret("FORGEJO_CLAUDE_TOKEN") platform = "forgejo" } - env = { - ANTHROPIC_API_KEY = h.gha_secret("ANTHROPIC_API_KEY") - } } { name = "Verify execution output exists" @@ -233,7 +229,66 @@ fi""" # ============================================================================= # Upstream Sync Check workflow (weekly check for upstream updates) +# Detects upstream changes, creates/updates tracking issue, and triggers +# Claude to auto-merge and create a PR if changes are found. # ============================================================================= + +# Claude action URL for agent mode (merge job) +_claude_action_url = h.gha_action_url("actions", "claude-code-action", versions.CLAUDE_CODE_ACTION_VERSION, "READ_REPO_TOKEN") + +# GHA expression refs for the merge prompt +_merge_branch_ref = h.gha_steps("prepare", "branch") +_merge_behind_ref = h.gha_needs("check-upstream", "behind") +_merge_ahead_ref = h.gha_needs("check-upstream", "ahead") +_merge_clean_ref = h.gha_steps("prepare", "merge_clean") +_merge_affected_ref = h.gha_steps("prepare", "affected_files") + +_merge_prompt = """\ +You are automating an upstream sync for our Forgejo fork of anthropics/claude-code-action. + +## Current state +- Branch: ${_merge_branch_ref} +- ${_merge_behind_ref} commits behind upstream, ${_merge_ahead_ref} commits ahead (our Forgejo changes) +- Merge was attempted. Status: ${_merge_clean_ref} (true = clean, false = has conflicts) + +## Impact analysis +- ${"$"}FORK_MODIFIED_FILES — all files our fork has changed vs upstream +- ${"$"}AFFECTED_FORK_FILES — subset of fork files that upstream ALSO changed (overlap = conflict risk) +- If AFFECTED_FORK_FILES is empty, upstream only touched files we haven't modified — low risk, merge should be clean. +- If AFFECTED_FORK_FILES is non-empty, those files need careful review since both sides changed them. + +## If merge has conflicts (merge_clean=false) +Resolve all conflicts. For files in ${"$"}FORK_MODIFIED_FILES, our fork's changes take PRIORITY — they contain Forgejo-specific adaptations. + +## Review ALL upstream changes for fork compatibility +Read CLAUDE.md for context on our Forgejo adapter pattern and platform-specific code. +Even files we haven't modified can break our fork. Check upstream changes for: +- Renamed/moved imports or exports that our Forgejo-specific files depend on +- Changed function signatures, types, or interfaces used by our adapter code +- New assumptions (e.g. GitHub-only APIs) that don't hold on Forgejo +- Removed or restructured code that our platform branches reference +If you find incompatibilities, fix them to maintain Forgejo support. + +## Maintainability principle +When fixing incompatibilities, minimize fork divergence to keep future merges easy. +Prefer adapter patterns (separate Forgejo files) over inline modifications to upstream files. +The fewer upstream files we modify, the fewer conflicts in the next sync. + +## After merge is resolved and reviewed +1. Run tests: export GITHUB_SERVER_URL=https://github.com && bun test +2. Run typecheck: bun run typecheck +3. Check formatting: bun run format:check — if issues, fix with bun run format +4. Commit any remaining changes and push the branch +5. Create a PR targeting main via the Forgejo API: + POST ${"$"}GITHUB_API_URL/repos/${"$"}GITHUB_REPOSITORY/pulls + Auth: token ${"$"}GITHUB_TOKEN + Title: "chore: merge ${_merge_behind_ref} upstream commits from anthropics/claude-code-action" + Body should include: + - Summary of notable upstream changes + - Impact assessment: which fork files (if any) were affected + - List of conflicts resolved (if any) + - If no fork files were affected, note that this is a low-risk merge""" + _upstream_sync_workflow = { name = "Upstream Sync Check" on = { @@ -247,6 +302,11 @@ _upstream_sync_workflow = { "check-upstream" = { name = "Check for upstream updates" "runs-on" = "grunt" + outputs = { + needs_sync = h.gha_steps("compare", "needs_sync") + behind = h.gha_steps("compare", "behind") + ahead = h.gha_steps("compare", "ahead") + } steps = [ checkout_mod.checkout() | {"with" = {"fetch-depth" = "0"}} { @@ -410,6 +470,93 @@ fi""" } ] } + # ================================================================= + # Auto-merge job: creates branch, merges upstream, runs Claude + # to review/fix conflicts/test/create PR + # ================================================================= + "merge-upstream" = { + name = "Auto-merge Upstream" + "runs-on" = "grunt" + needs = ["check-upstream"] + "if" = h.gha_needs_eq("check-upstream", "needs_sync", "true") + steps = [ + { + name = "Checkout" + uses = h.gha_action_url("xternal", "checkout", versions.CHECKOUT_VERSION, "READ_REPO_TOKEN") + "with" = { + "fetch-depth" = "0" + token = h.gha_secret("CLAUDE_BOT_TOKEN") + } + } + { + name = "Prepare upstream merge" + id = "prepare" + run = """\ +git config user.name "claude" +git config user.email "claude@wxs.ro" + +git remote add upstream https://github.com/anthropics/claude-code-action.git +git fetch upstream main --quiet + +# Files our fork has modified vs upstream +FORK_FILES=${"$"}(git log upstream/main..main --name-only --pretty=format: | sort -u | grep -v '^${"$"}') +echo "=== Fork-modified files ===" +echo "${"$"}FORK_FILES" + +# Files upstream has changed since we last synced +UPSTREAM_FILES=${"$"}(git diff --name-only main...upstream/main | sort -u) +echo "" +echo "=== Upstream-changed files ===" +echo "${"$"}UPSTREAM_FILES" + +# Intersection: fork files that upstream also changed (conflict risk) +AFFECTED=${"$"}(comm -12 <(echo "${"$"}FORK_FILES") <(echo "${"$"}UPSTREAM_FILES")) +echo "" +echo "=== Affected fork files (overlap) ===" +if [ -z "${"$"}AFFECTED" ]; then + echo "(none — low risk merge)" +else + echo "${"$"}AFFECTED" +fi + +# Export all lists as multiline outputs +echo "fork_files<> "${"$"}GITHUB_OUTPUT" +echo "${"$"}FORK_FILES" >> "${"$"}GITHUB_OUTPUT" +echo "EOF" >> "${"$"}GITHUB_OUTPUT" + +echo "affected_files<> "${"$"}GITHUB_OUTPUT" +echo "${"$"}{AFFECTED:-(none)}" >> "${"$"}GITHUB_OUTPUT" +echo "EOF" >> "${"$"}GITHUB_OUTPUT" + +BRANCH="chore/upstream-sync-${"$"}(date +%Y%m%d)" +git checkout -b "${"$"}BRANCH" +echo "branch=${"$"}BRANCH" >> "${"$"}GITHUB_OUTPUT" + +if git merge upstream/main --no-edit; then + echo "merge_clean=true" >> "${"$"}GITHUB_OUTPUT" + echo "Merge completed cleanly" +else + echo "merge_clean=false" >> "${"$"}GITHUB_OUTPUT" + echo "Merge has conflicts — Claude will resolve them" +fi""" + } + { + name = "Claude: review merge and create PR" + uses = _claude_action_url + "with" = { + prompt = _merge_prompt + claude_code_oauth_token = h.gha_secret("CLAUDE_CODE_OAUTH_TOKEN") + github_token = h.gha_secret("CLAUDE_BOT_TOKEN") + platform = "forgejo" + } + env = { + GITHUB_TOKEN = h.gha_secret("CLAUDE_BOT_TOKEN") + FORK_MODIFIED_FILES = h.gha_steps("prepare", "fork_files") + AFFECTED_FORK_FILES = h.gha_steps("prepare", "affected_files") + } + } + ] + } } } diff --git a/.forgejo/workflows/claude.yml b/.forgejo/workflows/claude.yml index a233787..bb50305 100644 --- a/.forgejo/workflows/claude.yml +++ b/.forgejo/workflows/claude.yml @@ -39,8 +39,6 @@ jobs: bot_email: claude@wxs.ro platform: forgejo assignee_trigger: claude - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - name: Debug on failure if: failure() run: | diff --git a/.forgejo/workflows/test-action.yml b/.forgejo/workflows/test-action.yml index 85506dc..ca72cab 100644 --- a/.forgejo/workflows/test-action.yml +++ b/.forgejo/workflows/test-action.yml @@ -34,8 +34,6 @@ jobs: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ secrets.FORGEJO_CLAUDE_TOKEN }} platform: forgejo - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - name: Verify execution output exists run: |- EXEC_FILE="${{ steps.claude.outputs.execution_file }}" diff --git a/.forgejo/workflows/upstream-sync.yml b/.forgejo/workflows/upstream-sync.yml index d9d0fe3..357934c 100644 --- a/.forgejo/workflows/upstream-sync.yml +++ b/.forgejo/workflows/upstream-sync.yml @@ -12,6 +12,10 @@ jobs: check-upstream: name: Check for upstream updates runs-on: grunt + outputs: + needs_sync: ${{ steps.compare.outputs.needs_sync }} + behind: ${{ steps.compare.outputs.behind }} + ahead: ${{ steps.compare.outputs.ahead }} steps: - name: Checkout uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 @@ -164,3 +168,120 @@ jobs: "${{ env.GITHUB_API_URL }}/repos/${{ github.repository }}/issues" echo "Created new upstream sync issue" fi + merge-upstream: + name: Auto-merge Upstream + runs-on: grunt + needs: + - check-upstream + if: needs.check-upstream.outputs.needs_sync == 'true' + steps: + - name: Checkout + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 + with: + fetch-depth: '0' + token: ${{ secrets.CLAUDE_BOT_TOKEN }} + - name: Prepare upstream merge + id: prepare + run: |- + git config user.name "claude" + git config user.email "claude@wxs.ro" + + git remote add upstream https://github.com/anthropics/claude-code-action.git + git fetch upstream main --quiet + + # Files our fork has modified vs upstream + FORK_FILES=$(git log upstream/main..main --name-only --pretty=format: | sort -u | grep -v '^$') + echo "=== Fork-modified files ===" + echo "$FORK_FILES" + + # Files upstream has changed since we last synced + UPSTREAM_FILES=$(git diff --name-only main...upstream/main | sort -u) + echo "" + echo "=== Upstream-changed files ===" + echo "$UPSTREAM_FILES" + + # Intersection: fork files that upstream also changed (conflict risk) + AFFECTED=$(comm -12 <(echo "$FORK_FILES") <(echo "$UPSTREAM_FILES")) + echo "" + echo "=== Affected fork files (overlap) ===" + if [ -z "$AFFECTED" ]; then + echo "(none — low risk merge)" + else + echo "$AFFECTED" + fi + + # Export all lists as multiline outputs + echo "fork_files<> "$GITHUB_OUTPUT" + echo "$FORK_FILES" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + echo "affected_files<> "$GITHUB_OUTPUT" + echo "${AFFECTED:-(none)}" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + BRANCH="chore/upstream-sync-$(date +%Y%m%d)" + git checkout -b "$BRANCH" + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + + if git merge upstream/main --no-edit; then + echo "merge_clean=true" >> "$GITHUB_OUTPUT" + echo "Merge completed cleanly" + else + echo "merge_clean=false" >> "$GITHUB_OUTPUT" + echo "Merge has conflicts — Claude will resolve them" + fi + - name: 'Claude: review merge and create PR' + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/actions/claude-code-action@main + with: + prompt: |- + You are automating an upstream sync for our Forgejo fork of anthropics/claude-code-action. + + ## Current state + - Branch: ${{ steps.prepare.outputs.branch }} + - ${{ needs.check-upstream.outputs.behind }} commits behind upstream, ${{ needs.check-upstream.outputs.ahead }} commits ahead (our Forgejo changes) + - Merge was attempted. Status: ${{ steps.prepare.outputs.merge_clean }} (true = clean, false = has conflicts) + + ## Impact analysis + - $FORK_MODIFIED_FILES — all files our fork has changed vs upstream + - $AFFECTED_FORK_FILES — subset of fork files that upstream ALSO changed (overlap = conflict risk) + - If AFFECTED_FORK_FILES is empty, upstream only touched files we haven't modified — low risk, merge should be clean. + - If AFFECTED_FORK_FILES is non-empty, those files need careful review since both sides changed them. + + ## If merge has conflicts (merge_clean=false) + Resolve all conflicts. For files in $FORK_MODIFIED_FILES, our fork's changes take PRIORITY — they contain Forgejo-specific adaptations. + + ## Review ALL upstream changes for fork compatibility + Read CLAUDE.md for context on our Forgejo adapter pattern and platform-specific code. + Even files we haven't modified can break our fork. Check upstream changes for: + - Renamed/moved imports or exports that our Forgejo-specific files depend on + - Changed function signatures, types, or interfaces used by our adapter code + - New assumptions (e.g. GitHub-only APIs) that don't hold on Forgejo + - Removed or restructured code that our platform branches reference + If you find incompatibilities, fix them to maintain Forgejo support. + + ## Maintainability principle + When fixing incompatibilities, minimize fork divergence to keep future merges easy. + Prefer adapter patterns (separate Forgejo files) over inline modifications to upstream files. + The fewer upstream files we modify, the fewer conflicts in the next sync. + + ## After merge is resolved and reviewed + 1. Run tests: export GITHUB_SERVER_URL=https://github.com && bun test + 2. Run typecheck: bun run typecheck + 3. Check formatting: bun run format:check — if issues, fix with bun run format + 4. Commit any remaining changes and push the branch + 5. Create a PR targeting main via the Forgejo API: + POST $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls + Auth: token $GITHUB_TOKEN + Title: "chore: merge ${{ needs.check-upstream.outputs.behind }} upstream commits from anthropics/claude-code-action" + Body should include: + - Summary of notable upstream changes + - Impact assessment: which fork files (if any) were affected + - List of conflicts resolved (if any) + - If no fork files were affected, note that this is a low-risk merge + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github_token: ${{ secrets.CLAUDE_BOT_TOKEN }} + platform: forgejo + env: + GITHUB_TOKEN: ${{ secrets.CLAUDE_BOT_TOKEN }} + FORK_MODIFIED_FILES: ${{ steps.prepare.outputs.fork_files }} + AFFECTED_FORK_FILES: ${{ steps.prepare.outputs.affected_files }}