clud-bug 0.2.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,16 +13,74 @@ jobs:
13
13
  id-token: write
14
14
 
15
15
  steps:
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v5
17
+ with:
18
+ # fetch-depth: 0 so the strict-mode gate can `git show
19
+ # origin/<base_ref>:.claude/skills/.clud-bug.json` to read the base
20
+ # ref's manifest. Default depth=1 doesn't set up origin/<base_ref>
21
+ # and the gate would silently fall back to advisory.
22
+ fetch-depth: 0
23
+
24
+ # Three-way guard around the Claude action:
25
+ # 1. Secret present → no-op pass-through, action runs normally.
26
+ # 2. Secret missing AND PR is bot-authored or from a fork → post an
27
+ # advisory comment explaining the skip, then exit 0 (green check).
28
+ # GitHub deliberately withholds secrets from such PRs by design;
29
+ # failing red is the wrong signal.
30
+ # 3. Secret missing AND PR is owner-authored → fail loud with an
31
+ # actionable error so the maintainer knows to set the secret.
32
+ - name: Guard — require ANTHROPIC_API_KEY
33
+ id: guard
34
+ env:
35
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
36
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37
+ PR_NUMBER: ${{ github.event.pull_request.number }}
38
+ PR_AUTHOR: ${{ github.event.pull_request.user.login }}
39
+ HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
40
+ BASE_REPO: ${{ github.repository }}
41
+ run: |
42
+ if [ -n "$ANTHROPIC_API_KEY" ]; then
43
+ echo "skip=false" >> "$GITHUB_OUTPUT"
44
+ exit 0
45
+ fi
46
+
47
+ IS_FORK=false
48
+ [ "$HEAD_REPO" != "$BASE_REPO" ] && IS_FORK=true
49
+ IS_BOT=false
50
+ case "$PR_AUTHOR" in *\[bot\]) IS_BOT=true ;; esac
51
+
52
+ if $IS_FORK || $IS_BOT; then
53
+ REASON=$($IS_BOT && echo "bot author ($PR_AUTHOR)" || echo "fork ($HEAD_REPO)")
54
+ # Dedup: don't post the advisory comment if one already exists.
55
+ # pull_request: synchronize on a long-running Dependabot PR would
56
+ # otherwise stack one identical comment per rebase.
57
+ EXISTING=$(gh api "repos/${BASE_REPO}/issues/${PR_NUMBER}/comments?per_page=100" \
58
+ --jq '[.[] | select(.user.login == "claude[bot]" and (.body | startswith("## 🐛 Clud Bug skipped")))] | length')
59
+ if [ "${EXISTING:-0}" = "0" ]; then
60
+ BODY=$(printf '## 🐛 Clud Bug skipped\n\nThis PR is from a %s. GitHub deliberately does not pass repository secrets to such workflows, so Clud Bug could not authenticate against Anthropic. Review the diff manually.' "$REASON")
61
+ gh pr comment "$PR_NUMBER" --body "$BODY" || true
62
+ fi
63
+ echo "::warning title=Clud Bug 🐛::Skipped — $REASON cannot access repository secrets."
64
+ echo "skip=true" >> "$GITHUB_OUTPUT"
65
+ exit 0
66
+ fi
67
+
68
+ echo "::error title=Clud Bug 🐛::ANTHROPIC_API_KEY secret is not set on this repository."
69
+ echo "::error::Set it: Settings → Secrets and variables → Actions → New repository secret."
70
+ echo "::error::Without it, Clud Bug review is a silent no-op."
71
+ exit 1
17
72
 
18
73
  - uses: anthropics/claude-code-action@v1
74
+ # Skip the action when guard already posted the bot/fork advisory.
75
+ if: steps.guard.outputs.skip != 'true'
19
76
  env:
20
77
  PR_NUMBER: ${{ github.event.pull_request.number }}
78
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
21
79
  with:
22
80
  anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
23
81
  track_progress: true
24
82
  claude_args: |
25
- --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr review:*),Bash(gh api graphql:*),Bash(gh api repos/:*)"
83
+ --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh api graphql:*),Bash(gh api repos/:*),Bash(cat .claude/skills/.clud-bug.json)"
26
84
  prompt: |
27
85
  {{PROJECT_DESCRIPTION}}
28
86
 
@@ -36,8 +94,38 @@ jobs:
36
94
  Skip style suggestions, minor naming issues, or anything that
37
95
  doesn't affect correctness, security, or performance.
38
96
 
