Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
fab55a0
refactor(dashboard): define source-neutral project metadata
Railly Sep 27, 2026
188516e
fix(dashboard): capture repository-wide open totals
Railly Sep 27, 2026
5965292
feat(dashboard): unify graph views and clarify swarm legends
Railly Sep 27, 2026
5b55ee1
refactor(graph): support provider-neutral bounded traversal
Railly Sep 27, 2026
cbc80fd
feat(linear): add bounded read-only issue and project capture
Railly Sep 27, 2026
2561042
feat(linear): map projects and themes into the shared dashboard
Railly Sep 27, 2026
3fa6524
feat(cli): expose Linear commands and document read-only workflows
Railly Sep 27, 2026
e88c76a
test(dashboard): verify independent totals in the installed CLI
Railly Sep 27, 2026
8f5fe50
Merge branch 'railly/dashboard-polish' into railly/linear-integration
Railly Sep 27, 2026
77d53c7
fix(dashboard): align sidebar markers and PR hover states
Railly Sep 27, 2026
ac7bb27
test(package): remove duplicate open-count assertion
Railly Sep 27, 2026
637d2f4
Merge branch 'railly/dashboard-polish' into railly/linear-integration
Railly Sep 27, 2026
45127ef
Merge remote-tracking branch 'labs/main' into railly/linear-integration
Railly Oct 17, 2026
b650f42
fix(linear): fetch transport, Linear rate-limit headers, partial coun…
Railly Oct 17, 2026
ac3d872
fix(linear): label single-issue components as unconnected
Railly Oct 17, 2026
94c84a3
feat(linear): connect through Linear's MCP server with linear login
Railly Oct 17, 2026
d5f670b
fix(linear): an empty LINEAR_API_KEY no longer shadows LINEAR_ACCESS_…
Railly Oct 19, 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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ This README follows repository `main`, which can be ahead of the published packa
| --- | --- |
| Capture a backlog and open its dashboard | `issue-graph open owner/repo` |
| Inspect an issue or PR before starting work | `issue-graph graph vercel-labs/agent-browser#1113 --depth 1 --budget 12 --no-save` |
| Inspect explicit Linear relationships | `issue-graph linear ENG-123 --workspace your-workspace --depth 1 --json` |
| Survey labeled open issues | `issue-graph rank owner/repo --label bug` |
| Filter a saved model and open its exact dashboard view | `issue-graph query github:owner/repo --state open --view rank --open` |
| Count open PRs by author | `issue-graph status vercel-labs/portless --author ctate,Railly` |
Expand Down Expand Up @@ -92,6 +93,26 @@ issue-graph status vercel-labs/portless --author ctate,Railly --since last --sav

Status reports unknown counts as `?` or `null`. Check coverage before using totals, and review CI and unresolved review threads separately before merging.

## Linear

Connect once with `issue-graph linear login`, which approves read-only access in the browser through Linear's MCP server, with no API key or admin needed. `LINEAR_API_KEY` with Read access, or `LINEAR_ACCESS_TOKEN`, also works and takes precedence.

```bash
issue-graph linear https://linear.app/your-workspace/issue/ENG-123/example --depth 1 --max-nodes 20 --json
```

The command reads explicit relations in both directions, parents, sub-issues, and attachment URLs. It preserves native status and relation direction, checks the connected workspace, and paginates each connection. Archived issues are included; archived relations and attachments are excluded. It reports attachment targets without fetching them and does not collect text mentions.

`--json` prints the report to stdout. Add `--html linear.html --open` to visualize the graph and save the latest project run for `issue-graph dashboard --open`. Each Linear project has its own entry, labeled `Workspace / Project`, and includes only that project's issues. Use `--no-save` to skip saving the dashboard entry. Plain Markdown/JSON runs do not write files, and no remote mutations are sent.

Selector counts cover all open items in the project, independently of graph limits. Linear counts non-archived issues in triage, backlog, unstarted, or started states. GitHub counts open issues plus pull requests. Counts include their capture time; `≥` marks an incomplete count and `—` means unavailable. Saved dashboards are offline snapshots; rerun the graph command to refresh them.

Linear defaults to connected groups. Use `--cluster` to print a thematic grouping task for the calling agent, then `--clusters themes.json --html linear.html` to apply its reviewed assignments. The dashboard marks them as proposed themes, validates project membership, and preserves native relationships and open totals.

