npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

loom-beads

v0.1.0

Published

Reliable bidirectional sync between Linear and Beads. Unified CLI for beads + sync.

Downloads

106

Readme

loom-beads

Reliable bidirectional sync between Linear and Beads.

Why

bd linear sync is the current sync path between Beads and Linear. It works for initial imports but breaks down in real-world multi-user workflows:

The state sync bug (beads #2046): bd linear sync --push does not update issue state in Linear. The IssueToTracker field mapper in internal/linear/fieldmapper.go sends title, description, and priority — but omits stateId. CreateIssue handles state correctly; UpdateIssue does not. This means:

  1. A PM marks a Linear issue back to "Todo" with a comment
  2. bd linear sync pulls the "Todo" state into beads correctly
  3. Push detects the change (hash mismatch) but sends no state field
  4. Linear sees nothing to update. State drifts.

Hierarchy doesn't sync (beads #1528): Parent-child relationships (epics, sub-tasks) are not maintained. Linear's API supports parentId but beads doesn't send it on push.

Polling-only: bd linear sync must be run manually or via cron. No real-time reaction to changes on either side. Changes can sit unseen for hours.

Fragile conflict resolution: Last-write-wins by timestamp. When a PM and a developer both update the same issue, the newer timestamp wins silently. No visibility into what was overwritten.

No PR linking: Beads has no concept of linked PRs. Linear's native GitHub integration handles this, but there's no path from beads → Linear → PR status.

These aren't edge cases. In active projects with 3-5 concurrent agents and a PM managing the board, sync drift is a daily problem that erodes trust in the whole workflow.

What loom-beads does

A lightweight sync service that sits between Linear and Beads, handling bidirectional state synchronization with proper conflict resolution.

Inbound: Linear → Beads (polling by default, webhooks optional)

  • Polls Linear API on a configurable interval (default: every 30 minutes) — works out of the box with just an API key
  • Optionally receives Linear webhook events for instant sync when webhook_secret is configured
  • lb sync MEE-123 syncs a single issue on demand
  • Maps Linear fields → bead fields using configurable state/priority/label mappings
  • Creates, updates, or archives beads accordingly
  • Preserves hierarchy (parent/child, epic/sub-task)
  • Writes to .beads/ directory, which beads picks up natively

Outbound: Beads → Linear (near real-time)

  • Watches .beads/ directory for changes (inotify on Linux, polling fallback)
  • Diffs against last-known Linear state (stored in local sync DB)
  • Pushes deltas to Linear API via GraphQL — including state, priority, labels, hierarchy
  • Debounced to avoid rapid-fire updates from agent activity

Conflict resolution

  • Tracks last-synced state for every field independently (not just a whole-issue timestamp)
  • Three-way merge: compares local, remote, and last-synced baseline
  • If only one side changed a field: that side wins (no conflict)
  • If both sides changed the same field: configurable strategy per field type
    • status → prefer Linear (PM is source of truth for status)
    • description → prefer local (agents are source of truth for implementation details)
    • Other fields → prefer Linear by default, configurable
  • All conflicts logged with full before/after context

PR visibility

loom-beads does not replace Linear's native GitHub integration for PR linking — that works well and should be enabled separately. What loom-beads adds:

  • When a bead's PR number is known (set by agents during implementation), sync it to a custom field or comment on the Linear issue
  • When Linear's GitHub integration updates PR status, that's visible on the Linear issue without loom-beads involvement

Architecture

┌────────────┐    polling / webhook      ┌─────────────┐
│   Linear   │ ──────────────────────►  │ loom-beads  │
│            │ ◄──────────────────────  │   service   │
└────────────┘       GraphQL API        └──────┬──────┘
                                               │
                                    ┌──────────┼──────────┐
                                    │          │          │
                                    ▼          ▼          ▼
                              ┌─────────┐ ┌────────┐ ┌────────┐
                              │ .beads/ │ │ sync   │ │ config │
                              │ files   │ │ state  │ │        │
                              └─────────┘ │ (SQLite│ └────────┘
                                          └────────┘

Single process. Runs the HTTP server (with polling and optional webhook receiver) and the file watcher in the same Node.js process. Sync state stored in SQLite (separate from beads' own database).

Sync state model

For each synced issue, loom-beads tracks:

sync_state (
  bead_id         TEXT,
  linear_id       TEXT,
  field           TEXT,       -- 'status' | 'title' | 'description' | 'priority' | 'parent' | ...
  last_synced     TEXT,       -- last known value at sync time (the baseline)
  synced_at       DATETIME,
  direction       TEXT        -- which side last wrote this field
)

This per-field baseline is what enables three-way merge. Without it, you're stuck with whole-issue timestamps and silent data loss.

Getting started

Install loom-beads globally and run the setup wizard:

npm install -g loom-beads
lb init

lb init bootstraps everything in one step: initializes beads if .beads/ doesn't exist, creates .loom-beads/config.yaml, sets environment variables, and installs the git hook.

lb is the unified CLI for both beads and loom-beads. All beads commands work through lb, and Linear slugs (e.g. MEE-123) are resolved to bead IDs automatically.

# Sync commands
lb start               # start server with live sync and activity feed
lb start --verbose     # start with detailed field-level change logging
lb health              # check server health
lb status MEE-123      # sync state for an issue
lb create              # create paired bead + Linear ticket
lb sync                # trigger sync manually
lb sync MEE-123        # sync a single issue on demand
lb import              # import existing beads with external_ref into sync

# Beads commands (forwarded to bd)
lb list                # list beads
lb ready               # show ready work
lb show MEE-123        # show bead details (slug resolved automatically)
lb close MEE-123       # close a bead (slug resolved automatically)

lb start is the primary command — it starts the server, runs a startup reconcile (catching up on changes made while offline), watches .beads/ for local changes, and prints a color-coded activity feed to the terminal showing all sync activity as it happens. Use --verbose (-v) for field-level detail on every change.

For LLM agents, the wizard generates AGENT.md with workflow instructions. Add @AGENT.md to your CLAUDE.md.

See docs/setup.md for the full setup guide and manual setup instructions.

Configuration

# .loom-beads/config.yaml
linear:
  api_key: ${LINEAR_API_KEY}
  team_id: "abc-123"
  # webhook_secret: ${LINEAR_WEBHOOK_SECRET}  # optional — enable for instant webhook sync

beads:
  project_dir: "/path/to/project"
  bd_path: "bd"                     # optional, defaults to "bd"

sync:
  poll_interval_ms: 1800000         # 30 min default; set to 0 to disable polling
  debounce_ms: 3000
  conflict_strategy:
    status: prefer_linear
    priority: prefer_linear
    description: prefer_local
    title: prefer_local
    labels: prefer_linear
    default: prefer_linear

server:
  port: 3848
  host: "0.0.0.0"

state_map:
  backlog: open
  triage: open
  unstarted: open
  started: in_progress
  completed: closed
  canceled: closed
  duplicate: closed

API

GET  /health                    -- service health and uptime
POST /webhook/linear            -- Linear webhook receiver (HMAC verified, optional)
POST /hook/commit               -- git post-commit trigger for outbound sync

GET  /status/:slug              -- sync state for a Linear issue (e.g. MEE-123)
GET  /lookup/:slug              -- lookup sync state by Linear slug
GET  /lookup/bead/:beadId       -- lookup sync state by bead ID
GET  /log                       -- recent sync operations (?limit=N&slug=X)
GET  /conflicts                 -- unresolved conflicts with context
POST /conflicts/:id/resolve     -- manually resolve a conflict

POST /sync/trigger              -- trigger full sync (inbound + outbound)
POST /sync/trigger/inbound      -- trigger inbound sync only
POST /sync/trigger/inbound/:slug -- sync a single issue (e.g. MEE-123)
POST /sync/trigger/outbound     -- trigger outbound sync only

Relationship to Loom

loom-beads is a standalone service. It does not depend on Loom and can be used independently in any project that uses Beads + Linear.

Loom consumes loom-beads' API to:

  • Check sync health before launching threads
  • Trigger manual sync after agent completion
  • Surface sync conflicts in the Loom UI

Current status

v0.1.0 — all core sync infrastructure is implemented and tested:

  • Inbound sync (Linear → Beads) via polling (default) or webhooks, with conflict detection
  • Outbound sync (Beads → Linear) via filesystem watcher and git post-commit hooks
  • Startup reconcile: catches up on changes from both sides when the server starts
  • Live activity feed: color-coded terminal output showing all sync activity in real time
  • Per-field conflict resolution with configurable strategies (prefer_linear, prefer_local, most_recent, manual)
  • Three-way merge for multi-line fields using node-diff3
  • Echo guard to prevent sync loops
  • Webhook idempotency with automatic delivery cleanup
  • Retry logic with exponential backoff for Linear API calls
  • Graceful shutdown with SIGTERM/SIGINT handling
  • Interactive setup wizard via lb init — bootstraps beads and loom-beads in one step
  • lb unified CLI — handles sync commands directly, forwards all other commands to bd
  • Linear slug resolution — lb show MEE-123 resolves to the corresponding bead ID
  • Agent instruction template (AGENT.md) generated by the init wizard

See docs/roadmap.md for planned features and future work.