Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ reviews:
in_progress_fortune: false # Do not stall time with a message (spammy)
poem: false # Do not write a literal poem (spammy)
enable_prompt_for_ai_agents: false # Disable prompts for AI agents (spammy)
tools:
ast-grep:
essential_rules: true
packages: # list of packages to install, in future coderabbit will provide a set of packages, beside the essentials one.
- "hiero-ledger/sdk-collaboration-hub"

# TOKEN REVIEW INSTRUCTIONS
token_review_instructions: &token_review_instructions |
Expand Down
1 change: 1 addition & 0 deletions .github/coderabbit/release-pr-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hiero-sdk-python/.github/coderabbit/release-pr-prompt.md
133 changes: 133 additions & 0 deletions .github/scripts/release-pr-coderabbit-gate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* Posts a single "@coderabbit review" comment on release PRs, embedding the
* release review prompt. Designed to run with:
* - permissions: contents: read, pull-requests: write
*
* Safety:
* - Only runs for maintainer-authored PRs (MEMBER/OWNER)
* - Dedupe via hidden marker comment
*/

const fs = require("fs");
const path = require("path");

const MARKER = "<!-- coderabbit-release-gate: v1 -->";


function loadPrompt() {
const promptPath = path.join(
process.env.GITHUB_WORKSPACE || ".",
".github/coderabbit/release-pr-prompt.md"
);
try {
const content = fs.readFileSync(promptPath, "utf8").trim();
if (!content) {
throw new Error("Release prompt file is empty");
}
return content;
} catch (error) {
throw new Error(`Failed to load release prompt from ${promptPath}: ${error.message}`);
}
}

async function commentAlreadyExists({ github, owner, repo, issue_number }) {
try {
// Pull a bounded number of recent comments to avoid pagination complexity.
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number,
per_page: 100,
});
return data.some((c) => typeof c.body === "string" && c.body.includes(MARKER));
}
catch (error) {
console.error(`Error checking for existing comments: ${error.message}`);
return false; // Fail open: allow posting if check fails
}
}


function buildBody({ prompt, baseRef, headRef, baseLooksLikeTag }) {
// Keep it human-friendly but compact; instructions are collapsible.
const lines = [
"@coderabbitai review",
"",
MARKER,
"",
`This is a **release-gate** review request for diff **${baseRef} → ${headRef}**.`,
"",
];
if (!baseLooksLikeTag) {
lines.push(
"⚠️ Warning: The base ref does not look like a release tag. For a full release diff, set base to the previous tag (e.g., release-v0.1.10).",
""
);
}

lines.push(
"<details>",
"<summary>CodeRabbit release review instructions</summary>",
"",
prompt,
"",
"</details>",
);
return lines.join("\n");

}

module.exports = async ({ github, context }) => {
try {
const owner = context.repo.owner;
const repo = context.repo.repo;
const pr = context.payload.pull_request;

if (!pr) {
console.log("No pull_request payload; exiting.");
return;
}

// Safety: only maintainers
const assoc = pr.author_association;
if (!(assoc === "MEMBER" || assoc === "OWNER")) {
console.log(`author_association=${assoc}; skipping.`);
return;
}

const title = pr.title || "";
if (!title.toLowerCase().startsWith("chore: release v")) {
console.log("Not a release PR title; skipping.");
return;
}

const baseRef = pr.base?.ref || "";
const headRef = pr.head?.ref || "";

// Optional sanity check: base should look like a tag. If it doesn't, still comment but warn.
const baseLooksLikeTag = baseRef.startsWith("release-v") && /\d+\.\d+\.\d+/.test(baseRef);

const issue_number = pr.number;
if (await commentAlreadyExists({ github, owner, repo, issue_number })) {
console.log("Marker comment already exists; not posting again.");
return;
}

const prompt = loadPrompt();

const body = buildBody({ prompt, baseRef, headRef, baseLooksLikeTag });

await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
});

console.log("Posted CodeRabbit release-gate comment.");
console.log(`PR #${issue_number} (${headRef} → ${baseRef})`);
} catch (error) {
console.error(`Error in release PR coderabbit gate: ${error.message}`);
console.log(`PR #${issue_number || 'unknown'} (${headRef || '?'} → ${baseRef || '?'})`);
}
};
46 changes: 46 additions & 0 deletions .github/workflows/release-pr-coderabbit-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CodeRabbit Release Gate Comment

on:
pull_request:
types: [opened, reopened, synchronize, edited]

permissions:
contents: read
pull-requests: write

concurrency:
group: coderabbit-release-gate-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
coderabbit-release-gate:
runs-on: ubuntu-latest
# Only run for release PRs /title check as initial filter
if: |
github.event.pull_request &&
(startsWith(github.event.pull_request.title, 'chore: release v') ||
startsWith(github.event.pull_request.title, 'release v') ||
startsWith(github.event.pull_request.title, 'Release v'))

steps:
- name: Harden the runner
uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1
with:
egress-policy: audit

- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
with:
sparse-checkout: |
.github/coderabbit/release-pr-prompt.md
.github/scripts/post-coderabbit-release-gate-comment.js
sparse-checkout-cone-mode: false

- name: Post CodeRabbit release-gate prompt comment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #v8.0.0
with:
script: |
const script = require('./.github/scripts/release-pr-coderabbit-gate.js');
await script({ github, context});
10 changes: 4 additions & 6 deletions src/hiero_sdk_python/consensus/topic_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ class TopicMessage:
def __init__(
self,
consensus_timestamp: datetime,
message_data: Dict[str, Union[bytes, int]],
chunks: List[TopicMessageChunk],

transaction_id: Optional[TransactionId] = None,
) -> None:
"""
Expand All @@ -59,7 +58,7 @@ def __init__(
self.contents: Union[bytes, int] = message_data["contents"]
self.running_hash: Union[bytes, int] = message_data["running_hash"]
self.sequence_number: Union[bytes, int] = message_data["sequence_number"]
self.chunks: List[TopicMessageChunk] = chunks

self.transaction_id: Optional[TransactionId] = transaction_id

@classmethod
Expand Down Expand Up @@ -97,13 +96,13 @@ def of_many(cls, responses: List[mirror_proto.ConsensusTopicResponse]) -> "Topic
responses, key=lambda r: r.chunkInfo.number
)

chunks: List[TopicMessageChunk] = []

total_size: int = 0
transaction_id: Optional[TransactionId] = None

for r in sorted_responses:
c = TopicMessageChunk(r)
chunks.append(c)


total_size += len(r.message)

Expand Down Expand Up @@ -137,7 +136,6 @@ def of_many(cls, responses: List[mirror_proto.ConsensusTopicResponse]) -> "Topic
"running_hash": running_hash,
"sequence_number": sequence_number,
},
chunks,
transaction_id
)

Expand Down
Loading