Use `linear --project <project-uuid> --max-nodes 1000 --max-pages 100 --html linear.html --open` to capture a whole non-archived project, including disconnected issues and each issue's connections. Rerun it to refresh the saved dashboard. Successful reads are folded under Snapshot details; failed or truncated reads retain visible warnings. Linear allows an API key 2,500 requests and 3,000,000 complexity points per hour, shared by all of a user's keys. A full project capture can spend a large part of that: each issue takes at least one request, and every extra page of a connection takes another. When the limit is hit, the command stops and prints when the window resets instead of retrying.

The dashboard shows native Linear states, directional relationships, and coverage alongside saved GitHub graphs. Exit `0` means complete within the declared depth and sources; `1` means partial coverage or a read failure; `2` means invalid usage. Inspect per-node coverage, node caps, hubs, and depth boundaries before relying on the result. See [Linear](apps/docs/content/docs/linear.mdx).

## Query a saved dashboard

Save a dashboard model with `issue-graph open owner/repo --no-open`, then use its filters and scoring from the CLI without another provider request:
Expand Down
132 changes: 132 additions & 0 deletions apps/docs/content/docs/linear.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
title: Linear
description: Read explicit issue relationships with workspace-scoped identity and visible coverage limits.
---

Start with one Linear issue to see its blockers, related work, parent and sub-issues, and attachment links.

## Connect Linear

Connect once in the browser. No API key or workspace admin is needed:

```bash
issue-graph linear login
```

It approves read-only access through Linear's MCP server (OAuth with PKCE) and saves the session in the macOS Keychain, or a `0600` file under `~/.issue-graph/credentials` elsewhere. `issue-graph linear status` and `issue-graph auth status` show what is connected; `issue-graph linear logout` removes the session.

If your workspace allows personal API keys, `LINEAR_API_KEY` with Read access works too, as does `LINEAR_ACCESS_TOKEN` from an OAuth app. Environment keys take precedence over the session. Both paths send read-only requests and produce the same report, checked the same way; the report records which source it used.

```bash
issue-graph linear https://linear.app/your-workspace/issue/ENG-123/example --depth 1 --max-nodes 20
```

An identifier or native UUID also works. Use `--workspace` to require a particular workspace:

```bash
issue-graph linear ENG-123 --workspace your-workspace --json
```

The URL's workspace and `--workspace` must agree with the authenticated organization. Identities include the organization ID and native issue ID, so identifiers in different workspaces cannot collide.

## What the graph includes

- Explicit issue relations in both directions, retaining their native type and direction.
- Parent and sub-issue relationships.
- Attachment URLs and titles, including linked GitHub pull requests.
- Native workflow status, archive state, and update time.

Attachment targets are listed without fetching them. Text mentions in descriptions and comments are outside this command's scope. A linked pull request is not evidence that an issue is resolved.

The output is Markdown by default. `--json` emits the versioned report on stdout. These runs do not write local files.

## Dashboard

Generate an HTML explorer and save the latest run for this issue's project:

```bash
issue-graph linear ENG-123 --workspace your-workspace --html linear.html --open
issue-graph dashboard --open
```

For a project dashboard, capture the project's full non-archived issue list and read every selected issue's connections:

```bash
issue-graph linear --project <project-uuid> --workspace your-workspace --max-nodes 1000 --max-pages 100 --html linear.html --open
```

Project mode includes disconnected issues and empty projects. It paginates the inventory, then reads both directions of relations, parents, children, and attachments with bounded concurrency. Connections outside the project remain visible without fetching those issues. Depth and hub expansion limits apply only to issue neighborhoods. `--max-pages` also bounds the project inventory; reaching either limit leaves the capture visibly incomplete. Rerun the command to refresh a saved capture.

Each dashboard entry shows one project. In an issue neighborhood, related issues from other projects are counted in the coverage notice and kept in `--json`, but not drawn; their edges stay on the issues that point to them.

The dashboard keeps successful reads under **Snapshot details** and **Connection details**. Empty connections are valid results. Scope exclusions, such as archived issues or text mentions, do not flag individual issues. Failed, truncated, or inconsistent reads retain warnings.

The dashboard switches between saved Linear projects and GitHub repositories. Each Linear entry is labeled `Workspace / Project` and keyed by workspace and project UUID. Issues from other projects are excluded from that view; boundary relationships remain visible. Issues without a project are scoped to their team. Older captures without project metadata are labeled `Unscoped capture`.

Rate limits: Linear allows an API key 2,500 requests and 3,000,000 complexity points per hour, shared by all of a user's keys. A full project capture can spend a large part of that: each issue takes at least one request, and every extra page of a connection takes another. When the limit is hit, the command stops and prints when the window resets instead of retrying.

