Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.aider.*
.config/linear.toml

.idea/
src/__codegen__/
npm
2 changes: 1 addition & 1 deletion skills/linear-cli/references/issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ Options:
-s, --state <state> - Filter by issue state (can be repeated for multiple states) (Default: [ "unstarted" ], Values: "triage", "backlog",
"unstarted", "started", "completed", "canceled")
--all-states - Show issues from all states
--sort <sort> - Sort order (can also be set via LINEAR_ISSUE_SORT) (Values: "manual", "priority")
--sort <sort> - Sort order (default: priority, can also be set via LINEAR_ISSUE_SORT) (Values: "manual", "priority")
--team <team> - Team to list issues for (if not your default team)
--project <project> - Filter by project (UUID, slug ID, or name)
--project-label <projectLabel> - Filter by project label name (shows issues from all projects with this label)
Expand Down
10 changes: 3 additions & 7 deletions src/commands/issue/issue-mine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const mineCommand = new Command()
)
.option(
"--sort <sort:sort>",
"Sort order (can also be set via LINEAR_ISSUE_SORT)",
"Sort order (default: priority, can also be set via LINEAR_ISSUE_SORT)",
{
required: false,
},
Expand Down Expand Up @@ -179,12 +179,8 @@ export const mineCommand = new Command()
}

