One table answers "does this work on my origin?". A gadak workspace has exactly one origin — Jira (Atlassian Cloud), Linear, or Built-in (the tracker that travels with the app, from 0.16). The mirror is a cache on all three; every write passes through the origin and the mirror row is re-read after it lands.
Every cell carries a footnote pointing at the code that makes it true — a
path:line in this repository, or a line in the Built-in origin's
compatibility inventory (module
github.com/midagedev/issuetap@v0.0.0-20260903043728-916803b7b457, cited
below as issuetap/docs/COMPATIBILITY.md). A Built-in cell is never "same
as Jira": it means the Jira REST verb exists and the Built-in origin
implements the route.
Markers:
- ✅ — works
- ◐ — works, with a limitation the footnote names
- — — not on this origin; gadak refuses with a sentence rather than half-applying
| Capability | Jira | Linear | Built-in |
|---|---|---|---|
| Read · issue sync | ✅1 | ✅2 | ✅3 |
Read · full-text search (gadak search) |
✅4 | ✅4 | ✅4 |
Read · SQL (gadak sql, issues_full + RECIPES) |
✅5 | ✅6 | ✅5 |
Read · --jql / pasted Jira URL |
✅7 | ✅8 | ✅7 |
| Read · comments | ✅9 | ✅10 | ✅9 |
| Read · attachment bytes | ✅11 | ✅12 | ◐13 |
Read · history → status_changed_at, time-in-status, reopen_count |
✅14 | —15 | ✅16 |
| Read · issue links | ✅17 | ✅18 | ✅19 |
Read · remote issue links / cross-workspace refs (ref) |
—20 | —20 | ◐21 |
Read · development-panel links (dev) |
◐22 | —23 | ✅24 |
| Read · labels | ✅25 | ✅26 | ✅25 |
| Read · components | ✅25 | —27 | ✅28 |
Read · fix versions + versions catalog |
✅29 | —30 | ✅31 |
Read · sprints (columns sprint_id/sprint_name/sprint_state) |
✅32 | —33 | —34 |
Read · custom fields (fields --apply) |
✅35 | —36 | ◐37 |
| Read · issue type | ✅38 | —39 | ✅40 |
Read · hierarchy — parent_key / epic_key |
✅41 | ◐42 | ✅43 |
| Read · wiki pages | ✅44 | —45 | ✅46 |
Read · origin web URL — gadak open, web key anchor, copy link |
✅47 | ✅48 | ◐49 |
| Read · view link — toolbar / palette "Copy link to this view" | ✅50 | ◐51 | ◐52 |
| Write · create issue | ✅53 | ◐54 | ✅55 |
| Write · comment — visibility / internal | ✅56 | ◐57 | ✅58 |
| Write · transition — screen fields | ✅59 | ◐60 | ✅61 |
| Write · assign / unassign | ✅62 | ✅63 | ✅64 |
| Write · label edits | ✅65 | —66 | ✅67 |
| Write · component edits | ✅65 | —66 | ✅68 |
| Write · priority edit | ✅65 | ✅69 | ✅67 |
| Write · due date — set | ✅70 | ✅70 | ✅70 |
| Write · due date — clear | ✅71 | —72 | ✅71 |
| Write · summary / description edit | ✅73 | ◐74 | ✅73 |
| Write · custom-field edit | ✅35 | —75 | ◐37 |
Write · issue type edit (edit --type) |
✅76 | —77 | ✅78 |
| Write · parent set / clear | ✅79 | —80 | ✅81 |
| Write · attachment upload | ✅82 | ✅83 | ✅82 |
| Write · link / unlink issues | ✅84 | —85 | ✅86 |
| Write · wiki write — page create / edit / comment | ✅87 | —45 | ✅88 |
Write · claim |
◐89 | —90 | ✅91 |
Write · worklog (gadak api --write) |
✅92 | —93 | —94 |
Write · migrate --from (source) |
✅95 | ◐96 | ✅97 |
Write · migrate --to (destination) |
—98 | ◐98 | ✅99 |
| Surface · agent surfaces — skill / MCP / SQL | ✅100 | ✅100 | ✅100 |
| Surface · board layout (0.19) | ✅101 | ✅101 | ✅101 |
Surface · views open --keys - |
✅102 | ✅102 | ✅102 |
| Surface · watch feed + OS alerts | ✅103 | ◐104 | ✅103 |
| Surface · in-process origin (no network to the tracker) | —105 | —105 | ✅106 |
This file is the single owner of the matrix — the READMEs summarize it and
link here. A commit that touches internal/origin/writer.go,
internal/origin/linearwriter.go, internal/linear/, internal/sync/, or
bumps the issuetap dependency updates this file in the same commit: a
refusal added or removed there is a cell changed here. Structural drift — a
missing file, a malformed row, a footnote marker with no definition, a
README that stopped linking — is caught by tools/doc-checks.sh check
#39; whether a cell still tells the truth is review's job. Generating
this table from the code instead of maintaining it by hand is GDK-1301.
Footnotes
-
Atlassian Cloud REST (
internal/jira/client.go:165), mirrored by the Jira-family sync pass (internal/sync/run.go:50,internal/sync/sync.go:153). ↩ -
Read-only GraphQL at
api.linear.app(internal/linear/client.go), mirrored by the Linear pass (internal/sync/run.go:62,internal/sync/linear.go). ↩ -
The same Jira-family pass, answered in-process by the origin (
internal/origin/transport.go:102) or by a paired serve (:108). ↩ -
Runs on the mirror — SQLite FTS over
issues_full, origin-agnostic (cmd/gadak/agent.go:1167). ↩ ↩2 ↩3 -
The mirror's schema is the contract (
specs/000-product/data-model.md); queries never touch the origin. ↩ ↩2 -
Same schema, but columns Linear does not map (issue type, components, fix versions, sprint,
epic_key, custom fields) read empty/NULL (internal/sync/linear.go:218,internal/linear/MAPPING.md). ↩ -
The documented JQL subset (
docs/decisions/0007-jql-subset.md), evaluated in-memory over mirror rows (cmd/gadak/agent.go:1336) — the origin is not queried, so the subset is the same on every origin. ↩ ↩2 -
Same in-memory evaluation; a clause over a column Linear does not populate matches nothing rather than erroring. ↩
-
Comments mirror with the issue (
internal/sync/sync.go:843). ↩ ↩2 -
Fetched as follow-up passes after the issue list — comments, labels, attachments each round-trip once (
internal/sync/linear.go:90); comment bodies are Linear markdown. ↩ -
Serve proxies
cfg.Site + /rest/api/3/attachment/content/{id}(internal/server/attachment.go:279). ↩ -
Serve fetches
uploads.linear.appwith the workspace's Linear API key (internal/server/attachment.go:268). ↩ -
The origin serves the bytes (
issuetap/docs/COMPATIBILITY.md:76), but serve's proxy builds a site URL a Built-in workspace does not have — on a cold cache the proxy fails (internal/server/attachment.go:279with an emptycfg.Site; measured 502).gadak api GET /rest/api/3/attachment/content/{id}still returns the bytes — the same route the migrate export uses (cmd/gadak/migrate.go:105). ↩ -
Changelog events (
internal/jira/client.go:206) feedstatus_changed_atandreopen_count; time-in-status is computed fromstatus_changed_at, never stored as a column (specs/000-product/data-model.md). ↩ -
Linear's read path carries no state history —
status_changed_atandreopen_countstay NULL (internal/sync/linear.go:46,:223). ↩ -
The origin keeps a changelog and serves it (
issuetap/docs/COMPATIBILITY.md:71); the same columns derive from it. ↩ -
issuelinksin the issue payload plus the link-type catalog (internal/jira/write.go:234,:212). ↩ -
Issue.relations/inverseRelationsin the issue query (internal/linear/queries.go:89) land as outward / inwardlinksrows named like the Jira ones — Blocks, Duplicate, Relates (internal/sync/linear.go:286); GDK-1299. ↩ ↩2 -
Link-type catalog and both-direction elements (
issuetap/docs/COMPATIBILITY.md:59,:75). ↩ -
refneeds an origin that stores remote links where gadak can read them back; every non-Built-in origin is refused (internal/origin/writer.go:104,:169). ↩ ↩2 -
Works embedded and paired (
cmd/gadak/ref.go:118,internal/jira/remotelink.go:51); the sync pass refreshes the mirror only when the origin is embedded — on a paired workspace the list updates whenrefwrites, not on sync (internal/origin/writer.go:191). ↩ -
Opt-in:
dev_statusin config gates the fetch and the panel (internal/sync/sync.go:1361). ↩ -
Linear exposes no development panel to mirror — the Linear record builder has no dev-link half (
internal/sync/linear.go). ↩ -
Always fetched, embedded or paired (
internal/sync/sync.go:1367);dev link|deploy|buildwrites pass through (cmd/gadak/dev.go:55,issuetap/docs/COMPATIBILITY.md:77). ↩ -
Mirrored with the issue row (
internal/sync/sync.go:843). ↩ ↩2 ↩3 -
Labels arrive as a follow-up fetch (
internal/sync/linear.go:93). ↩ -
Not a Linear concept in gadak's mapping — the columns stay empty (
internal/sync/linear.go:218). ↩ -
A per-project catalog derived from the project's issues (
issuetap/docs/COMPATIBILITY.md:75). ↩ -
GET /project/{key}/versions(internal/jira/write.go:181). ↩ -
ErrNoVersionCatalog(internal/origin/writer.go:100); the columns stay empty (internal/sync/linear.go:218). ↩ -
The catalog is derived from the project's issues, and this is the one origin where a write may mint a version by name (GDK-678,
internal/origin/writer.go:51). ↩ -
The sprint columns come from the Jira Software sprint field, discovered per site (
internal/sync/sprint.go:18,:58); a site without it syncs empty. No origin gets sprint verbs — moving an issue between sprints or editing a sprint happens in Jira. ↩ -
Linear has no sprint concept in gadak's mapping (
internal/linear/MAPPING.md). ↩ -
The origin's issue model has no sprint field — the editable set carries none (
issuetap/docs/COMPATIBILITY.md:72). ↩ -
GET /fieldcatalog (internal/jira/client.go:311); editable kindstext,number,date,option,user,multi_option/version_array, gated by the issue's editmeta and the configured field allowlist. Cascading selects and textarea custom fields have no editor. ↩ ↩2 -
No custom-field mapping exists (
internal/linear/MAPPING.md). ↩ -
Only fields declared by the workspace's data exist, and there is no field-creation route (
issuetap/docs/COMPATIBILITY.md:72— "fixture custom fields"). ↩ ↩2 -
From create/edit metadata (
internal/jira/write.go:379,:322). ↩ -
issue_typemaps from nothing (internal/linear/MAPPING.md:82). ↩ -
Editable set with allowed values (
issuetap/docs/COMPATIBILITY.md:72). ↩ -
parent_keymirrors the direct parent;epic_key(nearest hierarchy-level-1 ancestor) is derived at sync (internal/sync/sync.go:843). ↩ -
parent_keyis a true mapping fromIssue.parent;epic_keystays NULL — "epic" is a Jira-ism that would lie whenever a team nests sub-issues (internal/linear/MAPPING.md:137). ↩ -
A parent must exist and sit exactly one hierarchy level above the child (
issuetap/docs/COMPATIBILITY.md:75). ↩ -
Confluence Cloud through the wiki client (
internal/origin/origin.go:408,internal/sync/confluence.go). A team-spaces fix landed on main after this table's base (17e48607). ↩ -
Linear provides no wiki — the client refuses with one sentence (
internal/origin/origin.go:70). ↩ ↩2 -
/wiki/rest/apispaces, CQL, pages, versions, comments (issuetap/docs/COMPATIBILITY.md:78). ↩ -
One resolver per surface, and it branches on the origin type with no fallback across them (GDK-1308): Jira is
cfg.Site + /browse/KEY(web/src/lib/issue-origin.ts:29,cmd/gadak/agent.go:2761); the header key anchor and the copy-link paste lead with it (web/src/components/detail/DetailHeader.svelte:126). ↩ -
Linear has no site; the page Linear itself minted is stored on the row (
items.url) by sync, andgadak open, the key anchor, copy-link and the palette all open that (web/src/lib/issue-origin.ts:32,cmd/gadak/agent.go:2746; GDK-1149). A row without a stored url is a missing link, never a Jira URL. ↩ -
There is no origin page — the Built-in tracker's page is this app.
gadak openfocuses the running serve on the issue (cmd/gadak/agent.go:2779); the web has no origin link and copy-link pastes app links only. ↩ -
The issue navigator with this view's JQL (
<site>/issues/?jql=…,web/src/lib/view-link.ts:52) leads the paste, thengadak://view?<hash>and the http link (GDK-1343). The JQL comes from the server'sjql/emit/; clauses it cannot carry are named in the toast. ↩ -
Linear has no public URL parameter that carries a filter, so the paste is the app lines alone (
web/src/lib/view-link.ts:49) — the same branch the issue copy-link takes on the Built-in origin. No stand-in. ↩ -
The Built-in tracker's page is this app: app lines only (
web/src/lib/view-link.ts:49). ↩ -
POST /issue(internal/jira/write.go:331). ↩ -
Create works; assignee, labels, parent, and issue type are refused on create (
internal/origin/linearwriter.go:297,cmd/gadak/create.go:377). ↩ -
issuetap/docs/COMPATIBILITY.md:75, with the same parent-hierarchy rule. ↩ -
ADF body with optional visibility and internal flag (
internal/jira/write.go:256). ↩ -
Neither visibility nor internal (
internal/origin/linearwriter.go:84); the body is serialized back to markdown (:95,adf.Markdown— the identity on the markdown subset, GDK-1386); mentions and inline media degrade to their text. ↩ -
visibilityplus thesd.public.commentinternal mapping (issuetap/docs/COMPATIBILITY.md:75). ↩ -
POST /issue/{key}/transitionswith fields and comment (internal/jira/write.go:116). ↩ -
Linear transitions carry no screen fields (
internal/origin/linearwriter.go:73). ↩ -
fields.resolutionandupdate.commentare honored, screen-checked (issuetap/docs/COMPATIBILITY.md:70). The seeded workflow puts an optionalresolutionon the done transition's screen (internal/origin/origin.go:739, GDK-1347); a workspace seeded before 0.20.1 has none, and its screen 400 is reworded in gadak's terms (cmd/gadak/agent.go:2394). ↩ -
PUT /issue/{key}/assignee(internal/jira/write.go:276). ↩ -
The fields path carries assign and unassign (
internal/origin/linearwriter.go:178); refused only at create time (:297). ↩ -
POST /issue/{key}/assignee(issuetap/docs/COMPATIBILITY.md:75). ↩ -
The two-part edit —
fieldsreplaces,updatecarries add/remove operations (internal/jira/write.go:293). ↩ ↩2 ↩3 -
The
updatehalf is refused outright (internal/origin/linearwriter.go:203) and any field outside the editable set is refused (:190); labels are deliberately absent from Linear edit metadata. ↩ ↩2 -
Full replace on
fields,add/remove/setonupdate, by id or by name (issuetap/docs/COMPATIBILITY.md:75). ↩ -
Linear's 0-4 scale; clearing a priority maps to 0, "No priority" (
internal/origin/linearwriter.go:160,:32). ↩ -
edit --due YYYY-MM-DD→fields.duedate(cmd/gadak/edit.go:355); Linear takes the date as-is (internal/origin/linearwriter.go:169). ↩ ↩2 ↩3 -
edit --due none→fields.duedate = nil(cmd/gadak/edit.go:353). ↩ ↩2 -
"clearing a due date is not supported yet" (
internal/origin/linearwriter.go:171). ↩ -
fields.summary/fields.description(cmd/gadak/edit.go:305,internal/jira/write.go:286). ↩ ↩2 -
The description is serialized back to markdown on write (
internal/origin/linearwriter.go:157, create:324) — headings, lists, tables and marks survive; panels, media and mentions degrade to text (GDK-1386). On Jira and Built-in, a body's nodes markdown cannot carry stand as placeholders in the editing source andedit -mputs them back (GDK-1396, decision 0012 addendum 1); a text with none over a body that has them is refused without--force-plain. Linear bodies have no such nodes, and a placeholder in a Linear edit is refused.edit --adf-file/comment --adf-filesend a document as it is and skip that guard (GDK-1395) — on Linear that document is still serialized to markdown on the way in. ↩ -
Any field outside Linear's editable set is refused (
internal/origin/linearwriter.go:190). ↩ -
edit --type→fields.issuetype(cmd/gadak/edit.go:301). ↩ -
ErrNoIssueTypes— Linear has no issue types (internal/origin/writer.go:133). ↩ -
issuetypewith allowed values (issuetap/docs/COMPATIBILITY.md:72). ↩ -
create --parent/edit --parent KEY|none→fields.parent(cmd/gadak/create.go:528,cmd/gadak/edit.go:305). Jira has no dedicated REST parent route — the edit fields path is the only road. ↩ -
Refused on create (
cmd/gadak/create.go:377) and on edit (internal/origin/linearwriter.go:190). ↩ -
Same
fields.parent, with hierarchy validation and honest 400s (issuetap/docs/COMPATIBILITY.md:75). ↩ -
POST /issue/{key}/attachmentsmultipart (internal/jira/write.go:449). ↩ ↩2 -
URL-first: reserve storage, PUT the bytes, confirm (
internal/origin/linearwriter.go:366). ↩ -
POST /issueLink,DELETE /issueLink/{id}, catalog viaGET /issueLinkType(internal/jira/write.go:222,:246,:212). ↩ -
The write half refuses with
ErrNoIssueLinks(internal/origin/writer.go:101); reading relations is 18. ↩ -
Same routes (
issuetap/docs/COMPATIBILITY.md:59,:75). ↩ -
gadak page create|edit|comment→ Confluence REST through the wiki client (cmd/gadak/page.go:189,:410,:326;internal/origin/origin.go:408). ↩ -
POST /wiki/rest/api/content,PUT …/{id}with a version check (issuetap/docs/COMPATIBILITY.md:83). ↩ -
No atomic claim route on Cloud — the fallback runs assignee + transition as two calls and says so (
internal/claim/claim.go:9,cmd/gadak/agent.go:2556). ↩ -
Refused before any call: claim is a Jira-workflow verb and the Linear writer does not implement it (
cmd/gadak/agent.go:2539). ↩ -
One atomic mutation — the origin's own extension route (
issuetap/docs/COMPATIBILITY.md:71). ↩ -
The
apiverb passes any Jira REST route through the origin client (cmd/gadak/api.go:140). ↩ -
The
apiverb needs a Jira-family credential; a Linear-only workspace is refused (internal/origin/origin.go:139). ↩ -
Unknown Jira routes get the honest 501
unsupported_endpoint(issuetap/internal/api/jira.go:89). ↩ -
The export reads the mirror; attachment bytes come from the origin's attachment route (
cmd/gadak/migrate.go:23). ↩ -
Linear attachment URLs are not byte-fetchable the way Jira's are — a workspace with attachments refuses without
--skip-attachments(cmd/gadak/migrate.go:89). ↩ -
The export's byte fetch uses the same passthrough route (
cmd/gadak/migrate.go:105). Bodies leave as the origin's ADF beside their text (internal/migrate/migrate.go:83), so headings, lists and paragraph breaks arrive as written (GDK-1382). ↩ -
--to linear --team KEYsends a mirror's issues into a Linear team through the Linear workspace the command runs in (cmd/gadak/migrate.go:56,internal/migrate/linear.go; GDK-1265): issues, comments, parents and relations land, idempotent on re-run via agadak-migrate: KEYfooter; change history, wiki pages, dev links, custom fields and sprints stay behind and the report says so. Jira is not a destination. ↩ ↩2 -
The migrate command creates a fresh Built-in workspace as its target, which must not exist yet (
cmd/gadak/migrate.go:23). The fixture'sdescriptionAdf/bodyAdfslots are stored verbatim when they parse as a document; a body without one is wrapped as a single paragraph (issuetap/internal/store/store.go,fixtureBody; GDK-1382). ↩ -
All three surfaces run against the mirror; the MCP tools expose no write verb on any origin (
internal/mcp/tools.go:24), andgadak_statusreports which origin the workspace has. ↩ ↩2 ↩3 -
The board is the same filtered list laid out as columns, saved per view (
web/src/lib/view-config.ts:141); a status-axis drag is a real transition where transitions exist. ↩ ↩2 ↩3 -
Mirror-side contract — reads saved views and emits keys; no origin call (
specs/000-product/data-model.md). ↩ ↩2 ↩3 -
The Jira-family source notifies — OS alerts on macOS and Linux (
internal/sync/run.go:59). ↩ ↩2 -
Linear issues are mirrored by the same loop but never notify (
internal/sync/run.go:70). Jira's own notification inbox, rules, and email are not mirrored on any origin. ↩ -
The origin is a remote host —
https://<site>.atlassian.netfor Jira,https://api.linear.appfor Linear (internal/linear/client.go). ↩ ↩2 -
Embedded in the same process (
internal/origin/transport.go:102), or one hop to a paired serve (:108). ↩