1- # .github/workflows/claude-code-review.yml
2- # Non-blocking strict review: comments on the PR using gh CLI; never fails the job.
3- name : Claude code review (strict, non-blocking)
1+ name : Claude code review (non-blocking)
42
53on :
64 pull_request :
7- types : [opened, synchronize, reopened, ready_for_review]
5+ types : [ opened, synchronize, reopened, ready_for_review ]
6+ paths :
7+ - ' **/*.scala'
8+ - ' **/*.sbt'
9+ - ' .scalafix.conf'
10+ - ' .scalafmt.conf'
11+ - ' project/**'
12+ - ' .github/workflows/**'
13+ - ' AGENTS.md'
14+ - ' docs/adr/**'
15+ - ' docs/plan/**'
16+
17+ permissions :
18+ contents : read
19+ pull-requests : write
20+ issues : write
21+ actions : read
22+ id-token : write # Required for OIDC token generation
823
924concurrency :
1025 group : claude-review-${{ github.event.pull_request.number }}
@@ -13,80 +28,119 @@ concurrency:
1328jobs :
1429 claude-review :
1530 runs-on : ubuntu-latest
16- permissions :
17- contents : read
18- pull-requests : write # needed for gh pr comment
19- id-token : write
31+ continue-on-error : true
2032
2133 steps :
2234 - name : Checkout repository
2335 uses : actions/checkout@v4
2436 with :
2537 fetch-depth : 0
2638
27- - name : Load CLAUDE.md rules
28- id : rules
39+ - name : Get PR diff summary
40+ id : diff
41+ run : |
42+ echo 'patch<<EOF' >> $GITHUB_OUTPUT
43+ git fetch origin ${{ github.event.pull_request.base.ref }}
44+ git --no-pager diff --unified=0 --minimal --patch origin/${{ github.event.pull_request.base.ref }}...HEAD | sed -n '1,6000p'
45+ echo 'EOF' >> $GITHUB_OUTPUT
46+
47+ - name : Generate FlowForge review context
48+ id : ctx
2949 shell : bash
3050 run : |
31- echo 'guidelines <<EOF' >> " $GITHUB_OUTPUT"
32- sed -n '1,6000p' CLAUDE.md >> "$GITHUB_OUTPUT"
33- echo 'EOF' >> " $GITHUB_OUTPUT"
51+ echo 'context <<EOF' >> $GITHUB_OUTPUT
52+ bash scripts/ff-claude-context.sh | sed -n '1,4000p'
53+ echo 'EOF' >> $GITHUB_OUTPUT
3454
35- - name : Run claude (repo-wide checklist, then publish via gh CLI)
55+ - name : Run claude code action (flowforge review)
56+ if : true
3657 id : claude
3758 uses : anthropics/claude-code-action@v1
3859 with :
3960 claude_code_oauth_token : ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
4061 prompt : |
41- You are a STRICT reviewer enforcing every rule in CLAUDE.md across the repository.
62+ You are the FlowForge strict code reviewer. Review the DIFF and
63+ produce a concise list of findings with severity labels (HIGH/MED/LOW),
64+ followed by minimal patch suggestions. Use this FlowForge checklist:
4265
43- Scope:
44- - Enumerate:
45- git ls-files '*.scala' '*.sbt' '.scalafmt.conf' 'build.sbt' 'project/*.sbt' 'project/*.scala' 'project/build.properties'
46- - Apply ALL rules to each file (not just diffs).
66+ 1) ADR‑022 Safety & Errors
67+ - No try/catch/Try in main sources; use Safety/EffectSystem.
68+ - Result/ValidatedResult naming; ErrorMapper mapping present.
69+ - bracket/guarantee for resource cleanup; no manual finally.
70+ 2) Idiomatic Scala (docs/plan/refactor-idiomatic-scala2.md)
71+ - Pure transforms; effectful IO via EffectSystem.
72+ - No concrete IO in main modules; avoid println; no null.
73+ - No asInstanceOf/Any; exhaustive matches; for‑comprehensions.
74+ 3) Spark/Delta best practices
75+ - Avoid collect on large data; prefer Dataset/DataFrame ops.
76+ - Use F.blocking for IO; avoid driver‑side loops; partitioning sane.
77+ - Delta constraints: only NOT NULL and CHECK; no UNIQUE claims.
78+ 4) CI/lint conformance
79+ - Scalafmt/scalafix friendly; no wildcard imports; organized imports.
4780
4881 Output:
49- - One markdown report grouped by file.
50- - For each rule: ✅ PASS or ❌ FAIL with minimal patch/diff.
51- - A short "Top 10 Fixes" section.
52- - Final line MUST be:
53- CLAUDE_RULES_STATUS: PASS # or FAIL
54-
55- PUBLISHING (MANDATORY):
56- - Use the PR number from $PR_NUMBER.
57- - Post your full markdown report as a single PR comment with one Bash command:
58- gh pr comment "$PR_NUMBER" --body-file - <<'MD'
59- <PASTE YOUR FULL MARKDOWN REPORT HERE>
60- MD
61-
62- Rules to enforce:
82+ - Findings: bullet list with (SEVERITY) and 1–2 line rationale.
83+ - Quick patches: fenced unified diffs with minimal edits (<= ~30 lines each).
84+ - Module plan: for modules touched in DIFF, list 2–4 next actions (e.g., replace Try with Safety, add bracket for streams, remove collect in hot path, swap println→logger).
85+ - Cross‑ref ADR names (e.g., ADR‑022) and docs (e.g., refactor-idiomatic-scala2.md) by name.
86+
87+ DIFF TO REVIEW:
6388 ---
64- ${{ steps.rules .outputs.guidelines }}
89+ ${{ steps.diff .outputs.patch }}
6590 ---
6691
92+ CONTEXT (repository map, pattern scans, ADRs heads):
93+ ---
94+ ${{ steps.ctx.outputs.context }}
95+ ---
6796 claude_args : >-
68- --allowedTools
69- "Read"
70- "Edit"
71- "MultiEdit"
72- "Bash(git ls-files:*)"
73- "Bash(rg:*)"
74- "Bash(find:*)"
75- "Bash(sed:*)"
76- "Bash(xargs:*)"
77- "Bash(gh pr view:*)"
78- "Bash(gh pr comment:*)"
79- --max-turns 10
97+ --allowedTools "Read" "Bash(rg:*)"
98+ --max-turns 4
8099 --verbose
81100
82- env :
83- GH_TOKEN : ${{ github.token }} # gh CLI auth
84- PR_NUMBER : ${{ github.event.pull_request.number }}
85-
86- - name : Soft gate summary (never fail)
87- if : always()
101+ - name : Post review (inline when available)
102+ if : steps.claude.outputs.response != ''
88103 env :
89104 GH_TOKEN : ${{ github.token }}
90105 run : |
91- echo "### Claude strict review" >> "$GITHUB_STEP_SUMMARY"
92- echo "Report should be posted via gh pr comment on PR #${{ github.event.pull_request.number }}." >> "$GITHUB_STEP_SUMMARY"
106+ echo "Preparing review body and inline comments"
107+ RESP_FILE=$(mktemp)
108+ echo "${{ steps.claude.outputs.response }}" > "$RESP_FILE"
109+
110+ REVIEW_EVENT=COMMENT
111+ if grep -q "(HIGH)" "$RESP_FILE"; then REVIEW_EVENT=REQUEST_CHANGES; fi
112+
113+ # Parse simple inline comments of the form: INLINE: path:line: message
114+ INLINE_FILE=$(mktemp)
115+ awk '/^INLINE: /{sub(/^INLINE: /, ""); print}' "$RESP_FILE" > "$INLINE_FILE"
116+
117+ if [ -s "$INLINE_FILE" ]; then
118+ echo "Found inline comments; creating review via GitHub Reviews API"
119+ COMMENTS_JSON=$(mktemp)
120+ PR_SHA="${{ github.event.pull_request.head.sha }}"
121+ echo '[' > "$COMMENTS_JSON"
122+ FIRST=1
123+ while IFS= read -r line; do
124+ path=$(echo "$line" | awk -F: '{print $1}')
125+ lineno=$(echo "$line" | awk -F: '{print $2}')
126+ msg=$(echo "$line" | cut -d: -f3- | sed 's/^ //')
127+ [ -z "$path" ] && continue
128+ [ -z "$lineno" ] && lineno=1
129+ if [ $FIRST -eq 0 ]; then echo ',' >> "$COMMENTS_JSON"; fi
130+ FIRST=0
131+ printf '{"path":"%s","line":%s,"side":"RIGHT","body":%s}' \
132+ "$path" "$lineno" "$(printf '%s' "$msg" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')" >> "$COMMENTS_JSON"
133+ done < "$INLINE_FILE"
134+ echo ']' >> "$COMMENTS_JSON"
135+
136+ gh api \
137+ repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews \
138+ -f event=$REVIEW_EVENT \
139+ -f body="FlowForge AI review ($REVIEW_EVENT)" \
140+ -f commit_id="$PR_SHA" \
141+ -F comments=@"$COMMENTS_JSON" \
142+ -H "Accept: application/vnd.github+json"
143+ else
144+ echo "Posting single review with $REVIEW_EVENT"
145+ gh pr review ${{ github.event.pull_request.number }} --body-file "$RESP_FILE" --event $REVIEW_EVENT
146+ fi
0 commit comments