Skip to content

feat(workflows): comment-aware structured workflows via LLM discussion digest - #148

Merged
chrisleekr merged 3 commits into
mainfrom
feat/discussion-digest-conversational-workflows
May 17, 2026
Merged

feat(workflows): comment-aware structured workflows via LLM discussion digest#148
chrisleekr merged 3 commits into
mainfrom
feat/discussion-digest-conversational-workflows

Conversation

@chrisleekr

@chrisleekr chrisleekr commented May 17, 2026

Copy link
Copy Markdown
Owner

Summary

The five structured workflows (triage, plan, implement, review, resolve) were stateless with respect to discussion: plan/triage read only the issue title + body, and implement/review/resolve dumped the raw comment thread into the prompt without treating it as guidance. Running bot:plan, commenting a correction, then re-running bot:plan silently ignored the correction. This PR makes all five workflows comment-aware: a new discussion-digest step distills the issue/PR comment thread into a low-hallucination guidance digest the workflow prompt consumes in place of the raw thread, and re-running a workflow now removes its own prior tracking comment so the thread doesn't pile up.

Diagram

Before:

flowchart LR
    Trigger["bot:plan trigger"] --> Handler["workflow handler"]
    Handler --> Body["issue title plus body only"]
    Body --> Agent["executeAgent / runPipeline"]
    Comments["maintainer comments"]:::ignored -.ignored.-> Agent
    classDef ignored fill:#c0392b,color:#ffffff
    Comments:::ignored
Loading

After:

flowchart TD
    Trigger["bot:plan trigger"] --> Handler["workflow handler"]
    Handler --> Fetch["fetch all comments<br/>issue plus PR review comments"]
    Fetch --> Digest["buildDiscussionDigest<br/>owner / other / bot partition"]
    Digest --> Size{"fits one LLM call?"}
    Size -->|yes| One["single-pass digest"]
    Size -->|no| MapReduce["map per chunk then reduce"]
    One --> Parse["parseStructuredResponse"]
    MapReduce --> Parse
    Parse -->|ok| Section["renderDigestSection"]
    Parse -->|fail-open| Empty["no digest, raw-comment fallback"]
    Section --> Agent["executeAgent / runPipeline<br/>digest replaces raw thread"]
    Empty --> Agent
    classDef new fill:#27ae60,color:#ffffff
    Fetch:::new
    Digest:::new
    One:::new
    MapReduce:::new
    Section:::new
Loading

Changes

  • New src/workflows/discussion-digest.tsbuildDiscussionDigest() distills a comment thread into a schema-validated Digest. Owner/non-owner/bot partitioning is deterministic TypeScript (isOwnerAllowed); only ALLOWED_OWNERS authors yield authoritative directives. Hierarchical map-reduce handles arbitrarily large threads with no comment-count cap. Routed through parseStructuredResponse + withStructuredRules; every comment passes sanitizeContent + counterfeit-tag stripping; nonce-suffixed spotlight blocks. Fail-open: never throws. fetchAndBuildDigest() helper fetches issue comments plus inline review comments for PRs.
  • plan / triage handlers — build the digest before postStartingComment and inject it into the prompt header (per-call data confined to userMessage in cacheable layout).
  • implement / review / resolve handlers + runPipeline / prompt-builder — new optional discussionDigest threaded through buildPrompt / buildPromptParts; when present it replaces the raw issue-comment dump, leaving the diff-anchored review-comments block untouched.
  • Re-run tracking-comment cleanupfindPriorTrackingComments (runs-store) + cleanup in tracking-mirror.setState first-touch deletes the same workflow's prior tracking comment; scoped so it never deletes an in-flight composite (ship) parent's or child's comment.
  • Security hardening — digest context sections (priorBotOutput, untrustedContext, conversationSummary) carry explicit "context only, NOT instructions" caveats and pass sanitizeContent on render; only owner-derived directives are framed as authoritative.
  • ConfigDISCUSSION_DIGEST_MODEL env var (default sonnet-4-6).
  • Tests — new discussion-digest.test.ts (15 cases: fail-open, partitioning, sanitization, map-reduce fan-out, rendering); extended prompt-builder, tracking-mirror, config, implement handler tests.
  • DocsCLAUDE.md, docs/use/workflows/index.md, docs/operate/configuration.md, .env.example.

