ztrack
v1.2.0
Published
A typechecker for your issue tracker — every checked acceptance criterion must cite a real commit and proof, verified by `ztrack check` (and enforced on coding agents via a loop gate).
Downloads
14,261
Maintainers
Readme
AI coding agents close tickets on prose. "All tests pass, feature complete" — and the commit it cited never existed. Your tracker stored the claim with perfect fidelity and verified nothing.
ztrack is a typechecker for your issue tracker. A checked acceptance criterion must cite a commit SHA that exists in git, plus the evidence and proof the preset requires. You use it two ways, over the same work:
ztrack check— verify on demand (CI, pre-merge, a spot check). Pass or exit non-zero.ztrack loop— a ralph loop: Stop/SubagentStop hooks hold your agent's turn (and any subagent's) until the work is actually green. Recommended while developing — the agent can't call it done until it is.
check is the oracle; loop runs that oracle on every turn until the agent earns "done."
What ztrack catches
| Claim in the tracker | What ztrack verifies |
|---|---|
| "Implemented in commit a1b2c3d" | the SHA exists in the local git object database |
| "This acceptance criterion is passed" | it cites evidence captured at a real commit, against the current AC version |
| "This evidence proves it" | the AC carries a proof: that names the evidence it relies on |
| "This ticket is ready/done" | the installed preset's lifecycle gates hold — every AC passed (and, on a PR-based preset, the PR exists/merged) |
Lint errors are fixed by editing text. Type errors are fixed by producing evidence.
What it does not verify (be honest with your team): the simple-sdlc preset checks that the
cited commit exists and — if you cite an image — that the image is committed at that commit (a
fabricated screenshot path fails). By default it does not check relevance — a real, unrelated commit
with a real screenshot still passes — but an AC can opt in to a paths: relevance anchor (or the
repo can require one on every passed AC) so a claimed commit must at least touch the area it claims;
see Relevance below. Full semantic judgment ("is this commit really the right fix for
the right reason") is still the irreducible thing a deterministic checker can't make. It raises the
floor from "prose can lie" to "the proof must exist, be real, and land in the claimed area"; encode
stricter, project-specific grounding in the editable preset.mts.
Setup
Prerequisites: Node ≥ 22.18 (the installed preset is
.mts, loaded via native type stripping, on by default from Node 22.18 / 23.6 / 24) andgit. No database, no Python. Theztrack visualizeradditionally needs Bun. Verified under npm, pnpm, yarn (classic + Berry + PnP), and bun.
ztrack is a project dev-dependency — the installed preset imports the mechanism from it, exactly
like an eslint config imports its plugins (a global or one-off npx install is not enough):
npm install -D ztrack # add ztrack to the project (the preset imports it)
npx ztrack init # installs .volter/tracker/validation/preset.mts — real, editable rulesThat's the whole setup for local verification. Three things you choose here:
1. Local or linked. The default is a local tracker — issues live as markdown in your repo, committed alongside the code. To make your issues be GitHub Issues, synced both ways:
npx ztrack init --sync github --repo owner/name # links + pulls existing issues; check/loop stay synced2. Your preset (the ruleset). ztrack init installs the recommended simple-sdlc baseline.
See all presets with ztrack init --list; choose one with --preset <name>. The installed
preset.mts is real, editable code — open it and change the rules. (Reference: Presets.)
3. The loop gate — only needed for ztrack loop usage. Install the Stop/SubagentStop-hook
plugin once; it's dormant unless a loop is armed, so it's safe to leave enabled globally:
/plugin marketplace add volter-ai/ztrack # in Claude Code
/plugin install ztrack@ztrackNot using Claude Code plugins? Wire the Stop and SubagentStop hooks yourself — see the Guide → drive an agent to green.
Usage
First decide where your work lives, then how you'll drive it — two choices each, four doors:
| You have… | Do |
|---|---|
| GitHub Issues already | npx ztrack init --sync github --repo owner/name — your issues pull in and GitHub stays the source of truth (linked sync) |
| a pile of tasks, no tracker | npx ztrack init, write the tasks down as you naturally would, then npx ztrack import notes/tasks.md --register materializes them into issues (or issue create one by one) (importing) |
| one issue to finish | ztrack loop start <id> --until done — the Stop-hook gate holds your agent's turn until the work is genuinely done (drive to green) |
| a whole backlog to burn down | groom → order → dispatch one loop-armed subagent per issue list --actionable row, wave by wave (orchestrating a backlog) |
Two patterns, the same targets. Pick by the job:
| | ztrack check | ztrack loop start |
|---|---|---|
| does | verifies once, exits 0/1 | a ralph loop — Stop/SubagentStop hooks hold every turn in the root (main agent and subagents alike) until the target is green, then disarm |
| use for | CI gate, pre-merge, a manual "is this real?" | driving an agent to actually finish — recommended during development |
| bounds | one run | capped iterations, with honest escapes; cooperative, not a sandbox |
Both take the same target — nothing, an id, a file, or the current branch's issue:
ztrack check # (nothing) the whole tracker
ztrack check LOCAL-1 # <issue-id> one issue
ztrack check ./body.md # <file.md> a markdown file (document grammar checks every issue in it)
ztrack check # (in a worktree named for an issue) → that issue, automatically
ztrack loop start LOCAL-1 # loop takes the exact same target grammarA <file.md> in document grammar (id-bearing headings like ## APP-1 — title, the same
grammar a registered format: "document" source uses) is checked as the multi-issue document it
is — every issue in the file, intra-file relations included — and a stderr note says whether the
file is a registered source (i.e. whether the tracker itself can see these issues), offering
ztrack import <file> --register when it is not. Anything else is one loose issue, checked for
structure + evidence (the core promise); lifecycle/PR gates (ready/in-review/done) apply only
to stored issues, so a loose file is treated as a draft.
Verify once — ztrack check
Install, init, author an acceptance criterion that claims done but cites a fabricated commit, and watch ztrack catch it:
cat > body.md <<'EOF'
Assignee: me
Status: ready
## Acceptance Criteria
- [x] dev/01 v1 GET /health returns 200
- status: passed
- evidence ev1: commit=deadbeef acv=1
- proof: "screenshot shows a 200 response" -> ev1
EOF
npx ztrack issue create --title "Add /health" --label type:case --state ready --assignee me --body-file body.md
npx ztrack check # ✗ the cited commit isn't in git✗ ztrack check failed issues 1 • errors 1 • warnings 0
LOCAL-1
╰─ ✗ error evidence_commit_not_found
└─ Evidence ev1 cites commit deadbeef, which does not exist.
✗ exit 1 — the checkbox says done. the commit doesn't exist.Now make it real — commit the work, cite a SHA that exists, re-import the body (the issue is stored
independently, so editing body.md alone isn't enough), and re-check:
git add -A && git commit -m "add /health endpoint"
SHA=$(git rev-parse HEAD)
sed "s/deadbeef/$SHA/" body.md > body.fixed.md # cite a SHA that exists
npx ztrack issue edit LOCAL-1 --body-file body.fixed.md # re-import the corrected body
npx ztrack check # ✓ now it passesThat's the whole idea: a checked acceptance criterion must cite proof that actually exists.
Drive to green — ztrack loop
The recommended development flow, in three steps:
1. Install the gate once — dormant unless a loop is armed, so it's safe to leave enabled (Claude Code; non-plugin wiring is in the Guide):
/plugin marketplace add volter-ai/ztrack
/plugin install ztrack@ztrack2. Arm a loop on the issue you're working — two modes, same start:
ztrack loop start LOCAL-1 # validate-current-stage: hold until LOCAL-1's CURRENT status passes check
ztrack loop start LOCAL-1 --until done # drive-to-stage: hold until LOCAL-1's status reaches "done" (or later) AND passes check there--until <stage> is for driving an issue somewhere it isn't yet — the stage is any value in the
active preset's status vocabulary (e.g. ready, in-review, done), validated at arm time (an
unknown stage fails loud, naming the real vocabulary). It's the difference between "is the current
stage real?" and "get this all the way to done" — without pre-flipping the issue to a stage that
isn't true yet just to make the oracle look at it. Flipping the status early doesn't cheat this:
that stage's own lifecycle gates (e.g. every AC passed before in-review) still have to pass for
real, so an early --state done with unpassed ACs stays red on its own. --until only makes
sense for a single issue (an id, or the bare/auto-resolved worktree issue) — a file or the whole
tracker has no one status to drive.
3. Point your agent at the issue — hand it the id (its working rules are in the
agent playbook). When the agent — or a subagent it delegates to — tries
to stop, the Stop/SubagentStop hook runs ztrack check; if the issue is still red — or, under
--until, its status hasn't reached the target stage yet — the turn is held and the agent keeps
working — until the work is genuinely green (then the loop disarms), or it hits the iteration cap.
It's cooperative, not a sandbox: the agent can disarm (ztrack loop stop), self-exempt for a
session, or an authority can waive a finding it knowingly accepts — so it
never grinds forever on something it can't satisfy honestly.
ztrack loop status # is a loop armed? capped?
ztrack loop stop # disarmYou set the target; the loop holds the standard.
Driving a whole backlog (not just one issue) is the same loop, dispatched wave by wave:
ztrack issue list --actionable names every not-done, unblocked issue — the dispatch frontier — and
--blocked names what's stalling the rest. See Guide → Orchestrating a whole
backlog for the full
intake → groom → order → dispatch flow.
When the checker is wrong
ztrack check is deterministic, but deterministic isn't the same as always right for your repo — a
legacy AC that predates evidence discipline, or a finding that genuinely doesn't apply. The sanctioned
override is a waiver, signed by a human (or an agent acting under one), not editing the check's
output or disabling a rule globally:
ztrack waiver sign <issue> --code <finding-code> [--ac <acId>] [--ref <subject>] --reason "..."
ztrack waiver status <issue>
ztrack waiver clear <issue> [--code <finding-code>]A waiver lives in the issue body's ## Waivers section, one line per waiver — pinned to the single
offending occurrence, // eslint-disable-next-line style:
- code: evidence_commit_not_found ac: dev/01 ref: cafebabe… reason: repo predates the import by: Jane Doe ([email protected])sign downgrades the matching finding from error to acknowledged — check still reports it, but
exits 0. The ref: pin (auto-captured by sign when the finding is unambiguous; pass --ref
<subject> yourself when it isn't) scopes the waiver to ONE occurrence — the offending value, e.g. a
commit SHA — so it self-expires the moment that occurrence changes instead of silencing the rule
forever. Sign-off is captured automatically from your git identity (git config user.name /
user.email); you can't waive anonymously. Hygiene is enforced in every direction: an unreasoned or
unsigned waiver is itself an error (waiver_missing_reason, waiver_missing_signoff); an unpinned
waiver that could pin is flagged waiver_overbroad (ztrack waiver migrate rewrites legacy broad
rows into per-occurrence pins); and a waiver that stops matching anything is flagged waiver_unused
(a warning, not blocking) so a stale waiver doesn't silently keep suppressing forever.
Prefer fixing the issue; waive only a finding you knowingly accept. Full grammar in Presets → Waivers.
Relevance
By default ztrack check verifies that cited evidence exists and is real — not that it's about
the claimed work. An acceptance criterion can opt in to a relevance anchor: a paths: glob
declaring the repo area it concerns. Once declared, a passed AC's cited commit(s) must touch at least
one of those paths, or the claim is rejected:
- [x] dev/01 v1 GET /health returns 200
- status: passed
- paths: src/**
- evidence ev1: commit=<sha> acv=1
- proof: "the commit adds the health endpoint" -> ev1A commit that only touches docs/ here fails with evidence_commit_unrelated; one that touches a
src/ file passes. paths: accepts globs (* within a segment, ** across segments, literal
files/dirs, comma-separated lists) and is satisfied by ANY cited commit touching ANY declared path.
paths: is opt-in — a passed AC that omits it is never relevance-checked (existing repos are
unaffected). Set "relevance": "required" in .volter/tracker-config.json to make it mandatory:
every passed AC must then declare paths:, or passed_ac_missing_paths fires. This is still not full
semantic judgment (a commit that touches the right files for the wrong reason still passes) — it's a
deterministic floor: the cited proof must at least be in the area it claims to fix.
How it works
ztrack is a verification layer, not a new tracker. It reads tasks from your work system (or a committed validated root), parses them into one strict multi-issue root through a Zod schema, gathers git/world facts into a typed context, and runs pure rules over it — exiting non-zero when a checked claim isn't backed by real proof. CI, MCP, or an agent Stop hook can block on that exit.
Keep Linear, Jira, or GitHub Issues as the human surface; ztrack sits next to them and validates the claims agents or humans make there. Two-way sync with GitHub Issues is built in (a synced issue is the GitHub issue) — see the Guide → linked sync.
One document, many issues
Your existing plan or backlog markdown can be the tracker. Declare it as a document source and
every heading whose text starts with an id (APP-1 — Add the /health endpoint) becomes an issue —
no reformatting, no separate store to keep in sync:
cat > PLAN.md <<'EOF'
## APP-1 — Add the /health endpoint
status: ready
assignee: me
### Acceptance Criteria
- [ ] dev/01 v1 GET /health returns 200
- status: pending
EOF// .volter/tracker-config.json
{ "sources": [{ "path": ".volter/tracker/markdown" }, { "path": "PLAN.md", "format": "document" }] }npx ztrack issue list --json identifier,title # -> APP-1 Add the /health endpoint
npx ztrack check # reads PLAN.md like any other storeac patch/issue edit splice a verified change straight back into the doc at the heading's
recorded span — patching dev/01 above changes only its checkbox and status/evidence/proof lines;
every other byte in PLAN.md is untouched. State, assignee, and anything wider than title/body
still fail closed, naming the file to edit directly instead. With 2+ declared sources,
--source <name> scopes issue list and check to one or more of them (repeatable AND
comma-separated, on both commands). Grammar, the write model, scoping, and diagnostics:
Sources.
Already have a messier backlog — headings, prose, checkboxes, no id tokens? ztrack import (accepts
a file, a directory, or a quoted glob) materializes it into that same grammar in place,
idempotently: ztrack import notes/backlog.md --dry-run previews the plan, plain ztrack import
writes it, --register declares the result as a source. Pre-checked [x] items import unchecked
with a preserved-claim marker (a checked-but-unproven claim is exactly what check rejects); an
Acceptance Criteria or Waivers section is recognized document-source structure and is never
turned into an issue, so waiver rows survive untouched — see
Sources → Importing a freeform backlog.
And a file that tracks work in a repo's own idiom — ### KQ1 sections with
- **Status**: 🟢/🟡/🔴 bullets, or - [x] **WS-A: title** checkbox rosters — needs no rewrite at
all: ztrack check <file> auto-detects the shape (a dialect) and checks every issue through a
read-only lens, printing the one command that adopts it. Registering the lens is config-only (the
file is never modified); its issues appear in issue list/check with their own ids and true
statuses, reported but never gating. Materializing later (ztrack import <lens-file> --register)
is the opt-in upgrade to the full grammar, with any id renames recorded as aliases so old
spellings keep resolving — see Sources → Dialect lenses.
Agent workflows
ztrack is built to be an AI agent's completion oracle — three ways to wire it, smallest to most autonomous:
- CI gate: the
volter-ai/ztrack@v1Action over a committed validated root (ornpx ztrack check --phase gate) — full recipe, including linked mode, in the Guide → gate it in CI. - MCP:
claude mcp add ztrack -- npx ztrack mcp serve— the agent callstracker_checkbefore finishing. - Autonomy loop: the
ztrack loopStop-hook gate above — the recommended development flow.
Full setup (MCP tools, the loop, the Stop-hook settings.json) is in the
Guide → drive an agent to green; the copy-paste agent
adoption prompt is in the agent playbook.
Presets
ztrack init --preset <name> installs one editable, standalone preset — its own schema, parser,
and rules, importing only ztrack/preset-kit:
| Preset | Use when |
|---|---|
| simple-sdlc | a dev lifecycle (draft→ready→in-progress→in-review→done) with commit+proof evidence — PR-free, runs locally. The recommended baseline; default is an alias for it |
| simple-gh-sdlc | the same, plus a GitHub PR at in-review and a merged PR for done |
| spec | issue bodies are specs whose ACs cite commit-backed evidence |
| speckit | GitHub Spec Kit style feature records (user stories, tasks, phases) |
ztrack init --list shows every preset; Presets documents the exact gates and how
to add your own rule.
Visualize
For a read-only web view of the tracker — issues, acceptance-criteria progress, findings, and audit-derived timestamps — run the visualizer (requires Bun):
ztrack visualizer # the active preset, http://localhost:3300
ztrack viz --preset speckit --port 4000It validates the live tracker on each request through the same core as check, so the board never
drifts from what CI enforces.
Why believe it
ztrack runs our own autonomous agent fleet in production — it's what we use to ship real code. Every release re-proves in CI that a fabricated commit SHA fails the check.
Stability & dependencies (be honest before adopting). Since 1.0.0 ztrack follows semver
proper: the CLI flag surface, the package-root API, and the bundled preset contracts break only at
a major version — minor and patch releases are safe to take. Still read the
CHANGELOG before upgrading across a major. The deterministic local core
(check, evidence, presets) depends only on the markdown store and git, and needs nothing beyond
ztrack itself. GitHub two-way sync and world-backed evidence route through @volter-ai-dev/twin (same publisher) — since 0.38.0 that's an optional
peer dependency, not a regular one: adopt only local verification and nothing extra installs or
runs; adopt sync and you must npm install -D @volter-ai-dev/twin @volter-ai-dev/twin-github
yourself, and run the sync command under bun (not npx/node) — see the canonical
recipe.
How it compares
| | Records claim | Validates structure | Verifies evidence of "done" | |---|:---:|:---:|:---:| | Linear / Jira | ✓ | shape only | — | | Beads / Backlog.md | ✓ | partial | — | | spec-kit / OpenSpec | ✓ | ✓ (prose shape) | — | | Eval / observability | ephemeral | — | scores outputs | | ztrack | ✓ | ✓ | ✓ |
Managed setup
The open-source core is free to self-host and run locally. Teams that want help wiring ztrack into an existing tracker, CI, MCP, and the agent Stop-hook loop can apply for Startup Pilot. No payment is collected until we confirm a good fit.
Community and support
- Questions and bugs: open a GitHub issue with the matching template.
- Feature ideas: include the workflow problem and the evidence you want ztrack to verify.
- Managed setup: use the Startup Pilot form.
- Security reports: use GitHub Security Advisories; do not open public security issues.
Documentation
The README is the front door; these go deep:
- Guide — setup, the two usage patterns, CI gate, agent enforcement, visualize.
- Presets — choose and customize the ruleset; the grammar; add a rule; build your own preset;
preset upgrade. - Sources — declare where issues live; the document format (one file, many issues); write-back and diagnostics.
- Evidence — cite, store, and verify proof; in-toto + DSSE attestation.
- Agent playbook — the copy-paste prompt for an agent adopting and driving ztrack.
- Programmatic API · Architecture · Visualizer
- Roadmap · Contributing · Security · Changelog
