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

wowbagger

v0.5.0

Published

Git-native work ledger for coding agents: deterministic ready queues, guarded CAS mutations, claims and fencing, and self-contained HTML reports.

Readme

wowbagger

The backlog may be infinite. The next item should not be ambiguous.

Don't Panic! The books that helped shape my childhood taught me to meet absurd systems with curiosity, humor, and a reliable way to find the next step. Douglas Adams's Hitchhiker's Guide creations are part of that inspiration; Wowbagger is an independent work, not an official or affiliated project.

Wowbagger is a work ledger for coding agents. Every backlog item is one Markdown file in your repository; every lifecycle change is a reviewable Git diff. There is no database, no hosted service, and no private agent memory to lose.

Who it is for. Maintainers running one or more coding agents across worktrees, sessions, and models, who need the backlog to survive a compaction, a restart, or a change of harness — and who want the agent's writes guarded rather than trusted.

What you get. A deterministic ready queue that answers "what is actionable today". Guarded single-item writes with exact-byte compare-and-swap, so an agent cannot silently clobber your edit. Honest capability discovery, so a tool can tell what this core will and will not promise. A shipped skill that teaches an agent to use those guarantees instead of hand-editing your Markdown.

Start here: install the core and set up a ledger.

Status: published and self-hosted. 0.5.0 is published on npm and has a matching repository tag. It is the version this repository runs its own backlog on. The API is stable at core contract version 5.

Channels. A stable release sets both latest and next to the stable version and publishes with npm publish --tag latest. A later prerelease moves only next and publishes with npm publish --tag next, so latest stays on the stable release. While every published release is a prerelease, latest mirrors next: install the prerelease explicitly with wowbagger@next. After the first stable release, a bare install resolves to the stable release.

What is proved. Contract version 5 validates the complete Markdown ledger, selects a deterministic ready queue, exposes bounded list and lossless inspect projections, and publishes guarded create, transition, patch, parent-migrate, and snooze mutations. Mutations use exact-byte compare-and-swap, atomic no-clobber publication, and explicit reconciliation when a response is lost. Claims, fencing, adoption, prospective merge verification, and publication finalization coordinate cooperating writers without pretending to be an exclusive dispatch lock.

report is a self-contained decision workspace: one scoped item browser with Work next and other quick views, an area/status matrix, scoped attention actions, dependency impact, interactive Flow charts, and a 3D dependency graph that all share one scope. Version 2 report configurations add named custom views whose statistics, readiness, attention, flow, graph, and impact all describe one filtered subset. Reports remain derived output, not mirrored ledger state.

The core ships Claude Code, Codex, and OpenCode adapter packages on one shared engine. Read capabilities --json before relying on a target-specific claim; platform support is evidence-based, not inferred from whether a CLI starts.

What is not a lock. A work claim is not an exclusive dispatch lease. On a provisioned Git-backed ledger, claims coordinate cooperating agents through a durable journal in Git's shared common directory: claim acquire uses observed-state compare-and-swap, publish-claimed fences one item against the active owner generation and expected revision, and claim-verify reconciles response-loss and post-merge outcomes. Capability discovery therefore reports mode: "merge-coordinated" and safe_exclusive_dispatch: false. Direct filesystem writes, hostile processes, other clones, and non-claim-aware tools still bypass the protocol.

TL;DR for agents

Wowbagger is the core authority for a Git-native work ledger. Use it instead of editing ledger Markdown by hand.

wowbagger --version                 # require 0.5.0
wowbagger capabilities --json       # require contract_version: 5
wowbagger validate --ledger ledger --json
wowbagger ready --ledger ledger --as-of YYYY-MM-DD --json
wowbagger inspect --ledger ledger --number N --json

For a write, inspect immediately before dispatch, send the returned exact-byte revision as the compare-and-swap witness, use the explicit core mutation, and validate again. On a provisioned ledger, commit each create, transition, parent-migrate, snooze, patch, or publish-claimed mutation before the next mutating command. Never replay a lost write: reconnect, re-read current state, and treat the outcome as unknown until the core or a human resolves it. Numbers are the human-facing item identity; wb_... ULIDs are internal identities.

The core owns validation, ready selection, projections, lifecycle, CAS, publication, claims, fencing, and reconciliation. The harness or host owns dispatch, process safety, routing, and human approval. Claims coordinate cooperating writers; they are not exclusive locks.

Start here

Install the core CLI, then verify it. The supported runtime is Node.js 24; Node 26 remains excluded because of the separate Vitest incompatibility:

npm install -g wowbagger@latest   # stable release
npm install -g [email protected]   # exact plugin-matched release
# or, from this release's Git tag:
# npm install -g github:lstutzman/wowbagger#v0.5.0
wowbagger --version         # 0.5.0
wowbagger capabilities --json              # must report contract_version: 5

In Claude Code, install the managed plugin:

claude plugins install wowbagger

Or, from inside a Claude Code session:

/plugin install wowbagger

This route is available after Wowbagger is listed in Claude Code's official marketplace. Until then, or when installing a fork or unreleased revision, use the direct repository marketplace:

/plugin marketplace add lstutzman/wowbagger
/plugin install wowbagger@wowbagger

For Codex and other agents, install the editable skill with skills:

npx skills@latest add lstutzman/wowbagger --skill wowbagger

Choose one route. Do not install both the managed Claude plugin and the editable skill, or the skill will be loaded twice.

The plugin and skills installer drive the separately installed core rather than bundling one, so a mismatch is detectable instead of silent. The skill reads wowbagger --version and capabilities; it requires the same distribution version as the plugin and core contract_version: 5. It refuses an absent or incompatible core. It will not fall back to editing ledger files by hand, because that would bypass validation and atomic publication.

Neither installer adds an MCP server, remote service, hook, or background process. The plugin and skill operate on the ledger through the installed core.

Set the ledger up before the first item

Decide where items live before your first create. A ledger publishes items to <ledger>/<id>.md unless a committed <ledger>/.wowbagger/layout.json binds a subdirectory:

mkdir -p path/to/ledger/.wowbagger path/to/ledger/items
echo '{"layout_version":1,"items_directory":"items"}' > path/to/ledger/.wowbagger/layout.json

layout_version must be 1 and items_directory names the committed item directory. create then publishes atomically to <items_directory>/<id>.md, and validation rejects parsed items outside it. Commit the directory the file names — create publishes into an existing directory and does not make one. Nothing is renamed after a create. This repository dogfoods that binding: its own items live in ledger/items/.