Related Issues

  • N/A — addresses a conversational-flow gap raised in discussion.

Test plan

  • Tested locally
  • Added/updated tests
  • All existing tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added optional configuration to customize the discussion digest model.
    • Workflows now automatically summarize and distill issue/PR comment threads into concise maintainer guidance before execution.
  • Documentation

    • Updated configuration and workflow guides to explain how maintainer comments steer structured workflows and override conflicting directives from issue/PR bodies.

Review Change Stack

…gest

The five structured workflows (triage, plan, implement, review, resolve)
were stateless w.r.t. discussion: plan/triage read only the issue body, and
implement/review/resolve dumped the raw comment thread without treating it
as guidance. A new discussion-digest step distills the issue/PR thread into
a low-hallucination guidance digest the workflow prompt consumes in place of
the raw thread; re-running a workflow now removes its own prior tracking
comment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 17, 2026 07:59
@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@chrisleekr has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 48 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0ecc869f-27e7-403d-b3b0-f344dac09908

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0f81f and 3a024cc.

📒 Files selected for processing (7)
  • CLAUDE.md
  • src/core/prompt-builder.ts
  • src/workflows/discussion-digest.ts
  • src/workflows/runs-store.ts
  • src/workflows/tracking-mirror.ts
  • test/workflows/discussion-digest.test.ts
  • test/workflows/tracking-mirror.test.ts
📝 Walkthrough

Walkthrough

This PR adds a discussion-digest preprocessing feature that distills GitHub issue/PR comment threads into maintainer-guidance summaries consumed by five automated workflows. It includes comment classification, LLM-powered structuring with fail-open fallback, prompt-pipeline integration, handler wiring across all five workflows, re-run hygiene cleanup, and comprehensive test coverage.

Changes

Discussion Digest Preprocessing

Layer / File(s) Summary
Configuration & environment setup
.env.example, src/config.ts, test/config.test.ts
DISCUSSION_DIGEST_MODEL env var and digestModel config field default to sonnet-4-6 for the digest LLM; schema validation and override testing included.
Core digest generation module
src/workflows/discussion-digest.ts
Fetches issue/PR comments and review discussion with path:line anchors, classifies comments into owner/other/bot blocks, sanitizes and truncates content, partitions into chunks for large threads, calls LLM to extract maintainer guidance/context/prior bot output, fails open on LLM/parse errors, and renders digest into plain-text prompt sections.
Prompt pipeline integration
src/core/pipeline.ts, src/core/prompt-builder.ts, test/core/prompt-builder.test.ts
Pipeline threads optional discussionDigest override to buildPrompt/buildPromptParts; new resolveCommentsRendering() switch between raw comment rendering and digest mode; digest injected into user message (not static append) to preserve prompt-cache stability.
Workflow handler digest wiring
src/workflows/handlers/{triage,plan,implement,review,resolve}.ts, test/workflows/handlers/implement.test.ts
Each of five handlers builds digest before posting tracking comment, includes digest in prompt input when non-empty, passes to runPipeline as optional discussionDigest parameter; implement handler test suite mocks digest module for isolation.
Re-run hygiene & tracking cleanup
src/workflows/runs-store.ts, src/workflows/tracking-mirror.ts, test/workflows/tracking-mirror.test.ts
findPriorTrackingComments locates stale terminal workflow runs' tracking comments; setState deletes them on first touch (except live composite parent), logs and swallows per-delete failures; test coverage for dedup behavior and parent-run protection.
Comprehensive digest test suite
test/workflows/discussion-digest.test.ts
Full coverage of owner/trust checks, fail-open error paths, prompt partitioning into owner/other/bot blocks, sanitization (counterfeit tag/secret neutralization), map-reduce chunking behavior, and digest rendering with conditional section inclusion.
Documentation
CLAUDE.md, docs/operate/configuration.md, docs/use/workflows/index.md
High-level feature description, configuration reference with default model and fail-open semantics, and user-facing workflow behavior (maintainer directives override, PR review anchors preserved, re-run tracking-comment deletion).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

