wowbagger 0.1.0-alpha.9 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +509 -0
  2. package/README.md +272 -136
  3. package/docs/adapter-contract.md +1 -1
  4. package/docs/host-contract.md +7 -1
  5. package/docs/mutation-contract.md +354 -82
  6. package/docs/work-claim-contract.md +466 -88
  7. package/package.json +2 -2
  8. package/schemas/core-capabilities-response.json +1 -1
  9. package/schemas/core-envelope.json +4 -3
  10. package/schemas/index.json +18 -0
  11. package/schemas/ledger-repair-proposal.json +170 -0
  12. package/schemas/ledger-repair-request.json +61 -0
  13. package/schemas/ledger-repair-response.json +90 -0
  14. package/schemas/report-config-v1.json +4 -0
  15. package/schemas/report-config-v2.json +5 -0
  16. package/skills/wowbagger/SKILL.md +242 -59
  17. package/src/adapter/core-probe.js +3 -4
  18. package/src/adapter/process-outcome.js +8 -1
  19. package/src/claim-capabilities.js +3 -3
  20. package/src/claim-coordinator.js +61 -12
  21. package/src/claim-journal.js +212 -7
  22. package/src/claim-prospective.js +1 -28
  23. package/src/claim-publication.js +412 -78
  24. package/src/claim-request.js +9 -0
  25. package/src/claim-store.js +9 -4
  26. package/src/cli.js +302 -56
  27. package/src/extensions.js +1 -0
  28. package/src/git-autocommit.js +106 -43
  29. package/src/git-reconciliation.js +74 -19
  30. package/src/git-worktrees.js +73 -0
  31. package/src/instrumentation.js +1 -0
  32. package/src/launch.js +2 -2
  33. package/src/ledger-repair.js +1170 -0
  34. package/src/mutation.js +73 -15
  35. package/src/reconciliation-classifier.js +117 -0
  36. package/src/report-evidence.js +158 -41
  37. package/src/report-graph.js +201 -73
  38. package/src/report-html.js +358 -155
  39. package/src/report-impact.js +106 -0
  40. package/src/report-selection.js +97 -0
  41. package/src/report-sequencing.js +4 -4
  42. package/src/report-svg.js +74 -20
  43. package/src/report-view.js +14 -1
  44. package/src/report.js +109 -23
  45. package/src/version-drift.js +98 -0
  46. package/src/worktree-identity.js +165 -0
package/README.md CHANGED
@@ -30,16 +30,17 @@ agent to use those guarantees instead of hand-editing your Markdown.
30
30
 
