## What this adds Implements the request in #287 and the Tier 1 audit gaps for the `linear` skill's `scripts/linear` CLI, reconciled against the live Linear GraphQL schema. ### New verbs - `linear project update` — name, description, status, start/target dates, priority, with the same `--dry-run`/`--confirm` gate as issue mutations, and a local 255-character description guard matching Linear's `projectUpdate` limit (Linear rejects longer descriptions with a generic error). - `linear issue archive` / `linear issue unarchive` — both gated, returning `IssueArchivePayload.entity`. - `linear state list --team ENG` — first-class workflow-state discovery (previously states were only visible in the `issue move` failure path). ### Richer issue verbs - `issue create` now accepts `--project`, `--parent`, `--assignee`, `--label` (repeatable), `--state`, `--due`. - `issue update` now accepts `--assignee`, `--label` (add), `--remove-label`, `--due`, `--project`. ### Resolution rules (all require exactly one match, mirroring `resolve_team`) - Project: UUID or exact name - Parent: issue identifier or UUID - Assignee: exact name, display name, or email (via `users`) - Label: exact name within the issue's team (via `team.labels`) - Workflow state: exact name within the issue's team (existing `team.states` resolver, now reusable for `--state` on create) - Project status: exact name or type (via `projectStatuses`) ### Docs, tests, evals - SKILL.md command map, state-change gate, and error/recovery sections; README; `domain-and-workflows.md` (project semantics + 255-char limit), `graphql-contract.md` (resolution queries), `integration-boundaries.md` (intentional exclusions list), `sources.md` (2026-08-05 schema re-verification note). - 15 new offline tests (45 total) covering resolution, gates, dry-run intent, payload shapes, and field guards. - Added a sixth eval case (`safe-project-and-issue-mutations`). ## Validation - `python3 -m unittest linear/tests/test_linear.py` — 45/45 pass - `python3 scripts/validate-evals.py`, `ruby scripts/validate-skills.rb`, `python3 scripts/check-artifacts.py`, `python3 scripts/eval-coverage.py --modified-from origin/main`, skill-quality validator, marketplace/codex/llms freshness, jscpd — all green locally Closes #287 Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
3.1 KiB
GraphQL Contract
Endpoint: https://api.linear.app/graphql. Send JSON GraphQL requests with Content-Type: application/json. A personal API key is the value of the Authorization header. An OAuth access
token is sent in the Authorization header with the Bearer prefix.
This CLI supports bounded reads for teams, issues, documents, projects, cycles, and workflow
states, plus issue create, update, move, archive, unarchive, and comment mutations, and project
update. Use raw for a documented operation outside that surface. Issue reads accept a UUID or
shorthand identifier. Document reads accept a UUID, slug ID, or Linear document URL; slug lookup
uses the documents filter.
Friendly-name resolution uses bounded queries: teams via the teams connection, users by exact
name/email via the users connection, labels within a team via team.labels, project statuses via
projectStatuses, and workflow states within a team via team.states. Each resolver requires
exactly one match and fails with the available names on ambiguity. Resolvers query at most 100
items; a workspace or team with more matches than the first page cannot be resolved by name.
For personal scripts, a personal API key is the simplest authentication method. OAuth is intended for applications acting on behalf of users; access tokens are sent with the Bearer prefix. This CLI does not initiate OAuth, store credentials, refresh tokens, or print either supported credential.
Filter input supports equality, inequality, collection membership, comparisons for number/date
fields, and string operators such as contains and startsWith. Relationship filters can narrow
results by related team, state, assignee, project, or labels. Prefer these filters to client-side
filtering and ask for only fields needed for the task.
All connections use Relay cursor pagination: request first and then pass pageInfo.endCursor as
after while hasNextPage is true. The CLI intentionally does not automatically paginate; keep
reads bounded with --limit. Use server filters rather than downloading a workspace and filtering
locally.
Check GraphQL's errors array even on HTTP 200 because data can be partial. Rate limits and query
complexity are reported in response headers. As accessed on 2026-07-17, the official rate-limit
page contains a conflicting API-key request limit (5,000 in prose and 2,500 in its table); inspect
current headers and documentation rather than hard-coding either value. It also documents API-key
complexity at 3,000,000 points per hour and a 10,000-point maximum for one query.
Linear does not version this GraphQL API. Inspect schema deprecations and the [API] changelog
before relying on a field. When a query fails after a schema change, re-run public introspection in
Apollo Studio, update the narrow field selection, and add an offline contract test before release.
Archived records are excluded from paginated responses by default and can be included with
includeArchived: true when the connection supports it. Do not add polling loops for updates:
Linear recommends webhooks for applications that need near-real-time changes.