GitHub transport: whole-map GraphQL read and single-ticket REST refresh - #126
Conversation
…issue refresh Implements #117 (spec §4). fetch_wayfinder_map runs the spec's verbatim one-query map read (cost 10, flat in map size) and normalizes straight into ticket::Effort: claims from assignees, Type from the wayfinder: label, dependencies from the blockedBy list (same-effort / external / unknown), never the eventually-consistent summary counters. get_issue / list_issues_with_label cover the single-ticket refresh and label listing; the §4.4 fallback dialect is detected (task-list map bodies, leading Part of / Blocked by lines) but never parsed into the model.
…ules Review fixes on #117: a malformed map now lands in failed_maps instead of discarding the whole read (spec §5.5); the Claim and wayfinder-label Type rules move to shared helpers so the single-ticket refresh (#120) can't drift from the map read; Destination extraction accepts any heading level; the summary types drop their unused GraphQL aliases and their docs now tell the truth about who deserializes them.
There was a problem hiding this comment.
🟡 Changes recommended
The new body_has_task_list helper uses strip_prefix(['-', '*', '+']), which is likely a compile error and must be corrected before merge.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds the GitHub-side transport needed for the Wayfinder ticket surface: a single GraphQL whole-map read normalized into ticket::Effort, plus REST endpoints/parsers for single-ticket refresh and label-filtered issue discovery, with shared wire/domain mapping utilities to avoid drift.
Changes:
- Introduces GitHub “Issue” wire/domain types and shared normalization helpers (
IssueStateparsing, claim/type derivation) insrc/github/types.rs. - Implements whole-map GraphQL read + normalization into
Effort(with degradation signals) and adds RESTget_issue/list_issues_with_labelwith pagination support. - Adds fixture-driven parsing tests for both the GraphQL map read and REST issue refresh/listing behavior.
File summaries
| File | Description |
|---|---|
| src/ticket.rs | Updates module TODO comment to reflect the local-parser/fetch-layer integration plan. |
| src/github/types.rs | Adds issue-related wire types + shared claim/type derivation helpers used by both GraphQL and REST paths. |
| src/github/rest.rs | Adds REST issue fetch + label-filtered listing; generalizes pagination helper to accept query params; adds issue parsing/filtering. |
| src/github/rest/tests.rs | Adds tests for REST single-issue parsing defaults, state handling, and listing PR-filter behavior. |
| src/github/graphql.rs | Adds GraphQL whole-map read response types + normalization pipeline and fallback-dialect detection helpers. |
| src/github/graphql/tests.rs | Adds comprehensive tests for map parsing, dependency classification/degradation, and body helper behavior. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| fn body_has_task_list(body: &str) -> bool { | ||
| body.lines().any(|line| { | ||
| let Some(rest) = line.trim_start().strip_prefix(['-', '*', '+']) else { | ||
| return false; | ||
| }; | ||
| let rest = rest.trim_start(); | ||
| rest.starts_with("[ ]") || rest.starts_with("[x]") || rest.starts_with("[X]") | ||
| }) | ||
| } |
🤖 Augment PR SummarySummary: This PR adds the GitHub transport foundation for the Wayfinder ticket surface.
🤖 Was this summary useful? React with 👍 or 👎 |
| } | ||
| let lower = trimmed.to_ascii_lowercase(); | ||
| let is_dependency_line = lower.starts_with("part of ") || lower.starts_with("blocked by:"); | ||
| if is_dependency_line && line_has_issue_ref(trimmed) { |
There was a problem hiding this comment.
src/github/graphql.rs:700: This loop never tracks fenced code blocks, so a leading code sample containing Part of #123 returns true even though the function documents that references in code fences do not match. That incorrectly reports the fallback dialect for a ticket using only native data.
Severity: low
Other Locations
src/github/graphql.rs:679
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Implements #117 (spec §4 of the wayfinder ticket surface spec — the resolution comment on #112). Part of map #123.
What
src/github/types.rs:Issue(thePRanalog for issues),IssueState,SubIssuesSummary,DependenciesSummary— permissive#[serde(default)]posture — plus theIssueState → TicketStatemapping and sharedclaim_from_assignees/ticket_type_from_labelsrules, so the map read and the single-ticket refresh can't drift.fetch_wayfinder_mapingraphql.rs: the spec's verbatim one-query map read (cost 10, flat in map size), normalized straight intoticket::Effort. The envelopeimpl GraphQlErrors(with a test proving HTTP-200 errors can't parse as empty success). Blocked-ness inputs come from theblockedBylist with per-nodestateandrepository.nameWithOwner— the eventually-consistent summary counters aren't even deserialized on this path.get_issue+list_issues_with_labelinrest.rsbesidelist_issue_comments, via a generalizedget_all_with.GET …/parentis never called; every 404 stays a genuine error (including the missing-Issues-scope case). The issues listing drops the PRs the endpoint mixes in.fallback_dialect;has_fallback_dependency_linescoversPart of #n/Blocked by: #nleading lines for the drill-in path. Nothing is parsed into the model.failed_mapsinstead of discarding the repo's other Efforts.parse_*fns with fixture tests ingraphql/tests.rs/rest/tests.rs(11 new tests; 761 total green).Decisions the spec didn't settle (small, resolved in-ticket)
TicketKey(thePrKey::html_url()precedent); Ticket detail page #122 adds the/issues/accessor.parent_issue_urlstays a raw wire string until a consumer navigates by it.blockedBypage (impossible under thefirst:50cap) degrades to an Unknown Dependency so unseen blockers can't put a ticket on the Frontier.