If your items will mirror an external tracker and carry consumer-owned fields, declare those fields before the first item. The declaration makes each named extension member patchable:

echo '{"extensions_version":1,"members":{"external_id":"string"}}' \
  > path/to/ledger/.wowbagger/extensions.json

Each member declares one value type: string, integer, boolean, or string-list. A ledger without the file has no patchable extension member, and set.extensions refuses the missing declaration by name. The declaration authorizes writes; it does not define item validity, so validate does not read it. Commit the declaration with the other ledger setup.

If an existing ledger already carries extension values, do not create the declaration by hand. Select every member and type explicitly in a request, review a dry run, then publish the same proposal:

{"members":{"tags":"string-list"}}
wowbagger extensions-provision --ledger path/to/ledger \
  --input declaration.json --json --dry-run
wowbagger extensions-provision --ledger path/to/ledger \
  --input declaration.json --json
git add path/to/ledger/.wowbagger/extensions.json
git commit -m "Declare patchable ledger extensions"

The command first requires a valid complete ledger. It validates every occurrence of each selected member, reports occurrence counts, changes no item bytes, and publishes one canonical declaration without overwriting a different one. Commit that file before the first corresponding patch, inspect the target for its current revision, then use set.extensions.tags. An item that writes the selected member with a YAML anchor or alias remains extension-anchored and requires a reviewed hand-edit.

Cores at 0.1.0-alpha.4 and earlier ignore the layout file and publish every item at the ledger root.

On such a core, relocating an item by hand has a trap. create writes an untracked file, so git mv refuses the path it just published; an unchecked batch then runs git add -A and commits the item at the ledger root, where it silently stays. Use plain mv, then git add both paths, and check the exit code of every command before the commit:

mv path/to/ledger/<id>.md path/to/ledger/items/<id>.md || exit 1
git add path/to/ledger || exit 1

A committed item outside the configured items directory then fails validation and refuses every read and every guarded mutation on that ledger, including ones that never touch it. The refusal names the expected path and the relocation that repairs it.

File the first item

Every command below takes --json and writes exactly one JSON object to standard output. mint-id prints a canonical ID so no caller writes base32 by hand:

wowbagger mint-id --json                     # -> result.id
cat > request.json <<'JSON'
{
  "id": "wb_...",
  "item": {
    "title": "Map the fictional route",
    "kind": "task",
    "priority": 2,
    "provenance": { "source": "user-request", "recorded_at": "2030-01-10T12:34:56.789Z" },
    "depends_on": [],
    "related": []
  },
  "body": "\n# Problem\n\nWhat is wrong.\n\n# Acceptance criteria\n\n1. ...\n"
}
JSON
wowbagger create --ledger path/to/ledger --input request.json --json

The item lands in triage with a core-assigned number — the integer handle you say out loud. create does not accept a number and does not accept a status. Move it into the backlog with an explicit accepting transition, then ask what is workable:

wowbagger inspect    --ledger path/to/ledger --number 1 --json   # -> revision
wowbagger transition --ledger path/to/ledger --input accept.json --json
wowbagger ready      --ledger path/to/ledger --as-of YYYY-MM-DD

transition fences on the expected_revision that inspect just returned, so inspect immediately before you transition. On a provisioned ledger, commit each mutation before running the next one — see Commit each mutation before the next one.

For an isolated consumer pilot, create or select the disposable worktree before the agent starts. Then launch a new session with that worktree as its project root. Follow the isolated dogfood pilot runbook; do not try to drive a sibling worktree from an already-running agent session.

To use the core directly from a clone instead, see Core commands.

Why the name?

Wowbagger the Infinitely Prolonged is a Douglas Adams character faced with an absurdly large, strictly ordered list and the prospect of working through it one entry at a time.

That is also a fair description of software maintenance.

The project is an independent literary nod and is not affiliated with or endorsed by Douglas Adams' estate.

Why adopt the skill instead of editing Markdown by hand

The ledger is plain Markdown, so an agent can edit it with a text editor. The skill exists because four things break when it does.

  • Validation is whole-ledger and fail-closed. One malformed item refuses every read and every guarded mutation on that ledger, including commands that never touch it. A hand-edit finds that out later, and usually in someone else's session. create, transition, parent-migrate, snooze, and patch validate the complete candidate ledger before publishing anything, and refuse unchanged.
  • A hand-edit has no lost-update guard. Every guarded write takes the exact SHA-256 revision inspect returned and refuses if the bytes moved. An editor writes over whatever is there.
  • Publication is atomic and no-clobber. A guarded write either lands whole or does not land. A half-written item from an interrupted editor is a broken ledger.
  • On a provisioned ledger, a hand-edit is a stale write. The claim journal validates recorded mutations against Git HEAD. An out-of-protocol edit makes the next mutation refuse exit 6 unauthorized-revision, and every later mutation stays blocked until an operator rules on it. The protocol used to force that edit for a wrong title; it no longer does — patch covers it.

What the skill adds on top of the CLI is the part an agent gets wrong unaided: it checks the core version before the first command, it says #N to people and ULIDs to the tool, it reads acceptance criteria out of the item body rather than the metadata, it derives an epic's progress from its children instead of its status, and it tells the user before a batch that filing ten items means ten commits, not one.

It also refuses to fall back. If the core is absent or the contract version is not 3, the skill stops and says so. It does not quietly start editing files.

Core, adapter, skill, plugin

Four separate things, deliberately:

| Piece | What it is | Where it lives | |---|---|---| | core | The wowbagger CLI. Harness-neutral; it knows no vendor. | src/, bin/, npm package wowbagger | | adapter | Per-harness machinery that lets an agent drive the core safely: negotiation, capability checks, forwarding, honest outcome mapping. It owns no lifecycle logic. | adapters/ | | skill | Harness-native instructions telling an agent when and how to use the core. | skills/wowbagger/SKILL.md | | plugin | The Claude Code distribution wrapper: marketplace entry, skill, adapter. Self-hosted from this repository. | .claude-plugin/ |

The core and the plugin install independently and must carry matching distribution versions. The core contract version and the adapter contract version are separate domains: the core is at 5, the adapter is at 2, and the legacy work-claim, ledger-publication, and ledger-mutation envelopes stay at 1.

Installation, compatibility, and security

Installation routes

