agentsclimarketplace

Linear

Skill andresd/jot-skills/linear

GitHub-only and Linear-only Claude Code skills that port the jot CLI loop — capture brain-dumps into well-formed tracker issues, decide what to work on next, and keep the backlog tidy. Each skill is a single SKILL.md invoked from inside Claude Code.

Install
npx -y skills add andresd/jot-skills --skill linear

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Capture, triage, and plan work on Linear issues from natural language, across one or many teams. Auto-triggers on phrases like "capture this in Linear:", "what should I work on next in Linear", "what's on my plate in Linear", "tidy the Linear backlog", "find duplicate Linear issues", "all <X> Linear bugs to @<user>", references to Linear issue keys (e.g. "ENG-123"), and explicit `/jot-linear <cmd>`, `linear:`, or `jot-linear:` prefixes. Use when the user wants to turn brain-dump text into a well-formed Linear issue, decide what to work on next in Linear, run dedup/stale passes, or bulk-edit Linear issues by natural-language intent. Requires the Linear MCP server to be configured in Claude Code (`https://mcp.linear.app/sse`).

SKILL.md

21.3 KB, as published. Nobody here has run it

jot-linear

A Linear-only port of the jot CLI loop: capture brain-dumps into well-formed issues, decide what to work on next, and keep the backlog tidy — all through Linear's official MCP server. No tracker abstraction (Linear only), no licensing, no persistent SQLite. Auth is handled by the MCP transport (OAuth).

This skill is the Linear sibling of jot-gh. If both are installed, disambiguate by the user's explicit cue (the word "Linear", a linear: prefix, or a Linear issue identifier like ENG-123). When the cue is missing and a git repo is present, prefer jot-gh.

When to invoke

Explicit (always invoke):

  • /jot-linear <command> — slash form
  • linear: <command> or jot-linear: <command> — prefix form

Auto-invoke (use judgment, prefer explicit when unsure):

  • "capture this in Linear: ..." / "log this in Linear: ..."
  • "what should I work on next in Linear?" / "what's next in Linear?"
  • "what's on my plate in Linear today?" / "what am I doing today in Linear?"
  • "tidy the Linear backlog" / "find duplicate Linear issues"
  • "(re)assign all <X> Linear bugs to @<user>"
  • "make all <X> Linear issues urgent/high/medium/low"
  • "what does the Linear backlog say about <topic>?"
  • Any input that names a Linear issue identifier (ENG-123, OPS-7) and asks for an issue-lifecycle action.

Do NOT invoke for:

  • General Linear questions unrelated to issue triage ("how do I configure Linear's GitHub integration?")
  • Cycle planning, roadmap editing, or project-level work (out of scope for this skill)
  • Anything that does not touch the issue lifecycle (issues, labels, assignees, comments, workflow states)

Prerequisites

Before any command, verify the Linear MCP is reachable. The MCP must be configured in Claude Code, typically via:

claude mcp add linear --transport sse https://mcp.linear.app/sse

The first call will trigger an OAuth flow in the browser. If the MCP tools are not present in this session's available tools, stop and tell the user:

The Linear MCP isn't configured. Add it with claude mcp add linear --transport sse https://mcp.linear.app/sse, complete the OAuth flow in the browser, and re-invoke this command.

MCP tool discovery

Linear's MCP server exposes a set of typed tools. The exact names depend on the MCP server version and may be prefixed by the name the user gave the server in their config (commonly linear or Linear).

Discover the actual tool names at invocation time from the available-tools list in this session. Look for tools matching these operations:

OperationLikely tool name
Create issuecreate_issue / createIssue
Update issue (any field)update_issue / updateIssue
Get issue by id or identifierget_issue / getIssueById
Search issues by query/filtersearch_issues / searchIssues
List issues (filtered)list_issues / getIssues
Get issues assigned to a userget_user_issues / list_my_issues
Add commentcreate_comment / addComment / createComment
Delete commentdelete_comment / deleteComment
List teamslist_teams / getTeams
List workflow states for a teamget_workflow_states / getWorkflowStates
List labels for a teamlist_labels / getLabels
Archive issuearchive_issue / archiveIssue