39
- Any project-specific skills loaded from .claude/skills/ should
40
- shape your review defer to their guidance over generic advice.
97
+ Skills are not background context they are review rules with
98
+ authority. Before flagging any finding, scan the loaded skills in
99
+ .claude/skills/ for relevant guidance. If a skill applies, your
100
+ review MUST reference it by name in the finding (e.g. "[evidence-
101
+ based-review]: this claim isn't anchored to a line"). Generic
102
+ advice that contradicts a project skill is wrong by definition.
103
+
104
+ At the end of every review, append a single-line footer:
105
+ Skills referenced: [skill-name-1, skill-name-2, ...]
106
+ If you genuinely cited none, list "[none]" and explain why no
107
+ installed skill applied to this diff.
108
+
109
+ Strict-mode header (opt-in): if .claude/skills/.clud-bug.json
110
+ contains { "strictMode": true }, the comment header you post
111
+ MUST signal whether you flagged a critical issue:
112
+ IF you flagged any critical issue (bug, security,
113
+ performance, missing test coverage):
114
+ ## 🐛 Clud Bug review — critical findings
115
+ OTHERWISE:
116
+ ## 🐛 Clud Bug review — clean
117
+ A post-step in this workflow greps your posted comment for
118
+ that header and fails the check on "critical findings." The
119
+ gate is deterministic on top of your judgment.
120
+
121
+ If strictMode is NOT set (or absent), keep the existing
122
+ "## 🐛 Clud Bug review" header — strict mode is opt-in and
123
+ other repos use the plain header.
124
+
125
+ Tone: address the author conversationally. A concise field-naturalist
126
+ voice is welcome (you are Clud Bug, examining specimens of code) but
127
+ never at the cost of clarity, evidence, or the critical-issues-only
128
+ discipline. Don't perform the bit; let the precision speak.
41
129
 
42
130
  When you finish, post your review as a single PR comment.
43
131
  The comment body MUST start with this exact line so the
@@ -66,3 +154,44 @@ jobs:
66
154
  For line-specific issues, use the github_inline_comment MCP tool
67
155
  with confirmed: true.
68
156
  If there are no critical issues, post a one-line comment saying so.
157
+
158
+ # Strict-mode gate. Fails the check when both
159
+ # (a) the BASE ref's .claude/skills/.clud-bug.json has
160
+ # { "strictMode": true } — read from base so a PR can't opt
161
+ # itself out of strict mode by editing its own manifest, AND
162
+ # (b) the LATEST clud-bug review comment on this PR has a body
163
+ # whose FIRST LINE is "## 🐛 Clud Bug review — critical findings".
164
+ #
165
+ # if: success() — only run when claude-code-action succeeded. If the
166
+ # action errored, no new comment was posted for this run; falling back
167
+ # to a prior run's comment would fail open or fail on stale data.
168
+ # Letting the action's own failure fail the check is louder and right.
169
+ - name: Strict mode — fail check on critical findings
170
+ if: success()
171
+ env:
172
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
173
+ PR_NUMBER: ${{ github.event.pull_request.number }}
174
+ run: |
175
+ # Loud failure if the base manifest can't be read — silently falling
176
+ # back to advisory would silently disable strict mode for every
177
+ # opted-in repo. (Requires fetch-depth: 0 on the checkout above.)
178
+ BASE_MANIFEST=$(git show "origin/${{ github.base_ref }}:.claude/skills/.clud-bug.json" 2>&1) || {
179
+ echo "::warning::Base manifest not found on ${{ github.base_ref }} — strict mode disabled for this run."
180
+ exit 0
181
+ }
182
+ STRICT=$(echo "$BASE_MANIFEST" | node -e "let s='';process.stdin.on('data',c=>s+=c);process.stdin.on('end',()=>{try{console.log(JSON.parse(s).strictMode===true)}catch(e){console.log('false')}})")
183
+ [ "$STRICT" = "true" ] || exit 0
184
+
185
+ # Use startswith (not regex contains) so comments that *quote* the
186
+ # sentinel header (other reviews, @claude responses, meta-PRs about
187
+ # strict mode itself) don't get picked up as "the latest review."
188
+ LATEST=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments?sort=created&direction=desc&per_page=100" \
189
+ --jq '[.[] | select(.user.login == "claude[bot]" and (.body | startswith("## 🐛 Clud Bug review")))][0].body // ""')
190
+
191
+ # Scope the critical-findings match to the FIRST LINE so quoted
192
+ # sentinels deeper in the review can't trip the gate.
193
+ if echo "$LATEST" | head -n1 | grep -q "Clud Bug review — critical findings"; then
194
+ echo "::error title=Clud Bug 🐛::Critical issues found and strictMode is enabled — failing this check."
195
+ echo "::error::See the latest Clud Bug review comment for details. Push a fix and the gate will clear on the next run."
196
+ exit 1
197
+ fi