apps/symphony/skills/sym-linear/SKILL.md
Use Symphony's `linear_graphql` client tool for raw Linear GraphQL operations such as comment editing and upload flows.
npx skillsauth add gannonh/kata-cloud-agents sym-linearInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill for raw Linear GraphQL work during Symphony app-server sessions.
Use the linear_graphql client tool exposed by Symphony's app-server session.
It reuses Symphony's configured Linear auth for the session.
Tool input:
{
"query": "query or mutation document",
"variables": {
"optional": "graphql variables object"
}
}
Tool behavior:
errors array as a failed GraphQL operation even if the
tool call itself completed.When you need an unfamiliar mutation, input type, or object field, use targeted
introspection through linear_graphql.
List mutation names:
query ListMutations {
__type(name: "Mutation") {
fields {
name
}
}
}
Inspect a specific input object:
query CommentCreateInputShape {
__type(name: "CommentCreateInput") {
inputFields {
name
type {
kind
name
ofType {
kind
name
}
}
}
}
}
Use these progressively:
issue(id: $identifier) when you have a ticket key such as
MT-686 or KAT-857.issues(filter: ...), split TEAM-NUMBER and filter with
team.key + number (do not use IssueFilter.identifier; it does not
exist).issue(id: $id) for narrower reads.Lookup by issue key/identifier:
query IssueByIdentifier($identifier: String!) {
issue(id: $identifier) {
id
identifier
title
state {
id
name
type
}
project {
id
name
}
branchName
url
description
updatedAt
}
}
Lookup by identifier with issues(filter: ...) (valid syntax):
query IssueByTeamKeyAndNumber($teamKey: String!, $number: Float!) {
issues(
filter: { team: { key: { eq: $teamKey } }, number: { eq: $number } }
first: 1
) {
nodes {
id
identifier
title
state {
id
name
type
}
project {
id
name
}
branchName
url
description
updatedAt
}
}
}
Given KAT-857, use teamKey = "KAT" and number = 857.
Resolve a key to an internal id:
query IssueByIdOrKey($id: String!) {
issue(id: $id) {
id
identifier
title
}
}
Read the issue once the internal id is known:
query IssueDetails($id: String!) {
issue(id: $id) {
id
identifier
title
url
description
state {
id
name
type
}
project {
id
name
}
attachments {
nodes {
id
title
url
sourceType
}
}
}
}
Use these when an issue represents a parent slice with child tasks and attached plan docs.
Get child issues of a slice:
query SliceChildren($id: String!) {
issue(id: $id) {
children {
nodes {
id
identifier
title
state {
name
type
}
}
}
}
}
Get documents attached to an issue:
query IssueDocuments($id: String!) {
issue(id: $id) {
documents {
nodes {
id
title
content
}
}
}
}
Get project documents:
query ProjectDocuments($id: String!) {
project(id: $id) {
documents {
nodes {
id
title
content
}
}
}
}
Get milestone from issue:
query IssueMilestone($id: String!) {
issue(id: $id) {
projectMilestone {
id
name
}
}
}
Get milestone documents:
query MilestoneDocuments($id: String!) {
projectMilestone(id: $id) {
documents {
nodes {
id
title
content
}
}
}
}
Use this before changing issue state when you need the exact stateId:
query IssueTeamStates($id: String!) {
issue(id: $id) {
id
team {
id
key
name
states {
nodes {
id
name
type
}
}
}
}
}
stateId first)Use a two-step flow:
issue(id: ...) { team { states { nodes { id name type } } } }.name exactly matches your target and pass that
stateId to issueUpdate.mutation MoveIssueToState($id: String!, $stateId: String!) {
issueUpdate(id: $id, input: { stateId: $stateId }) {
success
issue {
id
identifier
state {
id
name
}
}
}
}
Use commentUpdate through linear_graphql:
mutation UpdateComment($id: String!, $body: String!) {
commentUpdate(id: $id, input: { body: $body }) {
success
comment {
id
body
}
}
}
Use commentCreate through linear_graphql:
mutation CreateComment($issueId: String!, $body: String!) {
commentCreate(input: { issueId: $issueId, body: $body }) {
success
comment {
id
url
}
}
}
Use the GitHub-specific attachment mutation when linking a PR:
mutation AttachGitHubPR($issueId: String!, $url: String!, $title: String) {
attachmentLinkGitHubPR(
issueId: $issueId
url: $url
title: $title
linkKind: links
) {
success
attachment {
id
title
url
}
}
}
If you only need a plain URL attachment and do not care about GitHub-specific link metadata, use:
mutation AttachURL($issueId: String!, $url: String!, $title: String) {
attachmentLinkURL(issueId: $issueId, url: $url, title: $title) {
success
attachment {
id
title
url
}
}
}
query IssueAttachmentsAndRelations($id: String!) {
issue(id: $id) {
id
identifier
attachments(first: 20) {
nodes {
id
title
url
sourceType
}
}
relations(first: 20) {
nodes {
id
type
relatedIssue {
id
identifier
title
state {
name
type
}
}
}
}
inverseRelations(first: 20) {
nodes {
id
type
issue {
id
identifier
title
state {
name
type
}
}
}
}
}
}
IssueFilter fieldsUse these common fields inside issues(filter: ...):
idnumber (pair with team.key for identifier-style lookups)teamstateprojectassigneetitlesearchableContentcreatedAtupdatedAtand / orIssueFilter.identifier is invalid.
Use these when the exact field or mutation shape is unclear:
query QueryFields {
__type(name: "Query") {
fields {
name
}
}
}
query IssueFieldArgs {
__type(name: "Query") {
fields {
name
args {
name
type {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
Do this in three steps:
linear_graphql with fileUpload to get uploadUrl, assetUrl, and
any required upload headers.uploadUrl with curl -X PUT and the exact
headers returned by fileUpload.linear_graphql again with commentCreate (or commentUpdate) and
include the resulting assetUrl in the comment body.Useful mutations:
mutation FileUpload(
$filename: String!
$contentType: String!
$size: Int!
$makePublic: Boolean
) {
fileUpload(
filename: $filename
contentType: $contentType
size: $size
makePublic: $makePublic
) {
success
uploadFile {
uploadUrl
assetUrl
headers {
key
value
}
}
}
}
linear_graphql for comment edits, uploads, and ad-hoc Linear API
queries.issue(id: TEAM-NUMBER) -> issues(filter: { team.key + number }) ->
internal id.Issue.links; use Issue.attachments, Issue.relations, and
Issue.inverseRelations instead.IssueFilter.identifier; use IssueFilter.number with
IssueFilter.team.key when you need identifier-style filtering.stateId
instead of hardcoding names inside mutations.attachmentLinkGitHubPR over a generic URL attachment when linking a
GitHub PR to a Linear issue.fileUpload; those URLs already carry the needed authorization.tools
This skill should be used when a new project session starts and the user expresses what they want to build, asks to "start a project", "spec this out", "help me plan", or describes a feature/tool/system they want to create. Guides structured intent capture through goal, constraints, architecture, acceptance criteria, tasks, and non-goals.
tools
Push current branch changes to origin and create or update the corresponding pull request (with the correct base branch); use when asked to push, publish updates, or create pull request.
development
Pull latest origin/<base-branch> into the current local branch and resolve merge conflicts (aka update-branch). Use when Codex needs to sync a feature branch with origin, perform a merge-based update (not rebase), and guide conflict resolution best practices.
testing
Land a PR by monitoring conflicts, resolving them, waiting for checks, and squash-merging when green; use when asked to land, merge, or shepherd a PR to completion.