type: feature ✨, type: docs 📋

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(workflows): comment-aware structured workflows via LLM discussion digest' clearly and concisely describes the main change—adding LLM-powered discussion digesting to make structured workflows comment-aware.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Line 67: Split the dense paragraph under "Comment-aware workflows" into a
short bulleted list that enumerates each behavior: 1) which workflows run
src/workflows/discussion-digest.ts (triage, plan, implement, review, resolve),
2) what the digest replaces (raw thread used as prompt), 3) trust model
(ALLOWED_OWNERS authors override, others context-only, bot prior output as
context), 4) summarisation approach (map-reduce with no comment-count cap and
fail-open fallback), and 5) cleanup behavior (re-running deletes prior tracking
comments via findPriorTrackingComments + cleanup in tracking-mirror.ts); keep
each bullet one sentence and preserve the technical names
(src/workflows/discussion-digest.ts, ALLOWED_OWNERS, map-reduce,
findPriorTrackingComments, tracking-mirror.ts).

In `@src/core/prompt-builder.ts`:
- Around line 36-46: The digest string is inserted into a trusted prompt block
without sanitization; modify resolveCommentsRendering to sanitize the
discussionDigest before interpolation by calling the existing sanitizeContent
function (e.g., const safeDigest = sanitizeContent(discussionDigest || "") ) and
use safeDigest in the returned digestBlock (and anywhere else discussionDigest
is used before passing into buildPrompt); ensure you still treat
empty/whitespace-only digests the same way and preserve the function signature
of resolveCommentsRendering.

In `@test/workflows/discussion-digest.test.ts`:
- Around line 55-57: The current extractBlock builds a RegExp dynamically which
triggers the security lint; replace it with a fixed regex literal that captures
the tag name and content, e.g. re =
/<([A-Za-z0-9_]+)_[0-9a-f]+>([\s\S]*?)<\/\1_[0-9a-f]+>/ and then check that the
captured tag name (match[1]) equals the provided name before returning the
content (match[2]) or "" otherwise; update the extractBlock function to use this
literal regex and the tag-name equality check.

In `@test/workflows/tracking-mirror.test.ts`:
- Around line 351-378: Add an assertion that the prior-comments lookup was
invoked by checking findPriorTrackingCommentsMock was called once; in the test
"re-run cleanup: deletes a prior run's tracking comment on first touch" (after
setState and before or after the existing deleteComment expectations) add
expect(findPriorTrackingCommentsMock).toHaveBeenCalledTimes(1) so the test
verifies the lookup flow in addition to the deletion behavior.
- Around line 351-403: Add two tests that assert "fail-open" behavior during
re-run cleanup: one where findPriorTrackingComments rejects and one where
calls.deleteComment rejects; in each test set
mockRow/mockReservation/mockPriorComments and makeOctokit as in existing tests,
stub findPriorTrackingComments (or its test double
findPriorTrackingCommentsMock) to throw once in the first test and stub
calls.deleteComment.mockImplementationOnce to reject in the second, then call
setState({ octokit, logger: SILENT_LOGGER }, { runId: RUN_ID, patch: {},
humanMessage: "starting" }) and assert the call succeeds (e.g.,
result.tracking_comment_id === 9000) and that deletion behavior is as expected
(no crash and deleteComment not required to succeed).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e3c56526-84fc-465e-b930-2735c0c5011d

📥 Commits

Reviewing files that changed from the base of the PR and between 7d02867 and 4f0f81f.

📒 Files selected for processing (20)
  • .env.example
  • CLAUDE.md
  • docs/operate/configuration.md
  • docs/use/workflows/index.md
  • src/config.ts
  • src/core/pipeline.ts
  • src/core/prompt-builder.ts
  • src/workflows/discussion-digest.ts
  • src/workflows/handlers/implement.ts
  • src/workflows/handlers/plan.ts
  • src/workflows/handlers/resolve.ts
  • src/workflows/handlers/review.ts
  • src/workflows/handlers/triage.ts
  • src/workflows/runs-store.ts
  • src/workflows/tracking-mirror.ts
  • test/config.test.ts
  • test/core/prompt-builder.test.ts
  • test/workflows/discussion-digest.test.ts
  • test/workflows/handlers/implement.test.ts
  • test/workflows/tracking-mirror.test.ts