Wowbagger ships as an npm package with a single wowbagger binary. There are two supported install routes:

  • npm registrynpm install -g wowbagger installs the stable release once it exists; npm install -g wowbagger@next installs the newest prerelease. While every published release is a prerelease, latest mirrors next, so a bare install resolves to the same bytes. After the first stable release, latest stays on stable and only next follows later prereleases.
  • git tagnpm install -g github:lstutzman/wowbagger#v0.5.0 installs this release. Installing at a ref installs the core and every adapter that ref carries.

Either route installs the core and the wowbagger command. The Claude Code plugin is a separate artifact (see Start here).

Compatibility

The contract version is top-level contract_version, reported by wowbagger capabilities --json. Contracts change it; refactors do not. The npm/Git distribution version names release bytes. General API consumers negotiate the contract version. The shipped plugin skill additionally requires the exact core distribution version that shipped with it, because its instructions can depend on additive behavior from that release.

A widening inside one contract version does not move it, so the version field cannot answer every question. set.body_append and set.extensions both shipped after 0.1.0-alpha.5 published contract_version: 3. Probe for them by sending the request and reading the refusal — an unknown-member issue at /set/body_append means the core predates the append — or pin the distribution version.

  • Node.js: 24 and later. The release gate runs the full suite on Node 24.20.0 with the strict deprecation gate; Node 26 stays excluded because of the separate Vitest incompatibility.
  • Platforms: the core runs wherever Node.js runs. The Claude Code adapter declares Darwin, Linux, and Windows supported from native common-vector evidence. Every other shipped adapter target remains unverified.
  • Other tooling: wowbagger manages a Git-tracked Markdown ledger. It needs an accessible Git checkout for work-claim and namespace operations. Before provision, run wowbagger claim capabilities --ledger <dir> --json and require result.operations.work_claim.supported: true.

Security

  • Read-only by default. validate, ready, report, inspect, list, capabilities, and mint-id never modify anything. Every item mutation (create, transition, parent-migrate, snooze, patch, and publish-claimed) is an explicit, reviewable write.
  • Lock is not a claim. A short mutation lock serializes writers during one operation. It does not grant a work claim.
  • Claims are merge-coordinated, not exclusive. claim acquire uses compare-and-swap against the observed claim state. publish-claimed checks the active owner generation and expected ledger revision before it writes one item. claim-verify records the final Git outcome and detects later revision drift. Legacy create and transition refuse claim conflicts.
  • Adopting a revision is an operator ruling, not an escape hatch. claim-adopt moves the authorized revision to one named committed revision and stops. It writes no item byte, it is per item and per revision explicit, there is no adopt-all, and the next out-of-protocol edit refuses again.
  • Local authority only. The protocol protects cooperating worktrees in one Git repository. It does not stop direct filesystem writes, hostile processes, other clones, or alternate write paths. Capability discovery therefore reports mode: "merge-coordinated" and safe_exclusive_dispatch: false.
  • Supply chain. Install only from the npm registry or this repository's git tags, and verify the contract_version your adapter or script requires.

This README is documentation, not a substitute for the contracts. The machinery behind these properties is specified in the documents listed under Where the contracts live.

Upgrading from an earlier wowbagger

This section is written for agents as much as humans: if you already drive a wowbagger core, this is how you move forward safely.

Upgrade the pieces you installed:

npm install -g wowbagger@latest                  # public npm registry
npm install -g github:lstutzman/wowbagger#v0.5.0  # immutable Git release
git pull && npm ci                            # or: a direct checkout

In Claude Code, update the plugin the same way it was installed:

/plugin marketplace update wowbagger
/plugin update wowbagger@wowbagger
wowbagger --version
wowbagger capabilities --json

The plugin requires its exact core distribution version and top-level core contract_version: 5. Direct API consumers must check the contract version they support; installed plugin users must also keep the plugin and core distribution versions equal.

The shipped adapter selects only adapter contract version 2 and requires core contract version 5. The adapter contract and the core contract are separate version domains: the adapter stays at 2 while the core is at 5. A v1-only consumer receives unsupported-adapter-contract-version; it does not receive v2 behavior. The schema-2 transport is available. Ledger migration remains a separate quiesced maintenance operation. The schema-2 migration runbook documents the required backup, dry run, explicit --apply, lock refusal, and recovery procedure. The tool is dry-run-only by default:

TMPDIR=/tmp node scripts/migrate-schema-2.js --ledger path/to/ledger

Behaviour changes are recorded in CHANGELOG.md — read its Unreleased section on every upgrade. If you automated against an earlier core, these are the changes most likely to touch you:

  • Stop hand-editing frontmatter. wowbagger patch now covers title, priority, depends_on, related, the body, and every extension member the ledger declares, all under the same per-ID lock and revision compare-and-swap as transition. Hand-edits bypass validation and atomic publication, and on a provisioned ledger they block the next mutation.

  • number is not yours. On schema version 2 the core assigns the number at create and refuses a caller-supplied one; patch refuses it because it is immutable identity. Keep a legacy identifier in a declared extension member or in the item body.

  • Repair duplicate numbers through ledger-repair version 1. Generate a read-only proposal with number-repair-proposal, review every expected_revision and replacement_number, then apply the complete mapping with number-repair. The command preserves ULID identities and relations and does not change core contract version 5.

  • Run version-drift --json before mutation. It compares the installed skill pin, required core contract, and running core, and names the stale package, plugin cache, or linked checkout with remediation.

  • Delete your local ULID generator. wowbagger mint-id --json prints a canonical ID; --date YYYY-MM-DD selects the creation date the ID must encode.

  • Read core.number and core.priority from results instead of decoding source_base64. Every frontmatter field lives under item.core; item.id is the one deliberate duplicate.

  • ready without --json is for you to read: #number pri=priority title per line, in ready order. Machine consumers keep ready --json, which is byte-stable.

  • A claim request with an own __proto__ member is now refused as invalid-request instead of silently losing the member.

  • create tells you where the item landed: results report core.status: "triage", and the refusal for a caller-supplied status names the accepting transition (triage to backlog) that makes an item ready.

  • Bind a subdirectory layout in the ledger, not in each runner. Commit <ledger>/.wowbagger/layout.json with {"layout_version":1,"items_directory":"items"}. create then derives <ledger>/items/<id>.md; validation rejects parsed items outside items/. Without the file, the compatible layout remains <ledger>/<id>.md.

  • A date refusal now carries the item's own dates. date-before-created and date-before-updated both carry item_created and item_updated, so correcting the request costs no inspect round-trip. Item dates derive from the ULID timestamp, which is UTC: an item minted just after midnight UTC carries tomorrow's date for anyone west of UTC.