The selector counts all non-archived open issues in the project, independent of graph depth and node caps. Open means triage, backlog, unstarted, or started. A separate query reads up to 100 pages of 250 IDs across all teams in the project. `≥` marks a partial count; `—` means unavailable, including captures without a project. The tooltip records when the count was read. Saved dashboards do not refresh automatically. GitHub entries count open issues plus pull requests, with the breakdown in the tooltip.

Linear shows issue identifiers, native workflow states, archived issues, relationship direction, attachment links, and read coverage. Explore and Swarm support relationships and depth; GitHub-only impact and discussion ranking are not shown for Linear.

`--open` creates a temporary HTML file when `--html` is omitted. HTML runs save the latest project model under `~/.issue-graph/dashboard`. Add `--no-save` to write or open the HTML without saving a dashboard entry. `--json` can be combined with HTML output; file notices go to stderr.

### Thematic clusters

By default, groups are connected components. To group by topic instead, print a task for the calling agent, then supply its reviewed JSON:

```bash
issue-graph linear ENG-123 --cluster
issue-graph linear ENG-123 --clusters themes.json --html linear.html --open
```

The task includes native issue descriptions and explicit relationships from this project's capture. It does not invoke another model. `--cluster` cannot be combined with `--json`. The file uses the same cluster shape as GitHub:

```json
{"clusters":[{"label":"Keyboard input","root_cause":"Proposed theme: negotiated input handling","members":[{"key":"<exact captured node key>"}]}]}
```

For Linear, `root_cause` is a grouping rationale, not a verified causal finding. Explore and Swarm label these as proposed themes. Foreign-project keys, duplicate assignments, malformed input, verdicts, and cleanup actions are rejected. Issues without an assignment remain visible under Ungrouped. Clustering changes only the local grouping; issue states, relationships and project open totals stay intact.

Partial runs still generate HTML and exit `1`. Inspect the coverage notice and each issue's read details. The dashboard is a captured view, not a live connection, and opening it sends no mutations to Linear.

## Limits and coverage

| Option | Default | Range |
| --- | --- | --- |
| `--depth` | 1 | 0–8 |
| `--max-nodes` | 80 | 1–1000 |
| `--max-pages` | 5 | 1–100 per connection per issue |
| `--hub-threshold` | 12 | 1–1000 |
| `--concurrency` | 4 | 1–32 |

Each connection has its own cursor and pages contain up to 50 entries. Archived issues are included; archived relations and attachments are excluded. Non-seed hubs are fetched but not expanded.

`coverageComplete` describes the requested depth and relationship sources. It is false when a node cannot be read, a connection is incomplete, an issue changes while its pages are read, or a node cap or hub stops traversal. Depth boundaries are listed separately. API reads across pages are not an atomic snapshot.

Exit codes are `0` for complete coverage within scope, `1` for partial coverage or a runtime failure, and `2` for invalid arguments. JSON includes per-node connection coverage and the known nodes omitted by the node cap.

## Library

The reader is a separate entry point, keeping transport details out of the graph contract:

```ts
import { buildLinearGraph } from "issue-graph";
import { linearReader } from "issue-graph/transport/linear";

const reader = linearReader({ apiKey: process.env.LINEAR_API_KEY });
const report = await buildLinearGraph(reader, "ENG-123", {
workspace: "your-workspace",
maxDepth: 1,
maxNodes: 20,
maxPages: 3,
hubThreshold: 12,
concurrency: 4,
});
```

Use `buildLinearProject(reader, projectUuid, options)` for a project capture. Pass `linearDashboardModel(report)` to `renderDashboard()` to combine Linear and GitHub models in one HTML page. These functions are exported from `issue-graph`.

An authenticated Linear MCP session is separate from these credentials. MCP captures should record their actual source and supported relationship categories in the report scope. A returned empty array is a successful read; an unrequested or unexposed category belongs in the scope notes. Never mark a failed requested read as a scope exclusion.

The reader accepts a per-request timeout and bounded retry options. Rate limits use Linear's reset headers: a reset within the retry budget is awaited once, a later one fails with its reset time. Credentials and raw upstream error messages are not included in the report.
1 change: 1 addition & 0 deletions apps/docs/content/docs/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"get-started",
"graph",
"dashboard",
"linear",
"status",
"backlog",
"agents",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/src/lib/docs-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const docsSlugs = [
"get-started",
"graph",
"dashboard",
"linear",
"status",
"backlog",
"agents",
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"./transport/http": {
"types": "./dist/transports/http.d.ts",
"import": "./dist/transports/http.js"
},
"./transport/linear": {
"types": "./dist/transports/linear.d.ts",
"import": "./dist/transports/linear.js"
}
},
"main": "./dist/index.js",
Expand Down
Loading
Loading