diff --git a/.gitignore b/.gitignore index 565b06cf..814b580a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .aider.* .config/linear.toml - +.idea/ src/__codegen__/ npm diff --git a/skills/linear-cli/references/issue.md b/skills/linear-cli/references/issue.md index 67fb5cfa..68e531a4 100644 --- a/skills/linear-cli/references/issue.md +++ b/skills/linear-cli/references/issue.md @@ -364,7 +364,7 @@ Options: -s, --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 order (can also be set via LINEAR_ISSUE_SORT) (Values: "manual", "priority") + --sort - Sort order (default: priority, can also be set via LINEAR_ISSUE_SORT) (Values: "manual", "priority") --team - Team to list issues for (if not your default team) --project - Filter by project (UUID, slug ID, or name) --project-label - Filter by project label name (shows issues from all projects with this label) diff --git a/src/commands/issue/issue-mine.ts b/src/commands/issue/issue-mine.ts index dcb64059..27f4d459 100644 --- a/src/commands/issue/issue-mine.ts +++ b/src/commands/issue/issue-mine.ts @@ -61,7 +61,7 @@ export const mineCommand = new Command() ) .option( "--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, }, @@ -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(", ")}`, diff --git a/src/utils/linear.ts b/src/utils/linear.ts index 58cc3301..686d67c9 100644 --- a/src/utils/linear.ts +++ b/src/utils/linear.ts @@ -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 } }, diff --git a/test/commands/issue/__snapshots__/issue-list.test.ts.snap b/test/commands/issue/__snapshots__/issue-list.test.ts.snap index c32401ec..aa2040b4 100644 --- a/test/commands/issue/__snapshots__/issue-list.test.ts.snap +++ b/test/commands/issue/__snapshots__/issue-list.test.ts.snap @@ -15,7 +15,7 @@ Options: -s, --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 order (can also be set via LINEAR_ISSUE_SORT) (Values: \\x1b[32m"manual"\\x1b[39m, \\x1b[32m"priority"\\x1b[39m) + --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 to list issues for (if not your default team) --project - Filter by project (UUID, slug ID, or name) --project-label - Filter by project label name (shows issues from all projects with this label) diff --git a/test/commands/issue/__snapshots__/issue-mine.test.ts.snap b/test/commands/issue/__snapshots__/issue-mine.test.ts.snap index f4058724..e75423b2 100644 --- a/test/commands/issue/__snapshots__/issue-mine.test.ts.snap +++ b/test/commands/issue/__snapshots__/issue-mine.test.ts.snap @@ -15,7 +15,7 @@ Options: -s, --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 order (can also be set via LINEAR_ISSUE_SORT) (Values: \\x1b[32m"manual"\\x1b[39m, \\x1b[32m"priority"\\x1b[39m) + --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 to list issues for (if not your default team) --project - Filter by project (UUID, slug ID, or name) --project-label - Filter by project label name (shows issues from all projects with this label) diff --git a/test/commands/issue/issue-mine.test.ts b/test/commands/issue/issue-mine.test.ts index 5a8b5379..48fd4067 100644 --- a/test/commands/issue/issue-mine.test.ts +++ b/test/commands/issue/issue-mine.test.ts @@ -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 diff --git a/test/commands/issue/issue-start.test.ts b/test/commands/issue/issue-start.test.ts new file mode 100644 index 00000000..d894f9f2 --- /dev/null +++ b/test/commands/issue/issue-start.test.ts @@ -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) +})