const sort = sortFlag ||
getOption("issue_sort") as "manual" | "priority" | undefined
if (!sort) {
throw new ValidationError(
"Sort must be provided via command line flag, configuration file, or LINEAR_ISSUE_SORT environment variable",
)
}
(getOption("issue_sort") as "manual" | "priority" | undefined) ||
"priority"
if (!SortType.values().includes(sort)) {
throw new ValidationError(
`Sort must be one of: ${SortType.values().join(", ")}`,
Expand Down
14 changes: 3 additions & 11 deletions src/utils/linear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,17 +593,9 @@ export async function fetchIssuesForState(
createdAfter?: string,
updatedAfter?: string,
) {
const sort = sortParam ??
getOption("issue_sort") as "manual" | "priority" | undefined
if (!sort) {
throw new ValidationError(
"Sort must be provided",
{
suggestion:
"Use --sort parameter, set in configuration file, or set LINEAR_ISSUE_SORT environment variable",
},
)
}
const sort: "manual" | "priority" = sortParam ??
(getOption("issue_sort") as "manual" | "priority" | undefined) ??
"priority"

const filter: IssueFilter = {
team: { key: { eq: teamKey } },
Expand Down
2 changes: 1 addition & 1 deletion test/commands/issue/__snapshots__/issue-list.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Options:
-s, --state <state> - Filter by issue state (can be repeated for multiple states) (Default: [ \\x1b[32m"unstarted"\\x1b[39m ], Values: \\x1b[32m"triage"\\x1b[39m, \\x1b[32m"backlog"\\x1b[39m,
\\x1b[32m"unstarted"\\x1b[39m, \\x1b[32m"started"\\x1b[39m, \\x1b[32m"completed"\\x1b[39m, \\x1b[32m"canceled"\\x1b[39m)
--all-states - Show issues from all states
--sort <sort> - Sort order (can also be set via LINEAR_ISSUE_SORT) (Values: \\x1b[32m"manual"\\x1b[39m, \\x1b[32m"priority"\\x1b[39m)
--sort <sort> - Sort order (default: priority, can also be set via LINEAR_ISSUE_SORT) (Values: \\x1b[32m"manual"\\x1b[39m, \\x1b[32m"priority"\\x1b[39m)
--team <team> - Team to list issues for (if not your default team)
--project <project> - Filter by project (UUID, slug ID, or name)
--project-label <projectLabel> - Filter by project label name (shows issues from all projects with this label)
Expand Down
2 changes: 1 addition & 1 deletion test/commands/issue/__snapshots__/issue-mine.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Options:
-s, --state <state> - Filter by issue state (can be repeated for multiple states) (Default: [ \\x1b[32m"unstarted"\\x1b[39m ], Values: \\x1b[32m"triage"\\x1b[39m, \\x1b[32m"backlog"\\x1b[39m,
\\x1b[32m"unstarted"\\x1b[39m, \\x1b[32m"started"\\x1b[39m, \\x1b[32m"completed"\\x1b[39m, \\x1b[32m"canceled"\\x1b[39m)
--all-states - Show issues from all states
--sort <sort> - Sort order (can also be set via LINEAR_ISSUE_SORT) (Values: \\x1b[32m"manual"\\x1b[39m, \\x1b[32m"priority"\\x1b[39m)
--sort <sort> - Sort order (default: priority, can also be set via LINEAR_ISSUE_SORT) (Values: \\x1b[32m"manual"\\x1b[39m, \\x1b[32m"priority"\\x1b[39m)
--team <team> - Team to list issues for (if not your default team)
--project <project> - Filter by project (UUID, slug ID, or name)
--project-label <projectLabel> - Filter by project label name (shows issues from all projects with this label)
Expand Down
82 changes: 82 additions & 0 deletions test/commands/issue/issue-mine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,88 @@ Deno.test("Issue Mine Command - Filter By Label", async () => {
}
})

Deno.test("Issue Mine Command - Defaults To Priority Sort When None Configured", async () => {
const fixedNow = new Date("2026-03-30T10:00:00.000Z")
const RealDate = Date
const originalColorEnabled = getColorEnabled()
class MockDate extends RealDate {
constructor(value?: string | number | Date) {
super(value == null ? fixedNow.toISOString() : value)
}

static override now(): number {
return fixedNow.getTime()
}
}
globalThis.Date = MockDate as DateConstructor
setColorEnabled(false)

// No LINEAR_ISSUE_SORT and no --sort flag: the request must still go out,
// sorted by priority. The mock only matches when sort is the priority
// payload, so a missing/incorrect default would fail to match.
const { cleanup } = await setupMockLinearServer([
{
queryName: "GetIssuesForState",
variables: {
sort: [
{ workflowState: { order: "Descending" } },
{ priority: { nulls: "last", order: "Descending" } },
{ manual: { nulls: "last", order: "Ascending" } },
],
},
response: {
data: {
issues: {
nodes: [
{
id: "issue-1",
identifier: "ENG-101",
title: "Fix login bug",
priority: 1,
estimate: 3,
assignee: { initials: "MC" },
state: {
id: "state-1",
name: "In Progress",
color: "#f2c94c",
type: "started",
},
labels: { nodes: [] },
cycle: null,
team: {
id: "team-eng-id",
key: "ENG",
cyclesEnabled: false,
activeCycle: null,
},
inverseRelations: { nodes: [] },
updatedAt: "2026-03-13T10:00:00.000Z",
},
],
pageInfo: { hasNextPage: false, endCursor: null },
},
},
},
},
], { LINEAR_TEAM_ID: "ENG", NO_COLOR: "true" })

const logs: string[] = []
const logStub = stub(console, "log", (...args: unknown[]) => {
logs.push(args.map(String).join(" "))
})

try {
await mineCommand.parse(["--team", "ENG"])

assertEquals(logs.join("\n").includes("ENG-101"), true)
} finally {
logStub.restore()
globalThis.Date = RealDate
setColorEnabled(originalColorEnabled)
await cleanup()
}
})

Deno.test("Issue Mine Command - Shows Blocked Indicator", async () => {
const fixedNow = new Date("2026-03-30T10:00:00.000Z")
const RealDate = Date
Expand Down
55 changes: 55 additions & 0 deletions test/commands/issue/issue-start.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { assertEquals } from "@std/assert"
import { stub } from "@std/testing/mock"
import { startCommand } from "../../../src/commands/issue/issue-start.ts"
import { setupMockLinearServer } from "../../utils/test-helpers.ts"

// `issue start` with no issue id lists unstarted issues via the shared
// fetchIssuesForState helper without passing a sort, so it relies on that
// helper defaulting to priority.
Deno.test("Issue Start Command - Does Not Require Sort Config", async () => {
// Return no issues so the command stops at its empty-list check instead of
// opening the interactive prompt. Reaching that check confirms the request
// went out with the default priority sort.
const { cleanup } = await setupMockLinearServer([
{
queryName: "GetIssuesForState",
variables: {
sort: [
{ workflowState: { order: "Descending" } },
{ priority: { nulls: "last", order: "Descending" } },
{ manual: { nulls: "last", order: "Ascending" } },
],
},
response: {
data: {
issues: {
nodes: [],
pageInfo: { hasNextPage: false, endCursor: null },
},
},
},
},
], { LINEAR_TEAM_ID: "ENG", NO_COLOR: "true" })

const errorLogs: string[] = []
const errorStub = stub(console, "error", (...args: unknown[]) => {
errorLogs.push(args.map(String).join(" "))
})
const exitStub = stub(Deno, "exit", (_code?: number): never => {
throw new Error("EXIT")
})

try {
await startCommand.parse([])
} catch {
// expected: handleError calls the stubbed Deno.exit
} finally {
errorStub.restore()
exitStub.restore()
await cleanup()
}

const output = errorLogs.join("\n")
assertEquals(output.includes("Sort must be provided"), false)
assertEquals(output.includes("Unstarted issues not found"), true)
})
Loading