If a needed operation is not exposed (e.g. some servers omit delete_comment), surface that to the user rather than working around it — Linear's UI is fine for the missing step.

Throughout this file, calls are written as linear.createIssue({...}) for brevity. Substitute the actual MCP tool name at invocation time.

Team resolution

Most commands operate on one team at a time. To pick:

  1. If the user passed --team TEAM_KEY (e.g. --team ENG), use that.
  2. Else if LINEAR_DEFAULT_TEAM env var is set, use it.
  3. Else, call the list-teams tool and ask via AskUserQuestion. Offer the top 3 teams as options.

Hold both the team key (string like ENG, used for display and identifiers) and the team id (UUID, used in mutations). Most MCP tools accept either; pass the id when offered the choice.

Multi-team flows (next by default, ask if no team context): use the search/list tools without a team filter. Each issue carries its team in the result; treat writes per-team. Do not run tidy, assign, or reprioritize across teams — those are single-team only.

Issue identifiers

Always display issues as TEAM-KEY-NUMBER (e.g. ENG-42), not as bare numbers or UUIDs.

For mutations that require a UUID, resolve the identifier via the get-issue tool first:

linear.getIssue({ id: "ENG-42" })  // returns { id: <uuid>, identifier: "ENG-42", ... }

(Most MCP get-issue tools accept either form on the id parameter.)

Priority mapping

Linear has a native priority field (Int, 0–4). The skill maps to it directly — no priority labels.

Linear valueMeaningjot mental model
1UrgentP0
2HighP1
3MediumP2
4LowP3
0No prioritynone

When the user says "P0" / "urgent" / "blocker", map to 1. When they say "P3" / "low", map to 4. Treat the words as interchangeable; output uses the Linear name (Urgent/High/Medium/Low) for clarity.

Workflow states

Linear teams define their own workflow states, each with a type in { triage, backlog, unstarted, started, completed, canceled }.

  • "Open" issues = state type ∈ {triage, backlog, unstarted, started}.
  • "Close" an issue = move it (via update-issue with stateId) to a state of type completed (or, when the user explicitly says "cancel as duplicate / won't do", canceled).
  • For each team, call the list-workflow-states tool once per turn and cache in-memory.
  • Pick the canonical completed state: prefer one named exactly "Done", else the first type == "completed".

If a team has no completed state (unusual), stop and tell the user — do not fabricate one.

Memory file: ~/.config/jot-linear/<team-key>.md

Linear is not tied to a git repo, so memory lives in user config rather than alongside source.

  • Path: ~/.config/jot-linear/<team-key>.md (lowercase the team key, e.g. eng.md).
  • Cap at ~4KB. If larger, take the first 4KB and tell the user the file was truncated.
  • Read it before any LLM-y step (extraction, ranking, dedup decision).

The memory file holds team-specific rules: glossary, priority defaults, label conventions, "all bugs filed by @alice are High", "anything reported in #incidents Slack channel is Urgent", workflow-state preferences.

Apply memory rules when:

  • Extracting structured fields in capture (priority, labels, assignee)
  • Ranking issues in next
  • Deciding dedup similarity in capture and tidy

If memory rules conflict with the user's explicit request in this turn, the user wins.

Cache: ~/.cache/jot-linear/

Cache recent issue digests at ~/.cache/jot-linear/<team-key>.json so next/tidy don't re-fetch hundreds of issues per invocation.

  • TTL: 10 minutes. Compare file mtime to now; if older, refetch.
  • Bypass: any command with --no-cache.
  • Miss path: call the list-issues tool filtered to the team and open states, write the result to cache.
  • Cache is not load-bearing. Safe to delete at any time. Never treat a cached value as authoritative for writes — re-fetch the issue before any update.

Action log: ~/.cache/jot-linear/actions.jsonl

Every successful write appends one JSON line:

{"ts":"2026-05-20T10:00:00Z","cmd":"capture","team":"ENG","action":"created","identifier":"ENG-42","issue_id":"<uuid>","url":"https://linear.app/...","inverse":{"op":"archive","issue_id":"<uuid>"}}
  • Keep only the last 100 lines (trim head on each append).
  • Used exclusively by undo.
  • Skip the log for previews, dry-runs, and read-only commands.

