Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
607c954
fix(issues): honor explicit completed status filters
Apr 24, 2026
9d51b20
Merge pull request #180 from linearis-oss/fix/issue-179-completed-sta…
iamfj Apr 24, 2026
11b4a74
chore(release): 2026.4.9-next.1 [skip ci]
semantic-release-bot Apr 24, 2026
0c1874a
feat(labels): add issue label scope filters
iamfj Apr 24, 2026
c7638c2
Merge pull request #181 from linearis-oss/feat/issue-116-label-scope-…
iamfj Apr 24, 2026
e593539
chore(release): 2026.4.9-next.2 [skip ci]
semantic-release-bot Apr 24, 2026
963af95
feat(issues): batch-resolve search filter identifiers
iamfj Apr 25, 2026
1cc4bab
docs(agents): clarify resolver client exceptions
iamfj Apr 25, 2026
fb2c476
Merge pull request #178 from linearis-oss/issue-63-batch-resolve-impl
iamfj Apr 25, 2026
5aded6a
chore(release): 2026.4.9-next.3 [skip ci]
semantic-release-bot Apr 25, 2026
1ae10e1
feat(discussions): add GraphQL and service layer
iamfj Apr 25, 2026
6b3861c
feat(issues): add discussion commands
iamfj Apr 25, 2026
ff64f2e
feat(projects): add discussion commands
iamfj Apr 25, 2026
81bcd17
feat(initiatives): add discussion commands
iamfj Apr 25, 2026
d097eee
feat(comments): deprecate compatibility facade
iamfj Apr 25, 2026
7631b38
docs(discussions): add migration guidance
iamfj Apr 25, 2026
08af46c
Merge pull request #183 from linearis-oss/issue-146-discussion-design
iamfj Apr 25, 2026
938c457
chore(release): 2026.4.9-next.4 [skip ci]
semantic-release-bot Apr 25, 2026
a8aa2bd
chore(deps): update dev dependencies (non-major)
renovate[bot] Apr 27, 2026
1ba6079
Merge pull request #187 from linearis-oss/renovate/dev-dependencies-(…
iamfj Apr 27, 2026
20a532c
chore(deps): update github actions
renovate[bot] Apr 27, 2026
13f10e7
Merge pull request #188 from linearis-oss/renovate/major-github-actions
iamfj Apr 27, 2026
7e62fda
fix(deps): update dependency @linear/sdk to v82
renovate[bot] Apr 27, 2026
e54c132
Merge pull request #190 from linearis-oss/renovate/linear-sdk-82.x
iamfj Apr 27, 2026
f88d7c6
chore(release): 2026.4.9-next.5 [skip ci]
semantic-release-bot Apr 27, 2026
183f26a
chore(deps): update semantic-release monorepo
renovate[bot] Apr 27, 2026
c73999b
Merge pull request #189 from linearis-oss/renovate/major-semantic-rel…
iamfj Apr 27, 2026
e92b7ca
fix(ci): rerun validation when PR base changes
iamfj Apr 27, 2026
7aee69d
Merge pull request #193 from linearis-oss/fix/ci-pr-base-edited
iamfj Apr 27, 2026
69fa0f5
test: cover issue estimate team context resolution
iamfj Apr 27, 2026
9c94ff9
fix: resolve issue estimate team context via team resolver
iamfj Apr 27, 2026
09e6c1e
Merge pull request #192 from linearis-oss/chore/issue-186-update-esti…
iamfj Apr 27, 2026
47324f4
chore(release): 2026.4.9-next.6 [skip ci]
semantic-release-bot Apr 27, 2026
bd0ffac
feat(graphql): add reaction operations
iamfj Apr 27, 2026
bc82bc6
feat(emoji): add reaction input normalization
iamfj Apr 27, 2026
0a29bb1
feat(reactions): add shared service
iamfj Apr 27, 2026
4d6a00c
feat(cli): add reaction workflows
iamfj Apr 27, 2026
be0acf3
Merge pull request #191 from linearis-oss/chore/issue-83-reactions-de…
iamfj Apr 27, 2026
32be58b
chore(release): 2026.4.9-next.7 [skip ci]
semantic-release-bot Apr 27, 2026
705218f
chore(commitlint): enforce stricter validation
Apr 26, 2026
85a1d3a
fix(comments): constrain compatibility replies
iamfj Apr 27, 2026
9effd26
Merge pull request #185 from linearis-oss/chore/issue-97-commitlint-r…
iamfj Apr 27, 2026
8ee5931
chore(release): 2026.4.9-next.8 [skip ci]
semantic-release-bot Apr 27, 2026
2963db6
refactor(context): use getRootOpts for root options
iamfj Apr 27, 2026
bce9024
Merge pull request #184 from linearis-oss/refactor/issue-61-get-root-…
iamfj Apr 27, 2026
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 .github/workflows/ci-post-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Setup node v22
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm

- name: Install deps
Expand Down
29 changes: 28 additions & 1 deletion .github/workflows/ci-validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- synchronize
- ready_for_review
- reopened
- edited

permissions:
contents: read
Expand Down Expand Up @@ -76,6 +77,32 @@ jobs:
- name: TypeScript type check
run: npx tsc --noEmit

commitlint:
name: Validate Commit Messages
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup node v22
uses: actions/setup-node@v6
with:
node-version: 22
cache: "npm"

- name: Install deps
run: npm ci

- name: Validate PR commit range
run: |
npx commitlint \
--from "${{ github.event.pull_request.base.sha }}" \
--to "${{ github.event.pull_request.head.sha }}" \
--verbose

smoke-test:
name: Run Package Install Smoke Test
runs-on: ubuntu-latest
Expand All @@ -88,7 +115,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: "npm"

- name: Install deps
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-promote-next-to-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
- name: Generate linearis-bot app token
if: ${{ steps.commits-check.outputs.has_commits == 'true' }}
id: app-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
steps:
- name: Guard workflow_dispatch caller permissions
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
Expand Down Expand Up @@ -73,7 +73,7 @@ jobs:

- name: Create linearis-bot app token
id: app-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
Expand All @@ -100,7 +100,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm
registry-url: https://registry.npmjs.org

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-sync-main-back-to-next.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:

- name: Create linearis-bot app token
id: app-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
Expand Down
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ CLI Input → Command → Resolver → Service → JSON Output
- Resolvers must not import services (or vice versa).
- Commands must not import `GraphQLClient` directly.
3. **Client-layer contract:**
- Resolvers → `LinearSdkClient` only.
- Resolvers → `LinearSdkClient` by default.
- Services → `GraphQLClient` only.
- Commands → both, via `createContext()`.
- **Narrow exceptions allowed only when SDK lacks required capability**, with explicit `ARCHITECTURAL EXCEPTION` docstring in code (current examples: milestone/project-status lookups, initiative relation/link ID lookup helpers).
4. **ID resolution happens once**, in resolvers only. Services accept UUIDs.
5. **All commands** use `handleCommand()` wrapper and `outputSuccess()` for output.
6. **Explicit return types** on all exported functions.
Expand All @@ -83,8 +84,9 @@ Need a new GraphQL operation?