31
31
  **Start here:** [install the core and set up a ledger](#start-here).
32
32
 
33
- > **Status: alpha, published, and self-hosted.** `0.1.0-alpha.9` is on npm under
34
- > the `next` tag and on this repository's `v0.1.0-alpha.9` tag. It is the
35
- > version this repository runs its own backlog on. The API is not frozen and the
36
- > version will move before a stable release.
33
+ > **Status: published and self-hosted.** `0.5.0` is published on npm and has
34
+ > a matching repository tag. It is the version this repository runs its own
35
+ > backlog on. The API is stable at core contract version 5.
37
36
  >
38
- > **Install with `@next`.** Every published release is a prerelease. The
39
- > registry requires a `latest` dist-tag, so `latest` mirrors `next` — a bare
40
- > `npm install wowbagger` resolves to the current prerelease rather than an
41
- > older build — but `@next` is the documented install and the explicit
42
- > statement that you accept a prerelease.
37
+ > **Channels.** A stable release sets both `latest` and `next` to the stable
38
+ > version and publishes with `npm publish --tag latest`. A later prerelease
39
+ > moves only `next` and publishes with `npm publish --tag next`, so `latest`
40
+ > stays on the stable release. While every published release is a prerelease,
41
+ > `latest` mirrors `next`: install the prerelease explicitly with
42
+ > `wowbagger@next`. After the first stable release, a bare install resolves
43
+ > to the stable release.
43
44
  >
44
45
  > **What is proved.** Contract version **5** validates the complete Markdown
45
46
  > ledger, selects a deterministic ready queue, exposes bounded `list` and
@@ -50,11 +51,12 @@ agent to use those guarantees instead of hand-editing your Markdown.
50
51
  > verification, and publication finalization coordinate cooperating writers
51
52
  > without pretending to be an exclusive dispatch lock.
52
53
  >
53
- > `report` is a self-contained sequencing dashboard: **Work next**, **Attention**,
54
- > facet filters, inline evidence, terminal history, area-diverse batches, and a
55
- > 3D dependency graph. Version 2 report configurations add named custom views
56
- > whose statistics, readiness, attention, evidence, graph, and drill-down all
57
- > describe one filtered subset. Reports remain derived output, not mirrored
54
+ > `report` is a self-contained decision workspace: one scoped item browser with
55
+ > **Work next** and other quick views, an area/status matrix, scoped attention
56
+ > actions, dependency impact, interactive Flow charts, and a 3D dependency
57
+ > graph that all share one scope. Version 2 report configurations add named
58
+ > custom views whose statistics, readiness, attention, flow, graph, and impact
59
+ > all describe one filtered subset. Reports remain derived output, not mirrored
58
60
  > ledger state.
59
61
  >
60
62
  > The core ships Claude Code, Codex, and OpenCode adapter packages on one shared
@@ -77,7 +79,7 @@ Wowbagger is the core authority for a Git-native work ledger. Use it instead
77
79
  of editing ledger Markdown by hand.
78
80
 
79
81
  ```sh
80
- wowbagger --version # require 0.1.0-alpha.9
82
+ wowbagger --version # require 0.5.0
81
83
  wowbagger capabilities --json # require contract_version: 5
82
84
  wowbagger validate --ledger ledger --json
83
85
  wowbagger ready --ledger ledger --as-of YYYY-MM-DD --json
@@ -86,10 +88,12 @@ wowbagger inspect --ledger ledger --number N --json
86
88
 
87
89
  For a write, inspect immediately before dispatch, send the returned exact-byte
88
90
  revision as the compare-and-swap witness, use the explicit core mutation, and
89
- validate again. Commit each provisioned-ledger mutation before the next one.
90
- Never replay a lost write: reconnect, re-read current state, and treat the
91
- outcome as unknown until the core or a human resolves it. Numbers are the
92
- human-facing item identity; `wb_...` ULIDs are internal identities.
91
+ validate again. On a provisioned ledger, commit each `create`, `transition`,
92
+ `parent-migrate`, `snooze`, `patch`, or `publish-claimed` mutation before the
93
+ next mutating command. Never replay a lost write: reconnect, re-read current
94
+ state, and treat the outcome as unknown until the core or a human resolves it.
95
+ Numbers are the human-facing item identity; `wb_...` ULIDs are internal
96
+ identities.
93
97
 
94
98
  The core owns validation, ready selection, projections, lifecycle, CAS,
95
99
  publication, claims, fencing, and reconciliation. The harness or host owns
@@ -98,13 +102,15 @@ cooperating writers; they are not exclusive locks.
98
102
 
99
103
  ## Start here
100
104
 
101
- Install the core CLI, then verify it. The core requires Node.js 20 or later:
105
+ Install the core CLI, then verify it. The supported runtime is Node.js 24; Node
106
+ 26 remains excluded because of the separate Vitest incompatibility:
102
107
 
103
108
  ```sh
104
- npm install -g wowbagger@0.1.0-alpha.9 # exact plugin-matched release
109
+ npm install -g wowbagger@latest # stable release
110
+ npm install -g wowbagger@0.5.0 # exact plugin-matched release
105
111
  # or, from this release's Git tag:
106
- # npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.9
107
- wowbagger --version # 0.1.0-alpha.9
112
+ # npm install -g github:lstutzman/wowbagger#v0.5.0
113
+ wowbagger --version # 0.5.0
108
114
  wowbagger capabilities --json # must report contract_version: 5
109
115
  ```
110
116
 
@@ -166,21 +172,45 @@ names — `create` publishes into an existing directory and does not make one.
166
172
  Nothing is renamed after a create. This repository dogfoods that binding: its
167
173
  own items live in [`ledger/items/`](ledger/items/).
168
174
 
169
- If your items mirror an external tracker and carry your own identifier fields,
170
- declare them now too. `<ledger>/.wowbagger/extensions.json` is what makes a
171
- consumer-owned extension member patchable:
175
+ If your items will mirror an external tracker and carry consumer-owned fields,
176
+ declare those fields **before the first item**. The declaration makes each
177
+ named extension member patchable:
172
178
 
173
179
  ```sh
174
180
  echo '{"extensions_version":1,"members":{"external_id":"string"}}' \
175
181
  > path/to/ledger/.wowbagger/extensions.json
176
182
  ```
177
183
 
178
- Each member declares one value type — `string`, `integer`, `boolean`, or
179
- `string-list`. **A ledger without that file has no patchable extension member at
180
- all**, and a `set.extensions` patch against it is refused by name. The
181
- declaration authorizes a write; it never describes the ledger, so `validate`
182
- does not read it. Both files are ledger setup, not runner configuration: commit
183
- them.
184
+ Each member declares one value type: `string`, `integer`, `boolean`, or
185
+ `string-list`. A ledger without the file has no patchable extension member,
186
+ and `set.extensions` refuses the missing declaration by name. The declaration
187
+ authorizes writes; it does not define item validity, so `validate` does not
188
+ read it. Commit the declaration with the other ledger setup.
189
+
190
+ If an existing ledger already carries extension values, do not create the
191
+ declaration by hand. Select every member and type explicitly in a request,
192
+ review a dry run, then publish the same proposal:
193
+
194
+ ```json
195
+ {"members":{"tags":"string-list"}}
196
+ ```
197
+
198
+ ```sh
199
+ wowbagger extensions-provision --ledger path/to/ledger \
200
+ --input declaration.json --json --dry-run
201
+ wowbagger extensions-provision --ledger path/to/ledger \
202
+ --input declaration.json --json
203
+ git add path/to/ledger/.wowbagger/extensions.json
204
+ git commit -m "Declare patchable ledger extensions"
205
+ ```
206
+
207
+ The command first requires a valid complete ledger. It validates every
208
+ occurrence of each selected member, reports occurrence counts, changes no item
209
+ bytes, and publishes one canonical declaration without overwriting a different
210
+ one. Commit that file before the first corresponding `patch`, inspect the
211
+ target for its current revision, then use `set.extensions.tags`. An item that
212
+ writes the selected member with a YAML anchor or alias remains
213
+ `extension-anchored` and requires a reviewed hand-edit.
184
214
 
185
215
  Cores at `0.1.0-alpha.4` and earlier ignore the layout file and publish every
186
216
  item at the ledger root.
@@ -269,8 +299,9 @@ skill exists because four things break when it does.
269
299
  - **Validation is whole-ledger and fail-closed.** One malformed item refuses
270
300
  every read and every guarded mutation on that ledger, including commands that
271
301
  never touch it. A hand-edit finds that out later, and usually in someone
272
- else's session. `create`, `transition`, and `patch` validate the complete
273
- candidate ledger *before* publishing anything, and refuse `unchanged`.
302
+ else's session. `create`, `transition`, `parent-migrate`, `snooze`, and
303
+ `patch` validate the complete candidate ledger *before* publishing anything,
304
+ and refuse `unchanged`.
274
305
  - **A hand-edit has no lost-update guard.** Every guarded write takes the exact
275
306
  SHA-256 revision `inspect` returned and refuses if the bytes moved. An editor
276
307
  writes over whatever is there.
@@ -306,7 +337,7 @@ Four separate things, deliberately:
306
337
 
307
338
  The core and the plugin install independently and must carry matching
308
339
  distribution versions. The core contract version and the adapter contract
309
- version are separate domains: the core is at **3**, the adapter is at **2**, and
340
+ version are separate domains: the core is at **5**, the adapter is at **2**, and
310
341
  the legacy work-claim, ledger-publication, and ledger-mutation envelopes stay at
311
342
  **1**.
312
343
 
@@ -317,12 +348,14 @@ the legacy work-claim, ledger-publication, and ledger-mutation envelopes stay at
317
348
  Wowbagger ships as an npm package with a single `wowbagger` binary. There are
318
349
  two supported install routes:
319
350
 
320
- - **npm registry** — `npm install -g wowbagger@next` installs the current
321
- prerelease. `@next` is the documented spelling; `latest` mirrors it (the
322
- registry requires a `latest` tag), so a bare install resolves to the same
323
- bytes.
351
+ - **npm registry** — `npm install -g wowbagger` installs the stable release
352
+ once it exists; `npm install -g wowbagger@next` installs the newest
353
+ prerelease. While every published release is a prerelease, `latest` mirrors
354
+ `next`, so a bare install resolves to the same bytes. After the first
355
+ stable release, `latest` stays on stable and only `next` follows later
356
+ prereleases.
324
357
  - **git tag** —
325
- `npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.9` installs this
358
+ `npm install -g github:lstutzman/wowbagger#v0.5.0` installs this
326
359
  release. Installing at a ref installs the core and every adapter that ref
327
360
  carries.
328
361
 
@@ -345,8 +378,9 @@ sending the request and reading the refusal — an `unknown-member` issue at
345
378
  `/set/body_append` means the core predates the append — or pin the distribution
346
379
  version.
347
380
 
348
- - **Node.js:** 20 and later. The adapter conformance vectors run against Node
349
- 20 and the current runtime before each release.
381
+ - **Node.js:** 24 and later. The release gate runs the full suite on Node
382
+ 24.20.0 with the strict deprecation gate; Node 26 stays excluded because of
383
+ the separate Vitest incompatibility.
350
384
  - **Platforms:** the core runs wherever Node.js runs. The Claude Code adapter
351
385
  declares Darwin, Linux, and Windows `supported` from native common-vector
352
386
  evidence. Every other shipped adapter target remains `unverified`.
@@ -358,10 +392,10 @@ version.
358
392
 
359
393
  ### Security
360
394
 
361
- - **Read-only by default.** `validate`, `ready`, `report`, `inspect`,
362
- `capabilities`, and `mint-id` never modify anything. Every mutation
363
- (`create`, `transition`, `patch`, and `publish-claimed`) is an explicit,
364
- reviewable write.
395
+ - **Read-only by default.** `validate`, `ready`, `report`, `inspect`, `list`,
396
+ `capabilities`, and `mint-id` never modify anything. Every item mutation
397
+ (`create`, `transition`, `parent-migrate`, `snooze`, `patch`, and
398
+ `publish-claimed`) is an explicit, reviewable write.
365
399
  - **Lock is not a claim.** A short mutation lock serializes writers during one
366
400
  operation. It does not grant a work claim.
367
401
  - **Claims are merge-coordinated, not exclusive.** `claim acquire` uses
@@ -393,8 +427,8 @@ wowbagger core, this is how you move forward safely.
393
427
  Upgrade the pieces you installed:
394
428
 
395
429
  ```sh
396
- npm install -g wowbagger@next # public npm registry
397
- npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.9 # immutable Git release
430
+ npm install -g wowbagger@latest # public npm registry
431
+ npm install -g github:lstutzman/wowbagger#v0.5.0 # immutable Git release
398
432
  git pull && npm ci # or: a direct checkout
399
433
  ```
400
434
 
@@ -417,7 +451,7 @@ distribution versions equal.
417
451
 
418
452
  The shipped adapter selects only adapter contract version 2 and requires core
419
453
  contract version 5. The adapter contract and the core contract are separate
420
- version domains: the adapter stays at 2 while the core moves to 4. A v1-only
454
+ version domains: the adapter stays at 2 while the core is at 5. A v1-only
421
455
  consumer receives `unsupported-adapter-contract-version`; it does not receive v2
422
456
  behavior. The schema-2 transport is available. Ledger migration remains a
423
457
  separate quiesced maintenance operation. The
@@ -442,6 +476,16 @@ core, these are the changes most likely to touch you:
442
476
  `create` and refuses a caller-supplied one; `patch` refuses it because it is
443
477
  immutable identity. Keep a legacy identifier in a declared extension member or
444
478
  in the item body.
479
+
480
+ - **Repair duplicate numbers through `ledger-repair` version 1.** Generate a
481
+ read-only proposal with `number-repair-proposal`, review every
482
+ `expected_revision` and `replacement_number`, then apply the complete mapping
483
+ with `number-repair`. The command preserves ULID identities and relations and
484
+ does not change core contract version 5.
485
+
486
+ - **Run `version-drift --json` before mutation.** It compares the installed
487
+ skill pin, required core contract, and running core, and names the stale
488
+ package, plugin cache, or linked checkout with remediation.
445
489
  - **Delete your local ULID generator.** `wowbagger mint-id --json` prints a
446
490
  canonical ID; `--date YYYY-MM-DD` selects the creation date the ID must
447
491
  encode.
@@ -552,7 +596,8 @@ approval never rides the bootstrap request, which the model controls;
552
596
 
553
597
  ## Core commands
554
598
 
555
- The current core requires Node.js 20 or later. From a Wowbagger checkout,
599
+ The current core requires Node.js 24. Node 26 is not in the supported matrix.
600
+ From a Wowbagger checkout,
556
601
  `./bin/wowbagger.js --help` prints the full command inventory,
557
602
  `./bin/wowbagger.js <command> --help` prints that command's usage, and
558
603
  `./bin/wowbagger.js --version` prints the installed package version. The
@@ -565,21 +610,28 @@ npm ci
565
610
  ./bin/wowbagger.js ready --ledger path/to/ledger --as-of 2030-01-15
566
611
  ./bin/wowbagger.js report --ledger path/to/ledger --as-of 2030-01-15 --json
567
612
  ./bin/wowbagger.js capabilities --json
568
- ./bin/wowbagger.js mint-id --json
569
613
  ./bin/wowbagger.js inspect --ledger path/to/ledger --id wb_... --json
570
614
  ./bin/wowbagger.js inspect --ledger path/to/ledger --number 30 --json
615
+ ./bin/wowbagger.js list --ledger path/to/ledger --input query.json --json
571
616
  ./bin/wowbagger.js create --ledger path/to/ledger --input request.json --json
572
617
  ./bin/wowbagger.js transition --ledger path/to/ledger --input request.json --json
618
+ ./bin/wowbagger.js parent-migrate --ledger path/to/ledger --input request.json --json
619
+ ./bin/wowbagger.js snooze --ledger path/to/ledger --input request.json --json
573
620
  ./bin/wowbagger.js patch --ledger path/to/ledger --input request.json --json
621
+ ./bin/wowbagger.js extensions-provision --ledger path/to/ledger --input declaration.json --json
622
+ ./bin/wowbagger.js mint-id --json
623
+ ./bin/wowbagger.js publish-claimed --ledger path/to/ledger --input request.json --json
624
+ ./bin/wowbagger.js claim-merge-verify --ledger path/to/ledger --base main --head feature --json
625
+ ./bin/wowbagger.js claim-sync --ledger path/to/ledger --json
626
+ ./bin/wowbagger.js claim-adopt --ledger path/to/ledger --input request.json --json
627
+ ./bin/wowbagger.js mutation-finalize --ledger path/to/ledger --recovery-token token --json
574
628
  ./bin/wowbagger.js provision --ledger path/to/ledger --json
575
629
  ./bin/wowbagger.js claim capabilities --ledger path/to/ledger --json
576
630
  ./bin/wowbagger.js claim acquire --ledger path/to/ledger --input request.json --json
577
631
  ./bin/wowbagger.js claim read --ledger path/to/ledger --input request.json --json
578
632
  ./bin/wowbagger.js claim renew --ledger path/to/ledger --input request.json --json
579
633
  ./bin/wowbagger.js claim release --ledger path/to/ledger --input request.json --json
580
- ./bin/wowbagger.js publish-claimed --ledger path/to/ledger --input request.json --json
581
- ./bin/wowbagger.js claim-verify --ledger path/to/ledger --json
582
- ./bin/wowbagger.js claim-adopt --ledger path/to/ledger --input request.json --json
634
+ ./bin/wowbagger.js claim-verify --ledger path/to/ledger [--id wb_...] --json
583
635
  ```
584
636
 
585
637
  `validate` writes exactly one JSON result to standard output. A valid ledger
@@ -642,10 +694,12 @@ change is an addition. And **extension members are patchable only where the
642
694
  ledger declares them** — see [Set the ledger up before the first
643
695
  item](#set-the-ledger-up-before-the-first-item).
644
696
 
645
- Which members you own at all is a three-way split — core-owned,
646
- consumer-editable through `patch`, and create-once — stated member by member in
647
- the mutation contract's **frontmatter ownership** table. Read the table; do not
648
- send a patch and interpret the refusal.
697
+ Which members you own at all is a four-way split — core-owned,
698
+ consumer-editable through `patch`, mutable through a dedicated command, and
699
+ create-once — stated member by member in the mutation contract's
700
+ **frontmatter ownership** table. Use `parent-migrate` to repoint an existing
701
+ item to or from an epic, and `snooze` to set or clear `snoozed_until`. Read the
702
+ table; do not send a patch and interpret the refusal.
649
703
 
650
704
  ### An epic's progress is derived, never stored
651
705
 
@@ -670,9 +724,11 @@ command asks you to parse the Markdown by hand:
670
724
  refusal carries `error.details.item`, the complete snapshot of the item you
671
725
  asked for, whenever no validation error names that item's path. A faulted
672
726
  item is withheld; `validate` already names its repair.
673
- - `claim-verify --json` reports `result.ledger_validation`. Exit 0 with
674
- `findings: []` and `ledger_validation.valid: false` says the claim journal is
675
- consistent and validation alone is blocking every mutation.
727
+ - `claim-verify --json` reports `result.ledger_validation`. Bare verification
728
+ is strict repository-wide mode; `--id <item>` keeps all findings visible but
729
+ fails only for that item and global barriers. Exit 0 with an invalid
730
+ `ledger_validation` still means claim state is clean but validation blocks
731
+ mutation.
676
732
 
677
733
  ### Work claims
678
734
 
@@ -702,22 +758,29 @@ operating rule:
702
758
 
703
759
  **Commit each mutation to Git before running the next mutating command.**
704
760
 
705
- The durable claim store validates every recorded mutation against Git `HEAD`,
706
- not against working-tree bytes. That is what makes a recorded mutation durable
707
- rather than a local edit one `git checkout` away from vanishing. An uncommitted
708
- mutation is an unreconciled mutation, so the next `create`, `transition`, or
709
- `patch` refuses instead of writing on top of it.
761
+ The durable claim store reconciles every recorded mutation with Git `HEAD` and
762
+ the working tree. The next mutation refuses when that reconciliation finds an
763
+ `unauthorized-revision`, requires Git finalization, or requires synchronization
764
+ for the item the command targets. A synchronization finding on an unrelated
765
+ item remains visible to `claim-verify` but does not block the command.
766
+
767
+ An existing item's latest authorized working-tree bytes and an earlier
768
+ authorized revision at `HEAD` form an authorized predecessor/successor window.
769
+ That window produces no finding, so another mutation can run before the first
770
+ one is committed. Acceptance of the later mutation does not make either change
771
+ durable. Commit each mutation anyway, then run `claim-verify`.
710
772
 
711
773
  The loop that works:
712
774
 
713
775
  ```sh
714
776
  ./bin/wowbagger.js create --ledger path/to/ledger --input request.json --json
715
777
  git add path/to/ledger && git commit -m "Record the mutation"
716
- ./bin/wowbagger.js claim-verify --ledger path/to/ledger --json
778
+ ./bin/wowbagger.js claim-verify --ledger path/to/ledger --id wb_... --json
717
779
  ./bin/wowbagger.js transition --ledger path/to/ledger --input next.json --json
718
780
  ```
719
781
 
720
- Skip the commit and the next command returns exit 6:
782
+ For example, an authorized new item that is still absent from `HEAD` makes the
783
+ next command return exit 6:
721
784
 
722
785
  ```json
723
786
  {"ok":false,"namespace":"ledger-mutation","command":"create-v1","contract_version":1,
@@ -732,10 +795,16 @@ Skip the commit and the next command returns exit 6:
732
795
  `state: "unchanged"` is exact — nothing was written. **`claim-verify` is the
733
796
  reconciliation procedure.** Read `details.findings`, do what each
734
797
  `remediation` string says, run `claim-verify` until it returns exit 0, then
735
- repeat the refused command.
798
+ repeat the refused command. A `worktree-synchronization-required` finding on an
799
+ unrelated item does not block the requested mutation. The same finding on the
800
+ target item, and every `unauthorized-revision` finding, remains blocking.
736
801
 
737
- Batch work is where this bites. Filing ten items means ten commits, not one
738
- commit at the end.
802
+ Batch work is where this bites. Filing ten items means ten serial
803
+ `create --auto-commit` calls and ten commits, not one batch commit. Wowbagger
804
+ permanently rejects batch create for the direct-Markdown architecture:
805
+ `limits.multi_item_atomicity` remains `false`, request order is the supported
806
+ bulk order, and each create must finish or recover before the next begins. See
807
+ the [batch-create decision](docs/design/2026-08-30-batch-create.md).
739
808
 
740
809
  ### Or fold the commit into the mutation
741
810
 
@@ -748,16 +817,23 @@ ledger only:
748
817
 
749
818
  It is opt-in per invocation. There is no configuration setting or environment
750
819
  default, because a hidden default would make existing automation create Git
751
- commits unexpectedly. The flag is accepted on `create`, `transition`, `patch`,
752
- and `publish-claimed`.
820
+ commits unexpectedly. The flag is accepted on `create`, `transition`,
821
+ `parent-migrate`, `snooze`, `patch`, and `publish-claimed`.
753
822
 
754
823
  What one flagged invocation does: refuse if anything is staged anywhere or any
755
- path under the ledger is dirty; reconcile; run the mutation unchanged; commit
756
- exactly the changed item and at most one
824
+ foreign path under the ledger is dirty; reconcile; run the mutation unchanged;
825
+ commit exactly the changed item and at most one
757
826
  `.wowbagger/reconcile-<namespace>.md` with a fixed subject such as
758
827
  `wowbagger: transition item #7`; verify the commit; then run `claim-verify`
759
- before it answers. On success the result gains `git_commit`, `commit_paths`, and
760
- `claim_verified`.
828
+ before it answers. A command that owns the claim journal may rebuild only its
829
+ derived reconciliation log during preflight. `create` remains strict, and
830
+ every other dirty ledger path still refuses. On success the result gains
831
+ `git_commit`, `commit_paths`, and `claim_verified`.
832
+
833
+ If claim verification refuses, auto-commit preserves its code and reason in
834
+ `claim_verify_code` and `claim_verify_reason`. Only
835
+ `claim_verify_reason: "claim-store-locked"` is retryable; unresolved
836
+ reconciliation is not.
761
837
 
762
838
  Unstaged and untracked files **outside** the ledger are left alone. Hooks and
763
839
  signing are honoured; `--no-verify` is never passed. Nothing is pushed.
@@ -850,62 +926,108 @@ relative `--out` overrides resolve from the caller's working directory.
850
926
  "complexity": "/complexity",
851
927
  "rank": "/priority_rank",
852
928
  "class": "/class",
853
- "due": "/due"
929
+ "due": "/due",
930
+ "tags": "/tags"
854
931
  },
855
932
  "swarm": { "eligible_complexities": ["small", "medium"] }
856
933
  }