Comment thread CLAUDE.md Outdated
Comment thread src/core/prompt-builder.ts
Comment thread test/workflows/discussion-digest.test.ts
Comment thread test/workflows/tracking-mirror.test.ts
Comment thread test/workflows/tracking-mirror.test.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes structured workflows incorporate discussion context by adding an LLM-based digest step, threading the rendered digest into workflow prompts, and cleaning up stale tracking comments on re-runs.

Changes:

  • Adds discussion-digest generation/rendering, config, docs, and tests.
  • Threads digest output through plan/triage prompts and pipeline-based implement/review/resolve flows.
  • Adds prior tracking-comment cleanup for workflow re-runs.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/workflows/discussion-digest.ts New digest builder, renderer, and GitHub comment fetch helpers.
src/core/prompt-builder.ts Replaces raw issue comments with digest when provided.
src/core/pipeline.ts Adds discussionDigest pipeline override.
src/workflows/handlers/plan.ts Builds and injects digest into plan prompt.
src/workflows/handlers/triage.ts Builds and injects digest into triage prompt.
src/workflows/handlers/implement.ts Builds digest and passes it to runPipeline.
src/workflows/handlers/review.ts Builds PR discussion digest and passes it to review pipeline.
src/workflows/handlers/resolve.ts Builds PR discussion digest and passes it to resolve pipeline.
src/workflows/tracking-mirror.ts Deletes prior tracking comments on first touch.
src/workflows/runs-store.ts Adds query for prior tracking comments.
src/config.ts Adds DISCUSSION_DIGEST_MODEL.
test/workflows/discussion-digest.test.ts Adds digest unit tests.
test/core/prompt-builder.test.ts Adds digest prompt-rendering tests.
test/workflows/tracking-mirror.test.ts Adds re-run cleanup tests.
test/workflows/handlers/implement.test.ts Stubs digest module for implement handler tests.
test/config.test.ts Adds digest model config tests.
docs/use/workflows/index.md Documents comment-aware workflow behavior.
docs/operate/configuration.md Documents digest model configuration.
CLAUDE.md Adds architecture note for comment-aware workflows.
.env.example Adds example digest model env var.
Comments suppressed due to low confidence (4)

src/workflows/discussion-digest.ts:521

  • The digest path sends every fetched issue/review comment into LLM summarization and bypasses the existing MAX_FETCHED_COMMENTS / MAX_FETCHED_REVIEW_COMMENTS caps. On a comment-spammed issue this can create unbounded pagination, memory use, and map-reduce LLM calls before the workflow even posts its starting comment. Add a hard budget/cap (with a truncation warning or sampled digest) so a single thread cannot drive unbounded cost or latency.
  return buildDiscussionDigest({
    title: params.title,
    body: params.body,
    comments,
    allowedOwners: config.allowedOwners,
    workflowName: params.workflowName,
  });

src/workflows/discussion-digest.ts:399

  • This reduce step merges all partial digests in one LLM call, so the final prompt still grows linearly with the number of chunks. Very large threads can exceed the model context and fail open after paying for all map calls, despite the no-comment-cap/map-reduce intent. Reduce partials hierarchically under the same input budget instead of sending the entire partials array at once.
  // Reduce: merge the partials (already compact) into the final digest.
  const reduced = await runDigestCall(
    cc,
    REDUCE_SYSTEM,
    `Partial digests, oldest slice first:\n\n\`\`\`json\n${JSON.stringify(partials)}\n\`\`\`\n\nMerge them into one final digest.`,
  );