Commands

capture <text>

Natural-language brain-dump → well-formed Linear issue, with dedup check first.

Flow:

  1. Resolve team. Load ~/.config/jot-linear/<team-key>.md if present.
  2. Pull existing labels and the team's completed state via the list-labels and list-workflow-states tools (cached for the turn).
  3. Extract 3–5 keywords from <text> (noun phrases, key verbs).
  4. Dedup search via the search-issues tool, scoped to the team and to open states:
    linear.searchIssues({ query: "<keywords>", teamId: "<id>", first: 10 })
    
    If the MCP exposes only a list/filter tool, use it with an equivalent filter.
  5. Score top 5 candidates for semantic similarity vs. <text>. If best score reads as "likely duplicate" (same bug/feature, not just same topic), surface that. Treat completed/canceled candidates as informational only — do not propose commenting on them.
  6. Extract structured fields, constrained by the existing-labels list and memory rules:
    • title: concise, imperative for actions ("Fix login redirect loop"), declarative for observations ("Search returns 500 on empty query").
    • description: original text, then a blank line, then _Captured via jot-linear on YYYY-MM-DD._
    • priority: integer 0–4. Default 3 (Medium) unless memory rules say otherwise.
    • labelIds: pick zero-or-more from the existing labels (by UUID).
    • assigneeId: only if <text> explicitly names a person via @handle; resolve via the user-lookup tool (or fall back to the team's user list).
  7. Preview via AskUserQuestion:
    • Create issue (default)
    • Comment on dup ENG-N (only if a dup was found)
    • Edit fields first
    • Cancel
  8. On Create issue:
    linear.createIssue({
      teamId, title, description, priority,
      labelIds: [...], assigneeId
    })
    
    Append to action log with inverse: { op: "archive", issue_id: <new uuid> }.
  9. On Comment on dup ENG-N:
    linear.createComment({ issueId: "<uuid>", body: "<captured text>\n\n_Linked via jot-linear dedup._" })
    
    Log with inverse: { op: "delete_comment", comment_id }.
  10. On Edit fields first: ask which field (title/description/priority/labels/assignee), update, re-prompt.

Do not invent labels that don't already exist on the team. If a needed label is missing, ask the user before creating it (via the label-create tool if the MCP exposes it, or surface that the label needs to be created in the Linear UI).

next [--for @user] [--scope <filter>] [-n N] [--commit] [--team TEAM_KEY]

Top-N ranked open issues with a one-line rationale per item. Defaults: --for @me, n=5, multi-team.

Flow:

  1. List candidates via the list-issues or get-user-issues tool:
    # multi-team (default)
    linear.listIssues({
      filter: { assignee: { isMe: true }, state: { type: { nin: ["completed", "canceled"] } } },
      first: 100
    })
    # single-team when --team passed
    linear.listIssues({
      filter: { team: { key: "<KEY>" }, assignee: { isMe: true }, state: { type: { nin: ["completed", "canceled"] } } },
      first: 100
    })
    
    For --for @other-user, resolve the user first, then filter by assigneeId.
  2. For each distinct team in the results, load ~/.config/jot-linear/<team-key>.md (cache the load per turn).
  3. Rank by, in order:
    • Priority: 1 (Urgent) > 2 (High) > 3 (Medium) > 4 (Low) > 0 (No priority)
    • State type: started > unstarted > backlog > triage
    • jot-today label present (boost)
    • Age (newer wins for Urgent/High, older wins for Medium/Low)
    • Memory-rule hints
  4. Output a numbered list. Each line: [ENG-42] prefix, title, priority badge (e.g. [High]), state in parens, one-sentence rationale, then URL on the next line.
  5. If --commit: confirm via a single AskUserQuestion (Commit these N to today / Cancel), then for each chosen item:
    • Ensure the team has a jot-today label (create if missing, asking first).
    • linear.updateIssue({ id, labelIds: [...current, jotTodayLabelId] }).
    • Log each. inverse: { op: "set_labels", issue_id, labelIds: [<prior list>] }.

tidy [--team TEAM_KEY]

Interactive backlog hygiene. Single-team only (multi-team tidy is too noisy).

Flow:

  1. Resolve team.
  2. Fetch all open issues via the list-issues tool, requesting up to first: 250. Include description, labels, assignee, and last-comment timestamp where available.
  3. Run four passes:
    • dup — cluster by title+description similarity. Report any cluster of 2+, recommending the oldest as canonical and the rest to close-as-duplicate (move to a canceled-type state with a "duplicate of ENG-N" comment).
    • staleupdatedAt > 30 days ago and no recent comments. Recommend close with comment, or label stale.
    • stale-assignment — has assignee, no comment or edit in last 14 days. Recommend unassign and ping.
    • missing-prioritypriority == 0. Recommend priority: 3 (Medium) as default (or memory-rule default).
  4. Output a single numbered list (text mode — not AskUserQuestion; tidy routinely produces 20+ findings):
    1. [dup]      ENG-42 looks like dup of ENG-38 — close ENG-42 with link?
    2. [stale]    ENG-15 — no activity since 2026-04-12 (38d). Close with comment?
    3. [stale-asn] ENG-71 — @bob assigned 22d ago, no movement. Unassign?
    4. [priority] ENG-88 — no priority set. Add Medium?
    ...
    
  5. Prompt the user in plain text:

    Apply which? Reply with numbers (e.g. 1,3,5), all, or cancel.

  6. Apply each accepted finding via the appropriate MCP tool (updateIssue, createComment, etc.). Log every write.

ask <question> [--team TEAM_KEY]

NL Q&A over the backlog with ENG-N citations. Read-only.

Flow:

  1. Resolve team (or stay multi-team if none given).
  2. Search via the search-issues tool, including completed/canceled results:
    linear.searchIssues({ query: "<keywords>", teamId: "<id or null>", first: 30, includeArchived: false })
    
  3. Answer in 2–4 sentences. Cite each referenced issue as [ENG-42](URL) (markdown link).
  4. No writes. No action log.

assign <NL intent> [--team TEAM_KEY]

Bulk reassignment by natural language. Single-team only.

Example intents:

  • "all open auth bugs to @alice"
  • "@bob's open issues to @carol"
  • "everything labeled regression to @me"

Flow:

  1. Parse the intent into { label?, assignee_old?, assignee_new, state? }. If parsing is uncertain, ask via AskUserQuestion with the best-guess filter.
  2. Resolve assignee user(s) via the user-lookup or list-users tool. Hold the UUID for use in mutations.
  3. List candidates via the list-issues tool, building the filter from non-null fields only. Linear's filter accepts team, assignee, labels, state predicates; omit predicates whose argument is null.
  4. Show the numbered candidate list (text mode).
  5. Prompt:

    Reassign these to @<new>? Reply with numbers, all, or cancel. Numbers SKIPPED will keep their current assignee.

  6. For each chosen item, re-read the issue (fresh, no cache). If <new> is already assigned, skip. Else:
    linear.updateIssue({ id: "<uuid>", assigneeId: "<new uuid>" })
    
    Linear's assignee is single-valued; assigneeId replaces the previous.
  7. Log each. inverse: { op: "set_assignee", issue_id, assigneeId: <prev uuid or null> }.

reprioritize <NL intent> [--team TEAM_KEY]

Bulk priority change by natural language. Single-team only.

Example intents:

  • "all launch-blocker label → Urgent"
  • "all Low-priority bugs older than 90 days → cancel as stale" (this overlaps with tidy; route to tidy if the intent is hygiene)
  • "@alice's open issues to High"

Same flow as assign, but operating on the native priority field (1–4):

  • For each accepted item: linear.updateIssue({ id, priority: <new> }).
  • Log with inverse: { op: "set_priority", issue_id, priority: <prev> }.

status [--team TEAM_KEY]

Default: list issues with jot-today label assigned to @me, across teams. Show priority, team, and state.

linear.listIssues({
  filter: {
    assignee: { isMe: true },
    labels: { name: "jot-today" },
    state: { type: { nin: ["completed", "canceled"] } }
  },
  first: 50
})

--team TEAM_KEY: same query but scoped to one team.

--team TEAM_KEY --all-assignees: list jot-today issues in the team across all assignees, grouped by assignee.

No writes.

clear-plan

Remove jot-today from all open issues currently labeled with it for @me (across all teams).

Flow:

  1. List as in status.
  2. Show count and titles. Confirm via AskUserQuestion: Clear N items / Cancel.
  3. For each: re-fetch labels, linear.updateIssue({ id, labelIds: [<prior minus jot-today>] }). Log.

undo

Reverse the most recent action within 24 hours.

Flow:

  1. Read ~/.cache/jot-linear/actions.jsonl (tail).
  2. Find the most recent entry where ts > now - 24h. If none, tell the user no recent action is reversible.
  3. Show the action and its planned inverse via AskUserQuestion: Undo / Cancel.
  4. Re-read the affected issue's current state (no cache). If state has changed in a way that makes the inverse meaningless (e.g. issue already archived, label already gone), abort with a clear explanation rather than forcing it.
  5. Apply the inverse using the appropriate MCP tool:
    • created issue_idlinear.archiveIssue({ id }). Append a comment "Reverted by jot-linear undo" via createComment first so the audit trail is visible.
    • archived issue_id → unarchive tool if available; otherwise tell the user to unarchive in the Linear UI.
    • set_state to=X from=YupdateIssue({ id, stateId: Y })
    • set_priority to=X from=YupdateIssue({ id, priority: Y })
    • set_assignee to=X from=YupdateIssue({ id, assigneeId: Y }) (Y may be null)
    • set_labels prior=[...]updateIssue({ id, labelIds: [prior...] })
    • commented comment_iddeleteComment({ id })
  6. On success, remove the entry from the log. Do not chain undos — one undo reverses one action.

help

Print one line per command above. No MCP calls. No log.


Output conventions

  • Cross-team listings: always show [TEAM-KEY-N] (e.g. [ENG-42]).
  • Single-team listings: show bare ENG-42.
  • Every write reports the resulting URL on the next line (MCP createIssue / updateIssue responses include the issue URL — use it directly).
  • No emoji in skill output unless the user uses emoji first.
  • MCP tool errors surface verbatim — do not swallow them. If a mutation returns an error or success: false, stop the batch and report which item failed.
  • Priority labels in output use Linear's names (Urgent/High/Medium/Low/No priority), not the P0..P3 mental model — match Linear's UI vocabulary so URLs and screenshots line up.

Confirmation patterns

  • Single write (capture create, single-item assign, undo, --commit): AskUserQuestion with Confirm / Edit / Cancel.
  • Multi-write (tidy, bulk assign, bulk reprioritize, clear-plan): numbered text list, free-text reply (1,3,5 / all / cancel). AskUserQuestion only fits 4 options, so it does not scale here.
  • Read-only (status, ask, next without --commit): no confirmation.

Anti-patterns

  • Don't fall back to raw curl against the Linear GraphQL API while the MCP is present. The MCP is the supported transport; bypassing it loses auth handling and rate-limit retries.
  • Don't batch writes without showing a preview first, even when the user said "go ahead" earlier in the turn — previews are cheap insurance against an LLM misparse.
  • Don't invent labels that aren't already on the team. If a needed label is missing, ask before creating it.
  • Don't write to completed or canceled issues unless the user explicitly named the issue.
  • Don't store the OAuth token anywhere — the MCP transport owns it.
  • Don't treat the cache as authoritative for writes — always re-fetch the issue before update.
  • Don't run tidy, assign, or reprioritize across teams. They are single-team by design.
  • Don't chain undos. One undo reverses one action; if the user wants more, they invoke undo again.
  • Don't delete issues on undo — archive them. Linear's archive is soft and recoverable; delete is permanent and not a safe inverse of "created".
  • Don't use priority labels (P0, P1, ...). Linear has a native priority field; using labels for the same concept fragments the data.
  • Don't paper over a missing MCP tool by inventing a workaround. If the connected Linear MCP doesn't expose a needed operation (e.g. deleteComment), surface that to the user and ask them to perform that step in the Linear UI.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.