857
934
  ```
858
-
859
935
  `repository.logo`, `fields`, and `swarm` are optional. Field values resolve
860
936
  from parsed frontmatter with RFC 6901 JSON Pointers. A swarm requires mapped
861
937
  `area` and `complexity` fields. The report fetches nothing at view time.
862
938
 
863
- The report is a **sequencing dashboard**, not a state snapshot. It opens with
864
- **Work next**: the ready set in a recommended order, each entry carrying the
865
- factors that placed it. Below it sit **Attention** (blocked work naming its
866
- blockers, the oldest open work, and started work past this ledger's own
867
- 85th-percentile cycle time), then the **evidence layer**, then the **ledger
868
- graph**. State counts, item cards, filters, grouping, detail levels, terminal
869
- history, and area-diverse ready batches all remain, demoted below that decision
870
- surface.
871
-
872
- The drill-down filters are **facet groups**: Readiness, Status, Kind, and one
873
- group for every configured mapped field, each a fieldset of checkbox chips.
874
- Values inside a group are alternatives and groups narrow each other, so
875
- `ready` or `blocked` in Readiness plus `bug` in Class means ready-or-blocked
876
- bugs; the search box is one more condition on the same answer. Every chip
877
- carries the count it would leave, measured against the search and the other
878
- groups but never against its own, so two selections in one group cannot make
879
- their siblings read zero. The result count states how much of the open set is
880
- showing, and **Clear filters** gives every selection back. A Work next or
881
- Attention row still reaches its card: opening one clears the facets and the
882
- search first, because a row that names an item promises the item can be seen.
883
-
884
- The evidence layer is inline SVG, drawn at generation time and embedded in the
885
- file: an aging heatmap, weekly arrivals against completions, throughput with a
886
- four-week mean, a cumulative flow area, accept-to-complete cycle time, and a
887
- Monte Carlo forecast as 50, 85, and 95 percent bands. Each chart carries
888
- `role="img"` and an aria-label that states its finding in words, so the evidence
889
- survives a screen reader and a printout.
939
+ `tags` is the one multi-value mapped field. It accepts a nonempty string or an
940
+ array holding only nonempty strings: a scalar reads as a one-tag set, exact
941
+ duplicates collapse, and values sort deterministically. The mapping never
942
+ splits commas, lowercases values, coerces objects, or partially accepts a
943
+ mixed-type array. An empty array counts as missing; any other rejected value
944
+ is omitted from the item and counted as invalid metadata. `area` stays scalar.
945
+ The model carries `fieldCoverage`, one entry per configured field plus `area`
946
+ and `tags` when unmapped, ordered by field name with `present`, `missing`, and
947
+ `invalid` counts over the retained report population. An unmapped field counts
948
+ every retained item as missing, and a visible missing-mapping notice tells an
949
+ unconfigured mapping apart from missing item values. Missing metadata never
950
+ matches a filter value: there is no `Unclassified` bucket, so a filter for a
951
+ literal `Unclassified` tag matches only items really carrying that tag.
952
+
953
+ The report is a **decision-focused workspace**, not a state snapshot. It has
954
+ three sections behind accessible view navigation: **Items** (the default),
955
+ **Flow**, and **Dependencies**. Only the selected section is visible when
956
+ scripting runs; without scripting, every section stays readable through its
957
+ anchor and native `details` elements, and the artifact states its fixed scope.
958
+
959
+ Items opens with the state counts, then a sticky control strip: search, five
960
+ quick views (**Work next**, **In progress**, **Blocked**, **Needs triage**, and
961
+ **All open**), and the display controls (grouping, sorting, Basic/Standard/
962
+ Detailed, Show history, Expand all, Collapse all). Below 1100px the display
963
+ controls fold behind a **Display** toggle so search and quick views stay in
964
+ reach. Search and the facet groups form the **scope**; the scope narrows the
965
+ summaries, Flow, and Dependencies alike. Quick views and Show history change
966
+ only the list. Work next keeps its recommended order and prints the reasons
967
+ beside each row; any other sort presents itself as that sort.
968
+
969
+ There is one list and one canonical detail per retained item. Desktop widths
970
+ use a list/detail split; narrower widths show the selected detail inline.
971
+ Opening a detail never clears the search, the facets, or the list position, and
972
+ a detail opened from Flow or Dependencies returns to Items with the scope
973
+ intact. Expand all acts on visible rows only.
974
+
975
+ The **filters** are facet groups: Readiness, Status, Kind, Priority, and one
976
+ group for every configured mapped field, each a fieldset of checkbox chips
977
+ behind a collapsed **Filters** control. Values inside a group are alternatives
978
+ and groups narrow each other; the search box is one more condition on the same
979
+ answer. Every chip carries the count it would leave, measured against the
980
+ search and the other groups but never against its own. Missing metadata is its
981
+ own **Missing** chip, distinct from a literal `Unclassified` value. The result
982
+ count states how much of the retained set is showing, and **Clear filters**
983
+ gives every selection back.
984
+
985
+ Above the list sit scoped summaries that open exact contributing items through
986
+ a labelled drilldown pill: attention actions (in progress, blocked, needs
987
+ triage, oldest, and started work past this ledger's own 85th-percentile cycle
988
+ time), an **area/status matrix** with count and blocked count per cell, and
989
+ **Scoped members of existing batches**, which intersects the area-diverse
990
+ batches with the scoped ready set and omits empty batches. Each item detail
991
+ states its **downstream reach** (transitive dependents in the report) and,
992
+ separately, the items that become **ready if done**; neither alters core
993
+ readiness or the recommended order.
994
+
995
+ ### Flow
996
+
997
+ Flow recomputes from the scoped open and terminal population in the browser:
998
+ cumulative flow, weekly arrivals against closures with done counted
999
+ separately, throughput with a four-week mean, current aging by status,
1000
+ acceptance-to-completion samples, and the closure forecast. Inclusive **From**
1001
+ and **To** controls default to the twelve-week window; a start after the end,
1002
+ or an end after the report date, is refused with a visible error while the last
1003
+ valid charts stay. Weekly buckets, aging cells, completion samples, and
1004
+ cumulative date/band selections each drill into the exact contributing items,
1005
+ and the accessible tables offer the same actions. The forecast is computed only
1006
+ when Flow first opens and cached by cohort and range. Missing acceptance history
1007
+ is stated as reconstruction uncertainty; an item killed straight from triage is
1008
+ complete history, not a gap. The fixed server-rendered charts remain for
1009
+ readers without scripting, each with `role="img"` and an aria-label that states
1010
+ its finding in words.
890
1011
 
891
1012
  ### The ledger graph
892
1013
 
893
- Below the evidence layer the report draws the whole ledger as a force-directed
894
- 3D graph. Every item is a node, labelled `#N`, coloured by readiness for open
895
- items and by terminal status for closed ones, and sized by the same transitive
896
- unblocking leverage the recommended order uses. Edges run from a prerequisite
897
- or a parent to the item it releases: a `depends_on` edge is straight and
898
- arrowed, a `parent` edge is curved and unarrowed. Hovering or clicking a node
899
- opens a card with its number, title, status, age, leverage, and the same
900
- reasons line the ranked list prints for it.
901
-
902
- Above the stage sits one chip group of the lifecycle statuses the ledger holds,
903
- all selected, with **Select all** and **Clear**. Deselecting a status drops its
904
- nodes, every link that touched one of them, and their labels together, then
905
- reheats the layout in place: nothing is reloaded and the ledger is never
906
- touched. The roster and the node count follow the same selection, and clearing
907
- every status draws an empty graph that says it is empty rather than quietly
908
- showing the last one.
1014
+ Dependencies draws the scoped items as a force-directed 3D graph. Every item is
1015
+ a node, labelled `#N`, coloured by readiness for open items and by terminal
1016
+ status for closed ones, and sized by the same transitive unblocking leverage the
1017
+ recommended order uses. Edges run from a prerequisite or a parent to the item
1018
+ it releases: a `depends_on` edge is straight and arrowed, a `parent` edge is
1019
+ curved and unarrowed. Hovering a node opens a card with its number, title,
1020
+ status, age, leverage, and reasons; clicking it, or a roster row, opens the
1021
+ canonical detail in Items. Downstream and ready-if-done actions on the roster
1022
+ drill into the same sets the detail names.
1023
+
1024
+ The graph has no filter of its own: it follows the shared scope, so a scope
1025
+ change drops nodes, every link that touched one of them, and their labels
1026
+ together, then reheats the layout in place. A hidden blocker never turns a
1027
+ retained item ready, because readiness is projected over the complete ledger
1028
+ before any view narrows it. The graph starts only when Dependencies first
1029
+ opens, pauses while another section is shown, and an empty scope draws an
1030
+ empty graph that says so.
909
1031
 