The problem

Coding agents lose context. They are restarted, compacted, moved between worktrees, or replaced by a different model. A useful backlog therefore cannot live only in one conversation or one harness's private state.

Wowbagger makes the repository the durable coordination boundary:

  • One inspectable Markdown file per backlog item.
  • YAML metadata for lifecycle, priority, dependencies, and structured provenance.
  • Git history as the audit log and recovery mechanism.
  • Dependency-aware ready queues so an agent can ask what is actionable now.
  • Guarded one-item creation, patching, and lifecycle transitions with exact-byte revisions, cooperative locks, and explicit refusal when a change needs a multi-item transaction.
  • A documented adapter boundary for tool-capable agent harnesses, without coupling the core to one vendor.
  • Mechanical validation and derived reports instead of duplicated status data.

Harness-neutral by design

Claude Code is an adapter, not the architecture. The core schema and command interface will not depend on Claude-specific hooks, slash commands, paths, or environment variables.

flowchart TD
    Claude[Claude Code adapter] --> Core[Wowbagger core]
    Codex[Codex adapter] --> Core
    OpenCode[OpenCode adapter] --> Core
    Other[Kimi and other tool-capable agents] --> Core
    Core --> Markdown[Markdown and YAML backlog]
    Core --> Git[Git audit and history]

The documented compatibility targets are:

  • Claude Code
  • OpenAI Codex
  • OpenCode
  • Kimi and other OpenAI-compatible model APIs hosted in agent harnesses that provide repository filesystem and command-execution tools

An OpenAI-compatible API describes model transport; it does not by itself provide agent tools. The adapter contract records the required host capabilities and refusal rules, and the integration guide states what a Kimi or other OpenAI-compatible host can do today — driving the core CLI directly — versus what a verifiable compatibility claim requires. Neither claims that API compatibility alone makes a harness compatible.

This checkout ships three adapter packages on one shared entrypoint runtime: adapters/claude-code/, adapters/codex/, and adapters/opencode/. Each answers the bootstrap wire with its own identity and honest host declaration. Invocation forwarding, path and limit guards, approval, and context all enter through the shared shipped engine. Run the conformance suite to see the evidence:

TMPDIR=/tmp node spec/run-adapter-implementation.js                     # claude-code
TMPDIR=/tmp node spec/run-adapter-implementation.js --target codex
TMPDIR=/tmp node spec/run-adapter-implementation.js --target opencode

The native Darwin and Linux Claude Code reports each pass all 212 assertions across all 16 cases and report "status": "pass". Codex and OpenCode execute the same 212 assertions through the same engine, but both target reports remain "fail" pending target-specific evidence, and every platform declaration on those two manifests stays unverified. The Kimi and OpenAI-compatible harness adapters are not written.

All three shipped packages are read-only as they stand, and say so. None wires a consumer approval source, so each declares no trusted approval and refuses create, transition, and patch with capability-unavailable naming the missing capability, before any core process starts. Mutation authority is a runtime dependency a host supplies in code: a process that embeds runAdapterEntrypoint passes hostRuntime — the approval source, the clock, the redeemed-nonce store, and the core executable identity it attests — and its describe result then advertises trusted approval truthfully. The approval never rides the bootstrap request, which the model controls; docs/adapter-contract.md section 5.1 states the mechanism and its rules.

Core commands

The current core requires Node.js 24. Node 26 is not in the supported matrix. From a Wowbagger checkout, ./bin/wowbagger.js --help prints the full command inventory, ./bin/wowbagger.js <command> --help prints that command's usage, and ./bin/wowbagger.js --version prints the installed package version. The commands below are the current inventory:

npm ci
./bin/wowbagger.js validate --ledger path/to/ledger --json
./bin/wowbagger.js ready --ledger path/to/ledger --as-of 2030-01-15 --json
./bin/wowbagger.js ready --ledger path/to/ledger --as-of 2030-01-15
./bin/wowbagger.js report --ledger path/to/ledger --as-of 2030-01-15 --json
./bin/wowbagger.js capabilities --json
./bin/wowbagger.js inspect --ledger path/to/ledger --id wb_... --json
./bin/wowbagger.js inspect --ledger path/to/ledger --number 30 --json
./bin/wowbagger.js list --ledger path/to/ledger --input query.json --json
./bin/wowbagger.js create --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js transition --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js parent-migrate --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js snooze --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js patch --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js extensions-provision --ledger path/to/ledger --input declaration.json --json
./bin/wowbagger.js mint-id --json
./bin/wowbagger.js publish-claimed --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js claim-merge-verify --ledger path/to/ledger --base main --head feature --json
./bin/wowbagger.js claim-sync --ledger path/to/ledger --json
./bin/wowbagger.js claim-adopt --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js mutation-finalize --ledger path/to/ledger --recovery-token token --json
./bin/wowbagger.js provision --ledger path/to/ledger --json
./bin/wowbagger.js claim capabilities --ledger path/to/ledger --json
./bin/wowbagger.js claim acquire --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js claim read --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js claim renew --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js claim release --ledger path/to/ledger --input request.json --json
./bin/wowbagger.js claim-verify --ledger path/to/ledger [--id wb_...] --json

validate writes exactly one JSON result to standard output. A valid ledger returns:

{"valid":true,"errors":[]}

ready validates first, then returns only the normative ready result:

{"as_of":"2030-01-15","valid":true,"ready":["wb_..."]}

validate and ready require --ledger; ready also requires an ISO calendar --as-of date. Without --json, ready prints a human queue — #number pri=priority title per ready item — while ready --json stays byte-stable for machine consumers. Invalid ledgers return the validation JSON and exit nonzero. The core rejects invalid UTF-8, symbolic-link entries, unreadable paths, and .md special files rather than returning a partial view. Real directories ending in .md remain containers and are traversed. These checks provide deterministic read hygiene; they are not a sandbox against a privileged process racing filesystem changes.

inspect returns a lossless raw-byte snapshot and its SHA-256 revision, by --id or by --number. create publishes only a caller-supplied canonical ID through atomic no-clobber publication — mint-id prints one, so no consumer writes base32 by hand. transition compares the inspected revision while cooperative per-ID locks are held, then changes one lifecycle item or refuses the request if dependent cleanup or child disposition would require changing another item.

What patch may change

