clud-bug 0.6.24 → 0.6.26
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/lib/agents-md.js +29 -3
- package/lib/detect.js +9 -1
- package/lib/prompts.js +24 -0
- package/package.json +1 -1
- package/templates/workflow-py.yml.tmpl +148 -25
- package/templates/workflow-ts.yml.tmpl +148 -25
- package/templates/workflow.yml.tmpl +292 -45
package/lib/agents-md.js
CHANGED
|
@@ -45,11 +45,19 @@ const TOUCH_IF_PRESENT = [
|
|
|
45
45
|
// set) is a documented advisory upgrade path — `clud-bug init` deliberately
|
|
46
46
|
// preserves that state. If the block rendered "on" for that case, other
|
|
47
47
|
// agents reading AGENTS.md would get a wrong model of the gate.
|
|
48
|
-
|
|
48
|
+
//
|
|
49
|
+
// v0.6.25 (gotcha #2 fix): when the consuming repo IS the publisher of
|
|
50
|
+
// the clud-bug-collaboration skill (skill source lives at
|
|
51
|
+
// `skills/clud-bug-collaboration/SKILL.md` instead of the consumer-install
|
|
52
|
+
// path `.claude/skills/...`), render the LOCAL repo path. Otherwise the
|
|
53
|
+
// link is dead in the publisher repo (this used to require a manual fix
|
|
54
|
+
// every v0.6.* propagation cycle on agent-skills).
|
|
55
|
+
export function renderBlock({ version, strictMode, skillRelPath } = {}) {
|
|
49
56
|
const versionLine = version ? `_Installed at clud-bug v${version}._` : '';
|
|
50
57
|
const strictNote = strictMode === true
|
|
51
58
|
? '**on** in this repo (workflow check fails on critical findings)'
|
|
52
59
|
: '**off** in this repo (advisory only)';
|
|
60
|
+
const skillPath = skillRelPath || '.claude/skills/clud-bug-collaboration/SKILL.md';
|
|
53
61
|
return `${START_MARKER}
|
|
54
62
|
<!-- clud-bug-block-version: ${BLOCK_VERSION} -->
|
|
55
63
|
## clud-bug — Claude PR review
|
|
@@ -57,7 +65,7 @@ export function renderBlock({ version, strictMode } = {}) {
|
|
|
57
65
|
This repo uses [clud-bug](https://cludbug.dev) for automatic PR reviews.
|
|
58
66
|
Full collaboration rules — fix-push flow, skill structure, comment format,
|
|
59
67
|
strict-mode mechanics, workflow-edit constraint — live in the bundled
|
|
60
|
-
[\`clud-bug-collaboration\` skill](
|
|
68
|
+
[\`clud-bug-collaboration\` skill](${skillPath}).
|
|
61
69
|
Read that skill before pushing fixes addressing prior review threads.
|
|
62
70
|
|
|
63
71
|
Strict mode is ${strictNote}. Toggle via \`.claude/skills/.clud-bug.json\`
|
|
@@ -71,6 +79,19 @@ ${versionLine}
|
|
|
71
79
|
${END_MARKER}`;
|
|
72
80
|
}
|
|
73
81
|
|
|
82
|
+
// v0.6.25 (gotcha #2): detect repos that PUBLISH the
|
|
83
|
+
// clud-bug-collaboration skill (agent-skills is the canonical case).
|
|
84
|
+
// When the skill source exists at `skills/clud-bug-collaboration/SKILL.md`
|
|
85
|
+
// in the working tree, the AGENTS.md link should point there, not at the
|
|
86
|
+
// consumer-install `.claude/skills/...` path that doesn't exist in the
|
|
87
|
+
// publisher repo.
|
|
88
|
+
export async function detectSkillRelPath(cwd) {
|
|
89
|
+
const publisherPath = 'skills/clud-bug-collaboration/SKILL.md';
|
|
90
|
+
const consumerPath = '.claude/skills/clud-bug-collaboration/SKILL.md';
|
|
91
|
+
if (await fileExists(join(cwd, publisherPath))) return publisherPath;
|
|
92
|
+
return consumerPath;
|
|
93
|
+
}
|
|
94
|
+
|
|
74
95
|
// Replace an existing clud-bug block in `content`, OR append if absent.
|
|
75
96
|
// Idempotent: running multiple times leaves a single block.
|
|
76
97
|
export function upsertBlock(content, block) {
|
|
@@ -126,7 +147,12 @@ function escapeRe(s) {
|
|
|
126
147
|
//
|
|
127
148
|
// Returns { touched: string[], created: string[] } for the caller to log.
|
|
128
149
|
export async function applyToRepo(cwd, blockOpts = {}) {
|
|
129
|
-
|
|
150
|
+
// v0.6.25 / gotcha #2: detect publisher repo + render local skill path.
|
|
151
|
+
// Pre-v0.6.25 always rendered the consumer install path → broke
|
|
152
|
+
// agent-skills' check-links every propagation cycle. Detection runs
|
|
153
|
+
// before block render so the path is correct from the first write.
|
|
154
|
+
const skillRelPath = blockOpts.skillRelPath ?? await detectSkillRelPath(cwd);
|
|
155
|
+
const block = renderBlock({ ...blockOpts, skillRelPath });
|
|
130
156
|
const touched = [];
|
|
131
157
|
const created = [];
|
|
132
158
|
|
package/lib/detect.js
CHANGED
|
@@ -214,7 +214,15 @@ export function buildDescriptionLine(signals) {
|
|
|
214
214
|
const parts = [];
|
|
215
215
|
if (signals.name) parts.push(`This project is "${signals.name}".`);
|
|
216
216
|
if (signals.description) {
|
|
217
|
-
|
|
217
|
+
// v0.6.25 / issue #89: when signals.description comes from a
|
|
218
|
+
// README first paragraph or similar multi-paragraph source, the
|
|
219
|
+
// raw `\n` characters survive into the rendered YAML's
|
|
220
|
+
// APPEND_SYSTEM_PROMPT value. The renderer's indent-aware
|
|
221
|
+
// substitution preserves layout but a literal `\n` inside a YAML
|
|
222
|
+
// double-quoted string is interpreted as a newline, breaking
|
|
223
|
+
// the YAML. Collapse any whitespace run (including newlines + tabs)
|
|
224
|
+
// to a single space before further processing.
|
|
225
|
+
const desc = signals.description.replace(/\s+/g, ' ').trim();
|
|
218
226
|
parts.push(/[.!?]$/.test(desc) ? desc : `${desc}.`);
|
|
219
227
|
}
|
|
220
228
|
if (signals.primaryLanguage) {
|
package/lib/prompts.js
CHANGED
|
@@ -177,6 +177,30 @@ Keep total output under ~600 tokens. Per finding:
|
|
|
177
177
|
Not a hard cap (SDK doesn't expose max_tokens); brevity compounds
|
|
178
178
|
across the org on every review.
|
|
179
179
|
|
|
180
|
+
Turn budget self-rationing (v0.6.25 / §5.5 Layer 2):
|
|
181
|
+
You are run with a finite \`--max-turns\` cap. The exact value, plus
|
|
182
|
+
the pre-flight estimate for this PR, lands in the per-PR prompt
|
|
183
|
+
section below as \`max_turns=N, estimated=M, files=F, +A/-D lines, threads=T\`.
|
|
184
|
+
Treat that as your budget, not a ceiling to test.
|
|
185
|
+
|
|
186
|
+
Rules:
|
|
187
|
+
- Reserve the LAST 5 turns for structured-output emit. It is
|
|
188
|
+
non-skippable; if you run out before emitting, the workflow
|
|
189
|
+
falls back to a synthetic summary scraped from your inline
|
|
190
|
+
findings (v0.6.26+ Layer 6) — strictly worse than a real one.
|
|
191
|
+
- Rough rates: ~1 turn per 50 lines of code, ~1 turn per 150 lines
|
|
192
|
+
of docs, ~1 turn per 100 lines of tests or config. Each prior
|
|
193
|
+
unresolved thread is ~1.5 turns to walk + decide.
|
|
194
|
+
- Every line of the diff MUST be in your scan. Never silently skip
|
|
195
|
+
a file. If you must shorten coverage to fit budget, switch from
|
|
196
|
+
deep-dive analysis to one-sentence per-file verdicts — but every
|
|
197
|
+
file gets a verdict, even if it's "no issues found".
|
|
198
|
+
- If you're falling behind pace (files_reviewed/files_total
|
|
199
|
+
materially less than turns_used/max_turns), broaden+shallow over
|
|
200
|
+
deep+narrow. Audit visibility lives in \`per_skill_scan\` — list
|
|
201
|
+
every file's verdict so a maintainer can verify nothing was
|
|
202
|
+
skipped.
|
|
203
|
+
|
|
180
204
|
Incremental-diff handshake (v0.6.10+) — emit the SHA marker:
|
|
181
205
|
At the very end of the summary (after the Skills-referenced footer,
|
|
182
206
|
on its own line), append:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clud-bug",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.26",
|
|
4
4
|
"description": "Skill-driven Claude PR review. Ship a brand-voice skill, get brand reviews. Each finding cites the skill that motivated it. CLI installs the workflow + a baseline kit; add more from skills.sh.",
|
|
5
5
|
"homepage": "https://cludbug.dev",
|
|
6
6
|
"bugs": "https://github.com/thrillmade/clud-bug/issues",
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
# clud-bug-template-version:
|
|
1
|
+
# clud-bug-template-version: v12
|
|
2
2
|
name: Clud Bug 🐛 Crawls Your Code
|
|
3
3
|
|
|
4
4
|
on:
|
|
5
5
|
pull_request:
|
|
6
6
|
types: [opened, synchronize]
|
|
7
7
|
|
|
8
|
+
# v0.6.25: workflow-level concurrency — see workflow.yml.tmpl.
|
|
9
|
+
concurrency:
|
|
10
|
+
group: clud-bug-review-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
8
13
|
jobs:
|
|
9
14
|
# Pre-flight (v0.6.14 / 0.0.W + v0.6.15 / 0.0.R) — see workflow.yml.tmpl.
|
|
10
15
|
paths-check:
|
|
@@ -16,6 +21,12 @@ jobs:
|
|
|
16
21
|
is_workflow_only: ${{ steps.classify.outputs.is_workflow_only }}
|
|
17
22
|
model: ${{ steps.classify.outputs.model }}
|
|
18
23
|
max_turns: ${{ steps.classify.outputs.max_turns }}
|
|
24
|
+
# v0.6.25 / §5.5 Layer 1.5 (calibration) — see workflow.yml.tmpl.
|
|
25
|
+
turns_estimated: ${{ steps.classify.outputs.turns_estimated }}
|
|
26
|
+
files_count: ${{ steps.classify.outputs.files_count }}
|
|
27
|
+
lines_added: ${{ steps.classify.outputs.lines_added }}
|
|
28
|
+
lines_deleted: ${{ steps.classify.outputs.lines_deleted }}
|
|
29
|
+
threads_count: ${{ steps.classify.outputs.threads_count }}
|
|
19
30
|
steps:
|
|
20
31
|
- name: Classify PR diff
|
|
21
32
|
id: classify
|
|
@@ -34,20 +45,40 @@ jobs:
|
|
|
34
45
|
echo "is_workflow_only=false"
|
|
35
46
|
echo "model=$MODEL"
|
|
36
47
|
echo "max_turns=15"
|
|
48
|
+
echo "turns_estimated=0"
|
|
49
|
+
echo "files_count=0"
|
|
50
|
+
echo "lines_added=0"
|
|
51
|
+
echo "lines_deleted=0"
|
|
52
|
+
echo "threads_count=0"
|
|
37
53
|
} >> "$GITHUB_OUTPUT"
|
|
38
54
|
exit 0
|
|
39
55
|
fi
|
|
40
|
-
|
|
56
|
+
# v0.6.26 / 0.0.W² — see workflow.yml.tmpl for design notes.
|
|
57
|
+
ALL_IN_ALLOWLIST=true
|
|
58
|
+
HAS_WORKFLOW_CHANGE=false
|
|
41
59
|
while IFS= read -r f; do
|
|
42
60
|
case "$f" in
|
|
43
|
-
.github/workflows/clud-bug-*.yml) ;;
|
|
44
|
-
.github/actions/strict-mode-gate/*) ;;
|
|
45
|
-
|
|
61
|
+
.github/workflows/clud-bug-*.yml) HAS_WORKFLOW_CHANGE=true ;;
|
|
62
|
+
.github/actions/strict-mode-gate/*) HAS_WORKFLOW_CHANGE=true ;;
|
|
63
|
+
AGENTS.md) ;;
|
|
64
|
+
.cursorrules|.clinerules|.windsurfrules|.continuerules) ;;
|
|
65
|
+
.github/copilot-instructions.md) ;;
|
|
66
|
+
.claude/skills/.clud-bug.json) ;;
|
|
67
|
+
.claude/skills/critical-issues-only/SKILL.md) ;;
|
|
68
|
+
.claude/skills/evidence-based-review/SKILL.md) ;;
|
|
69
|
+
.claude/skills/respect-existing-conventions/SKILL.md) ;;
|
|
70
|
+
docs/timeline.md|docs/file-structure.md|docs/decisions.md) ;;
|
|
71
|
+
docs/decisions-branches/*.md) ;;
|
|
72
|
+
*) ALL_IN_ALLOWLIST=false; break ;;
|
|
46
73
|
esac
|
|
47
74
|
done <<< "$CHANGED"
|
|
75
|
+
IS_WORKFLOW_ONLY=false
|
|
76
|
+
if [ "$ALL_IN_ALLOWLIST" = "true" ] && [ "$HAS_WORKFLOW_CHANGE" = "true" ]; then
|
|
77
|
+
IS_WORKFLOW_ONLY=true
|
|
78
|
+
fi
|
|
48
79
|
echo "is_workflow_only=$IS_WORKFLOW_ONLY" >> "$GITHUB_OUTPUT"
|
|
49
80
|
if [ "$IS_WORKFLOW_ONLY" = "true" ]; then
|
|
50
|
-
echo "::notice title=Clud Bug 🐛::Skipping LLM review —
|
|
81
|
+
echo "::notice title=Clud Bug 🐛::Skipping LLM review — clud-bug update output."
|
|
51
82
|
echo "model=$MODEL" >> "$GITHUB_OUTPUT"
|
|
52
83
|
exit 0
|
|
53
84
|
fi
|
|
@@ -81,22 +112,57 @@ jobs:
|
|
|
81
112
|
fi
|
|
82
113
|
echo "model=$MODEL" >> "$GITHUB_OUTPUT"
|
|
83
114
|
|
|
84
|
-
#
|
|
85
|
-
|
|
115
|
+
# Smart budget (v0.6.25 / §5.5 Layer 1) — see workflow.yml.tmpl for design notes + formula.
|
|
116
|
+
FILE_COUNT=$(echo "$CHANGED" | wc -l | tr -d ' ')
|
|
117
|
+
THREAD_COUNT=$(gh api graphql -f query='{repository(owner:"'"$(echo "$REPO" | cut -d/ -f1)"'",name:"'"$(echo "$REPO" | cut -d/ -f2)"'"){pullRequest(number:'"$PR_NUMBER"'){reviewThreads(first:50){nodes{isResolved comments(first:1){nodes{author{login}}}}}}}}' --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and (.comments.nodes[0].author.login == "claude" or .comments.nodes[0].author.login == "claude[bot]"))] | length' 2>/dev/null || echo 0)
|
|
118
|
+
THREAD_COUNT=${THREAD_COUNT:-0}
|
|
119
|
+
|
|
86
120
|
if [ "$IS_TRIVIAL" = "true" ]; then
|
|
87
121
|
MAX_TURNS=10
|
|
122
|
+
TURNS_ESTIMATED=10
|
|
123
|
+
LINES_ADDED=0
|
|
124
|
+
LINES_DELETED=0
|
|
125
|
+
echo "::notice title=Clud Bug 🐛::Trivial (Haiku) budget: max_turns=10."
|
|
88
126
|
else
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
FILES_JSON=$(gh pr view "$PR_NUMBER" -R "$REPO" --json files --jq '.files' 2>/dev/null || echo '[]')
|
|
128
|
+
LINES_ADDED=$(echo "$FILES_JSON" | jq '[.[].additions] | add // 0' 2>/dev/null || echo 0)
|
|
129
|
+
LINES_DELETED=$(echo "$FILES_JSON" | jq '[.[].deletions] | add // 0' 2>/dev/null || echo 0)
|
|
130
|
+
LINES_ADDED=${LINES_ADDED:-0}
|
|
131
|
+
LINES_DELETED=${LINES_DELETED:-0}
|
|
132
|
+
|
|
133
|
+
# jq implements the §5.5 Layer 1 formula — see workflow.yml.tmpl.
|
|
134
|
+
TURNS_ESTIMATED=$(echo "$FILES_JSON" | jq --argjson threads "$THREAD_COUNT" '
|
|
135
|
+
def per_line(path):
|
|
136
|
+
if (path | test("\\.(test|spec)\\.|__tests__|^tests?/")) then 0.01
|
|
137
|
+
elif (path | test("\\.(md|txt|rst|adoc|mdx)$")) then 0.00666667
|
|
138
|
+
elif (path | test("\\.(yml|yaml|toml|json|cfg|ini|conf|tmpl)$")) then 0.01
|
|
139
|
+
elif (path | test("\\.(ts|tsx|py|js|jsx|mjs|cjs|go|rs|java|kt|rb|php|cs|c|cpp|h|hpp|swift|scala)$")) then 0.02
|
|
140
|
+
else 0.0125 end;
|
|
141
|
+
map(
|
|
142
|
+
select(.path != "docs/timeline.md"
|
|
143
|
+
and .path != "docs/file-structure.md"
|
|
144
|
+
and .path != "docs/decisions.md")
|
|
145
|
+
| (.additions) as $add | (.deletions) as $del
|
|
146
|
+
| (if $add < $del then $add else $del end) as $mod
|
|
147
|
+
| ($add - $mod) as $pa | ($del - $mod) as $pd
|
|
148
|
+
| per_line(.path) as $tw
|
|
149
|
+
| 0.3 + ($pa * $tw) + ($mod * 1.5 * $tw) + ($pd * 0.1 * $tw)
|
|
150
|
+
) | (add // 0) + 10 + ($threads * 1.5) | (. + 0.9999999) | floor
|
|
151
|
+
' 2>/dev/null || echo 15)
|
|
152
|
+
TURNS_ESTIMATED=${TURNS_ESTIMATED:-15}
|
|
153
|
+
MAX_TURNS=$(( (TURNS_ESTIMATED * 12 + 9) / 10 ))
|
|
154
|
+
[ "$MAX_TURNS" -lt 15 ] && MAX_TURNS=15
|
|
155
|
+
[ "$MAX_TURNS" -gt 60 ] && MAX_TURNS=60
|
|
156
|
+
echo "::notice title=Clud Bug 🐛::Smart budget: estimated $TURNS_ESTIMATED turns → max_turns=$MAX_TURNS ($FILE_COUNT files, +$LINES_ADDED/-$LINES_DELETED lines, $THREAD_COUNT prior threads)."
|
|
98
157
|
fi
|
|
99
|
-
|
|
158
|
+
{
|
|
159
|
+
echo "max_turns=$MAX_TURNS"
|
|
160
|
+
echo "turns_estimated=$TURNS_ESTIMATED"
|
|
161
|
+
echo "files_count=$FILE_COUNT"
|
|
162
|
+
echo "lines_added=$LINES_ADDED"
|
|
163
|
+
echo "lines_deleted=$LINES_DELETED"
|
|
164
|
+
echo "threads_count=$THREAD_COUNT"
|
|
165
|
+
} >> "$GITHUB_OUTPUT"
|
|
100
166
|
|
|
101
167
|
clud-bug-review:
|
|
102
168
|
needs: paths-check
|
|
@@ -180,43 +246,100 @@ jobs:
|
|
|
180
246
|
Review this pull request following the discipline in your
|
|
181
247
|
system prompt — every rule about skill routing, comment
|
|
182
248
|
format, the strict-mode header, the two-surface review
|
|
183
|
-
shape,
|
|
249
|
+
shape, the FIX-PUSH FLOW, and (v0.6.25+) turn-budget
|
|
250
|
+
self-rationing applies.
|
|
251
|
+
|
|
252
|
+
## Your turn budget for this PR (v0.6.25 / §5.5 Layer 2)
|
|
253
|
+
|
|
254
|
+
max_turns=${{ needs.paths-check.outputs.max_turns }},
|
|
255
|
+
estimated=${{ needs.paths-check.outputs.turns_estimated }},
|
|
256
|
+
files=${{ needs.paths-check.outputs.files_count }},
|
|
257
|
+
+${{ needs.paths-check.outputs.lines_added }}/-${{ needs.paths-check.outputs.lines_deleted }} lines,
|
|
258
|
+
prior_threads=${{ needs.paths-check.outputs.threads_count }}.
|
|
259
|
+
|
|
260
|
+
Plan accordingly per the "Turn budget self-rationing"
|
|
261
|
+
section in your system prompt. Reserve 5 turns for emit.
|
|
184
262
|
id: clud-bug-review
|
|
185
263
|
|
|
186
264
|
# v0.6.22 / 0.0.O: render structured output → post via gh pr comment.
|
|
265
|
+
# v0.6.25 / §5.5 Layer 1.5: append calibration marker.
|
|
187
266
|
- name: Render + post structured review
|
|
188
267
|
if: success() && steps.clud-bug-review.outputs.structured_output != ''
|
|
189
268
|
env:
|
|
190
269
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
191
270
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
192
271
|
STRUCTURED: ${{ steps.clud-bug-review.outputs.structured_output }}
|
|
272
|
+
TURNS_ESTIMATED: ${{ needs.paths-check.outputs.turns_estimated }}
|
|
273
|
+
MAX_TURNS: ${{ needs.paths-check.outputs.max_turns }}
|
|
274
|
+
FILES_COUNT: ${{ needs.paths-check.outputs.files_count }}
|
|
275
|
+
LINES_ADDED: ${{ needs.paths-check.outputs.lines_added }}
|
|
276
|
+
LINES_DELETED: ${{ needs.paths-check.outputs.lines_deleted }}
|
|
277
|
+
THREADS_COUNT: ${{ needs.paths-check.outputs.threads_count }}
|
|
193
278
|
run: |
|
|
194
279
|
set -euo pipefail
|
|
195
280
|
BODY=$(printf '%s\n' "$STRUCTURED" | npx --yes clud-bug@{{CLUD_BUG_VERSION}} render --stdin)
|
|
196
|
-
|
|
281
|
+
CALIBRATION="<!-- clud-bug-calibration: turns_estimated=$TURNS_ESTIMATED, max_turns=$MAX_TURNS, files=$FILES_COUNT, lines_added=$LINES_ADDED, lines_deleted=$LINES_DELETED, threads=$THREADS_COUNT -->"
|
|
282
|
+
gh pr comment "$PR_NUMBER" --body "$BODY
|
|
197
283
|
|
|
198
|
-
|
|
284
|
+
$CALIBRATION"
|
|
285
|
+
|
|
286
|
+
# v0.6.26 / §5.5 Layer 6 fallback render-from-inlines — see workflow.yml.tmpl for design notes.
|
|
199
287
|
- name: Fallback summary (structured_output empty)
|
|
200
288
|
if: success() && steps.clud-bug-review.outputs.structured_output == ''
|
|
201
289
|
env:
|
|
202
290
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
203
291
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
292
|
+
REPO: ${{ github.repository }}
|
|
293
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
294
|
+
TURNS_ESTIMATED: ${{ needs.paths-check.outputs.turns_estimated }}
|
|
295
|
+
MAX_TURNS: ${{ needs.paths-check.outputs.max_turns }}
|
|
296
|
+
FILES_COUNT: ${{ needs.paths-check.outputs.files_count }}
|
|
297
|
+
LINES_ADDED: ${{ needs.paths-check.outputs.lines_added }}
|
|
298
|
+
LINES_DELETED: ${{ needs.paths-check.outputs.lines_deleted }}
|
|
299
|
+
THREADS_COUNT: ${{ needs.paths-check.outputs.threads_count }}
|
|
204
300
|
run: |
|
|
205
301
|
set -euo pipefail
|
|
206
|
-
gh
|
|
302
|
+
INLINES=$(gh api "repos/$REPO/pulls/$PR_NUMBER/comments?per_page=100" \
|
|
303
|
+
--jq "[.[] | select(.user.login == \"claude[bot]\" and .commit_id == \"$HEAD_SHA\")]")
|
|
304
|
+
INLINE_COUNT=$(echo "$INLINES" | jq 'length')
|
|
305
|
+
CRITICAL=$(echo "$INLINES" | jq '[.[] | select(.body | test("🔴"))] | length')
|
|
306
|
+
MINOR=$(echo "$INLINES" | jq '[.[] | select(.body | test("🟡"))] | length')
|
|
307
|
+
PREEXISTING=$(echo "$INLINES" | jq '[.[] | select(.body | test("🟣"))] | length')
|
|
308
|
+
CALIBRATION="<!-- clud-bug-calibration: turns_estimated=$TURNS_ESTIMATED, max_turns=$MAX_TURNS, files=$FILES_COUNT, lines_added=$LINES_ADDED, lines_deleted=$LINES_DELETED, threads=$THREADS_COUNT, structured_output=empty, inline_findings=$INLINE_COUNT -->"
|
|
309
|
+
if [ "$INLINE_COUNT" -gt 0 ]; then
|
|
310
|
+
STATUS=""
|
|
311
|
+
[ "$CRITICAL" -gt 0 ] && STATUS=" — critical findings"
|
|
312
|
+
gh pr comment "$PR_NUMBER" --body "## 🐛 Clud Bug review${STATUS}
|
|
313
|
+
|
|
314
|
+
**This round:** $CRITICAL critical · $MINOR minor · 0 resolved from prior · 0 still open
|
|
315
|
+
|
|
316
|
+
Found: $CRITICAL 🔴 / $MINOR 🟡 / $PREEXISTING 🟣
|
|
317
|
+
|
|
318
|
+
⚠️ **Synthetic summary** (v0.6.26 §5.5 Layer 6 fallback). Inline findings above ARE the substantive review.
|
|
319
|
+
|
|
320
|
+
<!-- last-reviewed-sha: $HEAD_SHA -->
|
|
321
|
+
|
|
322
|
+
$CALIBRATION"
|
|
323
|
+
else
|
|
324
|
+
gh pr comment "$PR_NUMBER" --body "## 🐛 Clud Bug review
|
|
207
325
|
|
|
208
326
|
**This round:** 0 critical · 0 minor · 0 resolved from prior · 0 still open
|
|
209
327
|
|
|
210
328
|
Found: 0 🔴 / 0 🟡 / 0 🟣
|
|
211
329
|
|
|
212
|
-
⚠️ Structured output (\`--json-schema\`) returned empty
|
|
330
|
+
⚠️ Structured output (\`--json-schema\`) returned empty AND no inline findings posted. Investigate run logs.
|
|
331
|
+
|
|
332
|
+
Skills referenced: [none]
|
|
213
333
|
|
|
214
|
-
|
|
334
|
+
<!-- last-reviewed-sha: $HEAD_SHA -->
|
|
335
|
+
|
|
336
|
+
$CALIBRATION"
|
|
337
|
+
fi
|
|
215
338
|
|
|
216
339
|
# Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
|
|
217
340
|
- name: Strict mode — fail check on critical findings
|
|
218
341
|
if: success()
|
|
219
|
-
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.
|
|
342
|
+
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.26
|
|
220
343
|
with:
|
|
221
344
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
222
345
|
# v0.6.22 / 0.0.O: summary now posted by github-actions[bot].
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
# clud-bug-template-version:
|
|
1
|
+
# clud-bug-template-version: v12
|
|
2
2
|
name: Clud Bug 🐛 Crawls Your Code
|
|
3
3
|
|
|
4
4
|
on:
|
|
5
5
|
pull_request:
|
|
6
6
|
types: [opened, synchronize]
|
|
7
7
|
|
|
8
|
+
# v0.6.25: workflow-level concurrency — see workflow.yml.tmpl.
|
|
9
|
+
concurrency:
|
|
10
|
+
group: clud-bug-review-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
8
13
|
jobs:
|
|
9
14
|
# Pre-flight (v0.6.14 / 0.0.W + v0.6.15 / 0.0.R) — see workflow.yml.tmpl.
|
|
10
15
|
paths-check:
|
|
@@ -16,6 +21,12 @@ jobs:
|
|
|
16
21
|
is_workflow_only: ${{ steps.classify.outputs.is_workflow_only }}
|
|
17
22
|
model: ${{ steps.classify.outputs.model }}
|
|
18
23
|
max_turns: ${{ steps.classify.outputs.max_turns }}
|
|
24
|
+
# v0.6.25 / §5.5 Layer 1.5 (calibration) — see workflow.yml.tmpl.
|
|
25
|
+
turns_estimated: ${{ steps.classify.outputs.turns_estimated }}
|
|
26
|
+
files_count: ${{ steps.classify.outputs.files_count }}
|
|
27
|
+
lines_added: ${{ steps.classify.outputs.lines_added }}
|
|
28
|
+
lines_deleted: ${{ steps.classify.outputs.lines_deleted }}
|
|
29
|
+
threads_count: ${{ steps.classify.outputs.threads_count }}
|
|
19
30
|
steps:
|
|
20
31
|
- name: Classify PR diff
|
|
21
32
|
id: classify
|
|
@@ -34,20 +45,40 @@ jobs:
|
|
|
34
45
|
echo "is_workflow_only=false"
|
|
35
46
|
echo "model=$MODEL"
|
|
36
47
|
echo "max_turns=15"
|
|
48
|
+
echo "turns_estimated=0"
|
|
49
|
+
echo "files_count=0"
|
|
50
|
+
echo "lines_added=0"
|
|
51
|
+
echo "lines_deleted=0"
|
|
52
|
+
echo "threads_count=0"
|
|
37
53
|
} >> "$GITHUB_OUTPUT"
|
|
38
54
|
exit 0
|
|
39
55
|
fi
|
|
40
|
-
|
|
56
|
+
# v0.6.26 / 0.0.W² — see workflow.yml.tmpl for design notes.
|
|
57
|
+
ALL_IN_ALLOWLIST=true
|
|
58
|
+
HAS_WORKFLOW_CHANGE=false
|
|
41
59
|
while IFS= read -r f; do
|
|
42
60
|
case "$f" in
|
|
43
|
-
.github/workflows/clud-bug-*.yml) ;;
|
|
44
|
-
.github/actions/strict-mode-gate/*) ;;
|
|
45
|
-
|
|
61
|
+
.github/workflows/clud-bug-*.yml) HAS_WORKFLOW_CHANGE=true ;;
|
|
62
|
+
.github/actions/strict-mode-gate/*) HAS_WORKFLOW_CHANGE=true ;;
|
|
63
|
+
AGENTS.md) ;;
|
|
64
|
+
.cursorrules|.clinerules|.windsurfrules|.continuerules) ;;
|
|
65
|
+
.github/copilot-instructions.md) ;;
|
|
66
|
+
.claude/skills/.clud-bug.json) ;;
|
|
67
|
+
.claude/skills/critical-issues-only/SKILL.md) ;;
|
|
68
|
+
.claude/skills/evidence-based-review/SKILL.md) ;;
|
|
69
|
+
.claude/skills/respect-existing-conventions/SKILL.md) ;;
|
|
70
|
+
docs/timeline.md|docs/file-structure.md|docs/decisions.md) ;;
|
|
71
|
+
docs/decisions-branches/*.md) ;;
|
|
72
|
+
*) ALL_IN_ALLOWLIST=false; break ;;
|
|
46
73
|
esac
|
|
47
74
|
done <<< "$CHANGED"
|
|
75
|
+
IS_WORKFLOW_ONLY=false
|
|
76
|
+
if [ "$ALL_IN_ALLOWLIST" = "true" ] && [ "$HAS_WORKFLOW_CHANGE" = "true" ]; then
|
|
77
|
+
IS_WORKFLOW_ONLY=true
|
|
78
|
+
fi
|
|
48
79
|
echo "is_workflow_only=$IS_WORKFLOW_ONLY" >> "$GITHUB_OUTPUT"
|
|
49
80
|
if [ "$IS_WORKFLOW_ONLY" = "true" ]; then
|
|
50
|
-
echo "::notice title=Clud Bug 🐛::Skipping LLM review —
|
|
81
|
+
echo "::notice title=Clud Bug 🐛::Skipping LLM review — clud-bug update output."
|
|
51
82
|
echo "model=$MODEL" >> "$GITHUB_OUTPUT"
|
|
52
83
|
exit 0
|
|
53
84
|
fi
|
|
@@ -81,22 +112,57 @@ jobs:
|
|
|
81
112
|
fi
|
|
82
113
|
echo "model=$MODEL" >> "$GITHUB_OUTPUT"
|
|
83
114
|
|
|
84
|
-
#
|
|
85
|
-
|
|
115
|
+
# Smart budget (v0.6.25 / §5.5 Layer 1) — see workflow.yml.tmpl for design notes + formula.
|
|
116
|
+
FILE_COUNT=$(echo "$CHANGED" | wc -l | tr -d ' ')
|
|
117
|
+
THREAD_COUNT=$(gh api graphql -f query='{repository(owner:"'"$(echo "$REPO" | cut -d/ -f1)"'",name:"'"$(echo "$REPO" | cut -d/ -f2)"'"){pullRequest(number:'"$PR_NUMBER"'){reviewThreads(first:50){nodes{isResolved comments(first:1){nodes{author{login}}}}}}}}' --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and (.comments.nodes[0].author.login == "claude" or .comments.nodes[0].author.login == "claude[bot]"))] | length' 2>/dev/null || echo 0)
|
|
118
|
+
THREAD_COUNT=${THREAD_COUNT:-0}
|
|
119
|
+
|
|
86
120
|
if [ "$IS_TRIVIAL" = "true" ]; then
|
|
87
121
|
MAX_TURNS=10
|
|
122
|
+
TURNS_ESTIMATED=10
|
|
123
|
+
LINES_ADDED=0
|
|
124
|
+
LINES_DELETED=0
|
|
125
|
+
echo "::notice title=Clud Bug 🐛::Trivial (Haiku) budget: max_turns=10."
|
|
88
126
|
else
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
FILES_JSON=$(gh pr view "$PR_NUMBER" -R "$REPO" --json files --jq '.files' 2>/dev/null || echo '[]')
|
|
128
|
+
LINES_ADDED=$(echo "$FILES_JSON" | jq '[.[].additions] | add // 0' 2>/dev/null || echo 0)
|
|
129
|
+
LINES_DELETED=$(echo "$FILES_JSON" | jq '[.[].deletions] | add // 0' 2>/dev/null || echo 0)
|
|
130
|
+
LINES_ADDED=${LINES_ADDED:-0}
|
|
131
|
+
LINES_DELETED=${LINES_DELETED:-0}
|
|
132
|
+
|
|
133
|
+
# jq implements the §5.5 Layer 1 formula — see workflow.yml.tmpl.
|
|
134
|
+
TURNS_ESTIMATED=$(echo "$FILES_JSON" | jq --argjson threads "$THREAD_COUNT" '
|
|
135
|
+
def per_line(path):
|
|
136
|
+
if (path | test("\\.(test|spec)\\.|__tests__|^tests?/")) then 0.01
|
|
137
|
+
elif (path | test("\\.(md|txt|rst|adoc|mdx)$")) then 0.00666667
|
|
138
|
+
elif (path | test("\\.(yml|yaml|toml|json|cfg|ini|conf|tmpl)$")) then 0.01
|
|
139
|
+
elif (path | test("\\.(ts|tsx|py|js|jsx|mjs|cjs|go|rs|java|kt|rb|php|cs|c|cpp|h|hpp|swift|scala)$")) then 0.02
|
|
140
|
+
else 0.0125 end;
|
|
141
|
+
map(
|
|
142
|
+
select(.path != "docs/timeline.md"
|
|
143
|
+
and .path != "docs/file-structure.md"
|
|
144
|
+
and .path != "docs/decisions.md")
|
|
145
|
+
| (.additions) as $add | (.deletions) as $del
|
|
146
|
+
| (if $add < $del then $add else $del end) as $mod
|
|
147
|
+
| ($add - $mod) as $pa | ($del - $mod) as $pd
|
|
148
|
+
| per_line(.path) as $tw
|
|
149
|
+
| 0.3 + ($pa * $tw) + ($mod * 1.5 * $tw) + ($pd * 0.1 * $tw)
|
|
150
|
+
) | (add // 0) + 10 + ($threads * 1.5) | (. + 0.9999999) | floor
|
|
151
|
+
' 2>/dev/null || echo 15)
|
|
152
|
+
TURNS_ESTIMATED=${TURNS_ESTIMATED:-15}
|
|
153
|
+
MAX_TURNS=$(( (TURNS_ESTIMATED * 12 + 9) / 10 ))
|
|
154
|
+
[ "$MAX_TURNS" -lt 15 ] && MAX_TURNS=15
|
|
155
|
+
[ "$MAX_TURNS" -gt 60 ] && MAX_TURNS=60
|
|
156
|
+
echo "::notice title=Clud Bug 🐛::Smart budget: estimated $TURNS_ESTIMATED turns → max_turns=$MAX_TURNS ($FILE_COUNT files, +$LINES_ADDED/-$LINES_DELETED lines, $THREAD_COUNT prior threads)."
|
|
98
157
|
fi
|
|
99
|
-
|
|
158
|
+
{
|
|
159
|
+
echo "max_turns=$MAX_TURNS"
|
|
160
|
+
echo "turns_estimated=$TURNS_ESTIMATED"
|
|
161
|
+
echo "files_count=$FILE_COUNT"
|
|
162
|
+
echo "lines_added=$LINES_ADDED"
|
|
163
|
+
echo "lines_deleted=$LINES_DELETED"
|
|
164
|
+
echo "threads_count=$THREAD_COUNT"
|
|
165
|
+
} >> "$GITHUB_OUTPUT"
|
|
100
166
|
|
|
101
167
|
clud-bug-review:
|
|
102
168
|
needs: paths-check
|
|
@@ -180,43 +246,100 @@ jobs:
|
|
|
180
246
|
Review this pull request following the discipline in your
|
|
181
247
|
system prompt — every rule about skill routing, comment
|
|
182
248
|
format, the strict-mode header, the two-surface review
|
|
183
|
-
shape,
|
|
249
|
+
shape, the FIX-PUSH FLOW, and (v0.6.25+) turn-budget
|
|
250
|
+
self-rationing applies.
|
|
251
|
+
|
|
252
|
+
## Your turn budget for this PR (v0.6.25 / §5.5 Layer 2)
|
|
253
|
+
|
|
254
|
+
max_turns=${{ needs.paths-check.outputs.max_turns }},
|
|
255
|
+
estimated=${{ needs.paths-check.outputs.turns_estimated }},
|
|
256
|
+
files=${{ needs.paths-check.outputs.files_count }},
|
|
257
|
+
+${{ needs.paths-check.outputs.lines_added }}/-${{ needs.paths-check.outputs.lines_deleted }} lines,
|
|
258
|
+
prior_threads=${{ needs.paths-check.outputs.threads_count }}.
|
|
259
|
+
|
|
260
|
+
Plan accordingly per the "Turn budget self-rationing"
|
|
261
|
+
section in your system prompt. Reserve 5 turns for emit.
|
|
184
262
|
id: clud-bug-review
|
|
185
263
|
|
|
186
264
|
# v0.6.22 / 0.0.O: render structured output → post via gh pr comment.
|
|
265
|
+
# v0.6.25 / §5.5 Layer 1.5: append calibration marker.
|
|
187
266
|
- name: Render + post structured review
|
|
188
267
|
if: success() && steps.clud-bug-review.outputs.structured_output != ''
|
|
189
268
|
env:
|
|
190
269
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
191
270
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
192
271
|
STRUCTURED: ${{ steps.clud-bug-review.outputs.structured_output }}
|
|
272
|
+
TURNS_ESTIMATED: ${{ needs.paths-check.outputs.turns_estimated }}
|
|
273
|
+
MAX_TURNS: ${{ needs.paths-check.outputs.max_turns }}
|
|
274
|
+
FILES_COUNT: ${{ needs.paths-check.outputs.files_count }}
|
|
275
|
+
LINES_ADDED: ${{ needs.paths-check.outputs.lines_added }}
|
|
276
|
+
LINES_DELETED: ${{ needs.paths-check.outputs.lines_deleted }}
|
|
277
|
+
THREADS_COUNT: ${{ needs.paths-check.outputs.threads_count }}
|
|
193
278
|
run: |
|
|
194
279
|
set -euo pipefail
|
|
195
280
|
BODY=$(printf '%s\n' "$STRUCTURED" | npx --yes clud-bug@{{CLUD_BUG_VERSION}} render --stdin)
|
|
196
|
-
|
|
281
|
+
CALIBRATION="<!-- clud-bug-calibration: turns_estimated=$TURNS_ESTIMATED, max_turns=$MAX_TURNS, files=$FILES_COUNT, lines_added=$LINES_ADDED, lines_deleted=$LINES_DELETED, threads=$THREADS_COUNT -->"
|
|
282
|
+
gh pr comment "$PR_NUMBER" --body "$BODY
|
|
197
283
|
|
|
198
|
-
|
|
284
|
+
$CALIBRATION"
|
|
285
|
+
|
|
286
|
+
# v0.6.26 / §5.5 Layer 6 fallback render-from-inlines — see workflow.yml.tmpl for design notes.
|
|
199
287
|
- name: Fallback summary (structured_output empty)
|
|
200
288
|
if: success() && steps.clud-bug-review.outputs.structured_output == ''
|
|
201
289
|
env:
|
|
202
290
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
203
291
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
292
|
+
REPO: ${{ github.repository }}
|
|
293
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
294
|
+
TURNS_ESTIMATED: ${{ needs.paths-check.outputs.turns_estimated }}
|
|
295
|
+
MAX_TURNS: ${{ needs.paths-check.outputs.max_turns }}
|
|
296
|
+
FILES_COUNT: ${{ needs.paths-check.outputs.files_count }}
|
|
297
|
+
LINES_ADDED: ${{ needs.paths-check.outputs.lines_added }}
|
|
298
|
+
LINES_DELETED: ${{ needs.paths-check.outputs.lines_deleted }}
|
|
299
|
+
THREADS_COUNT: ${{ needs.paths-check.outputs.threads_count }}
|
|
204
300
|
run: |
|
|
205
301
|
set -euo pipefail
|
|
206
|
-
gh
|
|
302
|
+
INLINES=$(gh api "repos/$REPO/pulls/$PR_NUMBER/comments?per_page=100" \
|
|
303
|
+
--jq "[.[] | select(.user.login == \"claude[bot]\" and .commit_id == \"$HEAD_SHA\")]")
|
|
304
|
+
INLINE_COUNT=$(echo "$INLINES" | jq 'length')
|
|
305
|
+
CRITICAL=$(echo "$INLINES" | jq '[.[] | select(.body | test("🔴"))] | length')
|
|
306
|
+
MINOR=$(echo "$INLINES" | jq '[.[] | select(.body | test("🟡"))] | length')
|
|
307
|
+
PREEXISTING=$(echo "$INLINES" | jq '[.[] | select(.body | test("🟣"))] | length')
|
|
308
|
+
CALIBRATION="<!-- clud-bug-calibration: turns_estimated=$TURNS_ESTIMATED, max_turns=$MAX_TURNS, files=$FILES_COUNT, lines_added=$LINES_ADDED, lines_deleted=$LINES_DELETED, threads=$THREADS_COUNT, structured_output=empty, inline_findings=$INLINE_COUNT -->"
|
|
309
|
+
if [ "$INLINE_COUNT" -gt 0 ]; then
|
|
310
|
+
STATUS=""
|
|
311
|
+
[ "$CRITICAL" -gt 0 ] && STATUS=" — critical findings"
|
|
312
|
+
gh pr comment "$PR_NUMBER" --body "## 🐛 Clud Bug review${STATUS}
|
|
313
|
+
|
|
314
|
+
**This round:** $CRITICAL critical · $MINOR minor · 0 resolved from prior · 0 still open
|
|
315
|
+
|
|
316
|
+
Found: $CRITICAL 🔴 / $MINOR 🟡 / $PREEXISTING 🟣
|
|
317
|
+
|
|
318
|
+
⚠️ **Synthetic summary** (v0.6.26 §5.5 Layer 6 fallback). Inline findings above ARE the substantive review.
|
|
319
|
+
|
|
320
|
+
<!-- last-reviewed-sha: $HEAD_SHA -->
|
|
321
|
+
|
|
322
|
+
$CALIBRATION"
|
|
323
|
+
else
|
|
324
|
+
gh pr comment "$PR_NUMBER" --body "## 🐛 Clud Bug review
|
|
207
325
|
|
|
208
326
|
**This round:** 0 critical · 0 minor · 0 resolved from prior · 0 still open
|
|
209
327
|
|
|
210
328
|
Found: 0 🔴 / 0 🟡 / 0 🟣
|
|
211
329
|
|
|
212
|
-
⚠️ Structured output (\`--json-schema\`) returned empty
|
|
330
|
+
⚠️ Structured output (\`--json-schema\`) returned empty AND no inline findings posted. Investigate run logs.
|
|
331
|
+
|
|
332
|
+
Skills referenced: [none]
|
|
213
333
|
|
|
214
|
-
|
|
334
|
+
<!-- last-reviewed-sha: $HEAD_SHA -->
|
|
335
|
+
|
|
336
|
+
$CALIBRATION"
|
|
337
|
+
fi
|
|
215
338
|
|
|
216
339
|
# Strict-mode gate — composite action; see workflow.yml.tmpl for design notes.
|
|
217
340
|
- name: Strict mode — fail check on critical findings
|
|
218
341
|
if: success()
|
|
219
|
-
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.
|
|
342
|
+
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.26
|
|
220
343
|
with:
|
|
221
344
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
222
345
|
# v0.6.22 / 0.0.O: summary now posted by github-actions[bot].
|
|
@@ -1,20 +1,62 @@
|
|
|
1
|
-
# clud-bug-template-version:
|
|
1
|
+
# clud-bug-template-version: v12
|
|
2
2
|
name: Clud Bug 🐛 Crawls Your Code
|
|
3
3
|
|
|
4
4
|
on:
|
|
5
5
|
pull_request:
|
|
6
6
|
types: [opened, synchronize]
|
|
7
7
|
|
|
8
|
+
# v0.6.25: workflow-level concurrency — newer pushes cancel older
|
|
9
|
+
# in-flight runs on the same PR. Avoids the duplicate-review failure
|
|
10
|
+
# mode hit on tokenomics #21 where my merge-of-main and logmind's
|
|
11
|
+
# auto-regen-derived-docs follow-up fired two concurrent reviews
|
|
12
|
+
# (each posted its own "in-progress" todo comment).
|
|
13
|
+
concurrency:
|
|
14
|
+
group: clud-bug-review-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
8
17
|
jobs:
|
|
9
18
|
# Pre-flight: classify the PR diff to decide (a) whether to skip the
|
|
10
19
|
# LLM review entirely, and (b) which model to route to.
|
|
11
20
|
#
|
|
12
|
-
# (a) Workflow-only PRs (v0.6.14 / 0.0.W
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
21
|
+
# (a) Workflow-only / clud-bug-update PRs (v0.6.14 / 0.0.W;
|
|
22
|
+
# v0.6.26 / 0.0.W²): the LLM call is skipped when EITHER condition:
|
|
23
|
+
#
|
|
24
|
+
# - Every changed file matches `.github/workflows/clud-bug-*.yml`
|
|
25
|
+
# or `.github/actions/strict-mode-gate/**` (original 0.0.W —
|
|
26
|
+
# pure workflow PRs that claude-code-action would refuse anyway
|
|
27
|
+
# via its self-modification guard).
|
|
28
|
+
#
|
|
29
|
+
# - The PR is a recognized `clud-bug update` propagation: every
|
|
30
|
+
# changed file is in the clud-bug-update output allowlist AND a
|
|
31
|
+
# workflow file (or strict-mode-gate composite) is part of the
|
|
32
|
+
# change. v0.6.26 / 0.0.W² added this branch so the propagation
|
|
33
|
+
# dance doesn't require admin-bypass every cycle.
|
|
34
|
+
#
|
|
35
|
+
# Allowlist (files `clud-bug update` legitimately touches):
|
|
36
|
+
# .github/workflows/clud-bug-*.yml (workflow re-render)
|
|
37
|
+
# .github/actions/strict-mode-gate/* (composite re-render)
|
|
38
|
+
# AGENTS.md (logmind block + clud-bug stanza)
|
|
39
|
+
# .cursorrules / .clinerules / .windsurfrules / .continuerules
|
|
40
|
+
# .github/copilot-instructions.md
|
|
41
|
+
# .claude/skills/.clud-bug.json (manifest)
|
|
42
|
+
# .claude/skills/{critical-issues-only,evidence-based-review,
|
|
43
|
+
# respect-existing-conventions}/SKILL.md (baselines)
|
|
44
|
+
# docs/timeline.md / docs/file-structure.md / docs/decisions.md
|
|
45
|
+
# docs/decisions-branches/*.md (logmind side-effects)
|
|
46
|
+
#
|
|
47
|
+
# The workflow-change requirement is the SIGNATURE that distinguishes
|
|
48
|
+
# "agent ran clud-bug update" from "user is editing AGENTS.md by hand."
|
|
49
|
+
# An AGENTS.md-only PR (no workflow change) still goes through normal
|
|
50
|
+
# review — this catches the prompt-injection-via-AGENTS.md attack
|
|
51
|
+
# surface that a naive allowlist would open.
|
|
52
|
+
#
|
|
53
|
+
# Safety envelope:
|
|
54
|
+
# - Workflow file is still protected by the App-side guard (it just
|
|
55
|
+
# never runs in this branch).
|
|
56
|
+
# - Files in the allowlist are non-executable; modifying them can't
|
|
57
|
+
# grant code execution.
|
|
58
|
+
# - strictMode toggle is read from BASE ref so a PR can't disable
|
|
59
|
+
# strict-mode on itself.
|
|
18
60
|
#
|
|
19
61
|
# (b) Trivial PRs (v0.6.15 / 0.0.R): if the PR author is a dep-bumping
|
|
20
62
|
# bot (dependabot, renovate) OR the diff is small (<2KB) AND only
|
|
@@ -30,6 +72,17 @@ jobs:
|
|
|
30
72
|
is_workflow_only: ${{ steps.classify.outputs.is_workflow_only }}
|
|
31
73
|
model: ${{ steps.classify.outputs.model }}
|
|
32
74
|
max_turns: ${{ steps.classify.outputs.max_turns }}
|
|
75
|
+
# v0.6.25 / §5.5 Layer 1.5 (calibration measurement): emit the
|
|
76
|
+
# raw estimated-turns alongside the actual max_turns the workflow
|
|
77
|
+
# passes to claude-code-action. The post-step records both into
|
|
78
|
+
# the summary comment as a hidden HTML marker so we can tune the
|
|
79
|
+
# per-file coefficients (Layer 1) from real consumer-review data
|
|
80
|
+
# over the Step 4 30-day window. Re-tune in v0.6.26+.
|
|
81
|
+
turns_estimated: ${{ steps.classify.outputs.turns_estimated }}
|
|
82
|
+
files_count: ${{ steps.classify.outputs.files_count }}
|
|
83
|
+
lines_added: ${{ steps.classify.outputs.lines_added }}
|
|
84
|
+
lines_deleted: ${{ steps.classify.outputs.lines_deleted }}
|
|
85
|
+
threads_count: ${{ steps.classify.outputs.threads_count }}
|
|
33
86
|
steps:
|
|
34
87
|
- name: Classify PR diff
|
|
35
88
|
id: classify
|
|
@@ -53,22 +106,58 @@ jobs:
|
|
|
53
106
|
echo "is_workflow_only=false"
|
|
54
107
|
echo "model=$MODEL"
|
|
55
108
|
echo "max_turns=15"
|
|
109
|
+
echo "turns_estimated=0"
|
|
110
|
+
echo "files_count=0"
|
|
111
|
+
echo "lines_added=0"
|
|
112
|
+
echo "lines_deleted=0"
|
|
113
|
+
echo "threads_count=0"
|
|
56
114
|
} >> "$GITHUB_OUTPUT"
|
|
57
115
|
exit 0
|
|
58
116
|
fi
|
|
59
117
|
|
|
60
|
-
# --- (a) workflow-only classifier
|
|
61
|
-
|
|
118
|
+
# --- (a) workflow-only / clud-bug-update classifier
|
|
119
|
+
# (v0.6.14 / 0.0.W; widened in v0.6.26 / 0.0.W²) ---
|
|
120
|
+
# Two-track check:
|
|
121
|
+
# ALL_IN_ALLOWLIST = every changed file is in the
|
|
122
|
+
# clud-bug-update output allowlist (see header design notes
|
|
123
|
+
# for the full list + rationale).
|
|
124
|
+
# HAS_WORKFLOW_CHANGE = at least one workflow-file or
|
|
125
|
+
# strict-mode-gate change is present. This is the signature
|
|
126
|
+
# that distinguishes "clud-bug update output" from "user
|
|
127
|
+
# edited AGENTS.md by hand" — naked AGENTS.md edits go
|
|
128
|
+
# through normal review.
|
|
129
|
+
#
|
|
130
|
+
# Skip when both are true. Backward-compat with v0.6.14's
|
|
131
|
+
# `is_workflow_only` output name preserved (semantic widened to
|
|
132
|
+
# "skip-review-eligible"; the dependent clud-bug-review job
|
|
133
|
+
# gates on this output without needing a rename).
|
|
134
|
+
ALL_IN_ALLOWLIST=true
|
|
135
|
+
HAS_WORKFLOW_CHANGE=false
|
|
62
136
|
while IFS= read -r f; do
|
|
63
137
|
case "$f" in
|
|
64
|
-
.github/workflows/clud-bug-*.yml) ;;
|
|
65
|
-
.github/actions/strict-mode-gate/*) ;;
|
|
66
|
-
|
|
138
|
+
.github/workflows/clud-bug-*.yml) HAS_WORKFLOW_CHANGE=true ;;
|
|
139
|
+
.github/actions/strict-mode-gate/*) HAS_WORKFLOW_CHANGE=true ;;
|
|
140
|
+
# v0.6.26 / 0.0.W² additions — files clud-bug update produces.
|
|
141
|
+
AGENTS.md) ;;
|
|
142
|
+
.cursorrules|.clinerules|.windsurfrules|.continuerules) ;;
|
|
143
|
+
.github/copilot-instructions.md) ;;
|
|
144
|
+
.claude/skills/.clud-bug.json) ;;
|
|
145
|
+
.claude/skills/critical-issues-only/SKILL.md) ;;
|
|
146
|
+
.claude/skills/evidence-based-review/SKILL.md) ;;
|
|
147
|
+
.claude/skills/respect-existing-conventions/SKILL.md) ;;
|
|
148
|
+
# logmind side-effects of `logmind log` on the same commit.
|
|
149
|
+
docs/timeline.md|docs/file-structure.md|docs/decisions.md) ;;
|
|
150
|
+
docs/decisions-branches/*.md) ;;
|
|
151
|
+
*) ALL_IN_ALLOWLIST=false; break ;;
|
|
67
152
|
esac
|
|
68
153
|
done <<< "$CHANGED"
|
|
154
|
+
IS_WORKFLOW_ONLY=false
|
|
155
|
+
if [ "$ALL_IN_ALLOWLIST" = "true" ] && [ "$HAS_WORKFLOW_CHANGE" = "true" ]; then
|
|
156
|
+
IS_WORKFLOW_ONLY=true
|
|
157
|
+
fi
|
|
69
158
|
echo "is_workflow_only=$IS_WORKFLOW_ONLY" >> "$GITHUB_OUTPUT"
|
|
70
159
|
if [ "$IS_WORKFLOW_ONLY" = "true" ]; then
|
|
71
|
-
echo "::notice title=Clud Bug 🐛::Skipping LLM review —
|
|
160
|
+
echo "::notice title=Clud Bug 🐛::Skipping LLM review — clud-bug update output (workflow + allowlist files, no review surface)."
|
|
72
161
|
echo "model=$MODEL" >> "$GITHUB_OUTPUT"
|
|
73
162
|
exit 0
|
|
74
163
|
fi
|
|
@@ -113,37 +202,106 @@ jobs:
|
|
|
113
202
|
fi
|
|
114
203
|
echo "model=$MODEL" >> "$GITHUB_OUTPUT"
|
|
115
204
|
|
|
116
|
-
# --- (c)
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
205
|
+
# --- (c) smart budget estimation (v0.6.25 / §5.5 Layer 1) ---
|
|
206
|
+
# Replaces v0.6.23's 4-bucket if-elif with a line-based
|
|
207
|
+
# formula. File count was a crude proxy; lines + edit-type +
|
|
208
|
+
# file-class is much better. Per-file cost in turns:
|
|
209
|
+
#
|
|
210
|
+
# per_file_cost = 0.3 + added × tw × 1.0
|
|
211
|
+
# + modified × tw × 1.5 # context-heavy
|
|
212
|
+
# + deleted × tw × 0.1 # trivial
|
|
123
213
|
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
|
|
214
|
+
# type_weight tw (turns per line):
|
|
215
|
+
# code (.ts/.py/.js/.go/.rs/.java/...) : 1/50
|
|
216
|
+
# docs (.md/.txt/.rst/.adoc/...) : 1/150
|
|
217
|
+
# tests (.test.*/.spec.*/__tests__/*) : 1/100
|
|
218
|
+
# config (.yml/.toml/.json/.cfg) : 1/100
|
|
219
|
+
# derived (timeline.md/file-structure.md/decisions.md) : 0
|
|
220
|
+
#
|
|
221
|
+
# estimated_turns = 10 + sum(per_file_cost) + 1.5 × prior_threads
|
|
222
|
+
# max_turns = max(estimated × 1.2, 15) # 20% safety margin
|
|
223
|
+
# max_turns = min(max_turns, 60) # ceiling; L5 retry above
|
|
224
|
+
#
|
|
225
|
+
# Emit overhead 10 (raised from initial 5 design): tokenomics
|
|
226
|
+
# #21 (26 docs files, +310/-235) used ~25 turns vs old-formula
|
|
227
|
+
# 16 — structured-output emit + JSON-schema retries + initial
|
|
228
|
+
# context loading cost ~5-10 turns themselves. Will retune from
|
|
229
|
+
# Layer 1.5 calibration data in v0.6.26+.
|
|
230
|
+
#
|
|
231
|
+
# `gh` is the source for additions/deletions + threads.
|
|
232
|
+
# `jq` is the inline estimator (preinstalled on ubuntu-latest
|
|
233
|
+
# runners; an earlier design used python3 but jq sidesteps the
|
|
234
|
+
# YAML-block-indent vs heredoc-content-indent dance).
|
|
235
|
+
# Calibration via the turns_estimated + turns_actually_used data
|
|
236
|
+
# points the post-step records (Layer 1.5).
|
|
237
|
+
FILE_COUNT=$(echo "$CHANGED" | wc -l | tr -d ' ')
|
|
238
|
+
# Count unresolved claude-bot threads. Best-effort: rate-limit
|
|
239
|
+
# or auth failures default to 0 (no escalation, fall back to
|
|
240
|
+
# the line-count-only estimate).
|
|
241
|
+
THREAD_COUNT=$(gh api graphql -f query='{repository(owner:"'"$(echo "$REPO" | cut -d/ -f1)"'",name:"'"$(echo "$REPO" | cut -d/ -f2)"'"){pullRequest(number:'"$PR_NUMBER"'){reviewThreads(first:50){nodes{isResolved comments(first:1){nodes{author{login}}}}}}}}' --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and (.comments.nodes[0].author.login == "claude" or .comments.nodes[0].author.login == "claude[bot]"))] | length' 2>/dev/null || echo 0)
|
|
242
|
+
THREAD_COUNT=${THREAD_COUNT:-0}
|
|
243
|
+
|
|
130
244
|
if [ "$IS_TRIVIAL" = "true" ]; then
|
|
245
|
+
# Haiku route stays on a small flat budget — trivial PRs
|
|
246
|
+
# need ~5 turns; 10 leaves margin.
|
|
131
247
|
MAX_TURNS=10
|
|
248
|
+
TURNS_ESTIMATED=10
|
|
249
|
+
LINES_ADDED=0
|
|
250
|
+
LINES_DELETED=0
|
|
251
|
+
echo "::notice title=Clud Bug 🐛::Trivial (Haiku) budget: max_turns=10."
|
|
132
252
|
else
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
253
|
+
FILES_JSON=$(gh pr view "$PR_NUMBER" -R "$REPO" --json files --jq '.files' 2>/dev/null || echo '[]')
|
|
254
|
+
LINES_ADDED=$(echo "$FILES_JSON" | jq '[.[].additions] | add // 0' 2>/dev/null || echo 0)
|
|
255
|
+
LINES_DELETED=$(echo "$FILES_JSON" | jq '[.[].deletions] | add // 0' 2>/dev/null || echo 0)
|
|
256
|
+
LINES_ADDED=${LINES_ADDED:-0}
|
|
257
|
+
LINES_DELETED=${LINES_DELETED:-0}
|
|
258
|
+
|
|
259
|
+
# jq implements the formula from §5.5 Layer 1:
|
|
260
|
+
# per_file_cost = 0.3 + pa×tw + mod×1.5×tw + pd×0.1×tw
|
|
261
|
+
# estimated = 10 + sum(per_file_cost) + 1.5×threads
|
|
262
|
+
# type_weight tw (turns per line; reciprocal of lines-per-turn):
|
|
263
|
+
# code 1/50 = 0.02, docs 1/150 ≈ 0.00667,
|
|
264
|
+
# tests/config 1/100 = 0.01, unknown 1/80 = 0.0125
|
|
265
|
+
# Emit overhead 10 (raised from 5 in initial design): empirical
|
|
266
|
+
# tokenomics #21 (26 docs files, +310/-235) used ~25 turns vs
|
|
267
|
+
# formula-predicted 16 — structured-output emit + JSON-schema
|
|
268
|
+
# retries + initial context loading cost ~5-10 turns themselves.
|
|
269
|
+
# 10 matches better; will retune from Layer 1.5 calibration data.
|
|
270
|
+
# ceil via (+ 0.9999999 | floor) since jq has no ceil.
|
|
271
|
+
TURNS_ESTIMATED=$(echo "$FILES_JSON" | jq --argjson threads "$THREAD_COUNT" '
|
|
272
|
+
def per_line(path):
|
|
273
|
+
if (path | test("\\.(test|spec)\\.|__tests__|^tests?/")) then 0.01
|
|
274
|
+
elif (path | test("\\.(md|txt|rst|adoc|mdx)$")) then 0.00666667
|
|
275
|
+
elif (path | test("\\.(yml|yaml|toml|json|cfg|ini|conf|tmpl)$")) then 0.01
|
|
276
|
+
elif (path | test("\\.(ts|tsx|py|js|jsx|mjs|cjs|go|rs|java|kt|rb|php|cs|c|cpp|h|hpp|swift|scala)$")) then 0.02
|
|
277
|
+
else 0.0125 end;
|
|
278
|
+
map(
|
|
279
|
+
select(.path != "docs/timeline.md"
|
|
280
|
+
and .path != "docs/file-structure.md"
|
|
281
|
+
and .path != "docs/decisions.md")
|
|
282
|
+
| (.additions) as $add | (.deletions) as $del
|
|
283
|
+
| (if $add < $del then $add else $del end) as $mod
|
|
284
|
+
| ($add - $mod) as $pa | ($del - $mod) as $pd
|
|
285
|
+
| per_line(.path) as $tw
|
|
286
|
+
| 0.3 + ($pa * $tw) + ($mod * 1.5 * $tw) + ($pd * 0.1 * $tw)
|
|
287
|
+
) | (add // 0) + 10 + ($threads * 1.5) | (. + 0.9999999) | floor
|
|
288
|
+
' 2>/dev/null || echo 15)
|
|
289
|
+
TURNS_ESTIMATED=${TURNS_ESTIMATED:-15}
|
|
290
|
+
|
|
291
|
+
# max_turns = ceil(estimated × 1.2), floor 15, ceiling 60
|
|
292
|
+
MAX_TURNS=$(( (TURNS_ESTIMATED * 12 + 9) / 10 ))
|
|
293
|
+
[ "$MAX_TURNS" -lt 15 ] && MAX_TURNS=15
|
|
294
|
+
[ "$MAX_TURNS" -gt 60 ] && MAX_TURNS=60
|
|
295
|
+
echo "::notice title=Clud Bug 🐛::Smart budget: estimated $TURNS_ESTIMATED turns → max_turns=$MAX_TURNS ($FILE_COUNT files, +$LINES_ADDED/-$LINES_DELETED lines, $THREAD_COUNT prior threads). v0.6.25 Phase 1; coefficients TBD from calibration data."
|
|
145
296
|
fi
|
|
146
|
-
|
|
297
|
+
{
|
|
298
|
+
echo "max_turns=$MAX_TURNS"
|
|
299
|
+
echo "turns_estimated=$TURNS_ESTIMATED"
|
|
300
|
+
echo "files_count=$FILE_COUNT"
|
|
301
|
+
echo "lines_added=$LINES_ADDED"
|
|
302
|
+
echo "lines_deleted=$LINES_DELETED"
|
|
303
|
+
echo "threads_count=$THREAD_COUNT"
|
|
304
|
+
} >> "$GITHUB_OUTPUT"
|
|
147
305
|
|
|
148
306
|
clud-bug-review:
|
|
149
307
|
needs: paths-check
|
|
@@ -288,44 +446,133 @@ jobs:
|
|
|
288
446
|
Review this pull request following the discipline in your
|
|
289
447
|
system prompt — every rule about skill routing, comment
|
|
290
448
|
format, the strict-mode header, the two-surface review
|
|
291
|
-
shape,
|
|
449
|
+
shape, the FIX-PUSH FLOW, and (v0.6.25+) turn-budget
|
|
450
|
+
self-rationing applies.
|
|
451
|
+
|
|
452
|
+
## Your turn budget for this PR (v0.6.25 / §5.5 Layer 2)
|
|
453
|
+
|
|
454
|
+
max_turns=${{ needs.paths-check.outputs.max_turns }},
|
|
455
|
+
estimated=${{ needs.paths-check.outputs.turns_estimated }},
|
|
456
|
+
files=${{ needs.paths-check.outputs.files_count }},
|
|
457
|
+
+${{ needs.paths-check.outputs.lines_added }}/-${{ needs.paths-check.outputs.lines_deleted }} lines,
|
|
458
|
+
prior_threads=${{ needs.paths-check.outputs.threads_count }}.
|
|
459
|
+
|
|
460
|
+
Plan accordingly per the "Turn budget self-rationing"
|
|
461
|
+
section in your system prompt. The estimate is the
|
|
462
|
+
pre-flight's best guess; the cap is the workflow's hard
|
|
463
|
+
stop. Reserve 5 turns for emit.
|
|
292
464
|
id: clud-bug-review
|
|
293
465
|
|
|
294
466
|
# v0.6.22 / 0.0.O: render the structured output to the summary
|
|
295
467
|
# comment shape, post via gh pr comment. Guarded so we only run
|
|
296
468
|
# when the action returned a non-empty structured payload
|
|
297
469
|
# (max-retries hit → empty → fall through to the next step).
|
|
470
|
+
#
|
|
471
|
+
# v0.6.25 / §5.5 Layer 1.5 (calibration measurement): append a
|
|
472
|
+
# hidden HTML marker carrying the pre-flight estimate + the
|
|
473
|
+
# actual max_turns we passed to claude-code-action. Aggregation
|
|
474
|
+
# script lives in clud-bug usage --calibration (TBD v0.6.26+);
|
|
475
|
+
# for now, the marker is just an audit trail visible via
|
|
476
|
+
# `gh api ...issues/N/comments`.
|
|
298
477
|
- name: Render + post structured review
|
|
299
478
|
if: success() && steps.clud-bug-review.outputs.structured_output != ''
|
|
300
479
|
env:
|
|
301
480
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
302
481
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
303
482
|
STRUCTURED: ${{ steps.clud-bug-review.outputs.structured_output }}
|
|
483
|
+
TURNS_ESTIMATED: ${{ needs.paths-check.outputs.turns_estimated }}
|
|
484
|
+
MAX_TURNS: ${{ needs.paths-check.outputs.max_turns }}
|
|
485
|
+
FILES_COUNT: ${{ needs.paths-check.outputs.files_count }}
|
|
486
|
+
LINES_ADDED: ${{ needs.paths-check.outputs.lines_added }}
|
|
487
|
+
LINES_DELETED: ${{ needs.paths-check.outputs.lines_deleted }}
|
|
488
|
+
THREADS_COUNT: ${{ needs.paths-check.outputs.threads_count }}
|
|
304
489
|
run: |
|
|
305
490
|
set -euo pipefail
|
|
306
491
|
BODY=$(printf '%s\n' "$STRUCTURED" | npx --yes clud-bug@{{CLUD_BUG_VERSION}} render --stdin)
|
|
307
|
-
|
|
492
|
+
CALIBRATION="<!-- clud-bug-calibration: turns_estimated=$TURNS_ESTIMATED, max_turns=$MAX_TURNS, files=$FILES_COUNT, lines_added=$LINES_ADDED, lines_deleted=$LINES_DELETED, threads=$THREADS_COUNT -->"
|
|
493
|
+
gh pr comment "$PR_NUMBER" --body "$BODY
|
|
494
|
+
|
|
495
|
+
$CALIBRATION"
|
|
308
496
|
|
|
309
497
|
# Fallback comment when the model couldn't produce schema-valid
|
|
310
498
|
# output after max retries (structured_output is empty). Keeps a
|
|
311
499
|
# bare H2 header so the strict-mode gate sees a comment and falls
|
|
312
500
|
# open (advisory) rather than panicking on a missing summary.
|
|
501
|
+
# v0.6.26 / §5.5 Layer 6: when structured_output is empty BUT inline
|
|
502
|
+
# findings were posted before the budget exhausted (tokenomics #21
|
|
503
|
+
# pattern), scrape the inline findings via gh api and render a
|
|
504
|
+
# synthetic summary that cites the real findings. Avoids the
|
|
505
|
+
# "0-findings-shown / 5-inline-posted" failure mode where the
|
|
506
|
+
# maintainer reading the PR sees a meaningless empty summary even
|
|
507
|
+
# though the review actually surfaced issues.
|
|
508
|
+
#
|
|
509
|
+
# If NO inline findings were posted either, falls through to the
|
|
510
|
+
# original advisory bare-H2 (legacy v0.6.22 behaviour).
|
|
313
511
|
- name: Fallback summary (structured_output empty)
|
|
314
512
|
if: success() && steps.clud-bug-review.outputs.structured_output == ''
|
|
315
513
|
env:
|
|
316
514
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
317
515
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
516
|
+
REPO: ${{ github.repository }}
|
|
517
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
518
|
+
TURNS_ESTIMATED: ${{ needs.paths-check.outputs.turns_estimated }}
|
|
519
|
+
MAX_TURNS: ${{ needs.paths-check.outputs.max_turns }}
|
|
520
|
+
FILES_COUNT: ${{ needs.paths-check.outputs.files_count }}
|
|
521
|
+
LINES_ADDED: ${{ needs.paths-check.outputs.lines_added }}
|
|
522
|
+
LINES_DELETED: ${{ needs.paths-check.outputs.lines_deleted }}
|
|
523
|
+
THREADS_COUNT: ${{ needs.paths-check.outputs.threads_count }}
|
|
318
524
|
run: |
|
|
319
525
|
set -euo pipefail
|
|
320
|
-
|
|
526
|
+
|
|
527
|
+
# Layer 6: count inline findings claude[bot] posted on THIS
|
|
528
|
+
# head SHA via the inline-review-thread endpoint. Filter to
|
|
529
|
+
# the current SHA so older review-pass findings don't inflate
|
|
530
|
+
# the count.
|
|
531
|
+
INLINES=$(gh api "repos/$REPO/pulls/$PR_NUMBER/comments?per_page=100" \
|
|
532
|
+
--jq "[.[] | select(.user.login == \"claude[bot]\" and .commit_id == \"$HEAD_SHA\")]")
|
|
533
|
+
INLINE_COUNT=$(echo "$INLINES" | jq 'length')
|
|
534
|
+
CRITICAL=$(echo "$INLINES" | jq '[.[] | select(.body | test("🔴"))] | length')
|
|
535
|
+
MINOR=$(echo "$INLINES" | jq '[.[] | select(.body | test("🟡"))] | length')
|
|
536
|
+
PREEXISTING=$(echo "$INLINES" | jq '[.[] | select(.body | test("🟣"))] | length')
|
|
537
|
+
# Findings with no emoji prefix (cross-cutting / un-prefixed) — count once.
|
|
538
|
+
UNPREFIXED=$(( INLINE_COUNT - CRITICAL - MINOR - PREEXISTING ))
|
|
539
|
+
[ "$UNPREFIXED" -lt 0 ] && UNPREFIXED=0
|
|
540
|
+
|
|
541
|
+
CALIBRATION="<!-- clud-bug-calibration: turns_estimated=$TURNS_ESTIMATED, max_turns=$MAX_TURNS, files=$FILES_COUNT, lines_added=$LINES_ADDED, lines_deleted=$LINES_DELETED, threads=$THREADS_COUNT, structured_output=empty, inline_findings=$INLINE_COUNT -->"
|
|
542
|
+
|
|
543
|
+
if [ "$INLINE_COUNT" -gt 0 ]; then
|
|
544
|
+
# L6 synthetic summary — cites the real inline findings the
|
|
545
|
+
# action managed to post before structured-output emit failed.
|
|
546
|
+
STATUS=""
|
|
547
|
+
[ "$CRITICAL" -gt 0 ] && STATUS=" — critical findings"
|
|
548
|
+
gh pr comment "$PR_NUMBER" --body "## 🐛 Clud Bug review${STATUS}
|
|
549
|
+
|
|
550
|
+
**This round:** $CRITICAL critical · $MINOR minor · 0 resolved from prior · 0 still open
|
|
551
|
+
|
|
552
|
+
Found: $CRITICAL 🔴 / $MINOR 🟡 / $PREEXISTING 🟣
|
|
553
|
+
|
|
554
|
+
⚠️ **Synthetic summary** (v0.6.26 §5.5 Layer 6 fallback) — the action posted $INLINE_COUNT inline finding(s) before the structured-output emit step exhausted its turn budget (or hit schema-validation retries). The inline findings above ARE the substantive review; this summary is reconstructed from them. v0.6.26's Layer 5 (auto-retry on cap-hit) is the more permanent fix; this is the safety net.
|
|
555
|
+
|
|
556
|
+
<!-- last-reviewed-sha: $HEAD_SHA -->
|
|
557
|
+
|
|
558
|
+
$CALIBRATION"
|
|
559
|
+
else
|
|
560
|
+
# No inline findings either — legacy bare-H2 advisory. The
|
|
561
|
+
# action errored before it could post anything substantive.
|
|
562
|
+
gh pr comment "$PR_NUMBER" --body "## 🐛 Clud Bug review
|
|
321
563
|
|
|
322
564
|
**This round:** 0 critical · 0 minor · 0 resolved from prior · 0 still open
|
|
323
565
|
|
|
324
566
|
Found: 0 🔴 / 0 🟡 / 0 🟣
|
|
325
567
|
|
|
326
|
-
⚠️ Structured output (\`--json-schema\`) returned empty
|
|
568
|
+
⚠️ Structured output (\`--json-schema\`) returned empty AND no inline findings were posted. The action likely errored before producing review output — investigate the run logs.
|
|
569
|
+
|
|
570
|
+
Skills referenced: [none]
|
|
327
571
|
|
|
328
|
-
|
|
572
|
+
<!-- last-reviewed-sha: $HEAD_SHA -->
|
|
573
|
+
|
|
574
|
+
$CALIBRATION"
|
|
575
|
+
fi
|
|
329
576
|
|
|
330
577
|
# Strict-mode gate. Fails the check when the BASE ref's manifest
|
|
331
578
|
# has { "strictMode": true } AND the latest clud-bug review's first
|
|
@@ -342,7 +589,7 @@ jobs:
|
|
|
342
589
|
# Letting the action's own failure fail the check is louder and right.
|
|
343
590
|
- name: Strict mode — fail check on critical findings
|
|
344
591
|
if: success()
|
|
345
|
-
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.
|
|
592
|
+
uses: thrillmade/clud-bug/.github/actions/strict-mode-gate@v0.6.26
|
|
346
593
|
with:
|
|
347
594
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
348
595
|
# v0.6.22 / 0.0.O: the summary is now posted by the workflow
|