Skip to content

Commit abc0c2e

Browse files
authored
feat: Add Coderabbit Planning (hiero-ledger#540)
Signed-off-by: aceppaluni <aceppaluni@gmail.com>
1 parent 66e09d7 commit abc0c2e

2 files changed

Lines changed: 189 additions & 0 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Script to trigger CodeRabbit plan for beginner, intermediate, and advanced issues.
2+
3+
const CODERABBIT_MARKER = '<!-- CodeRabbit Plan Trigger -->';
4+
5+
async function triggerCodeRabbitPlan(
6+
github,
7+
owner,
8+
repo,
9+
issue,
10+
marker = CODERABBIT_MARKER,
11+
isDryRun = false,
12+
) {
13+
const comment = `${marker} @coderabbitai plan`;
14+
15+
if (isDryRun) {
16+
console.log(`[DRY RUN] Would trigger CodeRabbit plan for issue #${issue.number}`);
17+
return true;
18+
}
19+
20+
try {
21+
await github.rest.issues.createComment({
22+
owner,
23+
repo,
24+
issue_number: issue.number,
25+
body: comment,
26+
});
27+
console.log(`Triggered CodeRabbit plan for issue #${issue.number}`);
28+
return true;
29+
} catch (commentErr) {
30+
console.log('Failed to trigger CodeRabbit plan:', {
31+
message: commentErr?.message,
32+
status: commentErr?.status,
33+
owner,
34+
repo,
35+
issueNumber: issue?.number,
36+
});
37+
return false;
38+
}
39+
}
40+
41+
function hasBeginnerOrHigherLabel(issue, label) {
42+
// Check if issue has beginner, intermediate, or advanced label (case-insensitive).
43+
const allowed = ['beginner', 'intermediate', 'advanced'];
44+
45+
const hasAllowedLabel = issue.labels?.some((l) => allowed.includes(l?.name?.toLowerCase()));
46+
47+
// Also check if newly added label is beginner/intermediate/advanced.
48+
const isNewLabelAllowed = allowed.includes(label?.name?.toLowerCase());
49+
50+
return hasAllowedLabel || isNewLabelAllowed;
51+
}
52+
53+
async function hasExistingCodeRabbitPlan(github, owner, repo, issueNumber) {
54+
// Check for existing CodeRabbit plan comment (limited to first 500 comments).
55+
// Uses marker-based detection to avoid false positives from quoted text.
56+
try {
57+
const comments = [];
58+
const iterator = github.paginate.iterator(github.rest.issues.listComments, {
59+
owner,
60+
repo,
61+
issue_number: issueNumber,
62+
per_page: 100,
63+
});
64+
65+
let count = 0;
66+
for await (const { data: page } of iterator) {
67+
comments.push(...page);
68+
count += page.length;
69+
if (count >= 500) break; // Hard upper bound to prevent unbounded pagination.
70+
}
71+
72+
// Check for marker-based comment OR @coderabbitai plan text.
73+
return comments.some(
74+
(c) =>
75+
typeof c?.body === 'string' &&
76+
(c.body.includes(CODERABBIT_MARKER) || c.body.includes('@coderabbitai plan')),
77+
);
78+
} catch (error) {
79+
console.log('Failed to check existing CodeRabbit plan comments:', {
80+
message: error?.message,
81+
status: error?.status,
82+
owner,
83+
repo,
84+
issueNumber,
85+
});
86+
// Return false to allow plan trigger attempt (fail-open for better UX).
87+
return false;
88+
}
89+
}
90+
91+
function logSummary(owner, repo, issue) {
92+
console.log('=== Summary ===');
93+
console.log(`Repository: ${owner}/${repo}`);
94+
console.log(`Issue Number: ${issue.number}`);
95+
console.log(`Issue Title: ${issue.title || '(no title)'}`);
96+
console.log(`Labels: ${issue.labels?.map((l) => l.name).join(', ') || 'none'}`);
97+
}
98+
99+
// Main workflow handler (default export for workflow usage).
100+
async function main({ github, context }) {
101+
try {
102+
const { owner, repo } = context.repo;
103+
const { issue, label } = context.payload;
104+
105+
// Validations.
106+
if (!issue?.number) return console.log('No issue in payload');
107+
108+
if (!hasBeginnerOrHigherLabel(issue, label)) {
109+
return console.log('Issue does not have beginner/intermediate/advanced label');
110+
}
111+
112+
if (await hasExistingCodeRabbitPlan(github, owner, repo, issue.number)) {
113+
return console.log(`CodeRabbit plan already triggered for #${issue.number}`);
114+
}
115+
116+
// Check for dry run (default to true if not specified, for safety).
117+
const isDryRun = (process.env.DRY_RUN || 'true').toLowerCase() === 'true';
118+
119+
// Post CodeRabbit plan trigger.
120+
await triggerCodeRabbitPlan(github, owner, repo, issue, CODERABBIT_MARKER, isDryRun);
121+
122+
logSummary(owner, repo, issue);
123+
} catch (err) {
124+
console.log('Error:', {
125+
message: err?.message,
126+
status: err?.status,
127+
owner: context?.repo?.owner,
128+
repo: context?.repo?.repo,
129+
issueNumber: context?.payload?.issue?.number,
130+
});
131+
}
132+
}
133+
134+
// Default export for workflow usage: await script({ github, context }).
135+
module.exports = main;
136+
137+
// Named exports for reuse by other scripts.
138+
module.exports.triggerCodeRabbitPlan = triggerCodeRabbitPlan;
139+
module.exports.hasExistingCodeRabbitPlan = hasExistingCodeRabbitPlan;
140+
module.exports.CODERABBIT_MARKER = CODERABBIT_MARKER;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: CodeRabbit Plan Trigger
2+
'on':
3+
issues:
4+
types: [labeled]
5+
workflow_dispatch:
6+
inputs:
7+
dry_run:
8+
description: Run without posting comments
9+
required: false
10+
default: 'true'
11+
type: choice
12+
options:
13+
- 'true'
14+
- 'false'
15+
16+
permissions:
17+
issues: write
18+
contents: read
19+
20+
jobs:
21+
coderabbit_plan_trigger:
22+
runs-on: hl-web-lin-md
23+
concurrency:
24+
group: coderabbit-plan-${{ github.event.issue.number || github.run_id }}
25+
cancel-in-progress: false
26+
# Only run when the newly added label is beginner, intermediate, or advanced.
27+
if: >
28+
github.event_name == 'workflow_dispatch' ||
29+
(github.event.action == 'labeled' &&
30+
github.event.issue.state == 'open' &&
31+
contains(fromJson('["beginner","intermediate","advanced","Beginner","Intermediate","Advanced"]'), github.event.label.name))
32+
steps:
33+
- name: Harden Runner
34+
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
35+
with:
36+
egress-policy: audit
37+
38+
- name: Checkout repository
39+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
40+
41+
- name: Trigger CodeRabbit Plan
42+
env:
43+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || 'false' }}
45+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #v8.0.0
46+
with:
47+
script: |
48+
const script = require('./.github/scripts/coderabbit_plan_trigger.cjs');
49+
await script({ github, context });

0 commit comments

Comments
 (0)