patch re-scopes an existing item in band, under the same per-ID lock, exact-byte compare-and-swap, candidate whole-ledger validation, and atomic publication as transition. The patchable set is exactly:

| set member | Rule | |---|---| | title | Non-empty string, replaced whole. | | priority | Non-negative integer; null removes it. | | depends_on | Whole relation list, replaced. [] clears it. | | related | Whole relation list, replaced; null removes it. | | body | Whole body replaced. "" empties it. Never merges. | | body_append | Written after the current body. Mutually exclusive with body. | | extensions | Container naming declared extension members; each value replaces that member whole, null removes it. |

A set member outside that list is an invalid-request issue at its /set pointer — a typo becomes a refusal, never a new frontmatter member. number, kind, and provenance are refused deliberately.

Two traps are worth carrying here rather than leaving to discovery. A body patch replaces and never merges: a consumer mirroring an external source MUST read-modify-write from the body inspect just returned and MUST never regenerate from the source alone, because expected_revision is a byte-level lost-update guard with no semantic safety at all. Use set.body_append when the change is an addition. And extension members are patchable only where the ledger declares them — see Set the ledger up before the first item.

Which members you own at all is a four-way split — core-owned, consumer-editable through patch, mutable through a dedicated command, and create-once — stated member by member in the mutation contract's frontmatter ownership table. Use parent-migrate to repoint an existing item to or from an epic, and snooze to set or clear snoozed_until. Read the table; do not send a patch and interpret the refusal.

An epic's progress is derived, never stored

An epic stores no progress and has no backlog -> in-progress edge. Its progress is the terminal ratio: direct children whose status is done or killed, over all direct children. That is one definition with three surfaces — the mutation contract, the epic completion rollup, and the report's epic-enablement factor all read the same set. A terminal date is not the test: an archived or deferred child is work postponed, not work retired, and does not count.

Diagnosing an invalid ledger

One invalid item refuses every read and every guarded mutation on that ledger, including commands that never touch it. That refusal is the diagnosis, and no command asks you to parse the Markdown by hand:

  • validate --json lists every error. An error whose repair the validator can derive also carries expected_path and a remediation naming the repair.
  • inspect still refuses with exit 3 ledger-invalid — a revision from an unjudged ledger must never look like a mutation precondition — but the refusal carries error.details.item, the complete snapshot of the item you asked for, whenever no validation error names that item's path. A faulted item is withheld; validate already names its repair.
  • claim-verify --json reports result.ledger_validation. Bare verification is strict repository-wide mode; --id <item> keeps all findings visible but fails only for that item and global barriers. Exit 0 with an invalid ledger_validation still means claim state is clean but validation blocks mutation.

Work claims

provision binds one ledger namespace to the repository. claim manages durable acquire, read, renew, and release decisions. publish-claimed accepts the exact candidate item bytes and fences their publication against the active owner generation and expected revision. claim-verify reconciles pending publication outcomes against the working tree and Git HEAD; run it after a claimed publication is committed or merged, and before the next fenced operation. claim-adopt is the non-destructive remedy described below.

Core and work-claim versions use distinct negotiation fields. Read the top-level contract_version from core capabilities. Read result.operations.work_claim.api_version from claim capabilities --ledger <dir> --json. A claim response's top-level contract_version is the legacy claim-envelope marker; do not compare it with the core version. A contract consumer that receives an unsupported version refuses rather than guessing. The shipped plugin skill also requires its exact core distribution version. Direct checkout use — ./bin/wowbagger.js from a clone — remains supported and is what this repository's own ledger uses.

Commit each mutation before the next one

On a provisioned ledger — one where provision has bound a namespace and claim capabilities reports mode: "merge-coordinated" — there is one operating rule:

Commit each mutation to Git before running the next mutating command.

The durable claim store reconciles every recorded mutation with Git HEAD and the working tree. The next mutation refuses when that reconciliation finds an unauthorized-revision, requires Git finalization, or requires synchronization for the item the command targets. A synchronization finding on an unrelated item remains visible to claim-verify but does not block the command.

An existing item's latest authorized working-tree bytes and an earlier authorized revision at HEAD form an authorized predecessor/successor window. That window produces no finding, so another mutation can run before the first one is committed. Acceptance of the later mutation does not make either change durable. Commit each mutation anyway, then run claim-verify.

The loop that works:

./bin/wowbagger.js create --ledger path/to/ledger --input request.json --json
git add path/to/ledger && git commit -m "Record the mutation"
./bin/wowbagger.js claim-verify --ledger path/to/ledger --id wb_... --json
./bin/wowbagger.js transition --ledger path/to/ledger --input next.json --json

For example, an authorized new item that is still absent from HEAD makes the next command return exit 6:

{"ok":false,"namespace":"ledger-mutation","command":"create-v1","contract_version":1,
 "state":"unchanged","error":{"code":"claim-store-unavailable",
 "message":"The durable claim store is unavailable.",
 "details":{"reason":"publication-reconciliation-required","findings":[{
   "code":"stale-write-detected","reason":"git-finalization-required",
   "expected_path":"wb_....md",
   "remediation":"Commit wb_....md in Git, then run claim-verify."}]}}}

state: "unchanged" is exact — nothing was written. claim-verify is the reconciliation procedure. Read details.findings, do what each remediation string says, run claim-verify until it returns exit 0, then repeat the refused command. A worktree-synchronization-required finding on an unrelated item does not block the requested mutation. The same finding on the target item, and every unauthorized-revision finding, remains blocking.

Batch work is where this bites. Filing ten items means ten serial create --auto-commit calls and ten commits, not one batch commit. Wowbagger permanently rejects batch create for the direct-Markdown architecture: limits.multi_item_atomicity remains false, request order is the supported bulk order, and each create must finish or recover before the next begins. See the batch-create decision.

Or fold the commit into the mutation

--auto-commit performs that whole loop inside one invocation, on a provisioned ledger only:

./bin/wowbagger.js transition --ledger path/to/ledger --input next.json --json --auto-commit

It is opt-in per invocation. There is no configuration setting or environment default, because a hidden default would make existing automation create Git commits unexpectedly. The flag is accepted on create, transition, parent-migrate, snooze, patch, and publish-claimed.

