From 5c014823e0bad137f98d3101488b03d7d4aa52e8 Mon Sep 17 00:00:00 2001 From: Vlad Mocanu Date: Fri, 27 Feb 2026 11:40:47 +0200 Subject: [PATCH] feat: migrate workflows to kcl-ci standard - Add .forgejo/kcl-ci/ with kcl.mod, main.k, Taskfile.yaml - claude.yml: use claude.k module (fixes #comment YAML validation error, removes duplicate pull_request_comment trigger) - ci.yml: add paths-ignore for kcl-ci paths, named jobs for check constants - test-action.yml: regenerated from KCL (functionally identical) - upstream-sync.yml: regenerated from KCL (functionally identical) - kcl-ci-build.yml: new auto-regeneration workflow (waits for CI checks) Co-Authored-By: Claude Opus 4.6 --- .forgejo/kcl-ci/Taskfile.yaml | 16 + .forgejo/kcl-ci/kcl.mod | 7 + .forgejo/kcl-ci/kcl.mod.lock | 7 + .forgejo/kcl-ci/main.k | 423 +++++++++++++++++++++++++++ .forgejo/workflows/ci.yml | 157 +++++----- .forgejo/workflows/claude.yml | 122 +++----- .forgejo/workflows/kcl-ci-build.yml | 47 +++ .forgejo/workflows/test-action.yml | 127 ++++---- .forgejo/workflows/upstream-sync.yml | 298 +++++++++---------- 9 files changed, 821 insertions(+), 383 deletions(-) create mode 100644 .forgejo/kcl-ci/Taskfile.yaml create mode 100644 .forgejo/kcl-ci/kcl.mod create mode 100644 .forgejo/kcl-ci/kcl.mod.lock create mode 100644 .forgejo/kcl-ci/main.k create mode 100644 .forgejo/workflows/kcl-ci-build.yml diff --git a/.forgejo/kcl-ci/Taskfile.yaml b/.forgejo/kcl-ci/Taskfile.yaml new file mode 100644 index 0000000..04d0e20 --- /dev/null +++ b/.forgejo/kcl-ci/Taskfile.yaml @@ -0,0 +1,16 @@ +--- +version: "3" + +vars: + TASK_REPO_LOCAL: ~/stuff/gitrepos/wxs/task + TASK_REPO_BASE: git.wxs.ro/wxs/task/raw/branch/main + +includes: + kcl: + taskfile: "{{if .CI}}https://token:{{.READ_REPO_TOKEN}}@{{.TASK_REPO_BASE}}/kcl-ci-consumer.yml{{else}}{{.TASK_REPO_LOCAL}}/kcl-ci-consumer.yml{{end}}" + +tasks: + default: + desc: Show available tasks + cmds: + - task --list diff --git a/.forgejo/kcl-ci/kcl.mod b/.forgejo/kcl-ci/kcl.mod new file mode 100644 index 0000000..a00de7e --- /dev/null +++ b/.forgejo/kcl-ci/kcl.mod @@ -0,0 +1,7 @@ +[package] +name = "claude_code_action_workflows" +edition = "v0.12.3" +version = "0.0.1" + +[dependencies] +kcl-ci = { git = "https://git.wxs.ro/actions/kcl-ci", tag = "v1.34.3" } diff --git a/.forgejo/kcl-ci/kcl.mod.lock b/.forgejo/kcl-ci/kcl.mod.lock new file mode 100644 index 0000000..8c41ae0 --- /dev/null +++ b/.forgejo/kcl-ci/kcl.mod.lock @@ -0,0 +1,7 @@ +[dependencies] + [dependencies.kcl-ci] + name = "kcl-ci" + full_name = "kcl-ci_0.0.1" + version = "0.0.1" + url = "https://git.wxs.ro/actions/kcl-ci" + git_tag = "v1.34.3" diff --git a/.forgejo/kcl-ci/main.k b/.forgejo/kcl-ci/main.k new file mode 100644 index 0000000..4cd9959 --- /dev/null +++ b/.forgejo/kcl-ci/main.k @@ -0,0 +1,423 @@ +import kcl_ci.schemas.helpers as h +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 + +# ============================================================================= +# Check name constants +# Forgejo check names format: "WorkflowName / JobName (event)" +# Keep these in sync with job names in the workflows below +# ============================================================================= +_CI_WORKFLOW_NAME = "CI" +_CI_JOB_TEST = "Tests" +_CI_JOB_PRETTIER = "Prettier" +_CI_JOB_TYPECHECK = "Type Check" + +_CHECK_CI_TEST = "{} / {} (push)".format(_CI_WORKFLOW_NAME, _CI_JOB_TEST) +_CHECK_CI_PRETTIER = "{} / {} (push)".format(_CI_WORKFLOW_NAME, _CI_JOB_PRETTIER) +_CHECK_CI_TYPECHECK = "{} / {} (push)".format(_CI_WORKFLOW_NAME, _CI_JOB_TYPECHECK) + +# ============================================================================= +# Bun version pinned across all CI jobs +# Bun 1.3.x broke spyOn(fs, ...) in tests. +# Bump this when upstream tests are updated for newer Bun versions. +# ============================================================================= +_BUN_VERSION = "1.2.21" + +# Bun install step — checks installed version before reinstalling +_bun_install = lambda -> {str:} { + { + name = "Install Bun" + run = """\ +if command -v bun &> /dev/null; then + INSTALLED=${"$"}(bun --version) + if [ "${"$"}INSTALLED" = "${"$"}BUN_VERSION" ]; then + echo "Bun ${"$"}BUN_VERSION already installed" + else + echo "Bun ${"$"}INSTALLED installed, need ${"$"}BUN_VERSION — reinstalling" + curl -fsSL https://bun.sh/install | bash -s "bun-v${"$"}{BUN_VERSION}" + echo "${"$"}HOME/.bun/bin" >> "${"$"}GITHUB_PATH" + fi +else + curl -fsSL https://bun.sh/install | bash -s "bun-v${"$"}{BUN_VERSION}" + echo "${"$"}HOME/.bun/bin" >> "${"$"}GITHUB_PATH" +fi""" + } +} + +# bun install dependencies step +_bun_deps = lambda -> {str:} { + { + name = "Install dependencies" + run = "bun install" + } +} + +# ============================================================================= +# 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" +}) + +# ============================================================================= +# KCL CI Build workflow (auto-regenerates workflows from KCL) +# Waits for CI checks before building +# ============================================================================= +_kcl_build_workflow = kcl_build_mod.kcl_ci_build_workflow(kcl_build_mod.KclCiBuildConfig { + wait_for_checks = [_CHECK_CI_TEST, _CHECK_CI_PRETTIER, _CHECK_CI_TYPECHECK] +}) + +# ============================================================================= +# CI workflow (test, prettier, typecheck) +# ============================================================================= +_ci_workflow = { + name = _CI_WORKFLOW_NAME + on = { + push = { + branches = ["main"] + "paths-ignore" = cfg.KCL_CI_DEFAULT_PATHS_IGNORE + } + pull_request = { + branches = ["main"] + "paths-ignore" = cfg.KCL_CI_DEFAULT_PATHS_IGNORE + } + workflow_dispatch = {} + } + concurrency = { + group = "ci-" + h.gha_github("ref") + "cancel-in-progress" = True + } + env = { + BUN_VERSION = _BUN_VERSION + } + jobs = { + test = { + name = _CI_JOB_TEST + "runs-on" = "grunt" + steps = [ + checkout_mod.checkout() + _bun_install() + _bun_deps() + { + name = "Run tests" + # Unit tests assume GitHub context. Override Forgejo runner's + # GITHUB_SERVER_URL so module-level constants (config.ts) and + # detectPlatform() default to GitHub. Forgejo-specific tests + # explicitly set PLATFORM=forgejo in their own beforeEach. + # Inline export because Forgejo runner re-injects GITHUB_SERVER_URL + # after step-level env:, overriding it. + run = """\ +export GITHUB_SERVER_URL=https://github.com +bun test""" + } + ] + } + prettier = { + name = _CI_JOB_PRETTIER + "runs-on" = "grunt" + steps = [ + checkout_mod.checkout() + _bun_install() + _bun_deps() + { + name = "Run prettier check" + run = "bun run format:check" + } + ] + } + typecheck = { + name = _CI_JOB_TYPECHECK + "runs-on" = "grunt" + steps = [ + checkout_mod.checkout() + _bun_install() + _bun_deps() + { + name = "Run TypeScript type check" + run = "bun run typecheck" + } + ] + } + } +} + +# ============================================================================= +# Test Action workflow (integration test — agent mode on Forgejo) +# Uses ./ self-reference since this IS the claude-code-action repo +# ============================================================================= +_test_action_workflow = { + name = "Test Action (Forgejo)" + on = { + schedule = [ + # Weekly Monday at 06:00 UTC + {cron = "0 6 * * 1"} + ] + pull_request = { + branches = ["main"] + paths = ["src/**", "action.yml", ".forgejo/**", "base-action/**"] + } + workflow_dispatch = {} + } + concurrency = { + group = "test-action-" + h.gha_github("ref") + "cancel-in-progress" = True + } + jobs = { + "test-agent-mode" = { + name = "Test Agent Mode on Forgejo" + "runs-on" = "grunt" + steps = [ + checkout_mod.checkout() + { + name = "Run Claude Code Action (agent mode)" + id = "claude" + uses = "./" + "with" = { + prompt = "Respond with exactly the text FORGEJO_PLATFORM_TEST_OK and nothing else. Do not use any tools." + claude_code_oauth_token = h.gha_secret("CLAUDE_CODE_OAUTH_TOKEN") + 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" + run = """\ +EXEC_FILE="${h.gha_steps("claude", "execution_file")}" +if [ -z "${"$"}EXEC_FILE" ]; then + echo "FAIL: execution_file output is empty" + exit 1 +fi +if [ ! -f "${"$"}EXEC_FILE" ]; then + echo "FAIL: execution file does not exist at: ${"$"}EXEC_FILE" + exit 1 +fi +echo "OK: execution file exists at: ${"$"}EXEC_FILE" + +if grep -q "FORGEJO_PLATFORM_TEST_OK" "${"$"}EXEC_FILE"; then + echo "OK: Output contains expected test marker" +else + echo "WARN: Test marker not found in output (Claude may have formatted differently)" + echo "--- Output contents (first 50 lines) ---" + head -50 "${"$"}EXEC_FILE" +fi""" + } + { + name = "Debug on failure" + "if" = "failure()" + run = """\ +echo "=== Debug Info ===" +echo "Platform env: PLATFORM=${"$"}PLATFORM FORGEJO=${"$"}FORGEJO" +echo "Server URL: ${"$"}GITHUB_SERVER_URL" +echo "API URL: ${"$"}GITHUB_API_URL" +echo "Event: ${h.gha_github("event_name")}" +echo "Actor: ${h.gha_github("actor")}" +echo "" +EXEC_FILE="${h.gha_steps("claude", "execution_file")}" +if [ -f "${"$"}EXEC_FILE" ]; then + echo "--- Execution file contents ---" + cat "${"$"}EXEC_FILE" +else + echo "No execution file found" +fi""" + } + ] + } + } +} + +# ============================================================================= +# Upstream Sync Check workflow (weekly check for upstream updates) +# ============================================================================= +_upstream_sync_workflow = { + name = "Upstream Sync Check" + on = { + schedule = [ + # Weekly Monday at 08:00 UTC + {cron = "0 8 * * 1"} + ] + workflow_dispatch = {} + } + jobs = { + "check-upstream" = { + name = "Check for upstream updates" + "runs-on" = "grunt" + steps = [ + checkout_mod.checkout() | {"with" = {"fetch-depth" = "0"}} + { + name = "Configure upstream remote" + run = """\ +if ! git remote get-url upstream &> /dev/null; then + git remote add upstream https://github.com/anthropics/claude-code-action.git +fi""" + } + { + name = "Fetch upstream" + run = "git fetch upstream main --quiet" + } + { + name = "Compare with upstream" + id = "compare" + run = """\ +BEHIND_COUNT=${"$"}(git rev-list main..upstream/main --count) +AHEAD_COUNT=${"$"}(git rev-list upstream/main..main --count) + +echo "behind=${"$"}BEHIND_COUNT" >> "${"$"}GITHUB_OUTPUT" +echo "ahead=${"$"}AHEAD_COUNT" >> "${"$"}GITHUB_OUTPUT" + +echo "Our main is ${"$"}BEHIND_COUNT commits behind and ${"$"}AHEAD_COUNT commits ahead of upstream" + +if [ "${"$"}BEHIND_COUNT" -gt 0 ]; then + echo "needs_sync=true" >> "${"$"}GITHUB_OUTPUT" + + echo "--- New upstream commits ---" + git log main..upstream/main --oneline | head -30 + + echo "" + echo "--- Files changed ---" + DIFF_STAT=${"$"}(git diff --stat main...upstream/main) + echo "${"$"}DIFF_STAT" + + { + echo "diff_stat<> "${"$"}GITHUB_OUTPUT" + + { + echo "commit_log<> "${"$"}GITHUB_OUTPUT" +else + echo "needs_sync=false" >> "${"$"}GITHUB_OUTPUT" + echo "Already up to date with upstream" +fi""" + } + { + name = "Check for existing sync issue" + "if" = h.gha_steps_eq("compare", "needs_sync", "true") + id = "existing" + run = """\ +RESPONSE=${"$"}(curl -s \\ + -H "Authorization: token ${h.gha_github("token")}" \\ + -H "Content-Type: application/json" \\ + "${h.gha_env("GITHUB_API_URL")}/repos/${h.gha_github("repository")}/issues?state=open&labels=upstream-sync&limit=1") + +ISSUE_COUNT=${"$"}(echo "${"$"}RESPONSE" | jq 'length') + +if [ "${"$"}ISSUE_COUNT" -gt 0 ]; then + ISSUE_NUMBER=${"$"}(echo "${"$"}RESPONSE" | jq '.[0].number') + echo "issue_exists=true" >> "${"$"}GITHUB_OUTPUT" + echo "issue_number=${"$"}ISSUE_NUMBER" >> "${"$"}GITHUB_OUTPUT" + echo "Found existing sync issue #${"$"}ISSUE_NUMBER" +else + echo "issue_exists=false" >> "${"$"}GITHUB_OUTPUT" + echo "No existing sync issue found" +fi""" + } + { + name = "Create upstream-sync label" + "if" = h.gha_steps_eq("compare", "needs_sync", "true") + run = """\ +curl -s -o /dev/null -w "%{http_code}" \\ + -X POST \\ + -H "Authorization: token ${h.gha_github("token")}" \\ + -H "Content-Type: application/json" \\ + -d '{"name":"upstream-sync","color":"#0075ca","description":"Upstream sync tracking"}' \\ + "${h.gha_env("GITHUB_API_URL")}/repos/${h.gha_github("repository")}/labels" || true""" + } + { + name = "Create or update sync issue" + "if" = h.gha_steps_eq("compare", "needs_sync", "true") + run = """\ +BEHIND="${h.gha_steps("compare", "behind")}" +AHEAD="${h.gha_steps("compare", "ahead")}" +COMMIT_LOG=${"$"}(cat <<'LOG_EOF' +${h.gha_steps("compare", "commit_log")} +LOG_EOF +) +DIFF_STAT=${"$"}(cat <<'DIFF_EOF' +${h.gha_steps("compare", "diff_stat")} +DIFF_EOF +) + +BODY=${"$"}(cat < /dev/null; then - INSTALLED=$(bun --version) - if [ "$INSTALLED" = "$BUN_VERSION" ]; then - echo "Bun $BUN_VERSION already installed" - else - echo "Bun $INSTALLED installed, need $BUN_VERSION — reinstalling" - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" - echo "$HOME/.bun/bin" >> "$GITHUB_PATH" - fi + - name: Checkout + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 + - name: Install Bun + run: |- + if command -v bun &> /dev/null; then + INSTALLED=$(bun --version) + if [ "$INSTALLED" = "$BUN_VERSION" ]; then + echo "Bun $BUN_VERSION already installed" else + echo "Bun $INSTALLED installed, need $BUN_VERSION — reinstalling" curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" echo "$HOME/.bun/bin" >> "$GITHUB_PATH" fi - - - name: Install dependencies - run: bun install - - - name: Run tests - # Unit tests assume GitHub context. Override Forgejo runner's - # GITHUB_SERVER_URL so module-level constants (config.ts) and - # detectPlatform() default to GitHub. Forgejo-specific tests - # explicitly set PLATFORM=forgejo in their own beforeEach. - # Inline export because Forgejo runner re-injects GITHUB_SERVER_URL - # after step-level env:, overriding it. - run: | - export GITHUB_SERVER_URL=https://github.com - bun test - + else + curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" + echo "$HOME/.bun/bin" >> "$GITHUB_PATH" + fi + - name: Install dependencies + run: bun install + - name: Run tests + run: |- + export GITHUB_SERVER_URL=https://github.com + bun test prettier: + name: Prettier runs-on: grunt steps: - - name: Checkout - uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 - with: - fetch-depth: 1 - - - name: Install Bun - run: | - if command -v bun &> /dev/null; then - INSTALLED=$(bun --version) - if [ "$INSTALLED" = "$BUN_VERSION" ]; then - echo "Bun $BUN_VERSION already installed" - else - echo "Bun $INSTALLED installed, need $BUN_VERSION — reinstalling" - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" - echo "$HOME/.bun/bin" >> "$GITHUB_PATH" - fi + - name: Checkout + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 + - name: Install Bun + run: |- + if command -v bun &> /dev/null; then + INSTALLED=$(bun --version) + if [ "$INSTALLED" = "$BUN_VERSION" ]; then + echo "Bun $BUN_VERSION already installed" else + echo "Bun $INSTALLED installed, need $BUN_VERSION — reinstalling" curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" echo "$HOME/.bun/bin" >> "$GITHUB_PATH" fi - - - name: Install dependencies - run: bun install - - - name: Run prettier check - run: bun run format:check - + else + curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" + echo "$HOME/.bun/bin" >> "$GITHUB_PATH" + fi + - name: Install dependencies + run: bun install + - name: Run prettier check + run: bun run format:check typecheck: + name: Type Check runs-on: grunt steps: - - name: Checkout - uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 - with: - fetch-depth: 1 - - - name: Install Bun - run: | - if command -v bun &> /dev/null; then - INSTALLED=$(bun --version) - if [ "$INSTALLED" = "$BUN_VERSION" ]; then - echo "Bun $BUN_VERSION already installed" - else - echo "Bun $INSTALLED installed, need $BUN_VERSION — reinstalling" - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" - echo "$HOME/.bun/bin" >> "$GITHUB_PATH" - fi + - name: Checkout + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 + - name: Install Bun + run: |- + if command -v bun &> /dev/null; then + INSTALLED=$(bun --version) + if [ "$INSTALLED" = "$BUN_VERSION" ]; then + echo "Bun $BUN_VERSION already installed" else + echo "Bun $INSTALLED installed, need $BUN_VERSION — reinstalling" curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" echo "$HOME/.bun/bin" >> "$GITHUB_PATH" fi - - - name: Install dependencies - run: bun install - - - name: Run TypeScript type check - run: bun run typecheck + else + curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" + echo "$HOME/.bun/bin" >> "$GITHUB_PATH" + fi + - name: Install dependencies + run: bun install + - name: Run TypeScript type check + run: bun run typecheck diff --git a/.forgejo/workflows/claude.yml b/.forgejo/workflows/claude.yml index 4429059..a233787 100644 --- a/.forgejo/workflows/claude.yml +++ b/.forgejo/workflows/claude.yml @@ -1,86 +1,56 @@ -name: Claude Code (Forgejo) +# Generated by KCL v1.34.3 - DO NOT EDIT +# Source: .forgejo/kcl-ci/main.k +# kcl-ci local: ~/stuff/gitrepos/wxs/actions/kcl-ci +# kcl-ci remote: https://git.wxs.ro/actions/kcl-ci -on: +name: Claude Code +'on': issue_comment: - types: [created] + types: + - created pull_request_review_comment: - types: [created] + types: + - created issues: - types: [opened, assigned, labeled] - # Forgejo never fires pull_request_review — it sends pull_request_comment - # for both review comments AND full reviews. Kept for GitHub compatibility. + types: + - opened + - assigned + - labeled pull_request_review: - types: [submitted] - + types: + - submitted concurrency: - group: claude-${{ github.event.issue.number || github.event.pull_request.number }}-${{ github.run_id }} - + group: claude-${{ github.event.issue.number || github.event.pull_request.number }} jobs: claude: - # Forgejo workflow if: limitations (expression parser rejects '#' comments): - # - pull_request_review_comment / pull_request_comment: Forgejo collapses both - # review comments and full reviews into pull_request_comment. Cannot gate on - # @claude here — payload fields are empty (run 12084). Check in trigger.ts. - # - pull_request_review: Only fires on GitHub, dead code on Forgejo. - # - issues: Forgejo does not populate github.event.action (run 12061 skipped - # despite action being 'labeled'). Let all through; trigger.ts validates. - if: >- - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - # Forgejo collapses both pull_request_review_comment AND pull_request_review - # into pull_request_comment. We cannot gate on @claude here because Forgejo's - # webhook payload does not populate github.event.comment.body or - # github.event.review.body for these events (tested: run 12084 was skipped). - # The @claude check is handled in trigger.ts which reads the actual payload. - (github.event_name == 'pull_request_review_comment' || github.event_name == 'pull_request_comment') || - # This condition only fires on GitHub (Forgejo never sends pull_request_review). - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues') + if: ((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues') runs-on: grunt steps: - - name: Checkout - uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: ./ - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - github_token: ${{ secrets.CLAUDE_BOT_TOKEN }} - bot_id: "48" - bot_name: "claude" - 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: | - echo "=== Debug Info ===" - echo "Platform: PLATFORM=$PLATFORM FORGEJO=$FORGEJO" - echo "Server URL: $GITHUB_SERVER_URL" - echo "API URL: $GITHUB_API_URL" - echo "Event: ${{ github.event_name }} / ${{ github.event.action }}" - echo "Actor: ${{ github.actor }}" - echo "" - echo "=== Event Payload ===" - echo "comment.body: ${{ github.event.comment.body }}" - echo "comment.id: ${{ github.event.comment.id }}" - echo "review.body: ${{ github.event.review.body }}" - echo "review.id: ${{ github.event.review.id }}" - echo "" - if [ -f "$GITHUB_EVENT_PATH" ]; then - echo "--- GITHUB_EVENT_PATH ---" - cat "$GITHUB_EVENT_PATH" - fi - echo "" - EXEC_FILE="${{ steps.claude.outputs.execution_file }}" - if [ -f "$EXEC_FILE" ]; then - echo "--- Execution file contents ---" - cat "$EXEC_FILE" - else - echo "No execution file found" - fi + - name: Checkout + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 + - name: Run Claude Code + id: claude + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/actions/claude-code-action@main + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github_token: ${{ secrets.CLAUDE_BOT_TOKEN }} + bot_id: '48' + bot_name: claude + 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: | + echo "=== Debug Info ===" + echo "Event: ${{ github.event_name }} / ${{ github.event.action }}" + echo "Actor: ${{ github.actor }}" + EXEC_FILE="${{ steps.claude.outputs.execution_file }}" + if [ -f "$EXEC_FILE" ]; then + echo "--- Execution file ---" + cat "$EXEC_FILE" + else + echo "No execution file" + fi diff --git a/.forgejo/workflows/kcl-ci-build.yml b/.forgejo/workflows/kcl-ci-build.yml new file mode 100644 index 0000000..0cb3462 --- /dev/null +++ b/.forgejo/workflows/kcl-ci-build.yml @@ -0,0 +1,47 @@ +# Generated by KCL v1.34.3 - DO NOT EDIT +# Source: .forgejo/kcl-ci/main.k +# kcl-ci local: ~/stuff/gitrepos/wxs/actions/kcl-ci +# kcl-ci remote: https://git.wxs.ro/actions/kcl-ci + +name: KCL CI Build +'on': + push: + branches: + - main + paths: + - '.forgejo/kcl-ci/**' + - '.forgejo/workflows/kcl-ci-build.yml' + workflow_dispatch: {} +concurrency: + group: kcl-ci-${{ github.ref }} + cancel-in-progress: true +jobs: + wait-for-tests: + name: Wait for Tests + runs-on: grunt + if: github.event_name == 'push' + steps: + - name: Wait for test checks to complete + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/actions/wait-for-checks@v2 + with: + token: ${{ github.token }} + timeout: '300' + checks: |- + CI / Tests (push), + CI / Prettier (push), + CI / Type Check (push) + build: + name: Build KCL Workflows + runs-on: grunt + needs: + - wait-for-tests + if: always() && (needs.wait-for-tests.result == 'success' || needs.wait-for-tests.result == 'skipped') + steps: + - name: Build KCL workflows + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/actions/kcl-ci@v1.34.3 + with: + read-repo-token: ${{ secrets.READ_REPO_TOKEN }} + working-dir: '.forgejo/kcl-ci' + commit-changes: 'true' + file-pattern: '.forgejo/workflows/*.yml' + commit-message: 'chore: regenerate workflows from KCL [skip ci]' diff --git a/.forgejo/workflows/test-action.yml b/.forgejo/workflows/test-action.yml index cd444d5..85506dc 100644 --- a/.forgejo/workflows/test-action.yml +++ b/.forgejo/workflows/test-action.yml @@ -1,80 +1,75 @@ -name: Test Action (Forgejo) +# Generated by KCL v1.34.3 - DO NOT EDIT +# Source: .forgejo/kcl-ci/main.k +# kcl-ci local: ~/stuff/gitrepos/wxs/actions/kcl-ci +# kcl-ci remote: https://git.wxs.ro/actions/kcl-ci -on: +name: Test Action (Forgejo) +'on': schedule: - # Weekly Monday at 06:00 UTC - - cron: "0 6 * * 1" + - cron: '0 6 * * 1' pull_request: branches: - - main + - main paths: - - "src/**" - - "action.yml" - - ".forgejo/**" - - "base-action/**" - workflow_dispatch: - + - src/** + - action.yml + - '.forgejo/**' + - base-action/** + workflow_dispatch: {} concurrency: group: test-action-${{ github.ref }} cancel-in-progress: true - jobs: test-agent-mode: name: Test Agent Mode on Forgejo runs-on: grunt steps: - - name: Checkout - uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 - with: - fetch-depth: 1 + - name: Checkout + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 + - name: Run Claude Code Action (agent mode) + id: claude + uses: './' + with: + prompt: Respond with exactly the text FORGEJO_PLATFORM_TEST_OK and nothing else. Do not use any tools. + 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 }}" + if [ -z "$EXEC_FILE" ]; then + echo "FAIL: execution_file output is empty" + exit 1 + fi + if [ ! -f "$EXEC_FILE" ]; then + echo "FAIL: execution file does not exist at: $EXEC_FILE" + exit 1 + fi + echo "OK: execution file exists at: $EXEC_FILE" - - name: Run Claude Code Action (agent mode) - id: claude - uses: ./ - with: - prompt: "Respond with exactly the text FORGEJO_PLATFORM_TEST_OK and nothing else. Do not use any tools." - 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 }}" - if [ -z "$EXEC_FILE" ]; then - echo "FAIL: execution_file output is empty" - exit 1 - fi - if [ ! -f "$EXEC_FILE" ]; then - echo "FAIL: execution file does not exist at: $EXEC_FILE" - exit 1 - fi - echo "OK: execution file exists at: $EXEC_FILE" - - # Check that the output contains our test marker - if grep -q "FORGEJO_PLATFORM_TEST_OK" "$EXEC_FILE"; then - echo "OK: Output contains expected test marker" - else - echo "WARN: Test marker not found in output (Claude may have formatted differently)" - echo "--- Output contents (first 50 lines) ---" - head -50 "$EXEC_FILE" - fi - - - name: Debug on failure - if: failure() - run: | - echo "=== Debug Info ===" - echo "Platform env: PLATFORM=$PLATFORM FORGEJO=$FORGEJO" - echo "Server URL: $GITHUB_SERVER_URL" - echo "API URL: $GITHUB_API_URL" - echo "Event: ${{ github.event_name }}" - echo "Actor: ${{ github.actor }}" - echo "" - EXEC_FILE="${{ steps.claude.outputs.execution_file }}" - if [ -f "$EXEC_FILE" ]; then - echo "--- Execution file contents ---" - cat "$EXEC_FILE" - else - echo "No execution file found" - fi + if grep -q "FORGEJO_PLATFORM_TEST_OK" "$EXEC_FILE"; then + echo "OK: Output contains expected test marker" + else + echo "WARN: Test marker not found in output (Claude may have formatted differently)" + echo "--- Output contents (first 50 lines) ---" + head -50 "$EXEC_FILE" + fi + - name: Debug on failure + if: failure() + run: |- + echo "=== Debug Info ===" + echo "Platform env: PLATFORM=$PLATFORM FORGEJO=$FORGEJO" + echo "Server URL: $GITHUB_SERVER_URL" + echo "API URL: $GITHUB_API_URL" + echo "Event: ${{ github.event_name }}" + echo "Actor: ${{ github.actor }}" + echo "" + EXEC_FILE="${{ steps.claude.outputs.execution_file }}" + if [ -f "$EXEC_FILE" ]; then + echo "--- Execution file contents ---" + cat "$EXEC_FILE" + else + echo "No execution file found" + fi diff --git a/.forgejo/workflows/upstream-sync.yml b/.forgejo/workflows/upstream-sync.yml index 2015826..d9d0fe3 100644 --- a/.forgejo/workflows/upstream-sync.yml +++ b/.forgejo/workflows/upstream-sync.yml @@ -1,176 +1,166 @@ +# Generated by KCL v1.34.3 - DO NOT EDIT +# Source: .forgejo/kcl-ci/main.k +# kcl-ci local: ~/stuff/gitrepos/wxs/actions/kcl-ci +# kcl-ci remote: https://git.wxs.ro/actions/kcl-ci + name: Upstream Sync Check - -on: +'on': schedule: - # Weekly Monday at 08:00 UTC - - cron: "0 8 * * 1" - workflow_dispatch: - + - cron: '0 8 * * 1' + workflow_dispatch: {} jobs: check-upstream: name: Check for upstream updates runs-on: grunt steps: - - name: Checkout - uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 - with: - fetch-depth: 0 + - name: Checkout + uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2 + with: + fetch-depth: '0' + - name: Configure upstream remote + run: |- + if ! git remote get-url upstream &> /dev/null; then + git remote add upstream https://github.com/anthropics/claude-code-action.git + fi + - name: Fetch upstream + run: git fetch upstream main --quiet + - name: Compare with upstream + id: compare + run: |- + BEHIND_COUNT=$(git rev-list main..upstream/main --count) + AHEAD_COUNT=$(git rev-list upstream/main..main --count) - - name: Configure upstream remote - run: | - if ! git remote get-url upstream &> /dev/null; then - git remote add upstream https://github.com/anthropics/claude-code-action.git - fi + echo "behind=$BEHIND_COUNT" >> "$GITHUB_OUTPUT" + echo "ahead=$AHEAD_COUNT" >> "$GITHUB_OUTPUT" - - name: Fetch upstream - run: git fetch upstream main --quiet + echo "Our main is $BEHIND_COUNT commits behind and $AHEAD_COUNT commits ahead of upstream" - - name: Compare with upstream - id: compare - run: | - BEHIND_COUNT=$(git rev-list main..upstream/main --count) - AHEAD_COUNT=$(git rev-list upstream/main..main --count) + if [ "$BEHIND_COUNT" -gt 0 ]; then + echo "needs_sync=true" >> "$GITHUB_OUTPUT" - echo "behind=$BEHIND_COUNT" >> "$GITHUB_OUTPUT" - echo "ahead=$AHEAD_COUNT" >> "$GITHUB_OUTPUT" + echo "--- New upstream commits ---" + git log main..upstream/main --oneline | head -30 - echo "Our main is $BEHIND_COUNT commits behind and $AHEAD_COUNT commits ahead of upstream" + echo "" + echo "--- Files changed ---" + DIFF_STAT=$(git diff --stat main...upstream/main) + echo "$DIFF_STAT" - if [ "$BEHIND_COUNT" -gt 0 ]; then - echo "needs_sync=true" >> "$GITHUB_OUTPUT" - - # Generate diff summary - echo "--- New upstream commits ---" - git log main..upstream/main --oneline | head -30 - - echo "" - echo "--- Files changed ---" - DIFF_STAT=$(git diff --stat main...upstream/main) + { + echo "diff_stat<> "$GITHUB_OUTPUT" - # Save for issue body (escape newlines for multiline output) - { - echo "diff_stat<> "$GITHUB_OUTPUT" + { + echo "commit_log<> "$GITHUB_OUTPUT" + else + echo "needs_sync=false" >> "$GITHUB_OUTPUT" + echo "Already up to date with upstream" + fi + - name: Check for existing sync issue + if: steps.compare.outputs.needs_sync == 'true' + id: existing + run: |- + RESPONSE=$(curl -s \ + -H "Authorization: token ${{ github.token }}" \ + -H "Content-Type: application/json" \ + "${{ env.GITHUB_API_URL }}/repos/${{ github.repository }}/issues?state=open&labels=upstream-sync&limit=1") - { - echo "commit_log<> "$GITHUB_OUTPUT" - else - echo "needs_sync=false" >> "$GITHUB_OUTPUT" - echo "Already up to date with upstream" - fi + ISSUE_COUNT=$(echo "$RESPONSE" | jq 'length') - - name: Check for existing sync issue - if: steps.compare.outputs.needs_sync == 'true' - id: existing - run: | - # Search for open issues with the upstream-sync label - RESPONSE=$(curl -s \ + if [ "$ISSUE_COUNT" -gt 0 ]; then + ISSUE_NUMBER=$(echo "$RESPONSE" | jq '.[0].number') + echo "issue_exists=true" >> "$GITHUB_OUTPUT" + echo "issue_number=$ISSUE_NUMBER" >> "$GITHUB_OUTPUT" + echo "Found existing sync issue #$ISSUE_NUMBER" + else + echo "issue_exists=false" >> "$GITHUB_OUTPUT" + echo "No existing sync issue found" + fi + - name: Create upstream-sync label + if: steps.compare.outputs.needs_sync == 'true' + run: |- + curl -s -o /dev/null -w "%{http_code}" \ + -X POST \ + -H "Authorization: token ${{ github.token }}" \ + -H "Content-Type: application/json" \ + -d '{"name":"upstream-sync","color":"#0075ca","description":"Upstream sync tracking"}' \ + "${{ env.GITHUB_API_URL }}/repos/${{ github.repository }}/labels" || true + - name: Create or update sync issue + if: steps.compare.outputs.needs_sync == 'true' + run: |- + BEHIND="${{ steps.compare.outputs.behind }}" + AHEAD="${{ steps.compare.outputs.ahead }}" + COMMIT_LOG=$(cat <<'LOG_EOF' + ${{ steps.compare.outputs.commit_log }} + LOG_EOF + ) + DIFF_STAT=$(cat <<'DIFF_EOF' + ${{ steps.compare.outputs.diff_stat }} + DIFF_EOF + ) + + BODY=$(cat <> "$GITHUB_OUTPUT" - echo "issue_number=$ISSUE_NUMBER" >> "$GITHUB_OUTPUT" - echo "Found existing sync issue #$ISSUE_NUMBER" - else - echo "issue_exists=false" >> "$GITHUB_OUTPUT" - echo "No existing sync issue found" - fi - - - name: Create upstream-sync label - if: steps.compare.outputs.needs_sync == 'true' - run: | - # Create label if it doesn't exist (ignore 409 conflict) - curl -s -o /dev/null -w "%{http_code}" \ + -d "$(jq -n --arg title "$TITLE" --arg body "$BODY" '{title: $title, body: $body}')" \ + "${{ env.GITHUB_API_URL }}/repos/${{ github.repository }}/issues/${ISSUE_NUMBER}" + echo "Updated existing issue #${ISSUE_NUMBER}" + else + curl -s -o /dev/null \ -X POST \ -H "Authorization: token ${{ github.token }}" \ -H "Content-Type: application/json" \ - -d '{"name":"upstream-sync","color":"#0075ca","description":"Upstream sync tracking"}' \ - "${GITHUB_API_URL}/repos/${{ github.repository }}/labels" || true - - - name: Create or update sync issue - if: steps.compare.outputs.needs_sync == 'true' - run: | - BEHIND="${{ steps.compare.outputs.behind }}" - AHEAD="${{ steps.compare.outputs.ahead }}" - COMMIT_LOG=$(cat <<'LOG_EOF' - ${{ steps.compare.outputs.commit_log }} - LOG_EOF - ) - DIFF_STAT=$(cat <<'DIFF_EOF' - ${{ steps.compare.outputs.diff_stat }} - DIFF_EOF - ) - - BODY=$(cat <