LearnNewsExamplesServices
Frontmatter
id10702
titleExtend get_conversation to issues (selector parity with PRs)
stateClosed
labels
enhancementai
assigneesneo-opus-4-7
createdAtMay 4, 2026, 8:56 PM
updatedAtMay 22, 2026, 10:10 AM
githubUrlhttps://github.com/neomjs/neo/issues/10702
authorneo-opus-4-7
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 22, 2026, 10:10 AM

Extend get_conversation to issues (selector parity with PRs)

Closedenhancementai
neo-opus-4-7
neo-opus-4-7 commented on May 4, 2026, 8:56 PM

Context

This session's epic-review on #10691 surfaced a gap in surgical-fetch primitives. The PR-side comment-id selector contract shipped via #10272 (get_conversation({comment_id, since_comment_id, last_n})) has no issue twin. Agents reviewing epics or fetching specific issue comments must currently call get_local_issue_by_id, which returns the entire markdown file — frontmatter, body, and the full timeline of cross-references.

For long-lived epics like #10030 (3000+ lines, ~80% timeline cross-references with no review-time signal), this drains context budget for content that isn't load-bearing.

The Problem

Two related symptoms:

  1. Live surgical fetch missing on issues. get_conversation is dispatched to PullRequestService.getConversation only (ai/mcp/server/github-workflow/services/toolService.mjs line 22), which queries GraphQL's pullRequest field (PullRequestService.mjs line 115). The OpenAPI schema requires pr_number (openapi.yaml lines 277-279). No code path serves issues.
  2. Empirical impact. During this session's epic-review on #10691, fetching the epic body to surface @neo-gemini-3-1-pro's prior comment required get_local_issue_by_id returning the full markdown. The same review on a PR would have used get_conversation({pr_number, comment_id}) for ~10× less surface. The cost compounds across epic-review + ticket-intake + memory-mining workflows that all touch issues.

The selector contract itself (comment_id precedence > since_comment_id > last_n > full) is generic. Only the GraphQL query is PR-specific.

The Architectural Reality

The Fix

Extend the existing get_conversation tool to accept either pr_number or issue_number, reusing the selector contract. One tool, two backends, identical caller experience.

Concrete touches:

  1. New IssueService.getConversation(options) mirroring the PR method shape. Same selector precedence (comment_id > since_comment_id > last_n > full). Queries the GraphQL issue field instead of pullRequest.
  2. New GET_ISSUE_CONVERSATION GraphQL query in issueQueries.mjs, symmetric to GET_CONVERSATION for the issue field shape (title, body, author, comments.nodes).
  3. toolService.mjs dispatch update: route get_conversation to a thin router that picks PullRequestService.getConversation or IssueService.getConversation based on input shape (pr_number vs issue_number). One tool, two backends.
  4. openapi.yaml schema update: change pr_number from required to one-of (pr_number xor issue_number). Keep selectors unchanged. Update tool description to mention both PR and issue use cases.
  5. Tests: Playwright unit tests symmetric to the PR getConversation suite for the issue path. Cover all three selectors + full-fetch default + bad-input rejection (neither pr_number nor issue_number, or both supplied).
  6. Workflow doc verification: .agents/skills/epic-review/references/epic-review-workflow.md §2 already prescribes get_conversation for "the live epic issue body and comment thread directly from GitHub" — once this lands, the prescription will actually work end-to-end. No skill content change needed; verify in PR.

Token-budget anchor: for an epic the size of #10030 (3000+ lines, mostly timeline cross-references), get_conversation({issue_number, comment_id}) would surface the same target content in ~50 lines. Multi-cycle session savings compound across epic-review, ticket-intake, and memory-mining workflows.

Acceptance Criteria

  • IssueService.getConversation(options) exists with selector precedence matching PullRequestService.getConversation.
  • GET_ISSUE_CONVERSATION GraphQL query in issueQueries.mjs returns issue title, body, author, and comments shape.
  • get_conversation MCP tool accepts pr_number OR issue_number (mutually exclusive); both selector sets work identically.
  • Calling with neither parameter returns a structured MISSING_ARGUMENTS error.
  • Calling with both pr_number and issue_number returns a structured error (decide single source of truth — do not silently prefer one in PR review).
  • OpenAPI schema reflects the dual-input shape; description mentions both PR and issue use cases.
  • Unit tests cover: full fetch (issue), comment_id (issue), since_comment_id (issue), last_n (issue), missing both, both supplied.
  • No regression on the existing PR-side get_conversation test suite.

Out of Scope

  • Discussion-conversation surgical fetch. DiscussionService doesn't yet have a getConversation method. If needed, file a separate ticket — same pattern, different service.
  • Local-side surgical fetch. Extending get_local_issue_by_id with timeline-elision selectors is a complementary local-path optimization (stale-but-cheap vs live-and-surgical). Different concern; would deserve its own ticket.
  • Bulk migration of existing get_local_issue_by_id callers. Agents adopt the new tool naturally as workflow docs reference it; no migration sweep needed.
  • Changing the existing PR-side get_conversation contract. Strictly additive.

Avoided Traps / Gold Standards Rejected

  • Rejected: separate get_issue_conversation tool. Adds parallel tool surface for an identical contract (selectors are the same; only the resource type differs). The existing get_conversation already abstracts the conversation concern; dual-purposing the dispatch is the elegance.
  • Rejected: bulk-extend get_local_issue_by_id with selectors instead. Solves a different problem (local stale-but-cheap path vs live surgical path). Mixing them obscures both. Local-path optimization is a separate ticket if pursued.
  • Rejected: lift the GraphQL conversation query into a shared ConversationQuery helper. Tempting DRY, but the GraphQL field shapes differ enough between issue and pullRequest types that the abstraction would mostly be parameter passing. Premature.

Related

  • Predecessor: #10272 (Comment-ID-aware PR workflow — shipped the PR-side primitive)
  • Empirical anchor: epic-review on #10691 (surfaced this gap during the current session — see comment IC_kwDODSospM8AAAABBK8osQ)
  • Adjacent token-budget vectors: #10537 (skill modularization), #10083 (AGENTS.md §9 single-full-read softening)
  • Workflow consumers (post-fix): .agents/skills/epic-review/references/epic-review-workflow.md §2; .agents/skills/pr-review/...; .agents/skills/ticket-intake/...

Origin Session ID: 7e52099b-9632-4c67-a2a1-4e1a1ad1c414

Retrieval Hint: "get_conversation issue surgical fetch comment_id selector parity PR token budget"

tobiu referenced in commit a2bb8b6 - "feat(github-workflow): extend get_conversation to issues (#10702) (#11746) on May 22, 2026, 10:10 AM
tobiu closed this issue on May 22, 2026, 10:10 AM