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

beadcyte

v0.9.0

Published

Gantt of bead history + projected future — historical-median estimator + greedy WIP-cap scheduler + self-contained SVG

Readme

beadcyte

npm

A local web-based history and projected future, for any beads project.

Pairing agents with beads has changed how our team works: far more is in flight than before. However, velocity in one place exposes bottlenecks in another. If production of ideas becomes cheap, managing the flow of ideas becomes critical. This tool is for teams using beads to coordinate with agents over long-horizon tasks. The feature tour below walks through each view.

beadcyte reads your beads DB with bd list --all --json and draws two things that look alike and are not: what happened, which is measured, and what it expects to happen, which is a model's opinion. Three models drive the projection: a historical-median duration estimator, a greedy WIP-cap scheduler calibrated against your real throughput, and an eight-signal triage score. Each is shown with its own uncertainty rather than as a flat number.

beadcyte is made by Incyte Studios.


Quick start

cd /path/to/your/beads/project
npx beadcyte start

That opens an interactive app on 127.0.0.1:4173 and gives you the terminal back; npx beadcyte stop ends it. Localhost only, no auth, no build step — same threat model as bv. To open it from a phone on your own LAN, start it with --host 0.0.0.0 (and --readonly unless you mean to let the LAN edit your tracker); see Flags.

The first start in a project offers to add a short block to your agents' instruction files (CLAUDE.md, AGENTS.md) saying what to record in beads so beadcyte has something to draw; beadcyte setup does the same on demand, and beadcyte prime prints the full guidance. See Flags.

Or install it once; bcyte is a short alias for the same command:

npm i -g beadcyte
beadcyte start

For a single self-contained SVG, with no server:

beadcyte --out gantt.svg

The SVG has a system font stack and no external references, so it embeds anywhere: MR comments, wiki pages, dashboards.


The tour

Mine — what should I be doing, what is going on

The mine view

The first tab, and the one the app opens on. Everything on it is about one person: what is ready for you (assigned to you, approved with spec:ready, not already moving, every blocker closed), what you have in progress and in review, which of your beads are waiting on someone else and who that is, whose beads are waiting on you, and what you are watching. Unassigned beads the scheduler would route to you appear as suggestions, marked as inferred.

Who "you" are is set once, in the options menu, from the assignees present in the data, and remembered by the browser. A shared link carrying ?me= shows that person's view for one load without changing your own choice. Until a user is chosen, the view asks.

The same two questions are two clicks away on the Gantt and grid too: the MINE buttons in the header's Filters chip set the filters to my ready work or my work in review outright, so the result never depends on what was selected before, and clicking the active one clears back to the defaults. The unblocked term they rely on is an ordinary filter as well, and beside it sits assigned only: off by default, and when on the assignee facet matches the assignee recorded on the bead alone, so the beads the scheduler routed to a person by affinity — drawn hatched and marked inferred elsewhere — leave the Gantt, grid and table instead of standing in their lane as if committed. What is on someone's plate is a fact from bd; what the plan would hand them is a projection, and the term keeps a lead from reading one as the other.

Gantt — history and projection on one timeline

The Gantt view

Past bars are real started_at → closed_at. Future bars are the scheduler's projection, and their length is a bucketed median of how long comparable work has actually taken. A dashed red rule marks today, group headers carry a progress chip, and dependency arrows connect blocks edges between visible bars.

Bar styling distinguishes the cases that matter: shipped work carries a ship marker, closed-without-shipping is dim, backlog beads that are not spec:ready are faint, and a bar whose assignee the scheduler guessed by affinity is hatched — with the lane header counting how many of its beads are guesses — rather than presented as a decision. The dim bars in the screenshot are this repo's beads closed without ship evidence: nothing says they shipped, so nothing draws them as if they had, and the footer counts them apart as closedNoShip.

The footer under the chart counts bars by kind, and each name explains itself on hover: shipped and closedNoShip are history; inProgressLive is the spent part of work under way; future, futureBacklog (not spec:ready, scheduled after the ready work) and unassigned (from the pool nobody owns) are the projection. A projected bar is the work, to scale. The waiting around it — unclaimed time before an affinity pick, review time before the landing — is drawn dull, hatched and cut short at the date it ends, with a dotted connector for what was cut: its length is not to scale, its end is. The tooltip gives both numbers.

