feat(tracker): multi-provider issue intake (GitHub, Linear, Jira)#2288
Draft
anirudh5harma wants to merge 6 commits into
Draft
feat(tracker): multi-provider issue intake (GitHub, Linear, Jira)#2288anirudh5harma wants to merge 6 commits into
anirudh5harma wants to merge 6 commits into
Conversation
This was referenced Jun 29, 2026
Add TrackerProviderLinear and TrackerProviderJira and extend TrackerIntakeConfig with the provider-specific scope fields the new adapters need: - github keeps Repo (owner/repo or derived from origin) - linear adds Team (team key) - jira adds BaseURL + ProjectKey (Cloud site + project key) TrackerRepo gains an optional BaseURL so Jira's tenant site can travel with cross-issue queries without forcing a separate parameter. Validate now branches per provider and rejects cross-provider field bleed (e.g. a Linear Team left set after switching back to GitHub) so a stale field can't silently survive a provider switch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements ports.Tracker against Linear's GraphQL API. Scope is the team key — List queries issues filtered by team + state.type + labels + assignee displayName, paginated via the first: argument. Workflow-state mapping: - backlog/unstarted/triage -> open - started -> in_progress - completed -> done - cancelled -> cancelled Credentials come from AO_LINEAR_TOKEN (with LINEAR_API_KEY as a fallback). Personal API keys and Bearer-prefixed OAuth tokens both pass through the Authorization header opaquely. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements ports.Tracker against Jira Cloud REST. Scope is the project key plus the tenant site base URL (carried per-call via TrackerRepo.BaseURL so one adapter instance can serve multiple Jira tenants under one account). List builds a JQL query (project + statusCategory + labels + assignee ORDER BY created DESC) and calls /rest/api/3/search. Quoted scalars are escaped so a `"` in a label can't break out of the JQL string. Status-category mapping: - new / unknown -> open - indeterminate -> in_progress - done -> done, or cancelled when the status name is "Cancelled" / "Won't Do" Description bodies use Jira's Atlassian Document Format; the adapter flattens text leaves into plaintext so the intake prompt stays readable without a heavyweight ADF dependency. Credentials come from AO_JIRA_EMAIL + AO_JIRA_TOKEN as Basic auth. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the observer's single ports.Tracker with a TrackerResolver interface so each project's poll is dispatched to the adapter that matches its configured provider. SingleTrackerResolver lets tests and single-provider deployments keep using one adapter without constructing a map. trackerRepo() now computes the per-provider scope: - github: configured Repo, falling back to the parsed origin URL - linear: configured Team key - jira: configured ProjectKey + BaseURL (passed via TrackerRepo.BaseURL) The daemon wires a multiTrackerResolver that owns three lazy adapters, constructed only on first use so projects that don't configure a given provider pay no auth or network cost. GitHub keeps the existing AO_GITHUB_TOKEN + `gh auth token` precedence; Linear reads AO_LINEAR_TOKEN; Jira reads AO_JIRA_EMAIL + AO_JIRA_TOKEN. The `seen` map no longer indexes by bare native ID. With three providers in play, native IDs collide (Linear "ENG-1" vs Jira "ENG-1"), so dedupe runs only on the canonical "<provider>:<native>" form. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Picks up TrackerIntakeConfig's new team / baseURL / projectKey fields and the github / linear / jira provider enum from the domain change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7972d0d to
3637ac4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Registered projects can now opt into issue-driven intake against GitHub, Linear, or Jira. The daemon polls explicitly eligible open issues for each project's configured provider and starts one worker session per issue, preserving the canonical
<provider>:<native>issue identifier so restarts cannot create duplicates. Existing projects remain unaffected until intake is enabled with a label or assignee rule; tracker or spawn failures back off per-project without blocking the daemon.Closes #2282. This builds on the original GitHub-only port of aoagents/ReverbCode#424 and extends it to two more providers behind the same observer.
What this PR adds
backend/internal/adapters/tracker/linear/) — GraphQL client, team-key scope, state mappingbacklog/unstarted/triage→open,started→in_progress,completed→done,cancelled→cancelled. Credentials fromAO_LINEAR_TOKEN(withLINEAR_API_KEYfallback).backend/internal/adapters/tracker/jira/) — REST + JQL client, project-key + tenant base-URL scope, status-category mapping plus"Cancelled"/"Won't Do"name heuristics, ADF description flattening. Credentials fromAO_JIRA_EMAIL+AO_JIRA_TOKENBasic auth.TrackerProviderLinear,TrackerProviderJira, and provider-specific scope fields onTrackerIntakeConfig(Teamfor Linear;BaseURL+ProjectKeyfor Jira;Repostays GitHub-only). Per-providerValidate()rejects cross-provider field bleed so a staleTeamcannot survive a switch back to GitHub.TrackerResolverinterface dispatches each project's poll to the right adapter. The daemon wires amultiTrackerResolverwith three lazy adapters; providers a project doesn't configure pay no auth or network cost.seendeduplication is now keyed by the canonical<provider>:<native>form so LinearENG-1and JiraENG-1don't collide.TrackerIntakeConfigpicks upteam,baseURL,projectKey, and thegithub | linear | jiraenum.Design notes
TrackerRepo.BaseURLcarries the tenant site per-call so a single adapter instance can serve multiple Jira tenants.AO_GITHUB_TOKEN+gh auth tokenprecedence and 5-minute token cache.Validation
cd backend && go build ./...cd backend && go vet ./...go test -race ./internal/observe/trackerintake/... ./internal/adapters/tracker/... ./internal/domain/...— all greenlinear/tracker_test.go— list filter shape, assignee wildcard pass-through, Get happy path / not-found, unauthorized →ErrAuthFailed, wrong-provider routingjira/tracker_test.go— JQL composition incl. quote escaping, base-URL normalization, Cancelled/Won't Do mapping, ADF flatten, wrong-provider routingdomain/projectconfig_test.go— per-provider Validate rows incl. cross-provider field bleedobserve/trackerintake/observer_test.go— three-project multi-provider poll asserts each adapter receives its own scope and no IDs collidenpm run apinpm run frontend:typecheckgo test ./...still hits the pre-existing duplicate migration version 20 panic onmain(0020_review_run_unique_pr_sha.sql+0020_pr_reviews.sql); this PR does not touch migrations.Post-deploy monitoring
For the first enabled project per provider, watch
tracker intakelog lines and confirm one eligible issue creates one worker session carrying the canonical<provider>:<native>id. Healthy signals: no repeated spawn after a second poll or daemon restart; no polling for projects with intake disabled;tracker intake disabled: no usable …warnings if a provider's env vars are missing. Rollback trigger: repeated spawn attempts for the same issue, broad intake without explicit rules, or daemon-startup degradation; the immediate mitigation is unsettingtrackerIntake.enabledper project.