clud-bug 0.6.34 → 0.7.0-rc.2
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/bin/clud-bug.js +10 -1353
- package/dist/cli/agents-md.d.ts +16 -0
- package/dist/cli/agents-md.d.ts.map +1 -0
- package/dist/cli/agents-md.js +226 -0
- package/dist/cli/agents-md.js.map +1 -0
- package/dist/cli/audit.d.ts +13 -0
- package/dist/cli/audit.d.ts.map +1 -0
- package/dist/cli/audit.js +90 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/branch-protection.d.ts +57 -0
- package/dist/cli/branch-protection.d.ts.map +1 -0
- package/dist/cli/branch-protection.js +118 -0
- package/dist/cli/branch-protection.js.map +1 -0
- package/dist/cli/edit-workflow.d.ts +18 -0
- package/dist/cli/edit-workflow.d.ts.map +1 -0
- package/dist/cli/edit-workflow.js +43 -0
- package/dist/cli/edit-workflow.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +1336 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/skill-usage.d.ts +109 -0
- package/dist/cli/skill-usage.d.ts.map +1 -0
- package/dist/cli/skill-usage.js +380 -0
- package/dist/cli/skill-usage.js.map +1 -0
- package/dist/cli/skills.d.ts +56 -0
- package/dist/cli/skills.d.ts.map +1 -0
- package/dist/cli/skills.js +292 -0
- package/dist/cli/skills.js.map +1 -0
- package/dist/cli/update.d.ts +29 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/update.js +186 -0
- package/dist/cli/update.js.map +1 -0
- package/dist/cli/usage.d.ts +142 -0
- package/dist/cli/usage.d.ts.map +1 -0
- package/dist/cli/usage.js +348 -0
- package/dist/cli/usage.js.map +1 -0
- package/dist/core/audit.d.ts +8 -0
- package/dist/core/audit.d.ts.map +1 -0
- package/dist/core/audit.js +47 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/detect.d.ts +77 -0
- package/dist/core/detect.d.ts.map +1 -0
- package/dist/core/detect.js +262 -0
- package/dist/core/detect.js.map +1 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +31 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/prompt-builder.d.ts +164 -0
- package/dist/core/prompt-builder.d.ts.map +1 -0
- package/dist/core/prompt-builder.js +419 -0
- package/dist/core/prompt-builder.js.map +1 -0
- package/dist/core/prompts.d.ts +9 -0
- package/dist/core/prompts.d.ts.map +1 -0
- package/dist/core/prompts.js +401 -0
- package/dist/core/prompts.js.map +1 -0
- package/dist/core/render-review.d.ts +6 -0
- package/dist/core/render-review.d.ts.map +1 -0
- package/dist/core/render-review.js +219 -0
- package/dist/core/render-review.js.map +1 -0
- package/dist/core/render.d.ts +13 -0
- package/dist/core/render.d.ts.map +1 -0
- package/dist/core/render.js +80 -0
- package/dist/core/render.js.map +1 -0
- package/dist/core/review-schema-zod.d.ts +240 -0
- package/dist/core/review-schema-zod.d.ts.map +1 -0
- package/dist/core/review-schema-zod.js +218 -0
- package/dist/core/review-schema-zod.js.map +1 -0
- package/dist/core/review-schema.d.ts +42 -0
- package/dist/core/review-schema.d.ts.map +1 -0
- package/dist/core/review-schema.js +156 -0
- package/dist/core/review-schema.js.map +1 -0
- package/dist/core/review-writeback.d.ts +139 -0
- package/dist/core/review-writeback.d.ts.map +1 -0
- package/dist/core/review-writeback.js +313 -0
- package/dist/core/review-writeback.js.map +1 -0
- package/dist/core/skills.d.ts +122 -0
- package/dist/core/skills.d.ts.map +1 -0
- package/dist/core/skills.js +636 -0
- package/dist/core/skills.js.map +1 -0
- package/package.json +30 -4
- package/{lib/agents-md.js → src/cli/agents-md.ts} +25 -14
- package/{lib/audit.js → src/cli/audit.ts} +37 -44
- package/{lib/branch-protection.js → src/cli/branch-protection.ts} +75 -11
- package/{lib/edit-workflow.js → src/cli/edit-workflow.ts} +32 -11
- package/src/cli/index.ts +101 -0
- package/src/cli/main.ts +1376 -0
- package/{lib/skill-usage.js → src/cli/skill-usage.ts} +168 -94
- package/src/cli/skills.ts +386 -0
- package/{lib/update.js → src/cli/update.ts} +68 -27
- package/{lib/usage.js → src/cli/usage.ts} +167 -76
- package/src/core/audit.ts +53 -0
- package/{lib/detect.js → src/core/detect.ts} +100 -47
- package/src/core/index.ts +155 -0
- package/src/core/prompt-builder.ts +561 -0
- package/{lib/prompts.js → src/core/prompts.ts} +16 -2
- package/{lib/render-review.js → src/core/render-review.ts} +57 -25
- package/{lib/render.js → src/core/render.ts} +36 -10
- package/src/core/review-schema-zod.ts +262 -0
- package/{lib/review-schema.js → src/core/review-schema.ts} +68 -5
- package/src/core/review-writeback.ts +446 -0
- package/{lib/skills.js → src/core/skills.ts} +339 -342
- package/templates/workflow-py.yml.tmpl +2 -2
- package/templates/workflow-ts.yml.tmpl +2 -2
- package/templates/workflow.yml.tmpl +17 -8
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// Source of truth for the clud-bug review prompt.
|
|
2
|
+
//
|
|
3
|
+
// Pre-v0.6.2 this prompt lived inline in templates/workflow{,-ts,-py}.yml.tmpl
|
|
4
|
+
// (215 lines × 3 templates, with language-specific bullets diverging). The
|
|
5
|
+
// extraction here lets v0.6.2+ ship downstream changes (caching prefix split,
|
|
6
|
+
// per-section budgets, comment format updates) by editing one function
|
|
7
|
+
// instead of three templates.
|
|
8
|
+
const LANGUAGE_HINT_BLOCKS = {
|
|
9
|
+
generic: ['- Broken or missing test coverage for new code'],
|
|
10
|
+
ts: [
|
|
11
|
+
'- Broken or missing test coverage for new code',
|
|
12
|
+
'- TypeScript type safety issues (unsafe casts, missing types, incorrect generics)',
|
|
13
|
+
'- Incorrect ESM/CJS module usage',
|
|
14
|
+
'- Improper async/await or Promise handling (unhandled rejections, missing awaits)',
|
|
15
|
+
'- Incorrect use of common Node.js patterns',
|
|
16
|
+
],
|
|
17
|
+
py: [
|
|
18
|
+
'- Incorrect exception handling (bare excepts, swallowed errors, wrong exception types)',
|
|
19
|
+
'- Missing type hints on new functions',
|
|
20
|
+
'- Incorrect use of Click (exit codes, error messages) if the project uses it',
|
|
21
|
+
'- Missing pytest coverage for new code',
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
// Returns the review prompt body as a multi-line string with no per-line
|
|
25
|
+
// indentation. Callers pass it through `renderFile`, which indent-aware
|
|
26
|
+
// substitutes it into the template's `{{REVIEW_PROMPT}}` placeholder so
|
|
27
|
+
// the result is properly indented inside the YAML `prompt: |` block.
|
|
28
|
+
//
|
|
29
|
+
// `language` selects the language-specific bullets in the "Focus on:"
|
|
30
|
+
// list:
|
|
31
|
+
// - 'generic' (default): just "test coverage"
|
|
32
|
+
// - 'ts': test coverage + 4 TypeScript-specific bullets
|
|
33
|
+
// - 'py': 4 Python-specific bullets (replaces "test coverage")
|
|
34
|
+
export function reviewPrompt(options = {}) {
|
|
35
|
+
const { projectDescription, language = 'generic' } = options;
|
|
36
|
+
if (projectDescription === undefined) {
|
|
37
|
+
throw new Error('reviewPrompt: projectDescription is required');
|
|
38
|
+
}
|
|
39
|
+
const hints = LANGUAGE_HINT_BLOCKS[language];
|
|
40
|
+
if (!hints) {
|
|
41
|
+
throw new Error(`reviewPrompt: unknown language '${language}'`);
|
|
42
|
+
}
|
|
43
|
+
const focusBullets = [
|
|
44
|
+
'- Bugs, logic errors, or incorrect behaviour',
|
|
45
|
+
'- Security vulnerabilities',
|
|
46
|
+
'- Performance problems',
|
|
47
|
+
...hints,
|
|
48
|
+
].join('\n');
|
|
49
|
+
return `${projectDescription}
|
|
50
|
+
|
|
51
|
+
Review this pull request for critical issues only. Focus on:
|
|
52
|
+
${focusBullets}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
Skip style suggestions, minor naming issues, or anything that
|
|
56
|
+
doesn't affect correctness, security, or performance.
|
|
57
|
+
|
|
58
|
+
Section budgets (v0.6.4+):
|
|
59
|
+
Cap fetched content with \`head -c\` to control input cost. Workflow
|
|
60
|
+
exposes MAX_DIFF_BYTES / MAX_COMMENT_BYTES / MAX_SKILL_BYTES. The
|
|
61
|
+
cached system prefix is free at 10%; per-PR fetches are not.
|
|
62
|
+
|
|
63
|
+
- PR diff (incremental on fix-push — v0.6.10+):
|
|
64
|
+
On a re-review (not first pass), fetch only the delta between
|
|
65
|
+
your prior pass and HEAD instead of the full PR. The handshake
|
|
66
|
+
state lives in your PRIOR SUMMARY COMMENT as an HTML marker:
|
|
67
|
+
\`<!-- last-reviewed-sha: <sha> -->\`.
|
|
68
|
+
|
|
69
|
+
Identifying the PRIOR SUMMARY (v0.6.22+ identity note):
|
|
70
|
+
From v0.6.22 (0.0.O) onward the summary is posted by a workflow
|
|
71
|
+
post-step under the \`github-actions[bot]\` identity, not
|
|
72
|
+
claude[bot]. Inline review threads are still claude[bot] (the
|
|
73
|
+
MCP tool routes through claude-code-action). Beware: the
|
|
74
|
+
in-progress \`Claude Code is working…\` comment claude-code-action
|
|
75
|
+
posts BEFORE this prompt runs is claude[bot]-authored but is NOT
|
|
76
|
+
your prior summary (no marker). Anchor on the H2 line
|
|
77
|
+
\`## 🐛 Clud Bug review\` — same anchor strict-mode uses.
|
|
78
|
+
|
|
79
|
+
Detection in three steps:
|
|
80
|
+
|
|
81
|
+
1. Fetch github-actions[bot] comments newest-first:
|
|
82
|
+
\`gh api "repos/$REPO_OWNER/$REPO_NAME/issues/$PR_NUMBER/comments?per_page=100&sort=created&direction=desc"\`
|
|
83
|
+
Walk them in order; filter to author \`github-actions[bot]\`,
|
|
84
|
+
and find the FIRST whose body starts with
|
|
85
|
+
\`## 🐛 Clud Bug review\`. THAT is your prior summary. In
|
|
86
|
+
its body, look for \`last-reviewed-sha: <sha>\`. (Pre-v0.6.22 summaries are claude[bot]-authored — if you find a recent claude[bot] comment with the H2 anchor and no newer github-actions[bot] match, treat it as the prior summary.)
|
|
87
|
+
|
|
88
|
+
2. If a SHA was found, verify it's still in HEAD's ancestry:
|
|
89
|
+
\`git merge-base --is-ancestor <prior_sha> $HEAD_SHA\`
|
|
90
|
+
(exit 0 = yes, ancestry intact; non-zero = rebased/force-pushed).
|
|
91
|
+
|
|
92
|
+
3. Branch:
|
|
93
|
+
- Marker present AND ancestor intact (well-behaved fix-push):
|
|
94
|
+
\`git diff <prior_sha>..$HEAD_SHA | head -c "$MAX_DIFF_BYTES"\`
|
|
95
|
+
- Marker missing OR not an ancestor (first review or rebase):
|
|
96
|
+
\`gh pr diff "$PR_NUMBER" | head -c "$MAX_DIFF_BYTES"\`
|
|
97
|
+
|
|
98
|
+
Default cap is 80,000 bytes — covers ~95% of real PRs unbruised.
|
|
99
|
+
If output looks truncated mid-line, request the omitted hunks via
|
|
100
|
+
\`gh pr diff "$PR_NUMBER" --name-only\` + a targeted re-fetch.
|
|
101
|
+
|
|
102
|
+
Span check: if a delta-finding might affect callers outside the
|
|
103
|
+
delta, do a one-time full \`gh pr diff\` before flagging — the
|
|
104
|
+
incremental view is for fast re-confirmation, not blind trust.
|
|
105
|
+
|
|
106
|
+
- Skill files: \`head -c "$MAX_SKILL_BYTES" .claude/skills/<name>/SKILL.md\`
|
|
107
|
+
per file (default 4,000 bytes). Baseline skills fit easily;
|
|
108
|
+
bloated user-added skills get truncated.
|
|
109
|
+
|
|
110
|
+
- PR comments: \`gh api "repos/$REPO_OWNER/$REPO_NAME/issues/$PR_NUMBER/comments?per_page=20" --jq '.[] | select(.user.login != "claude[bot]" and .user.login != "github-actions[bot]")' | head -c "$MAX_COMMENT_BYTES"\`
|
|
111
|
+
(default 20,000 bytes, 20 most-recent). Skips your own prior
|
|
112
|
+
comments — the FIX-PUSH FLOW handles those via reviewThreads
|
|
113
|
+
GraphQL instead.
|
|
114
|
+
|
|
115
|
+
Tee-hint on cap fire (v0.6.18, RTK-inspired):
|
|
116
|
+
When ANY \`head -c "$MAX_*"\` cap fires (last line cut mid-token, or
|
|
117
|
+
\`wc -c\` on the captured output equals the cap exactly), you MUST do
|
|
118
|
+
two things, in order:
|
|
119
|
+
|
|
120
|
+
1. Attempt ONE targeted re-fetch with double the cap on the specific
|
|
121
|
+
truncated section. Example for diff: \`gh pr diff "$PR_NUMBER" |
|
|
122
|
+
head -c $((MAX_DIFF_BYTES * 2))\`. For skills: re-fetch the
|
|
123
|
+
specific \`.claude/skills/<name>/SKILL.md\` that hit the cap with
|
|
124
|
+
\`head -c $((MAX_SKILL_BYTES * 2))\` — name the file. For
|
|
125
|
+
comments: re-fetch with \`per_page=40\` AND \`head -c
|
|
126
|
+
$((MAX_COMMENT_BYTES * 2))\` — doubling per_page alone is wasted
|
|
127
|
+
work when the original truncation was byte-bound.
|
|
128
|
+
|
|
129
|
+
2. Add a \`### Diagnostics\` block above the Skills-referenced
|
|
130
|
+
footer (the SHA marker still goes last on its own line —
|
|
131
|
+
Diagnostics is not the last thing in the comment). Each line
|
|
132
|
+
names a cap that fired, the section affected, and outcome
|
|
133
|
+
("recovered with 2x cap", "still truncated", "finding deferred").
|
|
134
|
+
|
|
135
|
+
Producer-side half of RTK's \`force_tee_tail_hint\`: never elide
|
|
136
|
+
without naming what was elided. If re-fetch still leaves you unable
|
|
137
|
+
to review safely, say so plainly instead of speculating.
|
|
138
|
+
|
|
139
|
+
Skills carry authority. Scan loaded skills in .claude/skills/ before
|
|
140
|
+
flagging any finding; if one applies, reference it by name (e.g.
|
|
141
|
+
"[evidence-based-review]: claim isn't anchored to a line"). Generic
|
|
142
|
+
advice contradicting a project skill is wrong by definition.
|
|
143
|
+
|
|
144
|
+
Skill routing — shared vs dedicated:
|
|
145
|
+
Each SKILL.md frontmatter (first \`---\`-delimited block) has a
|
|
146
|
+
\`review_mode:\` field:
|
|
147
|
+
- \`shared\` — bug-finding / convention / evidence. Findings bundle
|
|
148
|
+
into the standard "Critical findings" / "Minor findings" sections.
|
|
149
|
+
- \`dedicated\` — domain-specific (brand voice, compliance,
|
|
150
|
+
API-contract, test-discipline). Each gets its own H3 section.
|
|
151
|
+
- Missing → treat as \`shared\`.
|
|
152
|
+
|
|
153
|
+
Skill applies_to (v0.6.21 / 0.0.K):
|
|
154
|
+
Frontmatter may also have \`applies_to:\` with \`paths:\` (glob list)
|
|
155
|
+
and/or \`extensions:\` (extension list). Scan each skill's frontmatter
|
|
156
|
+
first (cheap — just the \`---\` block). If applies_to is present and
|
|
157
|
+
the PR's changed files (from \`gh pr diff --name-only\`) match NONE
|
|
158
|
+
of the declared paths or extensions, SKIP that skill's body — don't
|
|
159
|
+
read it, don't reference it. Skills without applies_to load
|
|
160
|
+
unconditionally (back-compat). Net effect: a UI-scoped skill stays
|
|
161
|
+
unread on a backend-only PR.
|
|
162
|
+
|
|
163
|
+
Read each applicable body capped: \`head -c "$MAX_SKILL_BYTES" .claude/skills/<name>/SKILL.md\`
|
|
164
|
+
|
|
165
|
+
At review end, append a single-line footer:
|
|
166
|
+
Skills referenced: [skill-name-1, skill-name-2, ...]
|
|
167
|
+
"[none]" with reason if no installed skill applied.
|
|
168
|
+
|
|
169
|
+
Output-token budget (v0.6.16 / 0.0.X):
|
|
170
|
+
Keep total output under ~600 tokens. Per finding:
|
|
171
|
+
- One-sentence claim
|
|
172
|
+
- <details>Reasoning</details> ≤ 80 words
|
|
173
|
+
- No code quotes > 2 lines
|
|
174
|
+
- Omit reasoning that doesn't change the verdict
|
|
175
|
+
Not a hard cap (SDK doesn't expose max_tokens); brevity compounds
|
|
176
|
+
across the org on every review.
|
|
177
|
+
|
|
178
|
+
Turn budget self-rationing (v0.6.25 / §5.5 Layer 2):
|
|
179
|
+
You are run with a finite \`--max-turns\` cap. The exact value, plus
|
|
180
|
+
the pre-flight estimate for this PR, lands in the per-PR prompt
|
|
181
|
+
section below as \`max_turns=N, estimated=M, files=F, +A/-D lines, threads=T\`.
|
|
182
|
+
Treat that as your budget, not a ceiling to test.
|
|
183
|
+
|
|
184
|
+
Rules:
|
|
185
|
+
- Reserve the LAST 5 turns for structured-output emit. It is
|
|
186
|
+
non-skippable; if you run out before emitting, the workflow
|
|
187
|
+
falls back to a synthetic summary scraped from your inline
|
|
188
|
+
findings (v0.6.26+ Layer 6) — strictly worse than a real one.
|
|
189
|
+
- Rough rates: ~1 turn per 50 lines of code, ~1 turn per 150 lines
|
|
190
|
+
of docs, ~1 turn per 100 lines of tests or config. Each prior
|
|
191
|
+
unresolved thread is ~1.5 turns to walk + decide.
|
|
192
|
+
- Every line of the diff MUST be in your scan. Never silently skip
|
|
193
|
+
a file. If you must shorten coverage to fit budget, switch from
|
|
194
|
+
deep-dive analysis to one-sentence per-file verdicts — but every
|
|
195
|
+
file gets a verdict, even if it's "no issues found".
|
|
196
|
+
- If you're falling behind pace (files_reviewed/files_total
|
|
197
|
+
materially less than turns_used/max_turns), broaden+shallow over
|
|
198
|
+
deep+narrow. Audit visibility lives in \`per_skill_scan\` — list
|
|
199
|
+
every file's verdict so a maintainer can verify nothing was
|
|
200
|
+
skipped.
|
|
201
|
+
|
|
202
|
+
Mid-review self-check-in (v0.6.27 / §5.5 Layer 3):
|
|
203
|
+
After every 5 tool_uses, write a single-line budget heartbeat as a
|
|
204
|
+
free-text "thinking" message (not a tool call — these don't cost a
|
|
205
|
+
turn) of the form:
|
|
206
|
+
|
|
207
|
+
[budget] files_reviewed=X/N, turns_used=Y/M, pace=ok|behind
|
|
208
|
+
|
|
209
|
+
Where:
|
|
210
|
+
- X / N is the count of files you've meaningfully looked at so far
|
|
211
|
+
over the total in this PR's diff.
|
|
212
|
+
- Y / M is your current turn count over max_turns.
|
|
213
|
+
- pace = "ok" when X / N >= Y / (M - 5). The denominator subtracts the
|
|
214
|
+
5-turn emit reservation: over the (M - 5) turns available for file
|
|
215
|
+
review, your file-coverage rate must match where you actually are
|
|
216
|
+
in the budget. (Don't subtract from Y — that would be saying "I've
|
|
217
|
+
used Y minus 5 turns" which double-counts the reservation.)
|
|
218
|
+
pace = "behind" otherwise.
|
|
219
|
+
|
|
220
|
+
When pace = "behind", immediately pivot strategy:
|
|
221
|
+
1. Stop deep-dive analysis on the current file.
|
|
222
|
+
2. Switch to one-sentence verdicts for every remaining file.
|
|
223
|
+
3. Keep going through the whole diff — silent skipping is
|
|
224
|
+
non-negotiable. Cover everything, even if some files only get
|
|
225
|
+
"no issues found in this file" as their verdict.
|
|
226
|
+
|
|
227
|
+
The heartbeat serves two purposes: (a) forces internal pacing — you
|
|
228
|
+
can't drift past budget without noticing; (b) lands in the action's
|
|
229
|
+
streaming output for post-hoc calibration of the per-line cost
|
|
230
|
+
coefficients used by paths-check's Layer 1 estimator.
|
|
231
|
+
|
|
232
|
+
Incremental-diff handshake (v0.6.10+) — emit the SHA marker:
|
|
233
|
+
At the very end of the summary (after the Skills-referenced footer,
|
|
234
|
+
on its own line), append:
|
|
235
|
+
|
|
236
|
+
<!-- last-reviewed-sha: $HEAD_SHA -->
|
|
237
|
+
|
|
238
|
+
(\`$HEAD_SHA\` from workflow env; literal value, not the variable
|
|
239
|
+
name.) Silent to humans (HTML comment), load-bearing for cost: every
|
|
240
|
+
subsequent fix-push re-fetches only the delta since this SHA. Omit
|
|
241
|
+
it and the next review falls back to full \`gh pr diff\`.
|
|
242
|
+
|
|
243
|
+
Strict-mode header (opt-in): if .claude/skills/.clud-bug.json has
|
|
244
|
+
\`{ "strictMode": true }\`, set the \`status_header\` schema field
|
|
245
|
+
to signal verdict:
|
|
246
|
+
- any critical issue flagged → \`status_header: "critical findings"\`
|
|
247
|
+
(renderer emits \`## 🐛 Clud Bug review — critical findings\`)
|
|
248
|
+
- otherwise → \`status_header: "clean"\` (renderer emits
|
|
249
|
+
\`## 🐛 Clud Bug review — clean\`)
|
|
250
|
+
The strict-mode gate post-step greps for "critical findings" and
|
|
251
|
+
fails the check.
|
|
252
|
+
|
|
253
|
+
If strictMode is unset or absent, set \`status_header: "bare"\` —
|
|
254
|
+
the renderer emits the bare \`## 🐛 Clud Bug review\` (no suffix),
|
|
255
|
+
matching the v0.6.21- visible behaviour for non-strict-mode repos.
|
|
256
|
+
|
|
257
|
+
Tone: conversational, concise field-naturalist voice (you are Clud
|
|
258
|
+
Bug examining specimens of code) — never at the cost of clarity,
|
|
259
|
+
evidence, or critical-issues-only discipline. Let precision speak.
|
|
260
|
+
|
|
261
|
+
Your review lives in TWO surfaces, in this order:
|
|
262
|
+
|
|
263
|
+
1. INLINE REVIEW THREADS — one per finding, anchored to
|
|
264
|
+
file:line via mcp__github_inline_comment__create_inline_comment
|
|
265
|
+
(critical, minor, AND per-skill findings). Body is the finding
|
|
266
|
+
text itself (no leading "- " bullet). Creates resolvable
|
|
267
|
+
conversations that branch protection's
|
|
268
|
+
required_review_thread_resolution rule gates on. Without inline
|
|
269
|
+
threads, the gate has nothing to gate on.
|
|
270
|
+
|
|
271
|
+
Pass \`confirmed: true\` on every call — these are final review
|
|
272
|
+
comments, not probes. Without it the tool defers to a post-hoc
|
|
273
|
+
classifier that can silently no-op a real finding.
|
|
274
|
+
|
|
275
|
+
Cross-cutting findings (no specific line) stay summary-only —
|
|
276
|
+
but default to inline whenever you can name file:line.
|
|
277
|
+
|
|
278
|
+
2. SUMMARY PR COMMENT — emitted as STRUCTURED JSON via the
|
|
279
|
+
workflow's \`--json-schema\` flag (0.0.O / v0.6.22+). Do NOT
|
|
280
|
+
post the summary yourself via \`gh pr comment\` — a post-step
|
|
281
|
+
reads your structured output and renders the comment with the
|
|
282
|
+
exact format the strict-mode gate expects. Populate every
|
|
283
|
+
schema field (\`status_header\`, \`summary_counts\`,
|
|
284
|
+
\`per_skill_scan\`, \`critical_findings\`, \`minor_findings\`,
|
|
285
|
+
\`preexisting_findings\`, \`skills_referenced\`,
|
|
286
|
+
\`last_reviewed_sha\`); \`dedicated_sections\` and
|
|
287
|
+
\`diagnostics\` are optional but emit them when applicable.
|
|
288
|
+
See workflow env for the schema; the format docs below describe
|
|
289
|
+
what each field becomes after rendering.
|
|
290
|
+
|
|
291
|
+
The comment body MUST start with:
|
|
292
|
+
|
|
293
|
+
## 🐛 Clud Bug review
|
|
294
|
+
|
|
295
|
+
(The post-step renders this H2 anchor — the strict-mode gate greps it.)
|
|
296
|
+
|
|
297
|
+
On the next non-empty line, emit:
|
|
298
|
+
|
|
299
|
+
**This round:** N critical · N minor · N resolved from prior · N still open
|
|
300
|
+
|
|
301
|
+
Applies to all H2 variants (bare / "— critical findings" / "— clean").
|
|
302
|
+
Always include all four counters even when 0 — fixed format is
|
|
303
|
+
grep-able. Definitions:
|
|
304
|
+
|
|
305
|
+
• critical — NEW critical findings (the ones strict mode gates on)
|
|
306
|
+
• minor — non-critical findings (nits, suggestions)
|
|
307
|
+
• resolved from prior — prior unresolved threads YOU resolved this pass
|
|
308
|
+
via resolveReviewThread (proves the bot read fixes)
|
|
309
|
+
• still open — prior unresolved threads whose issue still stands
|
|
310
|
+
|
|
311
|
+
First-time reviews → 0/0 on the last two. Fix-push reviews →
|
|
312
|
+
"resolved from prior" typically positive.
|
|
313
|
+
|
|
314
|
+
Stats header (line immediately after **This round:**):
|
|
315
|
+
ONE single-line header — emoji tiers let agents triage on re-read
|
|
316
|
+
without parsing the body:
|
|
317
|
+
|
|
318
|
+
🔴 important — bugs / security / perf / missing test coverage
|
|
319
|
+
🟡 nit — suggestions, style nits, observations
|
|
320
|
+
🟣 pre-existing — issues pre-dating this PR (worth surfacing)
|
|
321
|
+
|
|
322
|
+
Found: N 🔴 / N 🟡 / N 🟣
|
|
323
|
+
|
|
324
|
+
When all three are 0, the substantive body is optional.
|
|
325
|
+
|
|
326
|
+
Per-finding format (severity emoji + collapsible reasoning):
|
|
327
|
+
The summary line is load-bearing; the long-form reasoning lives in
|
|
328
|
+
a \`<details>\` block so re-reads can skip it token-cheaply.
|
|
329
|
+
|
|
330
|
+
🔴 [skill-name]: One-line claim (file:line).
|
|
331
|
+
<details><summary>Reasoning</summary>
|
|
332
|
+
|
|
333
|
+
Evidence anchors, suggested fix, edge cases.
|
|
334
|
+
|
|
335
|
+
</details>
|
|
336
|
+
|
|
337
|
+
Tier emoji: 🔴 important (strict-mode gates these), 🟡 nit,
|
|
338
|
+
🟣 pre-existing.
|
|
339
|
+
|
|
340
|
+
Per-skill scan block (immediately under the status line):
|
|
341
|
+
Emit "### Per-skill scan" with ONE line per loaded skill — even
|
|
342
|
+
silent ones. Anti-dilution: authors see their skill ran.
|
|
343
|
+
|
|
344
|
+
### Per-skill scan
|
|
345
|
+
- [<skill-name>]: <one-sentence outcome>
|
|
346
|
+
|
|
347
|
+
Examples:
|
|
348
|
+
- [critical-issues-only]: scanned all paths. 2 critical findings below.
|
|
349
|
+
- [evidence-based-review]: applied to all findings. ✓ all anchored.
|
|
350
|
+
- [respect-existing-conventions]: scanned for pattern fights. 0 findings.
|
|
351
|
+
- [brand-voice-review]: scanned 3 microcopy changes. 1 finding (below).
|
|
352
|
+
- [pii-and-compliance]: scanned logging + analytics. 0 findings.
|
|
353
|
+
|
|
354
|
+
Per-skill findings sections (dedicated-mode skills only):
|
|
355
|
+
For each dedicated-mode skill that produced one or more
|
|
356
|
+
findings, emit a dedicated H3 section before the standard
|
|
357
|
+
critical/minor buckets:
|
|
358
|
+
|
|
359
|
+
### Brand voice [brand-voice-review]
|
|
360
|
+
- Finding: button label "Click here!" violates verb-noun rule
|
|
361
|
+
(lib/ui/Button.tsx:42). Suggested: "Open settings."
|
|
362
|
+
|
|
363
|
+
Shared-mode skill findings stay in the combined "Critical findings"
|
|
364
|
+
/ "Minor findings" buckets — cross-correlation preserves signal
|
|
365
|
+
(e.g. a logging-PII issue belongs in both critical-issues-only and
|
|
366
|
+
pii-and-compliance at once).
|
|
367
|
+
|
|
368
|
+
Emit the summary as structured JSON output (the workflow's
|
|
369
|
+
--json-schema captures it; a post-step renders + posts via gh pr
|
|
370
|
+
comment). Do NOT post the summary yourself.
|
|
371
|
+
|
|
372
|
+
Inline findings still post via mcp__github_inline_comment__create_inline_comment
|
|
373
|
+
(with \`confirmed: true\`). Pass ordering: (a) post inline findings,
|
|
374
|
+
(b) resolve prior threads now fixed (FIX-PUSH FLOW below — feeds
|
|
375
|
+
"resolved_from_prior" counter in the JSON), (c) emit structured
|
|
376
|
+
summary output. Step (b) MUST complete before (c) so the counter
|
|
377
|
+
is accurate; (a)/(b) order between themselves doesn't matter.
|
|
378
|
+
|
|
379
|
+
FIX-PUSH FLOW (when prior claude[bot] threads exist):
|
|
380
|
+
List prior claude[bot] inline threads, resolve the ones whose issue
|
|
381
|
+
is verifiably fixed in the current diff. This closes the loop —
|
|
382
|
+
"resolved from prior" proves the bot read the fixes.
|
|
383
|
+
|
|
384
|
+
List threads:
|
|
385
|
+
|
|
386
|
+
gh api graphql -f query='{ repository(owner: "\${{ github.repository_owner }}", name: "\${{ github.event.repository.name }}") { pullRequest(number: '"$PR_NUMBER"') { reviewThreads(first: 30) { nodes { id isResolved comments(first: 1) { nodes { body author { login } } } } } } } }'
|
|
387
|
+
|
|
388
|
+
For each unresolved thread YOU (claude[bot]) authored that the head
|
|
389
|
+
diff now addresses:
|
|
390
|
+
|
|
391
|
+
gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "<id>"}) { thread { isResolved } } }'
|
|
392
|
+
|
|
393
|
+
Only resolve threads where the fix is verifiable. Unresolved-but-
|
|
394
|
+
still-standing threads become "still open" in the status block.
|
|
395
|
+
|
|
396
|
+
If there are no critical findings, you still post the summary
|
|
397
|
+
comment with the H2 header and "**This round:** 0 critical · …"
|
|
398
|
+
status line — strict mode + the status counters need the
|
|
399
|
+
comment to exist for every review pass.`;
|
|
400
|
+
}
|
|
401
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/core/prompts.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,8EAA8E;AAC9E,uEAAuE;AACvE,8BAA8B;AAe9B,MAAM,oBAAoB,GAAoD;IAC5E,OAAO,EAAE,CAAC,gDAAgD,CAAC;IAC3D,EAAE,EAAE;QACF,gDAAgD;QAChD,mFAAmF;QACnF,kCAAkC;QAClC,mFAAmF;QACnF,4CAA4C;KAC7C;IACD,EAAE,EAAE;QACF,wFAAwF;QACxF,uCAAuC;QACvC,8EAA8E;QAC9E,wCAAwC;KACzC;CACF,CAAC;AAEF,yEAAyE;AACzE,wEAAwE;AACxE,wEAAwE;AACxE,qEAAqE;AACrE,EAAE;AACF,sEAAsE;AACtE,QAAQ;AACR,gDAAgD;AAChD,0DAA0D;AAC1D,iEAAiE;AACjE,MAAM,UAAU,YAAY,CAAC,UAA6B,EAAE;IAC1D,MAAM,EAAE,kBAAkB,EAAE,QAAQ,GAAG,SAAS,EAAE,GAAG,OAAO,CAAC;IAC7D,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,mCAAmC,QAAQ,GAAG,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,YAAY,GAAG;QACnB,8CAA8C;QAC9C,4BAA4B;QAC5B,wBAAwB;QACxB,GAAG,KAAK;KACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,OAAO,GAAG,kBAAkB;;;EAG5B,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCA2V0B,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FindingSeverity, ReviewData } from './review-schema.js';
|
|
2
|
+
declare const SEVERITY_LABEL: Record<FindingSeverity, string>;
|
|
3
|
+
export { SEVERITY_LABEL };
|
|
4
|
+
type RenderReviewInput = Partial<ReviewData> & Record<string, unknown>;
|
|
5
|
+
export declare function renderReview(data: RenderReviewInput | null | undefined): string;
|
|
6
|
+
//# sourceMappingURL=render-review.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-review.d.ts","sourceRoot":"","sources":["../../src/core/render-review.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,eAAe,EAEf,UAAU,EAGX,MAAM,oBAAoB,CAAC;AAe5B,QAAA,MAAM,cAAc,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAInD,CAAC;AAIF,OAAO,EAAE,cAAc,EAAE,CAAC;AAM1B,KAAK,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAKvE,wBAAgB,YAAY,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAkD/E"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// Render a clud-bug review's structured-output JSON to the GitHub-markdown
|
|
2
|
+
// summary comment shape the workflow has been posting since v0.6.5.
|
|
3
|
+
//
|
|
4
|
+
// 0.0.O (v0.6.22): introduced as the receiver for `--json-schema` output.
|
|
5
|
+
// The LLM emits structured JSON (one bundled string via the action's
|
|
6
|
+
// `outputs.structured_output`); a workflow post-step pipes that JSON to
|
|
7
|
+
// `clud-bug render --stdin` (CLI subcommand), which calls renderReview()
|
|
8
|
+
// here, then posts the result via `gh pr comment`. Failure mode: if
|
|
9
|
+
// `structured_output` is empty (max retries hit), the post-step is
|
|
10
|
+
// skipped and the LLM's prior free-form behaviour stands (it had already
|
|
11
|
+
// been instructed to post via `gh pr comment` directly as a fallback).
|
|
12
|
+
//
|
|
13
|
+
// Why an outside renderer at all: with --json-schema the LLM can no
|
|
14
|
+
// longer paraphrase the comment format (good for consistency, bad if the
|
|
15
|
+
// rendered shape is wrong). Centralising the markdown shape here means a
|
|
16
|
+
// future format tweak edits one function rather than the prompt.
|
|
17
|
+
// Emoji constants: use explicit Unicode escape literals (`\u{HHHHH}`) so
|
|
18
|
+
// every step of the TS→JS toolchain — tsc, vitest's transformer, the
|
|
19
|
+
// publisher's tarball — emits the same byte sequence regardless of
|
|
20
|
+
// editor encoding settings. Per SPEC §6 byte-identical contract:
|
|
21
|
+
// \u{1F534} = 🔴 (red circle, critical / "important")
|
|
22
|
+
// \u{1F7E1} = 🟡 (yellow circle, minor / "nit")
|
|
23
|
+
// \u{1F7E3} = 🟣 (purple circle, pre-existing)
|
|
24
|
+
// \u{1F41B} = 🐛 (bug, H2 anchor for `## 🐛 Clud Bug review`)
|
|
25
|
+
const SEVERITY_EMOJI = {
|
|
26
|
+
critical: '\u{1F534}',
|
|
27
|
+
minor: '\u{1F7E1}',
|
|
28
|
+
preexisting: '\u{1F7E3}',
|
|
29
|
+
};
|
|
30
|
+
const SEVERITY_LABEL = {
|
|
31
|
+
critical: 'important',
|
|
32
|
+
minor: 'nit',
|
|
33
|
+
preexisting: 'pre-existing',
|
|
34
|
+
};
|
|
35
|
+
// SEVERITY_LABEL is retained for callers that import the constant table
|
|
36
|
+
// (the JS version exported it implicitly via module scope; keep the
|
|
37
|
+
// export so future renderer extensions can reuse it).
|
|
38
|
+
export { SEVERITY_LABEL };
|
|
39
|
+
// Render the full summary comment markdown. `data` is the parsed JSON
|
|
40
|
+
// matching the schema (see review-schema.ts). Returns a string suitable
|
|
41
|
+
// for `gh pr comment --body`.
|
|
42
|
+
export function renderReview(data) {
|
|
43
|
+
if (!data || typeof data !== 'object') {
|
|
44
|
+
throw new TypeError('renderReview: data must be an object');
|
|
45
|
+
}
|
|
46
|
+
const out = [];
|
|
47
|
+
out.push(renderHeader(data));
|
|
48
|
+
out.push('');
|
|
49
|
+
out.push(renderStatusLine(data.summary_counts));
|
|
50
|
+
out.push('');
|
|
51
|
+
out.push(renderStatsHeader(data.summary_counts));
|
|
52
|
+
out.push('');
|
|
53
|
+
out.push(...renderPerSkillScan(data.per_skill_scan));
|
|
54
|
+
out.push('');
|
|
55
|
+
for (const section of data.dedicated_sections || []) {
|
|
56
|
+
out.push(...renderDedicatedSection(section));
|
|
57
|
+
out.push('');
|
|
58
|
+
}
|
|
59
|
+
if (nonEmpty(data.critical_findings)) {
|
|
60
|
+
out.push('### Critical findings');
|
|
61
|
+
out.push('');
|
|
62
|
+
out.push(...renderFindings(data.critical_findings, 'critical'));
|
|
63
|
+
out.push('');
|
|
64
|
+
}
|
|
65
|
+
if (nonEmpty(data.minor_findings)) {
|
|
66
|
+
out.push('### Minor findings');
|
|
67
|
+
out.push('');
|
|
68
|
+
out.push(...renderFindings(data.minor_findings, 'minor'));
|
|
69
|
+
out.push('');
|
|
70
|
+
}
|
|
71
|
+
if (nonEmpty(data.preexisting_findings)) {
|
|
72
|
+
out.push('### Pre-existing findings');
|
|
73
|
+
out.push('');
|
|
74
|
+
out.push(...renderFindings(data.preexisting_findings, 'preexisting'));
|
|
75
|
+
out.push('');
|
|
76
|
+
}
|
|
77
|
+
if (nonEmpty(data.diagnostics)) {
|
|
78
|
+
out.push('### Diagnostics');
|
|
79
|
+
out.push('');
|
|
80
|
+
for (const line of data.diagnostics)
|
|
81
|
+
out.push(`- ${line}`);
|
|
82
|
+
out.push('');
|
|
83
|
+
}
|
|
84
|
+
out.push(renderSkillsReferenced(data.skills_referenced));
|
|
85
|
+
out.push('');
|
|
86
|
+
if (data.last_reviewed_sha) {
|
|
87
|
+
out.push(`<!-- last-reviewed-sha: ${data.last_reviewed_sha} -->`);
|
|
88
|
+
}
|
|
89
|
+
// Trim trailing blank lines but always keep a single trailing newline so
|
|
90
|
+
// the comment ends with a final newline (markdown rendering is unchanged
|
|
91
|
+
// either way, but it matches the prior LLM-driven shape).
|
|
92
|
+
return out.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, '') + '\n';
|
|
93
|
+
}
|
|
94
|
+
function renderHeader(data) {
|
|
95
|
+
const verdict = data.status_header;
|
|
96
|
+
const base = '## \u{1F41B} Clud Bug review';
|
|
97
|
+
if (verdict === 'critical findings')
|
|
98
|
+
return `${base} — critical findings`;
|
|
99
|
+
if (verdict === 'clean')
|
|
100
|
+
return `${base} — clean`;
|
|
101
|
+
// 'bare' (non-strict-mode default) OR an unexpected verdict — render
|
|
102
|
+
// the unsuffixed H2. Strict-mode gate's anchor stays intact either way.
|
|
103
|
+
return base;
|
|
104
|
+
}
|
|
105
|
+
function renderStatusLine(counts) {
|
|
106
|
+
const c = sanitizeCounts(counts);
|
|
107
|
+
return `**This round:** ${c.critical} critical · ${c.minor} minor · ${c.resolved_from_prior} resolved from prior · ${c.still_open} still open`;
|
|
108
|
+
}
|
|
109
|
+
// Severity-emoji stats header. Counts pre-existing in 🟣 even though
|
|
110
|
+
// it's not in summary_counts (the prompt counts pre-existing separately
|
|
111
|
+
// in preexisting_findings.length).
|
|
112
|
+
function renderStatsHeader(counts) {
|
|
113
|
+
const c = sanitizeCounts(counts);
|
|
114
|
+
return `Found: ${c.critical} \u{1F534} / ${c.minor} \u{1F7E1} / ${c.preexisting} \u{1F7E3}`;
|
|
115
|
+
}
|
|
116
|
+
function renderPerSkillScan(scan) {
|
|
117
|
+
const out = ['### Per-skill scan'];
|
|
118
|
+
if (!Array.isArray(scan) || scan.length === 0) {
|
|
119
|
+
out.push('- (no skills loaded — review proceeded against the baseline.)');
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
for (const entry of scan) {
|
|
123
|
+
if (!entry || typeof entry !== 'object')
|
|
124
|
+
continue;
|
|
125
|
+
const skill = String(entry.skill || '').trim();
|
|
126
|
+
const outcome = String(entry.outcome || '').trim();
|
|
127
|
+
if (!skill)
|
|
128
|
+
continue;
|
|
129
|
+
out.push(`- [${skill}]: ${outcome || 'scanned (no outcome reported).'}`);
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
function renderDedicatedSection(section) {
|
|
134
|
+
if (!section || typeof section !== 'object')
|
|
135
|
+
return [];
|
|
136
|
+
const name = String(section.section_name || '').trim();
|
|
137
|
+
const skill = String(section.skill || '').trim();
|
|
138
|
+
const header = skill && name
|
|
139
|
+
? `### ${name} [${skill}]`
|
|
140
|
+
: `### ${name || skill || 'Dedicated section'}`;
|
|
141
|
+
const out = [header, ''];
|
|
142
|
+
if (Array.isArray(section.findings) && section.findings.length > 0) {
|
|
143
|
+
// Dedicated-section findings use the same emoji-prefix block.
|
|
144
|
+
// Default severity for dedicated sections is "critical" — they're
|
|
145
|
+
// domain-specific findings the skill considers important.
|
|
146
|
+
out.push(...renderFindings(section.findings, 'critical'));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
out.push('No findings.');
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
function renderFindings(findings, severity) {
|
|
154
|
+
const emoji = SEVERITY_EMOJI[severity] || SEVERITY_EMOJI.critical;
|
|
155
|
+
const out = [];
|
|
156
|
+
if (!Array.isArray(findings))
|
|
157
|
+
return out;
|
|
158
|
+
for (const f of findings) {
|
|
159
|
+
if (!f || typeof f !== 'object')
|
|
160
|
+
continue;
|
|
161
|
+
const skill = String(f.skill || '').trim();
|
|
162
|
+
const summary = String(f.summary || '').trim();
|
|
163
|
+
if (!summary)
|
|
164
|
+
continue;
|
|
165
|
+
const skillPrefix = skill ? `[${skill}]: ` : '';
|
|
166
|
+
const anchor = locationAnchor(f);
|
|
167
|
+
const claim = anchor
|
|
168
|
+
? `${emoji} ${skillPrefix}${stripTrailingPunctuation(summary)} (${anchor}).`
|
|
169
|
+
: `${emoji} ${skillPrefix}${summary}`;
|
|
170
|
+
out.push(claim);
|
|
171
|
+
if (f.reasoning) {
|
|
172
|
+
out.push('<details><summary>Reasoning</summary>');
|
|
173
|
+
out.push('');
|
|
174
|
+
out.push(String(f.reasoning).trim());
|
|
175
|
+
out.push('');
|
|
176
|
+
out.push('</details>');
|
|
177
|
+
}
|
|
178
|
+
out.push('');
|
|
179
|
+
}
|
|
180
|
+
// Remove the trailing empty line — renderReview adds its own separators.
|
|
181
|
+
if (out[out.length - 1] === '')
|
|
182
|
+
out.pop();
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
function renderSkillsReferenced(skills) {
|
|
186
|
+
if (!Array.isArray(skills) || skills.length === 0) {
|
|
187
|
+
return 'Skills referenced: [none] — no installed skill applied to this diff.';
|
|
188
|
+
}
|
|
189
|
+
return `Skills referenced: [${skills.join(', ')}]`;
|
|
190
|
+
}
|
|
191
|
+
// --- helpers ---
|
|
192
|
+
function nonEmpty(arr) {
|
|
193
|
+
return Array.isArray(arr) && arr.length > 0;
|
|
194
|
+
}
|
|
195
|
+
function sanitizeCounts(counts) {
|
|
196
|
+
const c = counts && typeof counts === 'object' ? counts : {};
|
|
197
|
+
return {
|
|
198
|
+
critical: numOrZero(c.critical),
|
|
199
|
+
minor: numOrZero(c.minor),
|
|
200
|
+
preexisting: numOrZero(c.preexisting),
|
|
201
|
+
resolved_from_prior: numOrZero(c.resolved_from_prior),
|
|
202
|
+
still_open: numOrZero(c.still_open),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function numOrZero(v) {
|
|
206
|
+
const n = Number(v);
|
|
207
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0;
|
|
208
|
+
}
|
|
209
|
+
function locationAnchor(f) {
|
|
210
|
+
const file = String(f.file || '').trim();
|
|
211
|
+
if (!file)
|
|
212
|
+
return null;
|
|
213
|
+
const line = Number(f.line);
|
|
214
|
+
return Number.isFinite(line) && line > 0 ? `${file}:${line}` : file;
|
|
215
|
+
}
|
|
216
|
+
function stripTrailingPunctuation(s) {
|
|
217
|
+
return s.replace(/[.!?]+$/, '');
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=render-review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-review.js","sourceRoot":"","sources":["../../src/core/render-review.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface RenderDefaults {
|
|
2
|
+
CCA_VERSION: string;
|
|
3
|
+
CLUD_BUG_VERSION: string;
|
|
4
|
+
REVIEW_SCHEMA: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEFAULTS: RenderDefaults;
|
|
7
|
+
export type RenderVars = Partial<RenderDefaults> & Record<string, unknown>;
|
|
8
|
+
export declare function render(template: string, vars: RenderVars): string;
|
|
9
|
+
export declare function renderFile(path: string, vars: RenderVars): Promise<string>;
|
|
10
|
+
export declare function pickTemplate(languages: string[]): string;
|
|
11
|
+
export type TemplateLanguage = 'ts' | 'py' | 'generic';
|
|
12
|
+
export declare function templateLanguage(tmplName: string): TemplateLanguage;
|
|
13
|
+
//# sourceMappingURL=render.d.ts.map
|