Need to resolve a human-friendly ID?
→ Add/edit src/resolvers/*-resolver.ts
Use LinearSdkClient, return UUID string
Prefer LinearSdkClient, return UUID string
→ Pattern: UUID passthrough → SDK lookup → notFoundError()
→ If SDK cannot express lookup, use GraphQL as documented ARCHITECTURAL EXCEPTION (include rationale in resolver docstring)

Need business logic / CRUD?
→ Add/edit src/services/*-service.ts
Expand Down
56 changes: 56 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,59 @@
## [2026.4.9-next.8](https://github.com/linearis-oss/linearis/compare/v2026.4.9-next.7...v2026.4.9-next.8) (2026-04-27)

### Bug Fixes

* **comments:** constrain compatibility replies ([85a1d3a](https://github.com/linearis-oss/linearis/commit/85a1d3a1e2e1ecf46138632d637f00b046a1c12c))

## [2026.4.9-next.7](https://github.com/linearis-oss/linearis/compare/v2026.4.9-next.6...v2026.4.9-next.7) (2026-04-27)

### Features

* **cli:** add reaction workflows ([4d6a00c](https://github.com/linearis-oss/linearis/commit/4d6a00cbd9a2c9abe407d5071f3ccd5bbb81e01e)), closes [#83](https://github.com/linearis-oss/linearis/issues/83)
* **emoji:** add reaction input normalization ([bc82bc6](https://github.com/linearis-oss/linearis/commit/bc82bc6fa3e7c6ee64f6581b80206ec85bf8e9a8)), closes [#83](https://github.com/linearis-oss/linearis/issues/83)
* **graphql:** add reaction operations ([bd0ffac](https://github.com/linearis-oss/linearis/commit/bd0ffaccdba8659fedb6ae4074dc737e102b63b8)), closes [#83](https://github.com/linearis-oss/linearis/issues/83)
* **reactions:** add shared service ([0a29bb1](https://github.com/linearis-oss/linearis/commit/0a29bb1f6208a2ddb093f458a7d12d70a645ac91)), closes [#83](https://github.com/linearis-oss/linearis/issues/83)

## [2026.4.9-next.6](https://github.com/linearis-oss/linearis/compare/v2026.4.9-next.5...v2026.4.9-next.6) (2026-04-27)

### Bug Fixes

* **ci:** rerun validation when PR base changes ([e92b7ca](https://github.com/linearis-oss/linearis/commit/e92b7cad13e667f25d2f2bc02901e50f94646a66))
* resolve issue estimate team context via team resolver ([9c94ff9](https://github.com/linearis-oss/linearis/commit/9c94ff9fc5677e15380e94500933888a76f08e1d))

## [2026.4.9-next.5](https://github.com/linearis-oss/linearis/compare/v2026.4.9-next.4...v2026.4.9-next.5) (2026-04-27)

### Bug Fixes

* **deps:** update dependency @linear/sdk to v82 ([7e62fda](https://github.com/linearis-oss/linearis/commit/7e62fdacb56b9e14963303bb76dbd67a7056f554))

## [2026.4.9-next.4](https://github.com/linearis-oss/linearis/compare/v2026.4.9-next.3...v2026.4.9-next.4) (2026-04-25)

### Features

* **comments:** deprecate compatibility facade ([d097eee](https://github.com/linearis-oss/linearis/commit/d097eeee62dfa5446c866644ded70a779346b7e4))
* **discussions:** add GraphQL and service layer ([1ae10e1](https://github.com/linearis-oss/linearis/commit/1ae10e1313722c58102c8269cc97886ed8891d8d))
* **initiatives:** add discussion commands ([81bcd17](https://github.com/linearis-oss/linearis/commit/81bcd173f7caec48911e39738d6b4dd60672d7fc))
* **issues:** add discussion commands ([6b3861c](https://github.com/linearis-oss/linearis/commit/6b3861cbfbdb6324270cda1a7977547e9253ff2e))
* **projects:** add discussion commands ([ff64f2e](https://github.com/linearis-oss/linearis/commit/ff64f2edd7ceb5cdbc34de405b984582c86687d9))

## [2026.4.9-next.3](https://github.com/linearis-oss/linearis/compare/v2026.4.9-next.2...v2026.4.9-next.3) (2026-04-25)

### Features

* **issues:** batch-resolve search filter identifiers ([963af95](https://github.com/linearis-oss/linearis/commit/963af954dbc286f755f93b740b36fcca4626a2c4)), closes [#63](https://github.com/linearis-oss/linearis/issues/63)

## [2026.4.9-next.2](https://github.com/linearis-oss/linearis/compare/v2026.4.9-next.1...v2026.4.9-next.2) (2026-04-24)

### Features

* **labels:** add issue label scope filters ([0c1874a](https://github.com/linearis-oss/linearis/commit/0c1874aad8cbadf6e4d729ad0940f1f3bcdd4106)), closes [#116](https://github.com/linearis-oss/linearis/issues/116)

## [2026.4.9-next.1](https://github.com/linearis-oss/linearis/compare/v2026.4.8...v2026.4.9-next.1) (2026-04-24)

### Bug Fixes

* **issues:** honor explicit completed status filters ([607c954](https://github.com/linearis-oss/linearis/commit/607c954bb861519c9be5b5499c07844262b2b8de)), closes [#179](https://github.com/linearis-oss/linearis/issues/179)

## [2026.4.8](https://github.com/linearis-oss/linearis/compare/v2026.4.7...v2026.4.8) (2026-04-23)

### Features
Expand Down
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ type(scope): description
| `test` | Adding or fixing tests |
| `build` | Build system, dependencies |
| `chore` | Maintenance, tooling |
| `ci` | CI workflow and automation changes |
| `revert` | Revert a previous commit |

**Examples:**

Expand All @@ -97,6 +99,12 @@ docs: update README with new commands

Use imperative mood ("add" not "added"). Scope is optional.

Additional validation rules enforced locally and in CI:
- scopes must be lower-case (`feat(api): ...`, not `feat(API): ...`)
- subjects must be at least 10 characters long
- commit bodies and footers must be separated from the subject by a blank line when present
- PR commit ranges are validated in CI with commitlint, not only via the local hook

## Linearis is opinionated, because its maintainer is

I wish times were better and I wouldn't have to mention it, but they aren't and unfortunately, I do:
Expand Down
41 changes: 33 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ CLI tool for [Linear.app](https://linear.app) optimized for AI agents. JSON outp

The official Linear MCP works fine, but it eats up ~13k tokens just by being connected -- before the agent does anything. Linearis takes a different approach: instead of exposing the full API surface upfront, agents discover what they need through a two-tier usage system. `linearis usage` gives an overview in ~200 tokens, then `linearis <domain> usage` provides the full reference for one area in ~300-500 tokens. A typical agent interaction costs ~500-700 tokens of context, not ~13k.

The trade-off is coverage. An MCP exposes the entire Linear API; Linearis covers the operations that matter for day-to-day work with issues, comments, cycles, documents, and files. If you need to manage custom workflows, integrations, or workspace settings, the MCP is the better choice.

**This project scratches my own itches,** and satisfies my own usage patterns of working with Linear: I **do** work with tickets/issues and comments on the command line; I **do not** manage projects or workspaces etc. there. YMMV.
The trade-off is coverage. An MCP exposes the entire Linear API; Linearis covers the operations that matter for day-to-day work with issues, discussions, cycles, documents, and files. If you need to manage custom workflows, integrations, or workspace settings, the MCP is the better choice.

## Installation

Expand Down Expand Up @@ -68,8 +66,17 @@ linearis issues search "authentication bug"
# Create an issue
linearis issues create "Fix login flow" --team Platform --priority 2

# Add a comment
linearis comments create ENG-42 --body "Investigating this now"
# Start a discussion thread on an issue
linearis issues discuss ENG-42 --body "Investigating this now"

# List root discussion threads for an issue
linearis issues discussions ENG-42

# List replies in one root thread
linearis issues replies 6f4f28cd-4f53-4d76-ae95-80f1b6f6b87e

# Reply to a thread (use a root discussion thread ID)
linearis issues reply 6f4f28cd-4f53-4d76-ae95-80f1b6f6b87e --body "I found the root cause"
```

For the full reference of every command and flag, run:
Expand All @@ -78,6 +85,24 @@ For the full reference of every command and flag, run:
linearis <domain> usage
```

### Migration: `comments` → issue discussion commands

The `comments` domain remains available as a **deprecated compatibility facade**. For new automation and agent prompts, migrate to issue discussion commands in the `issues` domain:

| Deprecated | Preferred |
|---|---|
| `linearis comments create <issue> --body <text>` | `linearis issues discuss <issue> --body <text>` |
| `linearis comments list <issue>` | `linearis issues discussions <issue>` |
| `linearis comments reply <thread> --body <text>` | `linearis issues reply <thread> --body <text>` |
| `linearis comments edit <reply> --body <text>` | `linearis issues edit-reply <reply> --body <text>` |
| `linearis comments delete <reply>` | `linearis issues delete-reply <reply>` |

Notes:
- `issues discussions <issue>` lists **root** threads.
- Use `issues replies <thread>` to fetch replies in one thread, including nested replies.
- Replying requires a **root discussion thread ID** (not a reply ID).
- Compatibility `comments edit/delete` accepts root thread IDs and reply IDs.

## AI Agent Integration

### How agents use Linearis
Expand All @@ -95,7 +120,7 @@ This means the agent never loads the full API surface into context. It pays for
| | Linearis | Linear MCP |
|---|---|---|
| Context cost | ~500-700 tokens per interaction | ~13k tokens on connect |
| Coverage | Common operations (issues, comments, cycles, docs, files) | Full Linear API |
| Coverage | Common operations (issues, discussions, cycles, docs, files) | Full Linear API |
| Output | JSON via stdout | Tool call responses |
| Setup | `npm install -g linearis` + bash tool | MCP server connection |

Expand All @@ -116,9 +141,9 @@ Workflow rules:
- When creating a ticket, ask the user which project to assign it to if unclear.
- For subtasks, inherit the parent ticket's project by default.
- When a task in a ticket description changes status, update the description.
- For progress beyond simple checkbox changes, add a comment instead of editing the description.
- For progress beyond simple checkbox changes, start or reply in a discussion thread instead of editing the description.

File handling: `issues read` returns an `embeds` array with signed download URLs and expiration timestamps. Use `files download` to retrieve them. Use `files upload` to attach new files, then reference the returned URL in comments or descriptions.
File handling: `issues read` returns an `embeds` array with signed download URLs and expiration timestamps. Use `files download` to retrieve them. Use `files upload` to attach new files, then reference the returned URL in discussions or descriptions.
```

Add this (or a version adapted to your workflow) to your `AGENTS.md` or `CLAUDE.md` so every agent session has it in context automatically.
Expand Down
27 changes: 26 additions & 1 deletion commitlint.config.js
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
export default { extends: ["@commitlint/config-conventional"] };
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"body-leading-blank": [2, "always"],
"footer-leading-blank": [2, "always"],
"scope-case": [2, "always", "lower-case"],
"subject-min-length": [2, "always", 10],
"type-enum": [
2,
"always",
[
"build",
"chore",
"ci",
"docs",
"feat",
"fix",
"perf",
"refactor",
"revert",
"style",
"test",
],
],
},
};
62 changes: 62 additions & 0 deletions graphql/mutations/discussions.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
fragment DiscussionMutationCommentFields on Comment {
id
body
createdAt
editedAt
parentId
resolvedAt
resolvingComment {
id
}
resolvingUser {
id
displayName
}
user {
id
displayName
}
}

mutation StartDiscussion($input: CommentCreateInput!) {
commentCreate(input: $input) {
success
comment {
...DiscussionMutationCommentFields
}
}
}

mutation EditDiscussionReply($id: String!, $input: CommentUpdateInput!) {
commentUpdate(id: $id, input: $input) {
success
comment {
...DiscussionMutationCommentFields
}
}
}

mutation DeleteDiscussionReply($id: String!) {
commentDelete(id: $id) {
success
entityId
}
}

mutation ResolveDiscussion($id: String!, $resolvingCommentId: String) {
commentResolve(id: $id, resolvingCommentId: $resolvingCommentId) {
success
comment {
...DiscussionMutationCommentFields
}
}
}

mutation UnresolveDiscussion($id: String!) {
commentUnresolve(id: $id) {
success
comment {
...DiscussionMutationCommentFields
}
}
}
Loading
Loading