What one flagged invocation does: refuse if anything is staged anywhere or any foreign path under the ledger is dirty; reconcile; run the mutation unchanged; commit exactly the changed item and at most one .wowbagger/reconcile-<namespace>.md with a fixed subject such as wowbagger: transition item #7; verify the commit; then run claim-verify before it answers. A command that owns the claim journal may rebuild only its derived reconciliation log during preflight. create remains strict, and every other dirty ledger path still refuses. On success the result gains git_commit, commit_paths, and claim_verified.

If claim verification refuses, auto-commit preserves its code and reason in claim_verify_code and claim_verify_reason. Only claim_verify_reason: "claim-store-locked" is retryable; unresolved reconciliation is not.

Unstaged and untracked files outside the ledger are left alone. Hooks and signing are honoured; --no-verify is never passed. Nothing is pushed.

A refused mutation never commits. When the item is published but the commit fails, the answer is exit 6 git-commit-failed with a recovery token, and one idempotent command finishes the job:

./bin/wowbagger.js mutation-finalize --ledger path/to/ledger --recovery-token <token> --json

Repeating it creates no second commit, so a lost response and a failed commit recover the same way. docs/mutation-contract.md section 13 is the full contract.

When the response is lost

A mutation you dispatched can lose its response: the process is signalled or times out, a stream arrives truncated, or the transport to the machine that owns the ledger drops. None of that observes the ledger, so none of it says whether the write applied.

Only a complete observed result establishes an outcome. Exit 0 with state committed is a success; a complete refusal with state unchanged is a proven non-write; exit 6 post-commit-recovery-required says the item is published and cleanup remains; exit 6 write-outcome-unknown says publication was attempted and the visible bytes are indeterminate. Anything else — signal, timeout, truncated output, no envelope, no response at all — is unresolved.

Unresolved is answered by one sequence, never a retry: Dispatch once, never replay, invalidate the inspected revision, reconnect, then re-read the ledger. Re-reading is validate plus inspect of the ID you already know, compared against what you observed before you dispatched. What you read back is current ledger state; it never proves that the lost dispatch caused it. A person reviews that comparison before any new mutation, and the new mutation is built on the current revision, not resent.

Exit 4 is not response loss. A revision-conflict proves the write did not run; re-inspect and decide again.

There is no operation ID and no replay endpoint, because nothing replays. docs/mutation-contract.md section 10 is the full contract.

When the item was changed outside the protocol

An unauthorized-revision finding means someone edited the item without going through a guarded verb. There are two remedies, and choosing between them is an operator's decision, not the agent's:

  • Restore the authorized revision, commit, and run claim-verify. This discards the edit.
  • Adopt the committed revision with claim-adopt, then run claim-verify. This keeps the edit and moves the authorized revision to it.
./bin/wowbagger.js claim-adopt --ledger path/to/ledger --input adopt.json --json

Adoption is per item and per revision explicit — the request names the item, the revision it believes is authorized, the revision being adopted, and who is ruling. There is no adopt-all. It writes no item byte, so updated and the body survive exactly, and it appends one revision-adoption journal entry so the audit trail records the ruling. It refuses a stale witness, an unexpired claim on the item, a revision that is not at Git HEAD and in the caller's own working tree, and a ledger that would not validate. Adoption is not a fence hole: the next out-of-protocol edit refuses again, measured against the adopted revision.

Full rules, the other blocking finding codes, and why validating against working-tree bytes was rejected are in the mutation contract section 12 and the work-claim contract sections 3.2 and 3.3.

The HTML report

report validates the complete ledger, reads .wowbagger/report.json, and atomically publishes one self-contained HTML file. The output must be outside the ledger. Relative configured output paths resolve from .wowbagger/; relative --out overrides resolve from the caller's working directory.

{
  "report_version": 1,
  "repository": { "name": "Example repository", "logo": "logo.svg" },
  "title": "Ledger report",
  "output": "../../ledger-report.html",
  "fields": {
    "area": "/priority_area",
    "complexity": "/complexity",
    "rank": "/priority_rank",
    "class": "/class",
    "due": "/due",
    "tags": "/tags"
  },
  "swarm": { "eligible_complexities": ["small", "medium"] }
}

repository.logo, fields, and swarm are optional. Field values resolve from parsed frontmatter with RFC 6901 JSON Pointers. A swarm requires mapped area and complexity fields. The report fetches nothing at view time.

tags is the one multi-value mapped field. It accepts a nonempty string or an array holding only nonempty strings: a scalar reads as a one-tag set, exact duplicates collapse, and values sort deterministically. The mapping never splits commas, lowercases values, coerces objects, or partially accepts a mixed-type array. An empty array counts as missing; any other rejected value is omitted from the item and counted as invalid metadata. area stays scalar. The model carries fieldCoverage, one entry per configured field plus area and tags when unmapped, ordered by field name with present, missing, and invalid counts over the retained report population. An unmapped field counts every retained item as missing, and a visible missing-mapping notice tells an unconfigured mapping apart from missing item values. Missing metadata never matches a filter value: there is no Unclassified bucket, so a filter for a literal Unclassified tag matches only items really carrying that tag.

The report is a decision-focused workspace, not a state snapshot. It has three sections behind accessible view navigation: Items (the default), Flow, and Dependencies. Only the selected section is visible when scripting runs; without scripting, every section stays readable through its anchor and native details elements, and the artifact states its fixed scope.

Items opens with the state counts, then a sticky control strip: search, five quick views (Work next, In progress, Blocked, Needs triage, and All open), and the display controls (grouping, sorting, Basic/Standard/ Detailed, Show history, Expand all, Collapse all). Below 1100px the display controls fold behind a Display toggle so search and quick views stay in reach. Search and the facet groups form the scope; the scope narrows the summaries, Flow, and Dependencies alike. Quick views and Show history change only the list. Work next keeps its recommended order and prints the reasons beside each row; any other sort presents itself as that sort.

There is one list and one canonical detail per retained item. Desktop widths use a list/detail split; narrower widths show the selected detail inline. Opening a detail never clears the search, the facets, or the list position, and a detail opened from Flow or Dependencies returns to Items with the scope intact. Expand all acts on visible rows only.

The filters are facet groups: Readiness, Status, Kind, Priority, and one group for every configured mapped field, each a fieldset of checkbox chips behind a collapsed Filters control. Values inside a group are alternatives and groups narrow each other; the search box is one more condition on the same answer. Every chip carries the count it would leave, measured against the search and the other groups but never against its own. Missing metadata is its own Missing chip, distinct from a literal Unclassified value. The result count states how much of the retained set is showing, and Clear filters gives every selection back.