910
1032
  The renderer is [`3d-force-graph`](https://github.com/vasturiano/3d-force-graph)
911
1033
  over Three.js, vendored into `vendor/3d-force-graph/` at a pinned version
@@ -948,9 +1070,10 @@ weight and is shown as written.
948
1070
  ### Named custom report views
949
1071
 
950
1072
  A named custom view is a second self-contained report generated from the same
951
- complete ledger. Every section of it — statistics, **Work next**, **Attention**,
952
- the evidence layer, the graph, the open-item drill-down, terminal history, and
953
- the swarm batches — describes one configured subset, so the file is honest to
1073
+ complete ledger. Every section of it — statistics, **Work next** and the other
1074
+ quick views, the **Attention** summaries and area/status matrix, dependency
1075
+ impact, Flow, the graph, the drill-down pill, terminal history, and the
1076
+ swarm batches — describes one configured subset, so the file is honest to
954
1077
  share as a scoped report. Excluded items are absent from the bytes rather than
955
1078
  hidden by a stylesheet. The base report stays available and unchanged.
956
1079
 
@@ -1015,9 +1138,10 @@ A `fields` key must also be a configured report field. Each field filter is a
1015
1138
  non-empty array of unique JSON strings, finite numbers, or booleans, and matching
1016
1139
  preserves JSON scalar type and value: stringification is not equality, so a
1017
1140
  mapped `2` does not answer a filter for `"2"`. An item carrying no mapped value
1018
- for a field matches no value selected for that field. No title-text inference,
1019
- regular expression, arbitrary JSON pointer, or body search belongs in a view
1020
- filter.
1141
+ for a field matches no value selected for that field. A `tags` filter uses
1142
+ any-member matching, so one item carrying two tags answers either tag. No
1143
+ title-text inference, regular expression, arbitrary JSON pointer, or body
1144
+ search belongs in a view filter.
1021
1145
 
1022
1146
  Wowbagger validates the complete ledger and computes readiness against the
1023
1147
  complete ledger before it filters, so excluding a blocker never makes blocked
@@ -1081,6 +1205,16 @@ current UTC date:
1081
1205
  npm run report -- --as-of YYYY-MM-DD
1082
1206
  ```
1083
1207
 
1208
+ If you verify the report in a browser from a checkout, generate a deterministic
1209
+ synthetic report through the real pipeline:
1210
+
1211
+ ```sh
1212
+ node scripts/report-design-demo.js --out /private/tmp/wowbagger-report-demo.html --items 40
1213
+ ```
1214
+
1215
+ That output is synthetic and checkout-only: it describes fixed demo data,
1216
+ never this repository's ledger.
1217
+
1084
1218
  ## Where the contracts live
1085
1219
 
1086
1220
  The README is the map. These are the territory, and they are normative where
@@ -1143,18 +1277,20 @@ the finding as a ledger item rather than leaving it in a transcript.
1143
1277
 
1144
1278
  ### The verification gate
1145
1279
 
1146
- Four commands. All four must pass, and the test commands run on **both** the
1147
- current Node runtime and Node 20:
1280
+ Four commands. All four must pass, and the test commands run on **Node 24.20.0**:
1148
1281
 
1149
1282
  ```sh
1150
- TMPDIR=/tmp node --test test/*.test.js
1151
- TMPDIR=/tmp /opt/homebrew/opt/node@20/bin/node --test test/*.test.js
1152
- TMPDIR=/tmp node spec/run-adapter-implementation.js
1153
- node bin/wowbagger.js validate --ledger ledger --json
1283
+ TMPDIR=/tmp /opt/homebrew/opt/node@24/bin/node --test test/*.test.js
1284
+ TMPDIR=/tmp /opt/homebrew/opt/node@24/bin/node --pending-deprecation --throw-deprecation --test test/*.test.js
1285
+ TMPDIR=/tmp /opt/homebrew/opt/node@24/bin/node spec/run-adapter-implementation.js
1286
+ TMPDIR=/tmp /opt/homebrew/opt/node@24/bin/node bin/wowbagger.js validate --ledger ledger --json
1154
1287
  ```
1155
1288
 
1156
1289
  `TMPDIR=/tmp` is not optional: the default macOS temporary path makes the claim
1157
- lock socket path too long. Substitute your own Node 20 binary path.
1290
+ lock socket path too long. Use an explicit Node 24.20.0 binary path.
1291
+
1292
+ The supported runtime matrix is Node 24.20.0. Node 26 remains excluded until
1293
+ the separate Vitest incompatibility reported by Lee is resolved.
1158
1294
 
1159
1295
  `npm test`, `npm audit --omit=dev`, and `git diff --check` are useful alongside
1160
1296
  it; they are not a substitute for the four commands above.
@@ -1258,7 +1394,7 @@ It is the durable work ledger beneath those systems.
1258
1394
  **Shipped: the policy-input contract and the report's mapped fields keep
1259
1395
  consumer vocabulary out of the schema.**
1260
1396
  - Stabilize the machine-readable command contract and compatibility evidence.
1261
- **In progress at core contract version 5; the version is not frozen.**
1397
+ **Shipped: core contract version 5 is the stable contract.**
1262
1398
  - Ship Claude Code and Codex adapters. **Claude Code, Codex, and OpenCode
1263
1399
  packages share the version 2 engine; the Claude Code manifest declares Darwin
1264
1400
  `supported` after passing all 212 native assertions. Other adapter targets and