diff --git a/src/components/git/CommitList.tsx b/src/components/git/CommitList.tsx
index 473c6a0..bb11a6b 100644
--- a/src/components/git/CommitList.tsx
+++ b/src/components/git/CommitList.tsx
@@ -5,6 +5,12 @@ import { cn } from "~/lib/utils";
 /** Visible strip rows before the list scrolls (`h-10` per row → 12.5rem total). */
 const STRIP_VISIBLE_ROWS = 5;
 
+export function sortCommitsNewestFirst(commits: GitCommit[]): GitCommit[] {
+  return [...commits].sort(
+    (a, b) => b.committedAt.getTime() - a.committedAt.getTime()
+  );
+}
+
 export function CommitList({
   commits,
   selectedSha,
diff --git a/src/components/git/DiffFileTree.tsx b/src/components/git/DiffFileTree.tsx
index f4ca3af..a619ca4 100644
--- a/src/components/git/DiffFileTree.tsx
+++ b/src/components/git/DiffFileTree.tsx
@@ -34,7 +34,7 @@ export function DiffFileTree({
     paths,
     gitStatus,
     flattenEmptyDirectories: true,
-    initialExpansion: 1,
+    initialExpansion: "open",
     icons: "standard",
     density: "compact",
     initialSelectedPaths: activePath ? [activePath] : [],
diff --git a/src/components/git/GitHubMarkdownBody.tsx b/src/components/git/GitHubMarkdownBody.tsx
index 4c9d107..880ad2e 100644
--- a/src/components/git/GitHubMarkdownBody.tsx
+++ b/src/components/git/GitHubMarkdownBody.tsx
@@ -113,16 +113,42 @@ const markdownComponents: Components = {
   td: ({ children }) => (
     <td className="border-border/60 border px-2 py-1">{children}</td>
   ),
-  details: ({ children }) => (
-    <details className="bg-muted/30 my-2 rounded-md border border-border/60">
-      {children}
-    </details>
-  ),
-  summary: ({ children }) => (
-    <summary className="cursor-pointer px-2.5 py-2 text-xs font-medium select-none">
-      {children}
-    </summary>
-  ),
+  details: ({ className, children, ...props }) => {
+    const isAddressed = String(className ?? "").includes(
+      "gh-bot-finding-addressed"
+    );
+    return (
+      <details
+        {...props}
+        className={cn(
+          "my-2 rounded-md border",
+          isAddressed
+            ? "border-emerald-500/40 bg-emerald-50/50 dark:bg-emerald-950/20"
+            : "border-border/60 bg-muted/30",
+          className
+        )}
+      >
+        {children}
+      </details>
+    );
+  },
+  summary: ({ className, children, ...props }) => {
+    const isAddressed = String(className ?? "").includes(
+      "gh-bot-finding-addressed"
+    );
+    return (
+      <summary
+        {...props}
+        className={cn(
+          "cursor-pointer px-2.5 py-2 text-xs font-medium select-none",
+          isAddressed && "text-emerald-800 dark:text-emerald-200",
+          className
+        )}
+      >
+        {children}
+      </summary>
+    );
+  },
   hr: () => <hr className="border-border/60 my-3" />,
   ul: ({ children }) => (
     <ul className="my-1.5 list-disc space-y-0.5 pl-5">{children}</ul>
@@ -151,13 +177,22 @@ export function GitHubMarkdownBody({
   body,
   className,
   collapsible = true,
+  collapseAddressedFindings = false,
 }: {
   body: string;
   className?: string;
   collapsible?: boolean;
+  /** Wrap CodeRabbit "Addressed" finding blocks in closed &lt;details&gt;. */
+  collapseAddressedFindings?: boolean;
 }) {
   const [expanded, setExpanded] = useState(false);
-  const processed = useMemo(() => preprocessGitHubMarkdown(body), [body]);
+  const processed = useMemo(
+    () =>
+      preprocessGitHubMarkdown(body, {
+        collapseAddressedFindings,
+      }),
+    [body, collapseAddressedFindings]
+  );
   const hasNestedCollapsibles = useMemo(
     () => hasGitHubCollapsibleSections(processed),
     [processed]
diff --git a/src/components/git/PrReviewFeedItem.tsx b/src/components/git/PrReviewFeedItem.tsx
index df0c01d..8dfb03f 100644
--- a/src/components/git/PrReviewFeedItem.tsx
+++ b/src/components/git/PrReviewFeedItem.tsx
@@ -1,11 +1,14 @@
 import type { ReactNode } from "react";
 import {
+  AlertCircle,
   Check,
   GitPullRequest,
   MessageSquare,
   MessageSquarePlus,
   X,
 } from "lucide-react";
+import { summarizeBotReviewIssueComment } from "~/lib/git/bot-review-comment-summary";
+import { Badge } from "~/components/ui/badge";
 import { DiffLineLocationLink } from "~/components/git/DiffLineLocationLink";
 import { GitHubMarkdownBody } from "~/components/git/GitHubMarkdownBody";
 import { PrReviewFeedAttachedComments } from "~/components/git/PrReviewFeedAttachedComments";
@@ -198,8 +201,18 @@ export function PrReviewFeedItemIssueComment({
   const body = comment.body?.trim() ?? "";
   if (!body) return null;
 
+  const botSummary = summarizeBotReviewIssueComment(comment);
+
   return (
-    <FeedCard>
+    <FeedCard
+      className={cn(
+        botSummary?.needsAction &&
+          "border-amber-400/70 ring-1 ring-amber-400/30",
+        botSummary &&
+          !botSummary.needsAction &&
+          "border-emerald-500/50 ring-1 ring-emerald-500/20"
+      )}
+    >
       <FeedHeader
         login={comment.authorLogin}
         avatarUrl={comment.authorAvatarUrl}
@@ -209,15 +222,58 @@ export function PrReviewFeedItemIssueComment({
         githubUrl={comment.url}
         occurredAt={occurredAt}
         trailing={
-          comment.isBot ? (
-            <span className="bg-muted text-muted-foreground rounded px-1.5 py-0.5 text-[10px]">
-              Bot
-            </span>
-          ) : null
+          <span className="flex shrink-0 flex-wrap items-center justify-end gap-1.5">
+            {botSummary ? (
+              <Badge
+                className={cn(
+                  "h-5 border-0 text-[10px] font-semibold",
+                  botSummary.needsAction
+                    ? "bg-amber-500 text-white"
+                    : "bg-emerald-600 text-white"
+                )}
+              >
+                {botSummary.needsAction ? "Needs action" : "Addressed"}
+              </Badge>
+            ) : null}
+            {comment.isBot ? (
+              <span className="bg-muted text-muted-foreground rounded px-1.5 py-0.5 text-[10px]">
+                Bot
+              </span>
+            ) : null}
+          </span>
         }
       />
+      {botSummary ? (
+        <div
+          className={cn(
+            "mt-2 flex items-start gap-2 rounded-md border-2 px-2.5 py-2 text-xs font-medium",
+            botSummary.needsAction
+              ? "border-amber-400 bg-amber-100 text-amber-950 dark:border-amber-600 dark:bg-amber-950/50 dark:text-amber-50"
+              : "border-emerald-500 bg-emerald-100 text-emerald-950 dark:border-emerald-600 dark:bg-emerald-950/50 dark:text-emerald-50"
+          )}
+        >
+          {botSummary.needsAction ? (
+            <AlertCircle className="mt-0.5 size-4 shrink-0" aria-hidden />
+          ) : (
+            <Check className="mt-0.5 size-4 shrink-0" aria-hidden />
+          )}
+          <span>
+            <span className="block">{botSummary.label}</span>
+            {botSummary.actionableCount != null ? (
+              <span className="mt-0.5 block text-[10px] font-normal opacity-90">
+                {botSummary.addressedCount} of {botSummary.actionableCount}{" "}
+                marked addressed on GitHub
+              </span>
+            ) : null}
+          </span>
+        </div>
+      ) : null}
       <div className="mt-2 pl-8">
-        <GitHubMarkdownBody body={body} collapsible={false} />
+        <GitHubMarkdownBody
+          body={body}
+          collapsible={false}
+          collapseAddressedFindings={Boolean(botSummary)}
+        />
       </div>
     </FeedCard>
   );
diff --git a/src/components/git/TaskDevelopmentWorkspace.tsx b/src/components/git/TaskDevelopmentWorkspace.tsx
index 7f6a1d4..c0d1bdd 100644
--- a/src/components/git/TaskDevelopmentWorkspace.tsx
+++ b/src/components/git/TaskDevelopmentWorkspace.tsx
@@ -1,6 +1,9 @@
-import { useEffect, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
 import { PhaseBanner } from "~/components/git/PhaseBanner";
-import { CommitList } from "~/components/git/CommitList";
+import {
+  CommitList,
+  sortCommitsNewestFirst,
+} from "~/components/git/CommitList";
 import { DiffViewer } from "~/components/git/DiffViewer";
 import { DiffReviewPanel } from "~/components/git/DiffReviewPanel";
 import { DiffLoadingSkeleton } from "~/components/git/review/DiffLoadingSkeleton";
@@ -92,9 +95,16 @@ export function TaskDevelopmentWorkspace({
     usePrCommitsList
   );
 
-  const commits = usePrCommitsList
-    ? (prCommitsQuery.data?.commits ?? [])
-    : (branchCommitsQuery.data?.commits ?? []);
+  const commits = useMemo(() => {
+    const raw = usePrCommitsList
+      ? (prCommitsQuery.data?.commits ?? [])
+      : (branchCommitsQuery.data?.commits ?? []);
+    return sortCommitsNewestFirst(raw);
+  }, [
+    usePrCommitsList,
+    prCommitsQuery.data?.commits,
+    branchCommitsQuery.data?.commits,
+  ]);
 
   const prDiffQuery = useQuery({
     queryKey: ["git", "diff", taskId, "pr", reviewPr?.id],
diff --git a/src/components/git/TaskPullRequestReviewFeed.tsx b/src/components/git/TaskPullRequestReviewFeed.tsx
index 6a03ed0..2c386ff 100644
--- a/src/components/git/TaskPullRequestReviewFeed.tsx
+++ b/src/components/git/TaskPullRequestReviewFeed.tsx
@@ -1,4 +1,4 @@
-import { useLayoutEffect, useRef, useState } from "react";
+import { useLayoutEffect, useMemo, useRef, useState } from "react";
 import { toast } from "sonner";
 import {
   PrReviewFeedItemCommentThread,
@@ -16,7 +16,12 @@ import {
 } from "~/db/queries/git";
 import { useGitTaskLiveSync } from "~/hooks/use-git-task-live-sync";
 import { formatClientError } from "~/lib/git/errors";
+import {
+  aggregateBotReviewDigests,
+  feedEntryNeedsAction,
+} from "~/lib/git/bot-review-comment-summary";
 import { buildPrReviewFeedEntries } from "~/lib/git/pr-review-feed-entries";
+import { Button } from "~/components/ui/button";
 import { getLatestPr, getOpenPr } from "~/lib/git/task-dev-phase";
 import { cn } from "~/lib/utils";
 
@@ -51,6 +56,7 @@ export function TaskPullRequestReviewFeed({
   const [resolvingThreadNodeId, setResolvingThreadNodeId] = useState<
     string | null
   >(null);
+  const [needsActionOnly, setNeedsActionOnly] = useState(false);
 
   const canResolve = Boolean(connectionData?.userLink) && Boolean(reviewPr);
 
@@ -81,22 +87,44 @@ export function TaskPullRequestReviewFeed({
       setResolvingThreadNodeId(null);
     }
   };
-  const entries = reviewData
-    ? buildPrReviewFeedEntries(
-        reviewData.reviews,
-        reviewData.comments,
-        reviewData.pendingComments,
-        reviewData.issueComments
-      )
-    : [];
+  const entries = useMemo(
+    () =>
+      reviewData
+        ? buildPrReviewFeedEntries(
+            reviewData.reviews,
+            reviewData.comments,
+            reviewData.pendingComments,
+            reviewData.issueComments
+          )
+        : [],
+    [reviewData]
+  );
+
+  const botDigestRollup = useMemo(
+    () =>
+      reviewData
+        ? aggregateBotReviewDigests(reviewData.issueComments)
+        : null,
+    [reviewData]
+  );
+
+  const visibleEntries = useMemo(
+    () =>
+      needsActionOnly
+        ? entries.filter((entry) => feedEntryNeedsAction(entry))
+        : entries,
+    [entries, needsActionOnly]
+  );
+
+  const hiddenByFilterCount = entries.length - visibleEntries.length;
 
   const scrollRef = useRef<HTMLDivElement>(null);
 
   useLayoutEffect(() => {
     const el = scrollRef.current;
-    if (!el || isLoading || entries.length === 0) return;
+    if (!el || isLoading || visibleEntries.length === 0) return;
     el.scrollTop = el.scrollHeight;
-  }, [entries, isLoading]);
+  }, [visibleEntries, isLoading]);
 
   return (
     <div
@@ -105,11 +133,25 @@ export function TaskPullRequestReviewFeed({
         className
       )}
     >
-      <div className="flex items-center justify-between gap-2">
+      <div className="flex flex-wrap items-center justify-between gap-2">
         <TaskLabel>Pull request</TaskLabel>
-        {isFetching && !isLoading ? (
-          <LoadingSpinner isActive className="size-3.5" />
-        ) : null}
+        <div className="flex items-center gap-2">
+          {botDigestRollup &&
+          (botDigestRollup.digestCount > 0 || needsActionOnly) ? (
+            <Button
+              type="button"
+              variant={needsActionOnly ? "default" : "outline"}
+              size="sm"
+              className="h-7 text-xs"
+              onClick={() => setNeedsActionOnly((v) => !v)}
+            >
+              Needs action only
+            </Button>
+          ) : null}
+          {isFetching && !isLoading ? (
+            <LoadingSpinner isActive className="size-3.5" />
+          ) : null}
+        </div>
       </div>
 
       <div ref={scrollRef} className="min-h-0 flex-1 overflow-y-auto">
@@ -130,6 +172,14 @@ export function TaskPullRequestReviewFeed({
               ? "No reviews or comments yet."
               : "No reviews or comments yet. Connect your GitHub account in settings to see pending reviews and post comments."}
           </p>
+        ) : visibleEntries.length === 0 ? (
+          <p className="text-muted-foreground text-sm">
+            Nothing needs action right now
+            {hiddenByFilterCount > 0
+              ? ` (${hiddenByFilterCount} addressed item${hiddenByFilterCount === 1 ? "" : "s"} hidden)`
+              : ""}
+            .
+          </p>
         ) : (
           <>
             {!connectionData.userLink ? (
@@ -138,8 +188,28 @@ export function TaskPullRequestReviewFeed({
                 comments.
               </p>
             ) : null}
+            {botDigestRollup && botDigestRollup.digestCount > 0 ? (
+              <div
+                className={cn(
+                  "mb-3 rounded-md border-2 px-3 py-2 text-xs font-medium",
+                  botDigestRollup.openTotal > 0
+                    ? "border-amber-400 bg-amber-100 text-amber-950"
+                    : "border-emerald-500 bg-emerald-100 text-emerald-950"
+                )}
+              >
+                {botDigestRollup.openTotal > 0
+                  ? `${botDigestRollup.openTotal} CodeRabbit finding${botDigestRollup.openTotal === 1 ? "" : "s"} still need action across ${botDigestRollup.digestCount} review comment${botDigestRollup.digestCount === 1 ? "" : "s"}`
+                  : `All CodeRabbit findings addressed (${botDigestRollup.digestCount} review comment${botDigestRollup.digestCount === 1 ? "" : "s"})`}
+              </div>
+            ) : null}
+            {needsActionOnly && hiddenByFilterCount > 0 ? (
+              <p className="text-muted-foreground mb-2 text-xs">
+                Hiding {hiddenByFilterCount} addressed or resolved item
+                {hiddenByFilterCount === 1 ? "" : "s"}.
+              </p>
+            ) : null}
             <ul className="space-y-2.5">
-            {entries.map((entry) => {
+            {visibleEntries.map((entry) => {
               if (entry.kind === "issue_comment") {
                 return (
                   <li key={`issue-comment-${entry.comment.id}`}>
diff --git a/src/components/git/github-markdown-sanitize.ts b/src/components/git/github-markdown-sanitize.ts
index a85d280..acc428e 100644
--- a/src/components/git/github-markdown-sanitize.ts
+++ b/src/components/git/github-markdown-sanitize.ts
@@ -9,7 +9,7 @@ export const githubMarkdownSanitizeSchema = {
   ],
   attributes: {
     ...defaultSchema.attributes,
-    details: [...(defaultSchema.attributes?.details ?? []), "open"],
-    summary: defaultSchema.attributes?.summary ?? [],
+    details: [...(defaultSchema.attributes?.details ?? []), "open", "class"],
+    summary: [...(defaultSchema.attributes?.summary ?? []), "class"],
   },
 } as const;
diff --git a/src/lib/git/bot-review-comment-summary.ts b/src/lib/git/bot-review-comment-summary.ts
new file mode 100644
index 0000000..ee67be1
--- /dev/null
+++ b/src/lib/git/bot-review-comment-summary.ts
@@ -0,0 +1,139 @@
+import type { PrReviewFeedEntry } from "~/lib/git/pr-review-feed-entries";
+import type { GitPullRequestIssueComment } from "~/lib/git/types";
+
+/** Parsed status from CodeRabbit-style PR issue comments (timeline). */
+export type BotReviewCommentSummary = {
+  actionableCount: number | null;
+  addressedCount: number;
+  openCount: number;
+  needsAction: boolean;
+  label: string;
+};
+
+const ACTIONABLE_COUNT_RE = /Actionable comments posted:\s*(\d+)/i;
+
+/** Per-file finding header in CodeRabbit digests, e.g. `\n`@src/foo.ts:12` */
+const FINDING_PATH_HEADER_RE = /(?:^|\n)`@[^`\n]+`/;
+
+/** CodeRabbit status line on a resolved finding, e.g. `✅ Addressed in commits abc to def`. */
+export const ADDRESSED_FINDING_STATUS_RE =
+  /(?:^|\n)\s*✅?\s*Addressed in commits?\s+[\da-f]+(?:\s+to\s+[\da-f]+)?/i;
+
+function countAddressedMarkers(body: string): number {
+  const matches = body.match(
+    new RegExp(ADDRESSED_FINDING_STATUS_RE.source, "gi")
+  );
+  return matches?.length ?? 0;
+}
+
+function isCoderabbitAuthor(login: string): boolean {
+  return /coderabbit/i.test(login);
+}
+
+/** True when the body looks like a multi-finding bot review dump (CodeRabbit, etc.). */
+export function isBotReviewDigest(body: string): boolean {
+  return (
+    ACTIONABLE_COUNT_RE.test(body) ||
+    /Potential issue|Committable suggestion|Prompt for AI agents/i.test(body) ||
+    (FINDING_PATH_HEADER_RE.test(body) && countAddressedMarkers(body) > 0)
+  );
+}
+
+export function summarizeBotReviewComment(
+  body: string,
+  options?: { authorLogin?: string }
+): BotReviewCommentSummary | null {
+  const forceBot =
+    options?.authorLogin != null && isCoderabbitAuthor(options.authorLogin);
+
+  if (!isBotReviewDigest(body) && !forceBot) return null;
+
+  const actionableMatch = body.match(ACTIONABLE_COUNT_RE);
+  const actionableCount = actionableMatch
+    ? Number.parseInt(actionableMatch[1]!, 10)
+    : null;
+
+  const addressedCount = countAddressedMarkers(body);
+  const baseSuggestedCount =
+    /Committable suggestion|Potential issue/i.test(body) ? 1 : 0;
+  const openCount =
+    actionableCount != null
+      ? Math.max(0, actionableCount - addressedCount)
+      : Math.max(0, baseSuggestedCount - addressedCount);
+
+  const needsAction = openCount > 0;
+
+  let label: string;
+  if (actionableCount != null) {
+    if (needsAction) {
+      label = `${openCount} finding${openCount === 1 ? "" : "s"} still need action`;
+    } else if (actionableCount > 0) {
+      label = `All ${actionableCount} finding${actionableCount === 1 ? "" : "s"} addressed`;
+    } else {
+      label = "No open findings";
+    }
+  } else if (addressedCount > 0 && !needsAction) {
+    label = `${addressedCount} finding${addressedCount === 1 ? "" : "s"} addressed`;
+  } else if (needsAction) {
+    label = "Review feedback — action may be required";
+  } else {
+    label = "Review feedback";
+  }
+
+  return {
+    actionableCount,
+    addressedCount,
+    openCount,
+    needsAction,
+    label,
+  };
+}
+
+export function summarizeBotReviewIssueComment(
+  comment: GitPullRequestIssueComment
+): BotReviewCommentSummary | null {
+  const body = comment.body?.trim() ?? "";
+  if (!body) return null;
+  if (!comment.isBot && !isCoderabbitAuthor(comment.authorLogin)) return null;
+  return summarizeBotReviewComment(body, {
+    authorLogin: comment.authorLogin,
+  });
+}
+
+export function aggregateBotReviewDigests(
+  issueComments: GitPullRequestIssueComment[]
+): { openTotal: number; digestCount: number; allAddressed: boolean } {
+  let openTotal = 0;
+  let digestCount = 0;
+
+  for (const comment of issueComments) {
+    const summary = summarizeBotReviewIssueComment(comment);
+    if (!summary) continue;
+    digestCount += 1;
+    openTotal += summary.openCount;
+  }
+
+  return {
+    openTotal,
+    digestCount,
+    allAddressed: digestCount > 0 && openTotal === 0,
+  };
+}
+
+/** Whether a feed row should appear when "Needs action only" is enabled. */
+export function feedEntryNeedsAction(entry: PrReviewFeedEntry): boolean {
+  switch (entry.kind) {
+    case "issue_comment": {
+      const summary = summarizeBotReviewIssueComment(entry.comment);
+      if (summary) return summary.needsAction;
+      return true;
+    }
+    case "review":
+      return entry.review.state === "CHANGES_REQUESTED";
+    case "thread": {
+      const { thread } = entry;
+      if (thread.pendingCommentIds.size > 0) return true;
+      return !thread.isResolved;
+    }
+  }
+}
diff --git a/src/lib/git/preprocess-github-markdown.ts b/src/lib/git/preprocess-github-markdown.ts
index 47f38da..9e9039d 100644
--- a/src/lib/git/preprocess-github-markdown.ts
+++ b/src/lib/git/preprocess-github-markdown.ts
@@ -1,17 +1,69 @@
+import {
+  ADDRESSED_FINDING_STATUS_RE,
+  isBotReviewDigest,
+} from "~/lib/git/bot-review-comment-summary";
+
 /** GitHub bots often nest `<details>`; outer height clipping blocks interacting with them. */
 export function hasGitHubCollapsibleSections(body: string): boolean {
   return /<details[\s>]/i.test(body);
 }
 
-/** Prepare GitHub PR/issue comment bodies for markdown + limited HTML rendering. */
-export function preprocessGitHubMarkdown(body: string): string {
+/** Per-file finding header in CodeRabbit digests, e.g. `\n`@src/foo.ts:12` */
+const FINDING_PATH_HEADER_RE = /(?:^|\n)`@[^`\n]+`/;
+
+const FINDING_BLOCK_SPLIT_RE = /(?=\n`@[^`\n]+`)/;
+
+function isAddressedFindingBlock(segment: string): boolean {
   return (
-    body
-      // Bot metadata (e.g. coderabbit suggestion markers)
-      .replace(/<!--[\s\S]*?-->/g, "")
-      // GitHub-only fenced blocks → plain code fences for display
-      .replace(/```suggestion\n/g, "```\n")
-      .replace(/```suggestion\r\n/g, "```\r\n")
-      .trim()
+    FINDING_PATH_HEADER_RE.test(segment) &&
+    ADDRESSED_FINDING_STATUS_RE.test(segment)
   );
 }
+
+function addressedSummaryLine(segment: string): string {
+  const match = segment.match(ADDRESSED_FINDING_STATUS_RE);
+  return match?.[0]?.trim() ?? "✅ Addressed — show details";
+}
+
+function wrapAddressedSegment(segment: string): string {
+  const summary = addressedSummaryLine(segment);
+  return `<details class="gh-bot-finding-addressed">\n<summary>${summary}</summary>\n\n${segment.trim()}\n</details>`;
+}
+
+/** Wrap CodeRabbit finding blocks that are already marked addressed (collapsed by default). */
+export function collapseAddressedBotFindings(body: string): string {
+  if (!isBotReviewDigest(body)) return body;
+
+  const blocks = body.split(FINDING_BLOCK_SPLIT_RE);
+  if (blocks.length <= 1) {
+    return body;
+  }
+
+  return blocks
+    .map((block, index) => {
+      if (index === 0) return block;
+      return isAddressedFindingBlock(block)
+        ? wrapAddressedSegment(block)
+        : block;
+    })
+    .join("");
+}
+
+/** Prepare GitHub PR/issue comment bodies for markdown + limited HTML rendering. */
+export function preprocessGitHubMarkdown(
+  body: string,
+  options?: { collapseAddressedFindings?: boolean }
+): string {
+  let result = body
+    // Bot metadata (e.g. coderabbit suggestion markers)
+    .replace(/<!--[\s\S]*?-->/g, "")
+    // GitHub-only fenced blocks → plain code fences for display
+    .replace(/```suggestion\n/g, "```\n")
+    .replace(/```suggestion\r\n/g, "```\r\n");
+
+  if (options?.collapseAddressedFindings) {
+    result = collapseAddressedBotFindings(result);
+  }
+
+  return result.trim();
+}
