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.
- package/README.md +88 -9
- package/bin/clud-bug.js +237 -38
- package/lib/audit.js +111 -0
- package/lib/edit-workflow.js +47 -0
- package/lib/skills.js +8 -1
- package/lib/update.js +98 -0
- package/package.json +2 -4
- package/templates/audit.yml.tmpl +134 -0
- package/templates/self-update.yml.tmpl +92 -0
- package/templates/workflow-py.yml.tmpl +89 -4
- package/templates/workflow-ts.yml.tmpl +89 -4
- package/templates/workflow.yml.tmpl +133 -4
|
@@ -13,16 +13,74 @@ jobs:
|
|
|
13
13
|
id-token: write
|
|
14
14
|
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
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
|
|
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
|
-
|
|
40
|
-
|
|
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
|