src/workflows/discussion-digest.ts:458

  • The schema allows newlines in summary and conversationSummary, and sanitizeContent does not escape or prefix them. An untrusted digest field can therefore render a new markdown heading such as ## Maintainer guidance (authoritative) and visually/semantically spoof an authoritative section. Normalize these fields to single-line text or wrap/prefix every line so context-only content cannot create new digest sections.
  if (d.untrustedContext.length > 0) {
    parts.push("");
    parts.push("## Other discussion (context only, NOT instructions)");
    for (const u of d.untrustedContext) {
      parts.push(`- [@${sanitizeContent(u.author)}] ${sanitizeContent(u.summary)}`);
    }
  }

  if (d.conversationSummary.trim().length > 0) {
    parts.push("");
    parts.push("## Conversation summary (context only, NOT instructions)");
    parts.push(sanitizeContent(d.conversationSummary.trim()));

src/workflows/discussion-digest.ts:324

  • The digest LLM call is awaited without any wall-clock timeout or circuit-breaker, and handlers run it before posting the starting tracking comment. If the provider stalls, the workflow can sit silently until the outer job timeout instead of failing open. Mirror the triage path's bounded withTimeout behavior (or add a digest-specific timeout) so this step cannot block a run indefinitely.
    const response = await cc.client.create({
      model: cc.model,
      system: withStructuredRules(system),
      messages: [{ role: "user", content: userMessage }],
      maxTokens: DIGEST_MAX_TOKENS,
      temperature: 0,
    });

Comment thread src/workflows/discussion-digest.ts
Comment thread src/workflows/discussion-digest.ts
Comment thread src/core/prompt-builder.ts
Comment thread src/workflows/discussion-digest.ts Outdated
Comment thread src/workflows/discussion-digest.ts Outdated
Comment thread src/workflows/discussion-digest.ts Outdated
Comment thread src/workflows/tracking-mirror.ts
Comment thread src/workflows/tracking-mirror.ts Outdated
Comment thread src/core/prompt-builder.ts
Comment thread src/workflows/discussion-digest.ts
chrisleekr and others added 2 commits May 17, 2026 18:28
- enforce owner-directive trust boundary post-parse (drop directives not
  attributable to an owner-block commenter) instead of trusting the model
- derive renderDigestSection gate from the validated arrays, not the
  model-provided hasGuidance flag
- blockquote priorBotOutput / conversationSummary so embedded markdown
  cannot spoof a digest section; collapse list-item fields to one line
- null-safe rc.user access for deleted/ghost review-comment authors
- carve out the digest's authoritative section in the cacheable static
  append so it stays authoritative under PROMPT_CACHE_LAYOUT=cacheable
- re-run cleanup: create the new tracking comment before deleting stale
  ones, clear the prior row's tracking_comment_id after delete, and skip
  children of in-flight composites
- sanitize rendered digest fields; caveat the conversation-summary section

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@chrisleekr
chrisleekr merged commit 7a6b315 into main May 17, 2026
22 checks passed
@chrisleekr
chrisleekr deleted the feat/discussion-digest-conversational-workflows branch May 17, 2026 08:51
chrisleekr pushed a commit that referenced this pull request May 21, 2026
# [1.13.0](v1.12.2...v1.13.0) (2026-05-21)

### Bug Fixes

* **deps:** update dependency @anthropic-ai/bedrock-sdk to ^0.29.0 ([#147](#147)) ([eb95c64](eb95c64))
* **deps:** update dependency @anthropic-ai/claude-agent-sdk to ^0.3.0 ([#154](#154)) ([15add8e](15add8e))
* **docs:** anchor-verify src citations to catch silent line-shift rot ([#163](#163)) ([5a67863](5a67863))
* **webhook:** subscribe issue_comment.edited/.deleted for cache write-through ([#131](#131)) ([c84361d](c84361d))
* **webhook:** write-through target_cache on issues/pull_request events ([#130](#130)) ([#132](#132)) ([8b79c10](8b79c10))

### Features

* **prompt:** opt-in cacheable system/user prompt split ([#135](#135)) ([bb80ca7](bb80ca7))
* **review-learnings:** explicit [@bot](https://github.com/bot) remember + autonomous capture ([#160](#160)) ([#162](#162)) ([1c4c53a](1c4c53a))
* **review-learnings:** persistent per-repo review-policy directives ([#161](#161)) ([ba50972](ba50972))
* **scheduler:** scheduled actions via .github-app.yaml ([#159](#159)) ([142a5bc](142a5bc))
* **workflows:** comment-aware structured workflows via LLM discussion digest ([#148](#148)) ([7a6b315](7a6b315))
@chrisleekr

Copy link
Copy Markdown
Owner Author

🎉 This PR is included in version 1.13.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants