Files
claude-code-action/.forgejo/kcl-ci/main.k
T
Vlad MocanuandClaude Opus 4.6 5c014823e0 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 <noreply@anthropic.com>
2026-02-27 11:40:47 +02:00

424 lines
14 KiB
Plaintext

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<<DIFF_EOF"
echo "${"$"}DIFF_STAT"
echo "DIFF_EOF"
} >> "${"$"}GITHUB_OUTPUT"
{
echo "commit_log<<LOG_EOF"
git log main..upstream/main --oneline | head -30
echo "LOG_EOF"
} >> "${"$"}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 <<BODY_EOF
## Upstream Sync Required
Our \`main\` branch is **${"$"}{BEHIND} commits behind** upstream (\`anthropics/claude-code-action\`).
We are **${"$"}{AHEAD} commits ahead** (our Forgejo-specific changes).
### New upstream commits (latest ${"$"}{BEHIND}, max 30 shown)
\`\`\`
${"$"}{COMMIT_LOG}
\`\`\`
### Files changed
\`\`\`
${"$"}{DIFF_STAT}
\`\`\`
### How to merge
\`\`\`bash
git fetch upstream main
git merge upstream/main
# Resolve any conflicts in the 3-4 modified files (run.ts, tag/index.ts, etc.)
bun test
git push origin main
\`\`\`
### Conflict-prone files
These files have Forgejo modifications and may conflict:
- \`src/entrypoints/run.ts\`
- \`src/modes/tag/index.ts\`
- \`src/mcp/install-mcp-server.ts\`
- \`action.yml\`
_Auto-generated by upstream-sync workflow on ${"$"}(date -u +%Y-%m-%d)_
BODY_EOF
)
TITLE="Upstream sync: ${"$"}{BEHIND} new commits from anthropics/claude-code-action"
if [ "${h.gha_steps("existing", "issue_exists")}" = "true" ]; then
ISSUE_NUMBER="${h.gha_steps("existing", "issue_number")}"
curl -s -o /dev/null \\
-X PATCH \\
-H "Authorization: token ${h.gha_github("token")}" \\
-H "Content-Type: application/json" \\
-d "${"$"}(jq -n --arg title "${"$"}TITLE" --arg body "${"$"}BODY" '{title: ${"$"}title, body: ${"$"}body}')" \\
"${h.gha_env("GITHUB_API_URL")}/repos/${h.gha_github("repository")}/issues/${"$"}{ISSUE_NUMBER}"
echo "Updated existing issue #${"$"}{ISSUE_NUMBER}"
else
curl -s -o /dev/null \\
-X POST \\
-H "Authorization: token ${h.gha_github("token")}" \\
-H "Content-Type: application/json" \\
-d "${"$"}(jq -n --arg title "${"$"}TITLE" --arg body "${"$"}BODY" '{title: ${"$"}title, body: ${"$"}body, labels: [{"name": "upstream-sync"}]}')" \\
"${h.gha_env("GITHUB_API_URL")}/repos/${h.gha_github("repository")}/issues"
echo "Created new upstream sync issue"
fi"""
}
]
}
}
}
# =============================================================================
# Write workflows
# =============================================================================
h.dump_workflow(_claude_workflow, "../workflows/claude.yml")
h.dump_workflow(_kcl_build_workflow, "../workflows/kcl-ci-build.yml")
h.dump_workflow(_ci_workflow, "../workflows/ci.yml")
h.dump_workflow(_test_action_workflow, "../workflows/test-action.yml")
h.dump_workflow(_upstream_sync_workflow, "../workflows/upstream-sync.yml")