Above the list sit scoped summaries that open exact contributing items through a labelled drilldown pill: attention actions (in progress, blocked, needs triage, oldest, and started work past this ledger's own 85th-percentile cycle time), an area/status matrix with count and blocked count per cell, and Scoped members of existing batches, which intersects the area-diverse batches with the scoped ready set and omits empty batches. Each item detail states its downstream reach (transitive dependents in the report) and, separately, the items that become ready if done; neither alters core readiness or the recommended order.

Flow

Flow recomputes from the scoped open and terminal population in the browser: cumulative flow, weekly arrivals against closures with done counted separately, throughput with a four-week mean, current aging by status, acceptance-to-completion samples, and the closure forecast. Inclusive From and To controls default to the twelve-week window; a start after the end, or an end after the report date, is refused with a visible error while the last valid charts stay. Weekly buckets, aging cells, completion samples, and cumulative date/band selections each drill into the exact contributing items, and the accessible tables offer the same actions. The forecast is computed only when Flow first opens and cached by cohort and range. Missing acceptance history is stated as reconstruction uncertainty; an item killed straight from triage is complete history, not a gap. The fixed server-rendered charts remain for readers without scripting, each with role="img" and an aria-label that states its finding in words.

The ledger graph

Dependencies draws the scoped items as a force-directed 3D graph. Every item is a node, labelled #N, coloured by readiness for open items and by terminal status for closed ones, and sized by the same transitive unblocking leverage the recommended order uses. Edges run from a prerequisite or a parent to the item it releases: a depends_on edge is straight and arrowed, a parent edge is curved and unarrowed. Hovering a node opens a card with its number, title, status, age, leverage, and reasons; clicking it, or a roster row, opens the canonical detail in Items. Downstream and ready-if-done actions on the roster drill into the same sets the detail names.

The graph has no filter of its own: it follows the shared scope, so a scope change drops nodes, every link that touched one of them, and their labels together, then reheats the layout in place. A hidden blocker never turns a retained item ready, because readiness is projected over the complete ledger before any view narrows it. The graph starts only when Dependencies first opens, pauses while another section is shown, and an empty scope draws an empty graph that says so.

The renderer is 3d-force-graph over Three.js, vendored into vendor/3d-force-graph/ at a pinned version 1.80.0 with its upstream SHA-256 recorded in vendor/3d-force-graph/VERSIONS.json and pinned by a test. It is inlined into the report at generation time. Nothing is fetched from a CDN, at generation time or at view time, and the report's content security policy forbids every remote load, connect-src included. The bundle costs roughly 1.3 MB of the report's size; the report stays one self-contained file you can attach, open offline, and share.

Without WebGL the graph section says so and expands its own roster instead: one row per node, carrying that node's number, title, status, age, leverage, and reasons. No decision-relevant content exists only in the 3D view.

The recommended order is a report-layer derivation. It is recomputed from ledger bytes at render time, never persisted, never a mutation, and it does not change ready: the core still selects and sorts by priority, created date, then ID. Ordering runs as separate, visible steps rather than one opaque score — expedite class, then due proximity, then transitive unblocking leverage over depends_on, then epic enablement from parent, then priority, then age, then the mapped complexity as a WSJF-style size denominator — and every step that placed an entry is printed beside it.

Two mapped fields carry the value dimensions the schema deliberately does not:

  • class — a class of service, one of expedite, fixed-date, standard, or intangible. expedite lifts an item above every other ready item. An absent value means standard. An unrecognised value is ranked as standard and reported by number in the report, never silently dropped.
  • due — an ISO calendar date. The nearest due date sorts first and an overdue one sorts first of all; an item with no due date sorts behind every dated one at that step.

Both ride the ordinary extension-member channel, so the core neither reads nor validates them. complexity weighs xs/s/small as 1, m/medium as 2, l/large as 3, and xl/extra-large as 5; any other value carries no weight and is shown as written.

Named custom report views

A named custom view is a second self-contained report generated from the same complete ledger. Every section of it — statistics, Work next and the other quick views, the Attention summaries and area/status matrix, dependency impact, Flow, the graph, the drill-down pill, terminal history, and the swarm batches — describes one configured subset, so the file is honest to share as a scoped report. Excluded items are absent from the bytes rather than hidden by a stylesheet. The base report stays available and unchanged.

Report configuration report_version: 2 accepts every version 1 member and adds one more, views. A version 1 configuration keeps generating its base report unchanged, and a version 2 configuration with no view selected publishes the same base report from its inherited base members:

{
  "report_version": 2,
  "repository": { "name": "Example repository" },
  "title": "Ledger report",
  "output": "../../ledger-report.html",
  "fields": {
    "area": "/priority_area",
    "complexity": "/complexity",
    "class": "/class",
    "security": "/security"
  },
  "views": {
    "security-blockers": {
      "title": "Security blockers",
      "output": "../../reports/security-blockers.html",
      "filters": {
        "readiness": ["blocked"],
        "status": ["backlog", "in-progress"],
        "kind": ["task"],
        "fields": {
          "class": ["bug"],
          "security": ["high", "critical"]
        }
      }
    }
  }
}

Generate one view by name:

wowbagger report --ledger <dir> --view <name> --as-of YYYY-MM-DD --json

One invocation generates one artifact; no flag generates them all. --out <file> overrides the selected output for that invocation, view or base alike, and the configured paths are still validated when it is present.

A view name is a portable identifier matching ^[a-z][a-z0-9-]{0,63}$. Names are case-sensitive and views holds at most 64 views. Each view takes exactly title, output, and filters. Unknown members fail closed.

filters takes exactly readiness, status, kind, and fields, and at least one of readiness, status, kind, or fields must be present. The semantics are the drill-down's: OR within one filter group; AND across groups, so readiness: ["blocked"] with class: ["bug"] means blocked bugs. readiness takes ready, blocked, or ineligible; status takes triage, backlog, in-progress, done, killed, archived, or deferred; kind takes task or epic.

A fields key must also be a configured report field. Each field filter is a non-empty array of unique JSON strings, finite numbers, or booleans, and matching preserves JSON scalar type and value: stringification is not equality, so a mapped 2 does not answer a filter for "2". An item carrying no mapped value for a field matches no value selected for that field. A tags filter uses any-member matching, so one item carrying two tags answers either tag. No title-text inference, regular expression, arbitrary JSON pointer, or body search belongs in a view filter.

Wowbagger validates the complete ledger and computes readiness against the complete ledger before it filters, so excluding a blocker never makes blocked work read as ready. What a view derives from its retained set does change: statistics, ranking leverage, epic enablement, evidence, attention, graph membership, and the swarm batches are all view-scoped, so the same item can report smaller numbers here than in the base report. That is why the artifact names its view and its fixed criteria at the top. A retained item that names an excluded dependency, parent, blocker, or related item still prints that item's number instead of a raw ULID, and the excluded item gets no row, no graph node, no history entry, and no hidden payload.

Inside the file, the interactive facets and the graph status chips narrow the retained subset further and can never reveal an excluded item. Clear filters restores the complete custom-view subset, never the base ledger.

The base output and every view output must be pairwise distinct after path resolution, and each must resolve outside the ledger under the same no-follow containment rule the base output already obeys. A colliding or contained output is report-config-invalid before anything is rendered, --out present or not.

A named success adds exactly one member to the existing report result, result.view, and its item_count and ready_count describe the filtered subset. A base report's result gains no view member:

{
  "ok": true,
  "command": "report",
  "contract_version": 5,
  "result": {
    "report_version": 2,
    "as_of": "2026-08-21",
    "output": "/absolute/reports/security-blockers.html",
    "item_count": 12,
    "ready_count": 0,
    "view": "security-blockers"
  }
}

--view requires report_version: 2. A missing or unknown name is report-view-not-found at exit 2 and leaves every existing output untouched. An invalid filter value, an unmapped fields key, and a colliding output are report-config-invalid at exit 2. An empty matched subset is not a failure: it publishes a valid report with zero items and explicit empty-state copy. Failed publication preserves the prior artifact, so never read an existing output as fresh. wowbagger capabilities --json advertises the whole surface at result.operations.report, so no consumer has to probe by generating a file.

A custom view is scoped output and not a security boundary: it applies no redaction and no access control, and the artifact states plainly that it is a filtered subset of the named repository ledger. Automation reads the JSON result, never the generated HTML and never human output.

This repository keeps its report configuration in ledger/.wowbagger/report.json. Generate the ignored local report with the current UTC date:

npm run report -- --as-of YYYY-MM-DD

If you verify the report in a browser from a checkout, generate a deterministic synthetic report through the real pipeline:

node scripts/report-design-demo.js --out /private/tmp/wowbagger-report-demo.html --items 40

That output is synthetic and checkout-only: it describes fixed demo data, never this repository's ledger.

Where the contracts live

The README is the map. These are the territory, and they are normative where they disagree with anything above:

| Document | What it rules | |---|---| | SPEC.md | The ledger schema, validation rules, and ready selection. | | docs/mutation-contract.md | Response domains and dispatch, capabilities, inspect, create, transition, patch, the extension declaration, the frontmatter ownership table, errors and recovery, and commit-per-mutation. | | docs/work-claim-contract.md | Provisioning, claim CAS rules, claimed publication, reconciliation, revision adoption, and the difference between strict fenced and merge-coordinated backends. | | docs/adapter-contract.md | The harness adapter boundary: negotiation, forwarding, guards, approval, and honest outcome mapping. | | docs/host-contract.md | The direct-core host boundary: package resolution, the shell-free process tuple, bounded transport, owning-host paths, response dispatch, advertised limits, and the packaged JSON Schemas. | | docs/openai-compatible-integration.md | What an OpenAI-compatible host can do today, and what a compatibility claim would require. | | docs/schema-2-migration.md | The quiesced schema version 1 to 2 migration runbook. | | skills/wowbagger/SKILL.md | The shipped agent instructions. |

Where contract prose and a fixture disagree, the fixture is normative.

Working on wowbagger

This section is the contributor's route from "I want to help" to a merged change.

The ledger is the backlog

There is no separate issue tracker. The repository's real backlog is ledger/, and it is driven with the tool itself:

npm ci
./bin/wowbagger.js validate --ledger ledger --json
./bin/wowbagger.js ready --ledger ledger --as-of YYYY-MM-DD    # human queue
./bin/wowbagger.js inspect --ledger ledger --number 30 --json  # the item you picked

Replace YYYY-MM-DD with the current UTC date. ready prints #number pri=priority title per line in ready order. The acceptance criteria live in the item body, not in the metadatainspect and read result.item.body.

Items are created into triage and reach backlog only through an explicit accepting transition with a recorded decision. This repository dogfoods layout.json, so its items live in ledger/items/.

Which contributions are useful now

  • Reproducible defects. A failing command, its exact JSON envelope, the exit status, and the smallest ledger that shows it.
  • Dogfood friction. Anything the tool made harder than doing it by hand. That is a defect worth recording, not a personality flaw of the user.
  • Documentation drift. A claim in this README, a contract, or the skill that HEAD does not support. Name the claim and the source that refutes it.
  • Platform evidence. A native conformance run on Linux or Windows is what moves an adapter platform declaration off unverified. Attach the report.
  • Portability and coordination requirements. Concrete constraints from a real harness beat speculative API surface.

Prefer an adapter to a core change when a requirement is harness-specific. File the finding as a ledger item rather than leaving it in a transcript.

The verification gate

Four commands. All four must pass, and the test commands run on Node 24.20.0:

TMPDIR=/tmp /opt/homebrew/opt/node@24/bin/node --test test/*.test.js
TMPDIR=/tmp /opt/homebrew/opt/node@24/bin/node --pending-deprecation --throw-deprecation --test test/*.test.js
TMPDIR=/tmp /opt/homebrew/opt/node@24/bin/node spec/run-adapter-implementation.js
TMPDIR=/tmp /opt/homebrew/opt/node@24/bin/node bin/wowbagger.js validate --ledger ledger --json

TMPDIR=/tmp is not optional: the default macOS temporary path makes the claim lock socket path too long. Use an explicit Node 24.20.0 binary path.

The supported runtime matrix is Node 24.20.0. Node 26 remains excluded until the separate Vitest incompatibility reported by Lee is resolved.

npm test, npm audit --omit=dev, and git diff --check are useful alongside it; they are not a substitute for the four commands above.

The rules that are not negotiable

  • The oracles are independent. spec/adapter-reference.js and test/work-claim-reference.js are separate re-implementations that conformance tests compare against. src/adapter/ deliberately re-implements the first rather than importing it, and src/claim-request.js has the same arrangement with the second. Never import an oracle into src/, and never change an oracle to match an implementation. Collapsing either pair into a shared implementation would make its conformance tests p