Problem Statement
The linear/li CLI is increasingly driven by AI coding agents, not just humans typing interactively. An analysis of local Claude Code session logs (776 sessions across 187 projects; 93 distinct sessions actually drove the CLI) shows agents repeatedly fail on the same small set of operations, burning many turns per task:
- Setting an issue's state by name fails (43 sessions). Agents naturally try
linear issues update ANN-1 --state "In Progress" (→ Missing input. Provide --input) or --input '{"stateName":"In Progress"}' / '{"state":"In Progress"}' (→ GraphQL Field "stateName" is not defined by type "IssueUpdateInput". Did you mean "stateId"?). The CLI only accepts a raw stateId UUID, which the agent does not have.
- Discovering workflow states is a scavenger hunt (43 sessions). To get a
stateId, agents guess statuses, issue-statuses, workflow-states, list-states, workflow, get-status… none of which exist, before eventually finding states list (or piping teams get … | python and crashing on the output shape).
- Wrong read verb (57 sessions, 65 failures). Agents run
linear issues view ANN-1 → error: unknown command 'view'. The CLI only has get.
- Listing comments for an issue (21 sessions).
linear comments list --issue ANN-1 → unknown option '--issue'; there is no way to scope comments to an issue.
- Unbounded issue lists time out (6+ sessions).
linear issues list --team DUB (or --all) on a large team returns The operation was aborted due to timeout and hammers the API, contributing to rate-limit pressure.
- Smaller papercuts.
show guessed (12), save guessed from the MCP tool name (9), positional args to list → too many arguments (6), hand-crafted bad stateId UUID → Entity not found in validateAccess: stateId (4), and one workspace where the binary wasn't on PATH.
The worst observed case: a single agent took 11 commands to move one issue to "In Progress" (try --state, try {"state":…}, guess teams workflow-states, read two help screens, crash a python one-liner, finally find states list, copy a UUID, fail once on the wrong UUID, succeed).
Solution
Make the CLI forgiving of the inputs agents actually produce, and add a few high-level workflow commands that collapse multi-step rituals into one call.
- Set state by name.
linear issues update ANN-1 --state "In Progress" just works. So does --input '{"stateName":"In Progress"}' and --input '{"state":"In Progress"}'. The CLI resolves the name to the correct stateId for the issue's team. Raw stateId still works unchanged.
- Forgiving verbs.
get accepts view and show as aliases on every resource. Unknown commands print a "did you mean" suggestion, and states answers to statuses and workflow-states.
- Comments by issue.
linear comments list --issue ANN-1 returns that issue's comments.
- Guard expensive lists. A truly unbounded
issues list is blocked with a message suggesting a narrower query (--mine, --state, --query, --limit, etc.); bounded queries are untouched.
- Workflow commands.
linear prep ANN-1 pulls the issue plus its parent/project context plus its branch name and sets it to the team's in-progress state — one call to get ready to work. linear pr-ready ANN-1 flips the issue to its review state (and posts a comment if you pass one).
- The agent reference matches reality. The bundled
linear-cli skill documents the canonical commands and a "common mistakes → correct command" map, so agents stop guessing.
User Stories
- As an AI coding agent, I want to set an issue's state with
--state "In Progress", so that I don't need to discover a UUID first.
- As an AI coding agent, I want
--input '{"stateName":"In Progress"}' to be accepted, so that the pattern I naturally reach for succeeds.
- As an AI coding agent, I want
--input '{"state":"In Progress"}' to be accepted, so that my second-guess pattern also succeeds.
- As an AI coding agent, I want a clear error listing the team's valid state names when I name a state that doesn't exist, so that I can correct myself in one step instead of guessing.
- As an AI coding agent, I want an explicit
stateId UUID in --input to keep working, so that existing scripts and precise updates are unaffected.
- As an AI coding agent, I want
state resolution scoped to the target issue's team, so that a name like "In Progress" maps to the right team's state.
- As an AI coding agent, I want
linear issues create --state "Todo" --input '{"teamId":…,"title":…}' to resolve the state name against the named team, so that I can create issues in a specific state.
- As an AI coding agent, I want
linear issues bulk-update to accept a state name once and apply it across issues, so that batch status changes don't require per-team UUID lookups.
- As an AI coding agent, I want
linear issues view ANN-1 to behave like get, so that my habitual read verb works.
- As an AI coding agent, I want
linear issues show ANN-1 to behave like get, so that another common read verb works.
- As an AI coding agent, I want an "unknown command" error to suggest the closest real command, so that typos and near-misses self-correct.
- As an AI coding agent, I want
linear statuses list and linear workflow-states list to resolve to states list, so that the names I guess for state discovery still work.
- As an AI coding agent, I want
linear comments list --issue ANN-1, so that I can read an issue's discussion without fetching all comments.
- As an AI coding agent, I want
--issue to accept either a UUID or an identifier like ANN-123, so that I can use whichever I already have.
- As an AI coding agent, I want a truly unbounded
issues list to be blocked with a suggested narrower query, so that I don't trigger timeouts or rate limits.
- As an AI coding agent, I want a bounded
issues list (with --team, a filter, --mine, or --limit) to run normally, so that the guardrail never blocks legitimate queries.
- As an AI coding agent, I want the guardrail message to name concrete flags (
--mine, --state, --query, --limit), so that I know exactly how to make the query acceptable.
- As an AI coding agent, I want
linear prep ANN-1 to set the issue to the team's in-progress state, so that I don't run a separate update.
- As an AI coding agent, I want
prep to return the issue's branch name, so that I can create my working branch without a second command.
- As an AI coding agent, I want
prep to include the parent issue (or project) summary, so that I have the surrounding context before I start.
- As an AI coding agent, I want
prep --state <name> to override the target state, so that teams with non-standard state names still work.
- As an AI coding agent, I want
prep to pick the team's first started-type state when no exact "In Progress" exists, so that it works across differently-named workflows.
- As an AI coding agent, I want
prep output as a single JSON envelope, so that I can parse issue, context, branch, and new state in one read.
- As an AI coding agent, I want
linear pr-ready ANN-1 to set the issue to its review state, so that I can mark work ready in one call.
- As an AI coding agent, I want
pr-ready --comment "…" to post that comment on the issue, so that I can leave a review note in the same step.
- As an AI coding agent, I want
pr-ready --pr <url> to post a comment linking the PR, so that the Linear issue references the PR without me writing the body.
- As an AI coding agent, I want
pr-ready with no comment flags to only change state, so that I'm not forced to write a comment.
- As an AI coding agent, I want
pr-ready --state <name> to override the review state, so that custom workflows work.
- As an AI coding agent, I want the bundled skill doc to show
--state for status changes, so that I learn the right command before failing.
- As an AI coding agent, I want a "common mistakes → correct command" table in the skill doc, so that my likely wrong guesses are redirected.
- As a CLI operator (human), I want
linear prep ANN-1 to set up my issue and print its branch name, so that I can start work quickly.
- As a CLI operator, I want a friendlier error when I pass a positional team to
list (linear issues list ANN), so that I'm pointed at --team/filters.
- As a CLI operator, I want
linear doctor to report whether the binary is correctly installed/on PATH, so that I can diagnose "command not found".
- As a maintainer, I want state-name resolution implemented as one reusable module, so that update, create, bulk-update, prep, and pr-ready share identical behavior.
- As a maintainer, I want the resolver, normalization, ergonomics, comments filter, guardrail, prep, and pr-ready covered by tests, so that the agent-facing contract doesn't regress.
- As a maintainer, I want existing
stateId-based usage to remain byte-for-byte compatible, so that nothing breaks for current scripts.
Implementation Decisions
Module 1 — Workflow-state resolver (deep module, linear-core).
- Interface:
resolveStateId(teamRef, stateRef, options?) → Promise<stateId>, where options may include a preferred state type (e.g. started).
- Behavior: if
stateRef is a UUID, return it unchanged. Otherwise load the team's workflow states (via the existing listWorkflowStates/gateway), then match in precedence order: (a) exact case-insensitive name match; (b) if none and a preferred type is supplied, the lowest-position state of that type. On no match or ambiguous match, throw a typed error whose message lists the team's valid states as name (type).
- Depends only on a "list workflow states for a team" capability injected in, so it is unit-testable with a fake list and has no other coupling. Reused by Modules 2, 6, and 7.
- Note: Linear's workflow
type enum is triage | backlog | unstarted | started | completed | canceled — there is no "review" type. "In Review" is a custom started-type state, so review targeting is by name, not type.
- Docs/hints: the typed error is itself the primary agent hint — it must name the offending input and list the team's valid states as
name (type) so a wrong name self-corrects in one step.
Module 2 — Issue input normalization (cli).
- Before any
IssueUpdateInput/IssueCreateInput reaches GraphQL, fold a state reference into stateId using Module 1. Source precedence: explicit stateId in --input > --state flag (already a global option, read via optsWithGlobals) > state/stateName key inside --input JSON. The state/stateName keys are stripped after resolution so they never reach GraphQL.
- Team scope: for
update, fetch the target issue first and use its teamId; for create, use input.teamId. For bulk-update, resolve per distinct team and reuse within the run.
- Backward compatible: a payload already containing a valid
stateId UUID skips resolution entirely.
- Docs/hints: ship with the slice —
issues update/create/bulk-update --help document setting state by name (--state plus state/stateName in --input), the bundled linear-cli skill gains a "Set Issue State By Name" section and a "common mistakes → correct command" row, and the README shows a --state example.
Module 3 — Resource command ergonomics (cli, registerResourceCommand).
- Register
get with command aliases view and show for every resource entity (does not affect unrelated top-level skills show / project status subcommands).
- Enable Commander
showSuggestionAfterError(true) and showHelpAfterError, so unknown commands/options print a "did you mean" hint.
- Add explicit aliases
statuses and workflow-states to the states command.
- Docs/hints: ship with the slice — the "did you mean" suggestion is itself the hint; the skill's "common mistakes" table documents
get (not view/show) and states list (not statuses/workflow-states).
Module 4 — Comments --issue filter (cli + gateway).
comments list gains --issue <id-or-identifier>. The value is resolved to an issue id via the existing resolveIssueId, then comments are scoped to that issue (server-side filter if the API supports it; otherwise filter the connection client-side). Without --issue, behavior is unchanged.
- Docs/hints: ship with the slice —
comments list --help documents --issue (UUID or identifier), and the bundled skill shows scoping an issue's comments.
Module 5 — List guardrail (cli).
- A bound detector for
issues list. Block (error, exit code 1) only when the query is truly unbounded: no --team AND none of {--mine, --state, --status, --assignee, --label, --priority, --query, --project, --cycle, --parent, --filter, --updated-after, --created-after, --limit}; OR --all is set with no narrowing filter present.
- The error names concrete remedies (
--mine, --state "In Progress", --query …, --limit N). Bounded queries pass through untouched. Scope: issues list (the high-cardinality offender); other entities unaffected.
- Docs/hints: ship with the slice — the guardrail error is the hint (it names the narrowing flags);
issues list --help and the skill state the bounded-query expectation up front.
Module 6 — prep command (cli, top-level linear prep <issue>, mirroring my-work/triage).
- Composite orchestration over the gateway: resolve + fetch the issue; gather context (parent issue via
parentId, else project via projectId); compute the branch name (getIssueBranchName); resolve the target state via Module 1 (prefer exact name "In Progress", else first started-type state; overridable with --state); apply the state update.
- No git operations — the branch name is surfaced in the output, not checked out.
- Output: one envelope containing the issue, the parent/project summary, the branch name, and the new state.
- Orchestration logic lives in a service function that takes a gateway-like dependency, so it is testable in isolation.
- Docs/hints: ship with the slice —
prep --help, a README example, and a bundled-skill section show the one-call "get ready to work" flow and where to read the branch name in the output envelope.
Module 7 — pr-ready command (cli, top-level linear pr-ready <issue>).
- Default behavior: resolve the review state by name (prefer "In Review"; overridable with
--state; typed error listing valid states if unmatched) and apply it. State change only.
- Comment is optional: post a comment only when
--comment <body> is supplied; if --pr <url> is supplied without --comment, generate a minimal body linking the PR. No comment flags → no comment.
- Output: one envelope with the issue, the new state, and the comment (if posted). Shares the same testable service shape as
prep.
- Docs/hints: ship with the slice —
pr-ready --help, a README example, and a bundled-skill section show flipping to the review state and the optional --comment/--pr behavior.
Module 8 — Agent skill reference consolidation (skills-catalog).
- Consolidation/coherence pass, not the only place docs change: by the time this runs, every prior slice has already shipped its own
--help text, bundled-skill section, and error hints (see each module's Docs/hints bullet). Module 8 audits those for consistency, fills any cross-cutting gaps, and assembles the single consolidated "common mistakes → correct command" table covering all failure patterns from the log analysis.
- Ensures the bundled
linear-cli skill (and the issue-triage / cycle-planning skills where they touch status changes) coherently document: setting state via --state/name, get (not view), comments list --issue, the prep and pr-ready workflows, and the list-guardrail expectations.
Lower-value papercuts (in scope, minimal).
- Friendlier
too many arguments error on list that points at --team/filters.
doctor reports binary install / PATH status.
save is handled by the Commander suggestion mechanism (Module 3); no real save command is built.
Cross-cutting decisions.
- Output envelope shape (
ok/entity/action/data|error) is preserved for all new commands.
- All name→id resolution reuses the Module 1 pattern; this PRD scopes resolution to state only (assignee/label/project name resolution is explicitly deferred).
- Docs and agent hints ship with each slice, not at the end. The CLI's primary users are coding agents, so every module updates the surfaces its feature touches in the same PR: the command's
--help text, the bundled linear-cli skill (plus issue-triage/cycle-planning where relevant), the README examples, and — critically — the command's error messages, which are treated as a first-class agent UX surface (a failure must name the correct command or list valid values so the agent self-corrects in one step). Module 8 is the final coherence/consolidation pass over this per-slice work, not the only place docs change. Each module carries an explicit "Docs/hints" acceptance item.
Testing Decisions
A good test here exercises external behavior through a public interface — a command handler or a gateway/service method — using an in-memory or fake gateway, and asserts on the returned envelope and on the calls made to the gateway. Tests must not assert on internal helper structure or private shapes, so that refactors don't break them.
Modules to be tested:
- Workflow-state resolver (Module 1): unit tests over a fake state list — UUID passthrough; exact case-insensitive name match; preferred-
type fallback; not-found and ambiguous errors that list valid states.
- Issue input normalization (Module 2):
--state, state, and stateName all fold into stateId for update, create, and bulk-update; precedence is honored; an explicit stateId is left untouched; state/stateName keys never reach the gateway.
- Resource command ergonomics (Module 3):
view and show resolve to the get handler; an unknown command surfaces a suggestion.
- Comments
--issue filter (Module 4): an identifier is resolved and comments are scoped to that issue; absence of --issue is unchanged.
- List guardrail (Module 5): table-driven cases over which
issues list invocations are blocked vs allowed (no-args blocked; --mine/--team/--limit/filters allowed; --all without narrowing blocked).
prep orchestration (Module 6): composes issue + parent/project + branch + state via a fake gateway; --state override and started-type fallback both covered.
pr-ready orchestration (Module 7): flips to the review state; posts a comment only when --comment/--pr is provided; --pr-only generates a link body.
- Docs/hints (all modules): bundled-skill content is locked by assertions in the
skills-catalog test (e.g. the linear-cli skill must contain the state-by-name section and the common-mistakes table), so the agent-facing guidance each slice ships cannot silently regress in CI.
Prior art for the style and harness: the existing issues-bulk-update command test and the skills-catalog test (both use focused, behavior-level assertions). New linear-core resolver tests should follow the existing gateway-level test conventions in that package.
Out of Scope
- Running
git operations from prep (branch is surfaced, not checked out).
- Name→id resolution for assignee, label, project, or cycle on update/create — deferred; the resolver pattern from Module 1 is intended to be reused later.
- A real create-or-update
save command (only the suggestion hint is added).
- Timeout/retry tuning beyond what already shipped for Linear API timeouts.
- Persisting/caching workflow states across CLI invocations (caching is limited to within a single
bulk-update run).
- TUI changes.
Further Notes
- Evidence base: local Claude Code session-log analysis. 93 sessions invoked the CLI; failure counts —
view 65 (57 sessions), set-state-by-name 49 (43), state-discovery 48 (43), comments --issue 28 (21), show 12, save 9, too many arguments 7, unbounded-list timeout 6, bad stateId 5, binary-not-found 1. One session needed 11 commands to set a single issue's state.
- The single highest-leverage change is Module 1 + Module 2: name-based state resolution removes the two largest clusters (~90 failures across ~60 sessions) and collapses the 11-command ritual into one call.
--state is already a global option parsed via optsWithGlobals; today the update handler simply ignores it. No new flag is required to accept --state on update/create.
- Existing resolution helpers (
resolveIssueId, resolveIssueTemplateId, resolveViewerName) establish the pattern the state resolver should follow.
Problem Statement
The
linear/liCLI is increasingly driven by AI coding agents, not just humans typing interactively. An analysis of local Claude Code session logs (776 sessions across 187 projects; 93 distinct sessions actually drove the CLI) shows agents repeatedly fail on the same small set of operations, burning many turns per task:linear issues update ANN-1 --state "In Progress"(→Missing input. Provide --input) or--input '{"stateName":"In Progress"}'/'{"state":"In Progress"}'(→ GraphQLField "stateName" is not defined by type "IssueUpdateInput". Did you mean "stateId"?). The CLI only accepts a rawstateIdUUID, which the agent does not have.stateId, agents guessstatuses,issue-statuses,workflow-states,list-states,workflow,get-status… none of which exist, before eventually findingstates list(or pipingteams get … | pythonand crashing on the output shape).linear issues view ANN-1→error: unknown command 'view'. The CLI only hasget.linear comments list --issue ANN-1→unknown option '--issue'; there is no way to scope comments to an issue.linear issues list --team DUB(or--all) on a large team returnsThe operation was aborted due to timeoutand hammers the API, contributing to rate-limit pressure.showguessed (12),saveguessed from the MCP tool name (9), positional args tolist→too many arguments(6), hand-crafted badstateIdUUID →Entity not found in validateAccess: stateId(4), and one workspace where the binary wasn't onPATH.The worst observed case: a single agent took 11 commands to move one issue to "In Progress" (try
--state, try{"state":…}, guessteams workflow-states, read two help screens, crash a python one-liner, finally findstates list, copy a UUID, fail once on the wrong UUID, succeed).Solution
Make the CLI forgiving of the inputs agents actually produce, and add a few high-level workflow commands that collapse multi-step rituals into one call.
linear issues update ANN-1 --state "In Progress"just works. So does--input '{"stateName":"In Progress"}'and--input '{"state":"In Progress"}'. The CLI resolves the name to the correctstateIdfor the issue's team. RawstateIdstill works unchanged.getacceptsviewandshowas aliases on every resource. Unknown commands print a "did you mean" suggestion, andstatesanswers tostatusesandworkflow-states.linear comments list --issue ANN-1returns that issue's comments.issues listis blocked with a message suggesting a narrower query (--mine,--state,--query,--limit, etc.); bounded queries are untouched.linear prep ANN-1pulls the issue plus its parent/project context plus its branch name and sets it to the team's in-progress state — one call to get ready to work.linear pr-ready ANN-1flips the issue to its review state (and posts a comment if you pass one).linear-cliskill documents the canonical commands and a "common mistakes → correct command" map, so agents stop guessing.User Stories
--state "In Progress", so that I don't need to discover a UUID first.--input '{"stateName":"In Progress"}'to be accepted, so that the pattern I naturally reach for succeeds.--input '{"state":"In Progress"}'to be accepted, so that my second-guess pattern also succeeds.stateIdUUID in--inputto keep working, so that existing scripts and precise updates are unaffected.stateresolution scoped to the target issue's team, so that a name like "In Progress" maps to the right team's state.linear issues create --state "Todo" --input '{"teamId":…,"title":…}'to resolve the state name against the named team, so that I can create issues in a specific state.linear issues bulk-updateto accept a state name once and apply it across issues, so that batch status changes don't require per-team UUID lookups.linear issues view ANN-1to behave likeget, so that my habitual read verb works.linear issues show ANN-1to behave likeget, so that another common read verb works.linear statuses listandlinear workflow-states listto resolve tostates list, so that the names I guess for state discovery still work.linear comments list --issue ANN-1, so that I can read an issue's discussion without fetching all comments.--issueto accept either a UUID or an identifier like ANN-123, so that I can use whichever I already have.issues listto be blocked with a suggested narrower query, so that I don't trigger timeouts or rate limits.issues list(with--team, a filter,--mine, or--limit) to run normally, so that the guardrail never blocks legitimate queries.--mine,--state,--query,--limit), so that I know exactly how to make the query acceptable.linear prep ANN-1to set the issue to the team's in-progress state, so that I don't run a separate update.prepto return the issue's branch name, so that I can create my working branch without a second command.prepto include the parent issue (or project) summary, so that I have the surrounding context before I start.prep --state <name>to override the target state, so that teams with non-standard state names still work.prepto pick the team's firststarted-type state when no exact "In Progress" exists, so that it works across differently-named workflows.prepoutput as a single JSON envelope, so that I can parse issue, context, branch, and new state in one read.linear pr-ready ANN-1to set the issue to its review state, so that I can mark work ready in one call.pr-ready --comment "…"to post that comment on the issue, so that I can leave a review note in the same step.pr-ready --pr <url>to post a comment linking the PR, so that the Linear issue references the PR without me writing the body.pr-readywith no comment flags to only change state, so that I'm not forced to write a comment.pr-ready --state <name>to override the review state, so that custom workflows work.--statefor status changes, so that I learn the right command before failing.linear prep ANN-1to set up my issue and print its branch name, so that I can start work quickly.list(linear issues list ANN), so that I'm pointed at--team/filters.linear doctorto report whether the binary is correctly installed/on PATH, so that I can diagnose "command not found".stateId-based usage to remain byte-for-byte compatible, so that nothing breaks for current scripts.Implementation Decisions
Module 1 — Workflow-state resolver (deep module,
linear-core).resolveStateId(teamRef, stateRef, options?) → Promise<stateId>, whereoptionsmay include a preferred statetype(e.g.started).stateRefis a UUID, return it unchanged. Otherwise load the team's workflow states (via the existinglistWorkflowStates/gateway), then match in precedence order: (a) exact case-insensitivenamematch; (b) if none and a preferredtypeis supplied, the lowest-positionstate of thattype. On no match or ambiguous match, throw a typed error whose message lists the team's valid states asname (type).typeenum istriage | backlog | unstarted | started | completed | canceled— there is no "review" type. "In Review" is a customstarted-type state, so review targeting is by name, not type.name (type)so a wrong name self-corrects in one step.Module 2 — Issue input normalization (
cli).IssueUpdateInput/IssueCreateInputreaches GraphQL, fold a state reference intostateIdusing Module 1. Source precedence: explicitstateIdin--input>--stateflag (already a global option, read viaoptsWithGlobals) >state/stateNamekey inside--inputJSON. Thestate/stateNamekeys are stripped after resolution so they never reach GraphQL.update, fetch the target issue first and use itsteamId; forcreate, useinput.teamId. Forbulk-update, resolve per distinct team and reuse within the run.stateIdUUID skips resolution entirely.issues update/create/bulk-update --helpdocument setting state by name (--stateplusstate/stateNamein--input), the bundledlinear-cliskill gains a "Set Issue State By Name" section and a "common mistakes → correct command" row, and the README shows a--stateexample.Module 3 — Resource command ergonomics (
cli,registerResourceCommand).getwith command aliasesviewandshowfor every resource entity (does not affect unrelated top-levelskills show/project statussubcommands).showSuggestionAfterError(true)andshowHelpAfterError, so unknown commands/options print a "did you mean" hint.statusesandworkflow-statesto thestatescommand.get(notview/show) andstates list(notstatuses/workflow-states).Module 4 — Comments
--issuefilter (cli+ gateway).comments listgains--issue <id-or-identifier>. The value is resolved to an issue id via the existingresolveIssueId, then comments are scoped to that issue (server-side filter if the API supports it; otherwise filter the connection client-side). Without--issue, behavior is unchanged.comments list --helpdocuments--issue(UUID or identifier), and the bundled skill shows scoping an issue's comments.Module 5 — List guardrail (
cli).issues list. Block (error, exit code 1) only when the query is truly unbounded: no--teamAND none of{--mine, --state, --status, --assignee, --label, --priority, --query, --project, --cycle, --parent, --filter, --updated-after, --created-after, --limit}; OR--allis set with no narrowing filter present.--mine,--state "In Progress",--query …,--limit N). Bounded queries pass through untouched. Scope:issues list(the high-cardinality offender); other entities unaffected.issues list --helpand the skill state the bounded-query expectation up front.Module 6 —
prepcommand (cli, top-levellinear prep <issue>, mirroringmy-work/triage).parentId, else project viaprojectId); compute the branch name (getIssueBranchName); resolve the target state via Module 1 (prefer exact name "In Progress", else firststarted-type state; overridable with--state); apply the state update.prep --help, a README example, and a bundled-skill section show the one-call "get ready to work" flow and where to read the branch name in the output envelope.Module 7 —
pr-readycommand (cli, top-levellinear pr-ready <issue>).--state; typed error listing valid states if unmatched) and apply it. State change only.--comment <body>is supplied; if--pr <url>is supplied without--comment, generate a minimal body linking the PR. No comment flags → no comment.prep.pr-ready --help, a README example, and a bundled-skill section show flipping to the review state and the optional--comment/--prbehavior.Module 8 — Agent skill reference consolidation (
skills-catalog).--helptext, bundled-skill section, and error hints (see each module's Docs/hints bullet). Module 8 audits those for consistency, fills any cross-cutting gaps, and assembles the single consolidated "common mistakes → correct command" table covering all failure patterns from the log analysis.linear-cliskill (and theissue-triage/cycle-planningskills where they touch status changes) coherently document: setting state via--state/name,get(notview),comments list --issue, theprepandpr-readyworkflows, and the list-guardrail expectations.Lower-value papercuts (in scope, minimal).
too many argumentserror onlistthat points at--team/filters.doctorreports binary install / PATH status.saveis handled by the Commander suggestion mechanism (Module 3); no realsavecommand is built.Cross-cutting decisions.
ok/entity/action/data|error) is preserved for all new commands.--helptext, the bundledlinear-cliskill (plusissue-triage/cycle-planningwhere relevant), the README examples, and — critically — the command's error messages, which are treated as a first-class agent UX surface (a failure must name the correct command or list valid values so the agent self-corrects in one step). Module 8 is the final coherence/consolidation pass over this per-slice work, not the only place docs change. Each module carries an explicit "Docs/hints" acceptance item.Testing Decisions
A good test here exercises external behavior through a public interface — a command handler or a gateway/service method — using an in-memory or fake gateway, and asserts on the returned envelope and on the calls made to the gateway. Tests must not assert on internal helper structure or private shapes, so that refactors don't break them.
Modules to be tested:
typefallback; not-found and ambiguous errors that list valid states.--state,state, andstateNameall fold intostateIdforupdate,create, andbulk-update; precedence is honored; an explicitstateIdis left untouched;state/stateNamekeys never reach the gateway.viewandshowresolve to thegethandler; an unknown command surfaces a suggestion.--issuefilter (Module 4): an identifier is resolved and comments are scoped to that issue; absence of--issueis unchanged.issues listinvocations are blocked vs allowed (no-args blocked;--mine/--team/--limit/filters allowed;--allwithout narrowing blocked).preporchestration (Module 6): composes issue + parent/project + branch + state via a fake gateway;--stateoverride andstarted-type fallback both covered.pr-readyorchestration (Module 7): flips to the review state; posts a comment only when--comment/--pris provided;--pr-only generates a link body.skills-catalogtest (e.g. thelinear-cliskill must contain the state-by-name section and the common-mistakes table), so the agent-facing guidance each slice ships cannot silently regress in CI.Prior art for the style and harness: the existing
issues-bulk-updatecommand test and theskills-catalogtest (both use focused, behavior-level assertions). Newlinear-coreresolver tests should follow the existing gateway-level test conventions in that package.Out of Scope
gitoperations fromprep(branch is surfaced, not checked out).savecommand (only the suggestion hint is added).bulk-updaterun).Further Notes
view65 (57 sessions), set-state-by-name 49 (43), state-discovery 48 (43),comments --issue28 (21),show12,save9,too many arguments7, unbounded-list timeout 6, badstateId5, binary-not-found 1. One session needed 11 commands to set a single issue's state.--stateis already a global option parsed viaoptsWithGlobals; today theupdatehandler simply ignores it. No new flag is required to accept--stateon update/create.resolveIssueId,resolveIssueTemplateId,resolveViewerName) establish the pattern the state resolver should follow.