Rectilinear
Local-first Linear issue intelligence. Maintains a search-optimized SQLite mirror of your Linear issues with hybrid full-text + vector search. Find duplicates before filing, search across teams instantly, and manage issues from the terminal or through Claude Code via MCP.
Why
Linear teams accumulate hundreds of issues. Duplicate detection is hard, search is scattered, and context lives across many views. Rectilinear keeps a local copy with embeddings so you can:
- Find duplicates before creating new issues — semantic similarity, not just keyword matching
- Search fast — hybrid FTS5 + vector search with Reciprocal Rank Fusion, all local
- Manage issues from the CLI or let Claude Code do it through MCP tools
Linear remains the source of truth. The local database is a read-optimized cache. Writes go to Linear first, then sync back.
Architecture
┌─────────────────────────────────────────────────┐
│ rectilinear │
│ │
│ CLI (clap) MCP Server (rmcp) │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ sync │ │ search_issues │ │
│ │ search │ │ find_duplicates │ │
│ │ find │ │ get_issue │ │
│ │ show │ │ create_issue │ │
│ │ create │ │ update_issue │ │
│ │ append │ │ append_to_issue │ │
│ │ embed │ │ sync_team │ │
│ │ config │ │ issue_context │ │
│ └────┬─────┘ └────────┬─────────┘ │
│ │ │ │
│ ┌────┴───────────────────────────┴──────────┐ │
│ │ Core Engine │ │
│ │ Search (FTS5 + Vector + RRF) │ │
│ │ Embedding (Gemini API / local GGUF) │ │
│ │ Linear GraphQL Client │ │
│ │ SQLite (WAL, FTS5, blob embeddings) │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
│ │
▼ ▼
Linear API Gemini API
(source of truth) (embeddings)
| Component | Choice |
|---|---|
| Language | Rust — fast startup, single binary |
| CLI | clap (derive) |
| Database | rusqlite (bundled, FTS5) |
| Vector storage | f32 blobs + cosine similarity in Rust |
| Embeddings | Gemini API (768-dim), or local GGUF with local-embeddings feature (EmbeddingGemma, 256-dim) |
| Linear API | reqwest + GraphQL |
| MCP server | rmcp, stdio transport |
| Config | TOML at ~/.config/rectilinear/config.toml |
Install
From crates.io
To include the local GGUF embedding backend (EmbeddingGemma-300M, requires cmake):
From source
&&
Prerequisites
- A Linear API key (https://linear.app/settings/api)
- Optional: a Gemini API key for embeddings (https://aistudio.google.com/apikey)
Configure
# Required: Linear API key
# Recommended: set a default team so you don't have to pass --team every time
# Optional: Gemini API key for semantic search / embeddings
Environment variables LINEAR_API_KEY and GEMINI_API_KEY also work and override config values.
View your config:
Sync issues
# First sync (automatically does a full sync)
# Sync and generate embeddings in one step
# Force full re-sync
# Include archived issues
Generate embeddings
Embeddings power vector search and duplicate detection. Uses the Gemini API if GEMINI_API_KEY is set. If you installed with --features local-embeddings, it can also use a local GGUF model (EmbeddingGemma-300M, auto-downloaded on first use) as a fallback.
# Embed issues that don't have embeddings yet
# Regenerate all embeddings (e.g. after changing backend)
Usage
Search
# Hybrid search (FTS + vector, default)
# FTS-only (no embeddings needed)
# Filter by team and state
# JSON output for scripting
Find duplicates
# Check if an issue already exists before filing
# Lower the threshold to cast a wider net
View issues
Create and update issues
# Create an issue (writes to Linear, syncs back locally)
# Add a comment
# Append to description
MCP server (Claude Code integration)
Start the MCP server for use with Claude Code:
Add to your Claude Code MCP config (~/.claude/claude_desktop_config.json or project .mcp.json):
This exposes 10 tools to Claude Code:
| Tool | Purpose |
|---|---|
search_issues |
Hybrid search with team/state filters |
find_duplicates |
Semantic duplicate detection given title + description |
get_issue |
Full issue details with optional comments |
create_issue |
Create in Linear + sync back |
update_issue |
Update title, description, priority, state, labels, project |
append_to_issue |
Add comment or extend description |
sync_team |
Trigger sync for a team |
issue_context |
Issue + its N most similar issues |
get_triage_queue |
Batch of unprioritized issues enriched with similar issues and code search hints |
mark_triaged |
Set priority, state, labels, project + update title/description + add comment in one call |
Triage workflow
The MCP server includes built-in instructions that teach Claude Code how to triage issues conversationally. Setup:
# 1. Sync and embed your team's issues (needed once, then incremental)
# 2. Add rectilinear to your Claude Code MCP config (see above)
# 3. In Claude Code, just say:
# "triage CUT issues"
# "let's triage some random CUT issues" (uses shuffle for variety)
What happens: Claude calls get_triage_queue, which syncs from Linear to get fresh data. For each issue, Claude:
- Explores the codebase using extracted
code_search_hints(file paths, identifiers, labels) to understand the current implementation - Presents the issue with code findings and similar issues, then asks clarifying questions from the perspective of a staff engineer who would implement it
- Proposes priority, improved title/description (with code references), state changes, labels, and project assignment
- After you confirm, calls
mark_triagedto apply all changes to Linear in one call
Issues are presented one at a time — Claude waits for your input and applies changes before moving to the next.
Staleness protection: mark_triaged re-fetches the issue from Linear before applying changes. If someone else already prioritized it, Claude skips it. If the content changed since the queue was fetched, Claude shows what changed and re-evaluates. Embeddings are automatically updated when content changes.
Best results: Run triage from within your project directory so Claude can explore the actual codebase. If you use Cuttlefish, its MCP tools (get_symbols, find_references) give Claude even richer code context.
You can also add project-specific guidance in your CLAUDE.md:
When triaging Linear issues, present and resolve one issue at a time
before moving to the next. Explore the codebase to understand each
issue's context before asking questions.
Data storage
| Path | Contents |
|---|---|
~/.config/rectilinear/config.toml |
API keys, defaults, preferences |
~/.local/share/rectilinear/rectilinear.db |
SQLite database (issues, FTS index, embeddings) |
~/.local/share/rectilinear/models/ |
Local GGUF models (auto-downloaded) |
Search modes
FTS — BM25 keyword search via SQLite FTS5 with Porter stemming. Fast, no embeddings needed.
Vector — Embeds the query via Gemini API, computes cosine similarity against stored issue chunks, returns max similarity per issue.
Hybrid (default) — Runs both FTS and vector search, combines results with Reciprocal Rank Fusion (score = Σ 1/(k + rank)). For duplicate detection, vector results are weighted 0.7 vs FTS 0.3.