diff --git a/.github/workflows/weekly-update.yml b/.github/workflows/weekly-update.yml index e6ee6fe0..275b5983 100644 --- a/.github/workflows/weekly-update.yml +++ b/.github/workflows/weekly-update.yml @@ -65,13 +65,20 @@ jobs: with: gpg-private-key: ${{ secrets.BOT_GPG_PRIVATE_KEY }} - - name: Run updating skill with Claude Code - id: claude - timeout-minutes: 30 + - name: Update dependencies (haiku) + id: update + timeout-minutes: 10 env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} GITHUB_ACTIONS: 'true' run: | + if [ -n "$SFW_BIN" ]; then + mkdir -p /tmp/sfw-bin + printf '#!/bin/bash\nexec "%s" pnpm "$@"\n' "$SFW_BIN" > /tmp/sfw-bin/pnpm + chmod +x /tmp/sfw-bin/pnpm + export PATH="/tmp/sfw-bin:$PATH" + fi + if [ -z "$ANTHROPIC_API_KEY" ]; then echo "ANTHROPIC_API_KEY not set - skipping automated update" echo "success=false" >> $GITHUB_OUTPUT @@ -79,8 +86,10 @@ jobs: fi set +e - pnpm exec claude --print --dangerously-skip-permissions \ - --model sonnet \ + pnpm exec claude --print \ + --allowedTools "Bash(pnpm:*)" "Bash(git add:*)" "Bash(git commit:*)" "Bash(git status:*)" "Bash(git diff:*)" "Bash(git log:*)" "Bash(git rev-parse:*)" "Read" "Write" "Edit" "Glob" "Grep" \ + --model haiku \ + --max-turns 15 \ "$(cat <<'PROMPT' /updating @@ -103,7 +112,7 @@ jobs: PROMPT )" \ - 2>&1 | tee claude-output.log + 2>&1 | tee claude-update.log CLAUDE_EXIT=${PIPESTATUS[0]} set -e @@ -113,6 +122,130 @@ jobs: echo "success=false" >> $GITHUB_OUTPUT fi + - name: Run tests + id: tests + if: steps.update.outputs.success == 'true' + continue-on-error: true + run: | + if [ -n "$SFW_BIN" ]; then + mkdir -p /tmp/sfw-bin + printf '#!/bin/bash\nexec "%s" pnpm "$@"\n' "$SFW_BIN" > /tmp/sfw-bin/pnpm + chmod +x /tmp/sfw-bin/pnpm + export PATH="/tmp/sfw-bin:$PATH" + fi + + set +e + pnpm build 2>&1 | tee build-output.log + BUILD_EXIT=${PIPESTATUS[0]} + + pnpm test 2>&1 | tee test-output.log + TEST_EXIT=${PIPESTATUS[0]} + set -e + + if [ "$BUILD_EXIT" -eq 0 ] && [ "$TEST_EXIT" -eq 0 ]; then + echo "result=pass" >> $GITHUB_OUTPUT + else + echo "result=fail" >> $GITHUB_OUTPUT + fi + + - name: Fix test failures (sonnet) + id: claude + if: steps.tests.outputs.result == 'fail' + timeout-minutes: 15 + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + GITHUB_ACTIONS: 'true' + run: | + if [ -n "$SFW_BIN" ]; then + mkdir -p /tmp/sfw-bin + printf '#!/bin/bash\nexec "%s" pnpm "$@"\n' "$SFW_BIN" > /tmp/sfw-bin/pnpm + chmod +x /tmp/sfw-bin/pnpm + export PATH="/tmp/sfw-bin:$PATH" + fi + + BUILD_LOG=$(cat build-output.log 2>/dev/null || echo "No build output") + TEST_LOG=$(cat test-output.log 2>/dev/null || echo "No test output") + + set +e + pnpm exec claude --print \ + --allowedTools "Bash(pnpm:*)" "Bash(git add:*)" "Bash(git commit:*)" "Bash(git status:*)" "Bash(git diff:*)" "Bash(git log:*)" "Bash(git rev-parse:*)" "Read" "Write" "Edit" "Glob" "Grep" \ + --model sonnet \ + --max-turns 25 \ + "$(cat < + You are an automated CI agent fixing test failures after a dependency update. + Git is configured with GPG signing. Dependencies were already updated and committed. + + + + BUILD OUTPUT: + ${BUILD_LOG} + + TEST OUTPUT: + ${TEST_LOG} + + + + The dependency updates above caused build or test failures. + Analyze the failure logs, identify root causes, and fix them. + Create one atomic commit per fix with a conventional commit message. + Leave all changes local — the workflow handles pushing and PR creation. + + + + All build and test failures are resolved. + Each fix has its own commit. + No uncommitted changes remain in the working tree. + + PROMPT + )" \ + 2>&1 | tee claude-fix.log + CLAUDE_EXIT=${PIPESTATUS[0]} + set -e + + if [ "$CLAUDE_EXIT" -eq 0 ]; then + echo "success=true" >> $GITHUB_OUTPUT + else + echo "success=false" >> $GITHUB_OUTPUT + fi + + - name: Set final status + id: final + if: always() + env: + UPDATE_SUCCESS: ${{ steps.update.outputs.success }} + TESTS_RESULT: ${{ steps.tests.outputs.result }} + FIX_SUCCESS: ${{ steps.claude.outputs.success }} + run: | + if [ "$UPDATE_SUCCESS" != "true" ]; then + echo "success=false" >> $GITHUB_OUTPUT + elif [ "$TESTS_RESULT" != "fail" ]; then + echo "success=true" >> $GITHUB_OUTPUT + elif [ "$FIX_SUCCESS" = "true" ]; then + echo "success=true" >> $GITHUB_OUTPUT + else + echo "success=false" >> $GITHUB_OUTPUT + fi + + - name: Validate changes + id: validate + if: steps.final.outputs.success == 'true' + run: | + UNEXPECTED="" + for file in $(git diff --name-only origin/main..HEAD); do + case "$file" in + package.json|*/package.json|pnpm-lock.yaml|*/pnpm-lock.yaml|.npmrc|pnpm-workspace.yaml) ;; + src/*|test/*) ;; + *) UNEXPECTED="$UNEXPECTED $file" ;; + esac + done + if [ -n "$UNEXPECTED" ]; then + echo "::error::Unexpected files modified by Claude:$UNEXPECTED" + echo "valid=false" >> $GITHUB_OUTPUT + else + echo "valid=true" >> $GITHUB_OUTPUT + fi + - name: Check for changes id: changes run: | @@ -123,13 +256,13 @@ jobs: fi - name: Push branch - if: steps.claude.outputs.success == 'true' && steps.changes.outputs.has-changes == 'true' + if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true' env: BRANCH_NAME: ${{ steps.branch.outputs.branch }} run: git push origin "$BRANCH_NAME" - name: Create Pull Request - if: steps.claude.outputs.success == 'true' && steps.changes.outputs.has-changes == 'true' + if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true' env: GH_TOKEN: ${{ github.token }} BRANCH_NAME: ${{ steps.branch.outputs.branch }} @@ -158,7 +291,7 @@ jobs: --base main - name: Add job summary - if: steps.claude.outputs.success == 'true' && steps.changes.outputs.has-changes == 'true' + if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true' env: BRANCH_NAME: ${{ steps.branch.outputs.branch }} run: | @@ -173,7 +306,11 @@ jobs: uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: claude-output-${{ github.run_id }} - path: claude-output.log + path: | + claude-update.log + claude-fix.log + build-output.log + test-output.log retention-days: 7 - uses: SocketDev/socket-registry/.github/actions/cleanup-git-signing@6096b06b1790f411714c89c40f72aade2eeaab7c # main diff --git a/package.json b/package.json index 4e99eb62..0442a885 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "clean": "node scripts/clean.mjs", "cover": "node scripts/cover.mjs", "fix": "node scripts/lint.mjs --fix", - "format": "oxfmt .", + "format": "oxfmt --write .", "format:check": "oxfmt --check .", "generate-sdk": "node scripts/generate-sdk.mjs", "lint": "node scripts/lint.mjs", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index eeeb8b3d..f8c05760 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,2 @@ -settings: - # Wait 7 days (10080 minutes) before installing newly published packages. - minimumReleaseAge: 10080 +# Wait 7 days (10080 minutes) before installing newly published packages. +minimumReleaseAge: 10080