Status is one control, not two: the filter starts with every status except closed and deferred selected, and the header's Filters chip counts that selection as a term (Filters 1) rather than leaving you to infer a default from a chip that says nothing.

Hovering a bar gives the bead's detail and a projected finish date — not a day offset — including whether that date is a projection or just the end of the scheduling window.

A window that would squeeze below six pixels a day scrolls instead: the plot shows a readable span centred on today, with a scrollbar under it. Drag the axis to pan, wheel over the axis to zoom, swipe or shift-wheel over the plot to pan, and t brings today back to the centre. A window that fits is drawn whole, as before.

Grid — browse without the time axis

The grid view

The same filtered set as cards: title, chips, assignee, labels. Better than the Gantt for reading titles and worse for anything about time, which is why both exist.

Table — every derived number in one sortable place

The table view

The same filtered set as dense rows, one per bead, with everything the app knows in columns: type, priority, status, assignee (marked when the scheduler inferred it), labels, comments, estimate, projected finish, triage score, group. Every column sorts; the default is triage score, highest first, because the table's job is "what next". Unlike the Gantt it never drops rows to keep a group short, so it is also the readable equivalent of the chart — nothing in the app is reachable only by reading a bar.

Incytes — the project, not the beads

The incytes view

Ships per week over the velocity window — the project's total, and under it one line per person in that person's own colour, so whose rate moved is visible without picking a handle; the legend carries each person's window total, becomes the hovered week's counts while the plot is hovered, and a handle on it opens that person. A ship on a bead nobody ever claimed counts for the builder its ship record names, marked ~N in the legend as credited rather than assigned — see docs/economics.md. Then per-group completion sorted by size so the outlier is findable (grouped by epic, each row is the epic's id and title and opens it in the drawer), and Allocation: everyone's work in flight against their calibrated cap on one shared scale, with the run past the cap drawn at its real length. That block used to sit under the controls on every view; it is a project-wide fact, so it lives here.

Under the ship trend, Cost per week: seat hours stacked by seat, with tokens in and out in the figures and the table, in the same weekly buckets. Weeks behind today are what closed beads recorded; weeks ahead are projected from the median cost of comparable shipped work, placed where the schedule lands each bead, and drawn dulled and dashed so they cannot be read as measured. The same block appears on a person's pane for their beads. See docs/economics.md for exactly how.

This screenshot is beadcyte's own repo, and the spike is the honest shape of a tracker one week old. Every ship lands in the one week the beads have existed, and the weeks before it are structurally zero — the repo did not exist. (The earliest ships were not recorded as they happened either; their evidence was read back out of Closes <id> commit trailers afterwards, which is why the cost block says how many of its records are reconstructed.)

Which is why the headline says "first week of history — no baseline yet" rather than +700%. With one shipping week the mean is that week divided by the window length, so a percentage against it is decided by the window and not by the work: identical beads report +700% at ±90d and +300% at ±30d. A number that moves when you change the axis is an artefact with a percent sign on it, so the chart declines to print one.

If your own trend looks empty rather than spiky, nothing is broken and nothing was recorded — see what to record.

Pick a person — the chips at the top of the view, a swimlane header when grouped by assignee, a handle on a grid card or in the Allocation block — and the same two charts re-scope to them: their ships per week in the project's weeks, and the progress of the beads recorded as theirs. Below that, their roster line (the raw cap, the calibrated cap and how it was derived, or a plain statement that no roster was written and the cap is a default), what is in flight, what is ready for them, what the scheduler would route to them next — marked as a projection, because nobody decided it — and their recent ships. ?who= carries the selection in the link.

Two more blocks on that pane are the plan's edge for the person. Frontier is a ruler of the coming days — the window's forward span, 7 to 60 — with a hollow mark where a bead starts for them and a filled one where it lands, read off the same schedule the Gantt draws; dashed marks are assignments the scheduler inferred. Proposed next ranks what they could take, with the reasons stated beside each: the scheduler's own routing, roster affinity, triage score, what it unblocks, priority, whether they have room today — and, where someone else is over cap, that person's not-yet-started beads offered across. Stage puts a proposal into the what-if (below), where the diff shows what moves; nothing is written until it is confirmed.

Plan — loading up a team

The plan view

Everyone's room side by side, for the person handing out work. A scope strip across the top carries every roster human with their load against cap — bob 3/1, with over-cap in red and slack in green — so who is on the team and who is in trouble reads at a glance, without scrolling anywhere. Click a name and the board draws the pool and that person alone; click everyone for the full board. The selection is ?who=, the same one the incytes person pane uses, so a focused board is a link you can send.

Below it, one column per roster human: their in-flight load against their calibrated cap, how much room that leaves today, what is ready for them, what else of theirs has not started — and then Proposed, the same ranking the person pane offers, with the reasons stated under each row. A Pool column on the left holds every unassigned bead that is workable and unblocked, each carrying a chip for the people whose roster affinities match it (a dashed chip is the scheduler's own routing, which is a projection) and a to… picker for everyone else. Sweeping the pool is the quick way to load up a week.

Where someone is over cap, their not-yet-started beads get a Could move list that offers the people who have room. The planner proposes reassignments across people; it never makes one.

Because every affordance on the board stages a what-if rather than writing anything. Click a chip or stage and the board re-renders from the hypothetical plan — the bead leaves the pool and appears in its new column immediately — while the panel shows what moves, by how much, and each person's frontier before and after. Stage many, confirm once; What if is where it gets written, and nothing before that touches bd.

Unlike incytes, this view reads the filters, so a lead can plan one epic or one label at a time — all but assigned only, which hides inferred assignments when the pool is that inference; the board exists to show the routing, so that one term does not reach it and the chip says so here. Caps, in-flight and room keep counting every bead whatever the filters say — they are facts about the person, not about the filter — and the header says how many of your beads are listed.

Affinities are the fuel, and beadcyte says so when it has none. This screenshot is beadcyte's own repo, whose .beadcyte/roster.json names one person and no affinities, so the pool's chips carry only the scheduler's own routing and the ranking falls back to that, triage score, what a bead unblocks and priority — the reasons are stated under the proposed row. A repo with no roster file at all gets a notice at the top of the board saying the roster was derived from bead assignees and the caps are defaults, rather than a thin ranking presented as a considered one. Write affinities (docs/roster.md) and the chips fill in.

The drawer — why a bead ranks where it does

The bead drawer

Triage's eight signals as contribution meters. The track length is the signal's declared weight, so a signal that could never matter much is visibly short. The fill is what this bead actually contributed. A tick marks what a high value looks like on your repo, and a cut-off track marks weight the signal cannot reach at all.

Signals that cannot separate anything on your data are labelled FLAT — one of them carries the second-largest weight and is frequently non-zero on exactly one bead, because shallow dependency graphs give it nothing to measure. An authoritative-looking number that means nothing is worse than no number, so the drawer says which is which. Below that: the dependency subgraph — for an epic, the graph of all its children and the blocks edges between them, since an epic's own neighbourhood is just the epic, with a full-screen view for the large ones — the dossier with its dates (projected while the bead is open; the ship evidence once it has closed), where the bead's time went, and the bead's comments (bd comments <id>, read only for a bead that has some), with the comment badge in the header as the jump to them and a box under them that posts a new one (bd comments add; Ctrl+Enter or ⌘+Enter).

That last one distinguishes what was measured from what beadcyte computed. The queue wait is started_at − created_at, arithmetic on fields bd maintains for free, so it is derived wherever nobody recorded it — hatched, and its legend row tagged DERIVED, because a reconstructed number that looks measured is one you can never separate out again. Worked hours are never derived: the estimator prefers them over wall-clock, so an invented figure would override the one real signal rather than diluting it.

Every derived figure in the drawer explains itself. A dotted underline marks a term that will: the score, each of the eight signal names, the estimate's source ((feature), n=8 — n is the sample count behind the median, and a small one deserves less trust), the projected finish and why it sometimes says finishes after, the phase names, the tense of the dependency headings, and the synthetic-assignee marker. Hover shows the sentence; Tab reaches it; a click or tap pins it; Escape, a scroll or a tap elsewhere dismisses it. The sentences come from the modules that compute the figures, so a formula cannot change without its explanation changing beside it.

Epic page

An epic opens as a page — from the expand icon in its drawer header, on a Gantt group header, or beside an incytes progress row — and the page has a URL, ?view=epic&epic=<id>, that survives a reload. It shows the epic whole at full width: the header with its dates and ship evidence; progress as the incytes bar draws it, closed of every child before any filter, with the split by status; the estimate roll-up — remaining, done and total as the sum of the estimator's per-child medians, marked DERIVED because nobody measured a sum — and the projected finish, which is where the scheduler lands the last open child (an epic is never scheduled itself), saying finishes after when that is the window's end rather than a projection; a Gantt strip of the children under the current window and group-by; the sortable table of every child; and the children graph with the blocks edges between them, which used to be the drawer's full-screen overlay and now lives here. Rows open the drawer, so the page and a child's drawer can be open together.

It is a place the epic is shown, not a lens: the Gantt, grid and table never dim or filter because a page is open, and the filters do not reach the page — it exists to show every child. Escape, or the back button, returns to the view the page was opened from.

First run

The first visit opens a short walkthrough in the corner — the bar kinds, the projection being a model rather than a plan, triage, the views, the controls. It is not a modal and takes no focus; skip or finish it and it never returns unasked. The options menu re-opens it, and nothing in it exists only there.

The header also asks, once, whether beadcyte may check npm for newer versions — one line beside the version tag, yes or no. Nothing is checked until you say yes; the answer is remembered in this browser and the options menu can change it. See Update check.

What if

Stage changes without writing them: reassign, reprioritise or mark spec:ready from a bead's drawer, change someone's cap from their roster line on incytes, or add an idea with a rough size from the what-if panel. Every plan-reading view then shows the plan as it would be — the Gantt draws each moved bead's old work as a dashed ghost with the delta beside it — and the panel lists what moves and by how much, where ideas land, and the frontier: how many beads land for each person in the next two weeks, before and after. Discard drops it all. Confirm shows the bd commands the change set amounts to, then runs them one after another through the same whitelisted endpoint the quick actions use, and reports what happened to each; a cap is a roster.json edit and is reported rather than written.

Acting on a bead

Right-click a bar, a card, a table row or a mine row — or press a with the drawer open — for the quick actions the allocate ritual otherwise runs through the CLI: claim, assign to someone on the roster, unassign, mark or remove spec:ready, add a label, defer. Each runs one bd command on the server; How beadcyte uses bd lists every one of them and how to reverse it by hand. Nothing else writes to your tracker; there is no bulk action and no client-side undo, by design.

Options — projects, current user, theme, refresh

The options menu

Switch between local beads projects, say who you are for the mine view, pick light/dark/system and one of twelve styles with live previews — including ports of Catppuccin, Dracula, GitHub, Gruvbox, Nord, One, Solarized and Tokyo Night, and a High Contrast original — and set the poll interval. A project the current server cannot read is shown as unavailable with the reason, rather than failing when you click it.

Update check

beadcyte is localhost-only unless started with --host, has no telemetry, and makes no request off your machine that you did not ask for — so on first launch the header asks, once, whether it may check npm for newer versions. Off until answered; never asked again whatever the answer; the options menu's Update check toggle changes it later.

  • What is sent, when on: one GET https://registry.npmjs.org/beadcyte/latest from the beadcyte serve process (never from the page), with an Accept header and nothing else — no version, no id, no query string. The registry sees what any npm view beadcyte sends: your IP. A real answer is cached in memory for an hour, so a page load is not a request; a failed one is retried after five minutes, so a launch before the network is up is not an hour of silence.
  • What is stored: the answer, in this browser's localStorage under beadcyte:update-check (yes or no). Nothing else. A browser that blocks site data counts as no and is not asked.
  • What you see: a patch or minor ahead is a quiet update available link beside the version tag, in the tag's own colour; a major ahead is the same link in the warning colour naming both versions (0.6.1 → 1.0.0), because a major is a break worth knowing about. Level with or ahead of the registry (a dev checkout), or npm unreachable: nothing, and nothing is logged in the page — "could not reach npm" is not news. The link is dismissible for the session; the answer is not.

Sync status

Beside the bead count: whether this project's local Dolt commits have reached its remote — dim for no remote, not synced from this app or in sync, and in the warning colour with a count for N changes since last push. bd itself has no ahead/behind, so "since last push" is this app's own memory of what it last pushed from here, not a claim about the remote — the hover names the head commit, when this app last pushed and to what remote, and says a push made from the shell is invisible here until the next Sync (docs/bd-usage.md has the full account). Click it for Push (bd dolt push) and Sync (bd dolt pull, then bd dolt push) — Sync is the dangerous half: bd 1.2.2 is known to deadlock or conflict on a pull, so it runs under a 60-second timeout that ends the process rather than hang, is never retried automatically, and a failure names the command to run by hand. Both are disabled with no remote configured and, like every write, under --readonly.


Keyboard

Press ? for the shortcuts. The convention is single letters with no modifier — j/k walk the beads in view, 1–6 switch views, g and w cycle group-by and window, p pins, , opens options — and nothing fires while you are typing in a field. The keyboard cursor is the open drawer: there is no separate focused row, so exactly one thing ever looks selected. Escape closes whatever is on top — a menu, the overlay, the drawer, then the epic page, back to the view it was opened from. The overlay is generated from the same table that binds the keys, so it cannot drift from them.

Flags

beadcyte [flags] — SVG mode:

| Flag | Default | Meaning | | -------------------- | ----------------------- | -------------------------------------- | | --from -Nd | -90d | Window start, relative to today | | --to +Nd | +90d | Window end, relative to today | | --group-by MODE | epic | epic / assignee / seat / track | | --max-per-group N | 20 | Rows per swimlane before truncation | | --include-deferred | false | Include status=deferred beads | | --roster PATH | .beadcyte/roster.json | Per-project WIP caps and affinities | | --out PATH | ./beadcyte.svg | SVG output path |

beadcyte changelog [flags] — regenerate CHANGELOG.md from closed beads:

| Flag | Meaning | | ---------- | ------------------------------------------- | | (none) | Write CHANGELOG.md at the repo root | | --check | Exit 2 if the committed file is out of date | | --stdout | Print instead of writing |

An entry appears when a closed bead carries ship evidence. Spikes, upstream: bugs, chores and epics are excluded, and releases are cut by git tag — the file states both rules in its own header, so a reader can tell "nothing shipped" from "this file does not list that".

beadcyte setup [FILE…] — teach the project's agents what to record. Adds a short managed block to every agent instruction file found in the current directory (CLAUDE.md, AGENTS.md, GEMINI.md, .github/copilot-instructions.md), or to the files named: what beadcyte reads (ship evidence on every close, hours on every session), how to write it with bd update --metadata, and a pointer to the rest. Safe to re-run; an older block is refreshed in place. The first beadcyte start in a project offers to run it once, and remembers a no. --hook does for beadcyte prime what bd setup does for bd prime: a SessionStart hook in .claude/settings.json so the whole guidance lands in every Claude Code session. It is opt-in, never offered, and refused unless a beadcyte executable is on PATH (npm i -g beadcyte), because a hook whose command is missing fails at every session start; a project that runs beadcyte through npx keeps the block, whose pointer needs nothing on PATH. .claude/settings.json is usually committed, so the hook reaches everyone who clones the project while the PATH check covers only the machine that ran it: install it where the team installs beadcyte globally.

| Flag | Meaning | | ---------- | ------------------------------------------------------- | | --check | Exit 1 if any file lacks the block or carries an old one | | --remove | Take the block out again | | --print | Print the block instead of writing it | | --hook | Add the SessionStart hook running beadcyte prime --hook-json to .claude/settings.json, only where beadcyte is on PATH (a node_modules/.bin that npx put there does not count); with --check, --remove or --print, those apply to the hook instead of the block |

beadcyte prime [--hook-json] — print the full guidance the block points at, the way bd prime does for beads: the fields, the rules (no placeholders, no manufactured zeros, never an estimated agent_hours), and the measured bd update --metadata behaviour that decides how to write them. --hook-json wraps it in the Claude Code SessionStart hook envelope. The reasoning behind every field is in docs/economics.md.

beadcyte freeze [flags] — writes to the tracker. Before you squash a beads database's history (bd flatten, or bd compact --days N where it runs), write the two dates that only its history holds — when each bead first entered in_review, when it was first assigned — onto the bead itself as metadata.history, so the squash does not take them with it. After compaction a squashed transition reports the squash commit's date as its first in_review, later than the truth and indistinguishable from it; a frozen value is read first and never re-measured. Dates come, in order, from one read-only bulk read of the project's Dolt database — the dolt binary against .beads/embeddeddolt, two queries over the history table and two over bd's events table, which survives compaction and so recovers a bead whose history rows are already squashed — then from beadcyte serve's history cache for the project, then from one bounded bd history --json read per bead. This is the only place beadcyte reads Dolt directly, on by default when the binary runs and the directory exists, and --no-dolt refuses it. A bead with no known date gets no key, a date younger than the window waits for a later run, and a frozen date is never rewritten. Each date carries its provenance naming the source (measured:dolt-history, measured:dolt-events with how the events zone was decided, or measured:bd-history). The exact write and both queries are in docs/bd-usage.md.

| Flag | Default | Meaning | | ------------------ | --------- | ---------------------------------------------------- | | --project DIR | . | The beads project to freeze | | --older-than 7d | 7d | Freeze only dates at least this old (Nh, Nd, Nw); 0h before a bd flatten, or match bd compact --days | | --dry-run | false | Print each bd update command instead of running it | | --no-dolt | false | Skip the bulk read of .beads/embeddeddolt; use the cache and bd history only |

beadcyte start [flags] — interactive mode. Starts the server in the background, prints its address and the log path, and returns; beadcyte stop ends it, from the same directory. beadcyte serve [flags] runs the same server in the foreground instead, with Ctrl-C to stop. Both take the same flags:

| Flag | Default | Meaning | | ---------------- | ----------------------- | ----------------------------------- | | --port N | 4173 | Listen port; falls through if taken | | --host ADDR | 127.0.0.1 | Listen address: an IP or hostname; 0.0.0.0 or :: for every interface. Anything but loopback is reachable by everyone on that network — see below | | --readonly | off | Refuse writes: POST /api/mutate and POST /api/sync answer 405 and the app's quick actions, comment box, what-if confirm and Sync/Push buttons are disabled. Independent of --host | | --roster PATH | .beadcyte/roster.json | Same roster convention | | --cache-ttl MS | 30000 | bd shell-out cache TTL |

Who can reach it. By default the server listens on 127.0.0.1: this machine only, no auth, the same posture as bv. --host is the deliberate exception, for a phone or a second machine on a network you trust — and it is a decision, not a convenience, because every quick action in the app (claim, assign, label, defer, comment, and what-if's confirm) is a write to your tracker through bd, so --host 0.0.0.0 hands those to everyone on the segment. The banner then says so, in one line, and lists the URL to type on the other device for each interface (http://192.168.0.55:4173/ (wlan0)), as does beadcyte start's "running" line; beadcyte stop still finds it. --readonly is how to share a view: the route refuses with a body that says why, the page learns it from /api/beads and disables its actions with the same sentence, and staging what-ifs still works because nothing there writes. It is not the default, and --host without it prints the suggestion. Neither flag adds authentication; do not put this on a network you would not hand bd to. The rule for which addresses count as loopback, and the URLs, is src/posture.mjs.

The review phase (hours_in_review) needs one fact that only version history holds: when a bead first entered in_review. The server reads it, in order, from the bead's own frozen metadata.history (written by beadcyte freeze, above); then with a single bd sql query — MIN(commit_date) … GROUP BY id, which answers every bead at once in about a second — unless bd's own .beads/metadata.json says dolt_mode is embedded, the default for beads 1.2.2, where bd sql is known to answer "not yet supported in embedded mode" and is therefore not spawned at all (a project whose metadata does not say is probed once; that gate does not lift itself: when bd supports sql in embedded mode (beads#6307) it has to be lifted in src/history-sql.mjs); then by reading bd history one bead at a time, capped, where a bead whose history is too large to read reports its review phase as absent rather than as zero. The server never reads the Dolt database directly; the one place beadcyte does is beadcyte freeze, a deliberate command run before compaction. The serve banner's history line says which of these will be tried here, in order.

The version tag in the header opens the changelog, rendered in the app from the copy shipped with the build. Set BEADCYTE_CHANGELOG_URL to add a link out to the same file on your repo; it is absent when unset. The same server serves /api/update-check, the one request it ever makes off the machine, and only after the app has asked in the browser and been told yes — see Update check.

Most of the interactive app's state lives in the URL — view, grouping, filters, window — so a link shares what you were looking at. Links written before the status filter absorbed the old showClosed / includeDeferred toggles still resolve to the same view.


Documentation

| | | | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | What to record | What beadcyte reads out of your beads, what breaks when it is missing, and how to record it. Start here if your charts look empty. | | The model | How the estimator, the scheduler and the triage score work, what they are calibrated against, and where they are known to be wrong. | | How beadcyte uses bd | Every bd command the app runs — the reads, the background history walk, each quick action and how to reverse it — and whose name a claim carries. | | History size | What makes the history read expensive, the order that shrinks it without losing the dates (freeze, then compact), and which bd tools destroy the numbers. Measured. | | Roster | Per-project WIP caps and affinity routing. | | Programmatic use | Using the estimator and scheduler as libraries. | | Theming | The token layer, light/dark, named styles, and the audit that keeps a restyle from relabelling the data. | | CHANGELOG | What has shipped. Generated from the tracker, not written by hand. |


Development

npm install
npm run dev          # beadcyte serve against this repo's own beads (port 4173)
npm test                  # unit + parity + perf suites
npm run typecheck         # vue-tsc --noEmit
npm run build             # static bundle → src/web/dist
npm run changelog         # regenerate CHANGELOG.md
npm run changelog:check   # fail if the committed CHANGELOG.md is stale
npm run mark:png          # re-render the mark's PNGs: 16/32 from favicon.svg (small cut), 180 + docs/beadcyte-mark.png from mark.svg
npm run mark:review       # the mark at 16/32/48 on the tab-strip greys, screenshotted at 1x and 2x, for judging a redesign

npm run dev points the dev server at beadcyte's own beads DB, so the app is always dogfooding itself. The Pinia store (src/web/store.ts) hot-updates under Vite: an edited action reaches the live page without a reload.


Non-goals

  • Resource-constrained scheduling. Greedy dispatch, no solver. No cross-human load balancing and no auto-reassignment: the chart is a projection of current allocation, not a proposal for a better one.
  • Drag-editing the plan. Dates come from the model; changing them means changing the beads.
  • Non-bd trackers. The input shape is bd list --json; anything else needs an adapter.

Licence

AGPL-3.0-only. See LICENSE.

beadcyte is copyright Incyte Studios, LLC and released under the GNU Affero General Public License, version 3. You may use, change and redistribute it under the same terms. If you run a modified version as a network service, section 13 obliges you to offer its users the corresponding source.

Incyte Studios stays the sole copyright holder so that it can also offer the project under other terms. That is why contributions are accepted under a permissive inbound licence rather than the AGPL; see CONTRIBUTING.md.

Dependencies are MIT, ISC, BSD and Apache-2.0 licensed and carry their own notices. The triage score is a local reimplementation of a formula documented by another tool, not a copy of its code; see docs/model.md.