@adhd/backlog 1.0.4 โ†’ 1.0.6

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 (59) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +194 -39
  3. package/api.d.ts +87 -0
  4. package/api.ir.json +1 -1
  5. package/citation.d.ts +176 -0
  6. package/envelope.d.ts +36 -2
  7. package/index.d.ts +23 -3
  8. package/index.js +105 -57
  9. package/index.mjs +11450 -6758
  10. package/ir-artifact.d.ts +7 -3
  11. package/lifecycle.d.ts +49 -0
  12. package/package.json +5 -5
  13. package/query/canonical.d.ts +16 -0
  14. package/query/card.d.ts +116 -4
  15. package/query/get.d.ts +8 -1
  16. package/query/index.d.ts +1 -0
  17. package/query/query.d.ts +32 -22
  18. package/query/redirect.d.ts +30 -0
  19. package/query/resolve.d.ts +75 -0
  20. package/query/similar-clusters.d.ts +8 -0
  21. package/query/similarity-signals.d.ts +47 -0
  22. package/query/spec-staleness.d.ts +21 -0
  23. package/query/types.d.ts +249 -34
  24. package/query/verdict-core.d.ts +21 -0
  25. package/query/verdict.d.ts +22 -0
  26. package/query/views/catalog.d.ts +47 -0
  27. package/query/views/registry.d.ts +27 -7
  28. package/query/views/report.d.ts +49 -0
  29. package/query/views/semantic.d.ts +28 -5
  30. package/query/views/stats.d.ts +38 -2
  31. package/readiness.d.ts +29 -0
  32. package/retry-policy.d.ts +44 -0
  33. package/serve.d.ts +1 -1
  34. package/server.d.ts +71 -3
  35. package/service-config.d.ts +109 -0
  36. package/service-errors.d.ts +51 -0
  37. package/skill/SKILL.md +688 -81
  38. package/store/catalog-invariant-guard.d.ts +13 -7
  39. package/vocabulary.d.ts +48 -0
  40. package/write/anchor-check.d.ts +139 -0
  41. package/write/attestation.d.ts +64 -0
  42. package/write/catalog-merge.d.ts +15 -8
  43. package/write/catalog.d.ts +72 -2
  44. package/write/citation-path.d.ts +31 -0
  45. package/write/citation.d.ts +157 -0
  46. package/write/create-issue.d.ts +143 -29
  47. package/write/errors.d.ts +196 -1
  48. package/write/gate.d.ts +67 -0
  49. package/write/merge-project.d.ts +54 -0
  50. package/write/obligation.d.ts +133 -0
  51. package/write/relate.d.ts +1 -1
  52. package/write/revision.d.ts +27 -0
  53. package/write/similarity-scan.d.ts +84 -0
  54. package/write/spec-revision.d.ts +131 -0
  55. package/write/spec-revision.reconcile.d.ts +48 -0
  56. package/write/transition.d.ts +13 -2
  57. package/write/tx.d.ts +44 -2
  58. package/write/uid-prefix.d.ts +78 -0
  59. package/write/update.d.ts +23 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,72 @@
1
+ ## 1.0.6 (2026-09-29)
2
+
3
+ ### ๐Ÿš€ Features
4
+
5
+ - **backlog:** first-class citation verbs + one citation contract ([e46a0490](https://github.com/PseudoSky/adhd/commit/e46a0490))
6
+
7
+ ### ๐Ÿฉน Fixes
8
+
9
+ - **workspace:** restore the publish gate's heavy lane and canonicalise the gate ([45532731](https://github.com/PseudoSky/adhd/commit/45532731))
10
+ - **backlog:** drop banned prior-version/current-version tokens from the cross-repo attestation spec ([acf53458](https://github.com/PseudoSky/adhd/commit/acf53458))
11
+ - **backlog:** read verdict applies the write gate's close-predictor skip ([13e7c870](https://github.com/PseudoSky/adhd/commit/13e7c870))
12
+ - **backlog:** a terminal-scoped obligation gates the close, not the claim (demo 3.3) ([5db10040](https://github.com/PseudoSky/adhd/commit/5db10040))
13
+ - **backlog:** resolve a cross-repo anchor against the sibling project root that owns it (demo 2.4) ([068e554c](https://github.com/PseudoSky/adhd/commit/068e554c))
14
+
15
+ ### โค๏ธ Thank You
16
+
17
+ - pseudosky
18
+
19
+ ## 1.1.0 (2026-09-28)
20
+
21
+ ### ๐Ÿš€ Features
22
+
23
+ - **backlog:** deterministic dependent-weight tiebreak in view:"order" (C2 AC4) ([15dc77ba](https://github.com/PseudoSky/adhd/commit/15dc77ba))
24
+ - **backlog:** C6 verdict โ€” derived actionability with reasons; claim refuses live blockers ([c0c0ea5c](https://github.com/PseudoSky/adhd/commit/c0c0ea5c))
25
+ - **backlog:** C5 closure gate โ€” terminal transitions require satisfied obligations ([a49ec889](https://github.com/PseudoSky/adhd/commit/a49ec889))
26
+ - **backlog:** C10 โ€” a spec is a revision of its ticket (store-citizen documents) ([02a969ba](https://github.com/PseudoSky/adhd/commit/02a969ba))
27
+ - **backlog:** C4 obligation โ€” obligate/unobligate + closed predicate core ([72ddafbe](https://github.com/PseudoSky/adhd/commit/72ddafbe))
28
+ - **backlog:** C8 โ€” closed primitives, readable catalogs, self-describing surface ([5e8cb041](https://github.com/PseudoSky/adhd/commit/5e8cb041))
29
+ - **backlog:** C3 attestation โ€” attest/recheck, anchors, citation roots ([f675393b](https://github.com/PseudoSky/adhd/commit/f675393b))
30
+ - **backlog:** C9 cross-project similarity detection and reviewed linking ([f9e1eb96](https://github.com/PseudoSky/adhd/commit/f9e1eb96))
31
+ - **backlog:** D-A trustworthy service layer โ€” readiness, config-drift gate, retry+breaker ([aa894690](https://github.com/PseudoSky/adhd/commit/aa894690))
32
+ - **backlog:** foundation โ€” attestation/obligation node kinds + attests/has_obligation/satisfies edges (69632883) ([cc7eae49](https://github.com/PseudoSky/adhd/commit/cc7eae49))
33
+ - **backlog:** C1 reference โ€” prefix resolution, redirect, merge/rm-project ([2780b433](https://github.com/PseudoSky/adhd/commit/2780b433))
34
+ - **backlog:** C7 honest envelopes, score provenance, bounded sub-collections, report ([8fa60ffd](https://github.com/PseudoSky/adhd/commit/8fa60ffd))
35
+ - **backlog:** C2 kind-scope โ€” related exposes all rel types, order spans member kinds ([f02e8d23](https://github.com/PseudoSky/adhd/commit/f02e8d23))
36
+
37
+ ### ๐Ÿฉน Fixes
38
+
39
+ - **backlog:** collapse the kind case-fragments in the repair CLI, pin the transition-path refusal ([16b42095](https://github.com/PseudoSky/adhd/commit/16b42095))
40
+ - **backlog:** C10 spec discovery sees declared-kind SPECs, not only raw kind (603737c2) ([e4a6b51d](https://github.com/PseudoSky/adhd/commit/e4a6b51d))
41
+ - **backlog:** C10 reconcile discovers live kind:SPEC items, not only raw node kind ([9c87e1d4](https://github.com/PseudoSky/adhd/commit/9c87e1d4))
42
+ - **backlog:** C9 wire the cross-project structural second signal (A-side) ([c01ef514](https://github.com/PseudoSky/adhd/commit/c01ef514))
43
+ - **backlog:** D-A apply the resolved service.* config (port/transport/host/resilience) ([4405e717](https://github.com/PseudoSky/adhd/commit/4405e717))
44
+ - **backlog:** rename create partOf input to dedupeExcludeUid (c5460239; 081facec) ([7584a847](https://github.com/PseudoSky/adhd/commit/7584a847))
45
+ - **backlog:** C1 lookup โ€” match issue name, not body, so the registry is not shadowed ([46b3165d](https://github.com/PseudoSky/adhd/commit/46b3165d))
46
+ - **backlog:** C7 โ€” reconcile _score_kind doc/code vocabulary ([3c111d80](https://github.com/PseudoSky/adhd/commit/3c111d80))
47
+ - **backlog:** create dedupe excludes a declared parent + its part_of ancestors (c5460239) ([dfb5aca9](https://github.com/PseudoSky/adhd/commit/dfb5aca9))
48
+ - **backlog:** create dedupe gate fails closed, not open, on a degraded scan (BUG 4e8fce2a; cc2b5b87) ([04dd0283](https://github.com/PseudoSky/adhd/commit/04dd0283))
49
+
50
+ ### โค๏ธ Thank You
51
+
52
+ - pseudosky
53
+
54
+ ## 1.0.5 (2026-09-27)
55
+
56
+ ### ๐Ÿฉน Fixes
57
+
58
+ - **backlog:** lock-serialise the shared dist across build/e2e/test ([02a33e33](https://github.com/PseudoSky/adhd/commit/02a33e33))
59
+ - **backlog:** stop the catalog-invariant guard from gating writes ([dea2e476](https://github.com/PseudoSky/adhd/commit/dea2e476))
60
+ - **backlog:** scope e2e-lane serialization to the e2e config ([b441d93c](https://github.com/PseudoSky/adhd/commit/b441d93c))
61
+ - **nx-build:** make three verification gates actually fail when broken ([c4dc8ae0](https://github.com/PseudoSky/adhd/commit/c4dc8ae0))
62
+ - **backlog:** stop the catalog-invariant guard from aborting reads ([7c941505](https://github.com/PseudoSky/adhd/commit/7c941505))
63
+ - **backlog:** resolve uid prefixes with loud ambiguity refusal ([c9491ed4](https://github.com/PseudoSky/adhd/commit/c9491ed4))
64
+ - **backlog:** normalize ETL status vocabulary to canonical lowercase ([8809de97](https://github.com/PseudoSky/adhd/commit/8809de97))
65
+
66
+ ### โค๏ธ Thank You
67
+
68
+ - pseudosky
69
+
1
70
  ## 1.0.4 (2026-09-27)
2
71
 
3
72
  ### ๐Ÿฉน Fixes
package/README.md CHANGED
@@ -60,11 +60,20 @@ adhd-backlog create --input '{
60
60
  ```
61
61
 
62
62
  ```json
63
- { "ok": true, "data": { "created": true, "uid": "b3b2da0e-โ€ฆ", "item": { "uid": "b3b2da0e-โ€ฆ", "title": "Query pagination drops the last page under offset paging", "kind": "issue", "status": "open", "priority": "HIGH", "project": "49ec673b-โ€ฆ", "component": "65c4e373-โ€ฆ", "createdAt": "2026-09-24T00:14:38.802Z", "author": "agent:worker-1", "gitContext": "feat/backlog-hard-replacement @ 4bf902fc" } } }
63
+ { "ok": true, "data": { "created": true, "uid": "b3b2da0e-โ€ฆ", "item": { "uid": "b3b2da0e-โ€ฆ", "title": "Query pagination drops the last page under offset paging", "kind": "issue", "status": "open", "priority": "HIGH", "project": "49ec673b-โ€ฆ", "component": "65c4e373-โ€ฆ", "createdAt": "2026-09-24T00:14:38.802Z", "author": "agent:worker-1", "gitContext": "feat/backlog-hard-replacement @ 4bf902fc" }, "placementResolved": "default-root" } }
64
64
  ```
65
65
 
66
66
  > `project` and `component` in the result are `uid`s, not names. `gitContext`
67
67
  > is described under [Citations & git context](#citations--git-context).
68
+ > `placementResolved` is `"default-root"` when the omitted `component` fell
69
+ > back to the project's reserved `(root)` component, or `"explicit"` when a
70
+ > supplied one resolved โ€” a `default-root` item is invisible to
71
+ > component-scoped scans. A create result may additionally carry
72
+ > `duplicateScanDegraded: true` plus `duplicateScanDegradedReason`
73
+ > (`"no-search-backend"` / `"no-embed-query"` / `"no-vector-scores"`) when the
74
+ > pre-write dedupe scan could not run a calibrated comparison; both are absent
75
+ > on a healthy scan and never imply the write failed. Full field notes are in
76
+ > [`skill/SKILL.md` ยง3](skill/SKILL.md).
68
77
 
69
78
  Query for it:
70
79
 
@@ -187,11 +196,94 @@ resolve to the wrong record.
187
196
  { "ok": false, "error": { "code": "conflict", "message": "Issue \"63c2f57e-โ€ฆ\" was superseded by a body edit and is no longer the live issue; it now lives under \"e3b32183-โ€ฆ\"", "details": { "retryable": false } } }
188
197
  ```
189
198
 
199
+ ### 8. Obligation-gated transitions and the actionability verdict
200
+
201
+ Declare a typed requirement on an issue with `obligate`; the store then refuses
202
+ any transition that would violate it until the requirement is met. A
203
+ `block`-severity `evidence` obligation, for example, keeps a `closed`
204
+ transition refused until a matching verified attestation exists.
205
+
206
+ An obligation is scoped by `applies_to.to` to the **transition** it guards โ€” a
207
+ close-scoped obligation gates the close, **not** claimability. So `get` reports
208
+ the item actionable (the same answer `claim` gives), and the close is what gets
209
+ refused:
210
+
211
+ ```bash
212
+ adhd-backlog get --input '{"uid":"1d9b77e5-โ€ฆ","fields":["verdict","obligations"]}'
213
+ ```
214
+
215
+ ```json
216
+ { "ok": true, "data": { "uid": "1d9b77e5-โ€ฆ", "obligations": [{ "uid": "fbcee200-โ€ฆ", "applies_to": { "to": "closed" }, "requirement": { "op": "evidence", "kind": "published-artifact", "min": 1 }, "on_fail": "block" }], "verdict": { "actionable": true, "evaluated_at": "โ€ฆ", "revision": 0, "conditions": [] } } }
217
+ ```
218
+
219
+ Only an obligation scoped to a **non-terminal** transition (e.g.
220
+ `applies_to:{to:"in_progress"}`) is due at claim time, and only then does it
221
+ appear as a rung-2 block condition in the verdict.
222
+
223
+ Attempt the close and the gate refuses with a typed `precondition_failed`,
224
+ writing nothing:
225
+
226
+ ```json
227
+ { "ok": false, "error": { "code": "precondition_failed", "message": "Transition refused: EvidenceUnverified (requires \"published-artifact\") โ€” no verified attestation of kind \"published-artifact\" satisfies this obligation", "details": { "retryable": false } } }
228
+ ```
229
+
230
+ `actionable` is tri-state (`true` / `false` / `"unknown"`) โ€” `"unknown"` is
231
+ never a green light. The full declare โ†’ read โ†’ refuse โ†’ attest โ†’ close
232
+ workflow, the closed predicate grammar, and the permitted-override path are in
233
+ [`skill/SKILL.md` ยง4](skill/SKILL.md).
234
+
235
+ ### 9. Compare-and-swap spec revisions
236
+
237
+ A ticket's work product is a sequence of immutable spec revisions.
238
+ `spec-append` appends a fragment and advances the pointer in place, and its
239
+ required `base_revision` makes the append a compare-and-swap: a stale base is
240
+ refused with `precondition_failed` and nothing is written. `spec-check` tells a
241
+ reader whether the token it holds is still current โ€” an absent token is
242
+ `stale`, never `fresh` โ€” and `attest` records a comment keyed to an exact
243
+ revision (never to the ticket body) via a `SPEC` subject + a `revision:` anchor:
244
+
245
+ ```bash
246
+ adhd-backlog spec-check --input '{"uid":"c79b52b0-โ€ฆ"}'
247
+ ```
248
+
249
+ ```json
250
+ { "ok": true, "data": { "current_revision": "58b5dfd8-โ€ฆ", "current_token": "sha256:d0bcba4aโ€ฆ", "state": "stale", "method": "none", "reason": "no-token-supplied" } }
251
+ ```
252
+
253
+ See [`skill/SKILL.md` ยง5](skill/SKILL.md) for the append/check worked example.
254
+
255
+ ### 10. Live catalogs and dependency-ordered plans
256
+
257
+ `query --input '{"view":"catalogs"}'` returns every vocabulary the store
258
+ enforces (kinds, statuses, priorities, relations, fields, error codes, location
259
+ types, and the mounted verbs), each term naming the in-code source it was
260
+ generated from. Note that **`kind` is open/free-form, not an enumerable
261
+ allowlist**: `create` accepts any `kind` string and the `kind` catalog is a
262
+ usage census (`source: "store"`), so it is **empty on a fresh store** and then
263
+ lists what has actually been filed (`"issue"` is the default; `"plan"` is the
264
+ conventional parent kind). `filter.kind` is validated against that live census,
265
+ so filtering by a never-used kind is a `validation` error naming the values
266
+ that do exist. This mirrors `status`, which is likewise an open catalog.
267
+
268
+ `query --input '{"view":"order","filter":{"plan":"<uid>"}}'`
269
+ returns a plan's members topologically ordered by their `blocks` edges. Note
270
+ the trap: the `catalog` selector is honoured only with `view:"catalogs"` โ€”
271
+ `{"catalog":"status"}` without it silently returns the ordinary `list` view
272
+ (never catalog `terms`). See
273
+ [`skill/SKILL.md` ยง6](skill/SKILL.md).
274
+
190
275
  ## Command surface
191
276
 
192
277
  Every verb but `embedding-status` takes a single `--input` flag carrying one JSON
193
- object; there are no per-field flags. Nineteen operations (the 18 verbs plus
194
- `batch`):
278
+ object; `embedding-status` takes **no** options at all. **There are no per-field
279
+ flags** โ€” a per-field option (`get --uid โ€ฆ`, `query --view list`, `create
280
+ --title โ€ฆ`, `batch action --operation โ€ฆ`) fails with `invalid_argument` (exit 2)
281
+ and `Unknown option: --<field>. Available: --input`. (`--help` prints a
282
+ "per-field flags are also accepted" footer line; it is generated boilerplate
283
+ that does not hold for these commands โ€” `--input` is the only option they
284
+ accept. The special commands `serve`, `install-skill`, and `search` are the
285
+ exception: they take argv flags and no `--input`.) Thirty operations (the
286
+ 29 verbs plus `batch`):
195
287
 
196
288
  | Verb | CLI | MCP tool |
197
289
  | ----------------- | ------------------------------- | -------------------------- |
@@ -200,11 +292,20 @@ object; there are no per-field flags. Nineteen operations (the 18 verbs plus
200
292
  | `priorityMatrix` | `adhd-backlog priority-matrix` | `backlog_priority_matrix` |
201
293
  | `partOfRollup` | `adhd-backlog part-of-rollup` | `backlog_part_of_rollup` |
202
294
  | `openCurve` | `adhd-backlog open-curve` | `backlog_open_curve` |
295
+ | `report` | `adhd-backlog report` | `backlog_report` |
203
296
  | `embeddingStatus` | `adhd-backlog embedding-status` | `backlog_embedding_status` |
204
297
  | `lookup` | `adhd-backlog lookup` | `backlog_lookup` |
205
298
  | `create` | `adhd-backlog create` | `backlog_create` |
206
299
  | `update` | `adhd-backlog update` | `backlog_update` |
300
+ | `addCitation` | `adhd-backlog add-citation` | `backlog_add_citation` |
301
+ | `removeCitation` | `adhd-backlog remove-citation` | `backlog_remove_citation` |
207
302
  | `transition` | `adhd-backlog transition` | `backlog_transition` |
303
+ | `attest` | `adhd-backlog attest` | `backlog_attest` |
304
+ | `recheck` | `adhd-backlog recheck` | `backlog_recheck` |
305
+ | `obligate` | `adhd-backlog obligate` | `backlog_obligate` |
306
+ | `unobligate` | `adhd-backlog unobligate` | `backlog_unobligate` |
307
+ | `specAppend` | `adhd-backlog spec-append` | `backlog_spec_append` |
308
+ | `specCheck` | `adhd-backlog spec-check` | `backlog_spec_check` |
208
309
  | `claim` | `adhd-backlog claim` | `backlog_claim` |
209
310
  | `relate` | `adhd-backlog relate` | `backlog_relate` |
210
311
  | `move` | `adhd-backlog move` | `backlog_move` |
@@ -213,15 +314,24 @@ object; there are no per-field flags. Nineteen operations (the 18 verbs plus
213
314
  | `upsertComponent` | `adhd-backlog upsert-component` | `backlog_upsert_component` |
214
315
  | `upsertLocation` | `adhd-backlog upsert-location` | `backlog_upsert_location` |
215
316
  | `rmLocation` | `adhd-backlog rm-location` | `backlog_rm_location` |
317
+ | `mergeProject` | `adhd-backlog merge-project` | `backlog_merge_project` |
318
+ | `rmProject` | `adhd-backlog rm-project` | `backlog_rm_project` |
216
319
  | `batch` | `adhd-backlog batch action` | `batch_action` |
217
320
 
218
321
  `--help` prints these as `backlog <verb>`; the CLI also accepts the bare
219
322
  `adhd-backlog <verb>` form shown above, and accepts the explicit namespace
220
- prefix at any position. `get`/`query`/`lookup`/`embedding-status` are reads.
221
- `create`/`update`/`transition`/`claim`/`relate`/`move`/`delete` mutate one
222
- issue. The four `upsert*`/`rmLocation` verbs manage the **registry** โ€”
223
- projects, components, and locations. `batch action` fans any one of them out
224
- over many items.
323
+ prefix at any position. `get`/`query`/`lookup`/`embedding-status` and the four
324
+ stats/rollup ops are reads. `create`/`update`/`add-citation`/`remove-citation`/
325
+ `transition`/`claim`/`relate`/`move`/`delete` mutate one issue;
326
+ `attest`/`recheck` record anchored evidence,
327
+ and `obligate`/`unobligate` declare or retire a typed requirement that gates a
328
+ transition ([SKILL.md ยง4](skill/SKILL.md)); `specAppend`/`specCheck` advance
329
+ and verify a ticket's spec revisions, and a revision is annotated through
330
+ `attest` with a `SPEC` subject + a `revision:` anchor ([SKILL.md
331
+ ยง5](skill/SKILL.md)). The
332
+ `upsert*`/`rmLocation` verbs manage the **registry** (projects, components,
333
+ locations), and `mergeProject`/`rmProject` collapse or retire project rows.
334
+ `batch action` fans any one of them out over many items.
225
335
 
226
336
  ## Transports
227
337
 
@@ -239,14 +349,45 @@ simultaneously true everywhere:
239
349
  Every verb call returns one of two shapes, on every transport:
240
350
 
241
351
  ```ts
242
- { ok: true, data: T, warnings?: string[], meta?: { total, returned, limit?, offset?, truncated? } }
352
+ { ok: true, data: T, warnings?: string[],
353
+ meta?: { total, returned, limit?, offset?, truncated?, total_relation?, has_more?, next_cursor? } }
243
354
  { ok: false, error: { code, message, details? }, warnings?: string[] }
244
355
  ```
245
356
 
246
- `meta` is present on list-shaped reads (`query`) and its `total` is the true
247
- match count _before_ `limit`/`offset` are applied; a truncated page sets
248
- `data.hasMore: true` and `data.nextCursor`, so a short result never silently
249
- looks complete.
357
+ `meta` is present on the four **item-list** reads (`query`'s `list`/`ready`/
358
+ `stale`/`similar` views). `total` is the match count; on `list` it is the exact
359
+ count _before_ `limit`/`offset`, while on `ready`/`stale`/`similar` a true
360
+ pre-limit total is unknowable at bounded cost, so `meta` reports
361
+ `has_more` (derived by fetching one row beyond the page) and labels `total`
362
+ with `total_relation: 'gte'` โ€” a lower bound, never a fabricated exact number
363
+ (`'eq'` means exact). `graph`/`order`/`overlap` are a graph, a topological
364
+ order, and an axis grouping โ€” they deliberately carry **no** `meta`, because
365
+ none of them is a filtered row set with an honest "how many matched" count.
366
+
367
+ > **`view:"ready"` is not an actionability filter.** It selects the `open`
368
+ > issues that are unclaimed and whose every live incoming `blocks` blocker is
369
+ > terminal โ€” a pure claim/`blocks` predicate that never evaluates obligations
370
+ > or evidence. An item with an unsatisfied `block`-severity obligation
371
+ > (`fields:["verdict"]` reports `actionable:false`) still appears in `ready`.
372
+ > To find actionable work, read `fields:["verdict"]`; do not use `ready`.
373
+ > Full definition: [`skill/SKILL.md` ยง3](skill/SKILL.md).
374
+
375
+ A derived `_score` always carries its provenance: `_score_kind` is `'rrf'`
376
+ (the fused text+vec rank `view:'similar'` and semantic list reads use),
377
+ `'bm25'` (a grep-only FTS score), `'cosine'`, `'rank'`, or `'priority'`. A
378
+ rank-derived `_score` is **ordinal** โ€” never read it as a similarity or a
379
+ confidence.
380
+
381
+ Sub-collections can be bounded: `get { fields:["auditTrail"], lastN:5 }`
382
+ returns at most the newest 5 audit rows (oldest-first order preserved);
383
+ `part-of-rollup { uid, countOnly:true }` returns the counts and omits
384
+ `childrenOpenUids` entirely, while `part-of-rollup { uid, limit, after }` pages
385
+ the open-descendant uid list (`nextCursor`/`hasMore`).
386
+
387
+ The `report` verb is a grouped rollup whose every number is computed from the
388
+ store in that call: `byKind`, `byPriority` (composed from `priority-matrix`),
389
+ `byStatus`, and `avgAgeDays` of open in-scope items, with `statusScope` naming
390
+ exactly what was counted (`'open'` when the filter omits `status`).
250
391
 
251
392
  ### Error codes
252
393
 
@@ -269,11 +410,20 @@ code alone.
269
410
 
270
411
  ### Citations & git context
271
412
 
272
- A citation is `{ file, lines?, context?, symbol? }`. Pass `citations` on
273
- `create`, or on the `transition` that moves an issue into a terminal status
274
- when the project's policy requires it. `file` must name a file, not a
275
- directory: a directory target is rejected as a non-retryable `validation`
276
- error.
413
+ A citation is `{ file, lines?, context?, symbol?, blastRadius?, revision? }`.
414
+ Pass `citations` on `create`, or on the `transition` that moves an issue into a
415
+ terminal status when the project's policy requires it. Add one to an existing
416
+ issue with `add-citation` and retire one with `remove-citation` (addressed by
417
+ the citation's **own uid**, never by re-specifying `(file, lines)`). `update`
418
+ carries `citations` as a **diff-emitter** โ€” it adds what's new and removes what's
419
+ gone against the live set, in the same transaction, and never mints a new issue
420
+ uid (only a `body` edit supersedes). `file` must name a file, not a directory: a
421
+ directory target is rejected as a non-retryable `validation` error.
422
+
423
+ Pin a citation to a git revision with `revision` (a branch, tag, or sha) to cite
424
+ a file that exists **only on an unmerged branch** โ€” the target is read from
425
+ `git show <revision>:<path>` rather than the working tree, so the sha is a real
426
+ content hash even though the file is not checked out.
277
427
 
278
428
  The item-level **`gitContext`** is separate from a citation's own `context`:
279
429
  it records the repo disclosure contract's `<active git context>` โ€” the first
@@ -284,15 +434,17 @@ the item's `Citations:` block. Omit it and nothing is stored.
284
434
 
285
435
  ### Which build these docs describe
286
436
 
287
- These docs describe `entrypoint/backlog/dist/index.js` built from revision
288
- `9df2a5c7`, whose `create`/`transition` inputs include `gitContext`. A
289
- **globally installed** `adhd-backlog` may be an older build (it is whatever was
290
- last published/installed); on such a build `gitContext` is not in the schema
291
- and is rejected with `invalid_argument`, and the stats/rollup ops
292
- (`priority-matrix` / `part-of-rollup` / `open-curve`) are absent. Run
293
- `adhd-backlog --help` and compare the `backlog create` / `backlog
294
- priority-matrix` lines against this page if a documented field or verb is
295
- refused.
437
+ These docs describe `entrypoint/backlog/dist/index.js` built from the revision
438
+ captured here, which mounts 29 verbs โ€” including the citation
439
+ (`add-citation`/`remove-citation`), obligation
440
+ (`obligate`/`unobligate`), spec-pointer (`spec-append`/`spec-check`),
441
+ and catalog (`query --input '{"view":"catalogs"}'`) surfaces. A **globally
442
+ installed** `adhd-backlog` may be an older build (it is whatever was last
443
+ published/installed); on such a build `gitContext` may be rejected with
444
+ `invalid_argument`, and the stats/rollup ops (`priority-matrix` /
445
+ `part-of-rollup` / `open-curve` / `report`) and the obligation/spec verbs may be
446
+ absent. Run `adhd-backlog --help` and compare it against
447
+ [ยง1 of SKILL.md](skill/SKILL.md) if a documented field or verb is refused.
296
448
 
297
449
  ## Build & startup
298
450
 
@@ -322,29 +474,32 @@ the extract-stage IR cache, which persists the result for the next run.
322
474
  and store-free (it never opens the graph store and never writes under
323
475
  `~/.adhd`).
324
476
 
325
- | Setting | Env var | Default | Notes |
326
- | ---------------- | -------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
327
- | IR cache enabled | `APIGEN_IR_CACHE_ENABLED` | `1` | Governs ONLY the **runtime fallback** cache. It does **not** bypass the baked `api.ir.json` artifact โ€” that artifact is the startup path, not a cache |
328
- | IR cache file | `APIGEN_IR_CACHE_FILE` | a machine-global path under `~/.adhd` | Where a fallback extraction's result is cached |
477
+ | Setting | Env var | Default | Notes |
478
+ | ---------------- | ------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
479
+ | IR cache enabled | `APIGEN_IR_CACHE_ENABLED` | `1` | Governs ONLY the **runtime fallback** cache. It does **not** bypass the baked `api.ir.json` artifact โ€” that artifact is the startup path, not a cache |
480
+ | IR cache file | `APIGEN_IR_CACHE_FILE` | a machine-global path under `~/.adhd` | Where a fallback extraction's result is cached |
329
481
 
330
482
  ## Library API
331
483
 
332
484
  Beyond the CLI/MCP/HTTP surface, the package exports its query layer
333
- (`src/query/index.ts`), including three rollup/stats read views that are **not**
485
+ (`src/query/index.ts`), including four rollup/stats read ops that are **not**
334
486
  members of `query.view`:
335
487
 
336
488
  - `priorityMatrix(handle, { filter? })` โ€” a status-aware per-priority count
337
489
  breakdown (defaults to open work; `filter.status` may be `'open'`, `'closed'`,
338
490
  `'all'`, or a status name).
339
- - `partOfRollup(handle, { uid })` โ€” transitive `part_of` descendants of an
340
- issue, counted once each regardless of chain depth.
491
+ - `partOfRollup(handle, { uid, countOnly?, limit?, after? })` โ€” transitive
492
+ `part_of` descendants of an issue, counted once each regardless of chain
493
+ depth; `countOnly` omits the uid list, `limit`/`after` page it.
341
494
  - `openCurve(handle, { filter?, at })` โ€” per-sampled-instant counts of issues
342
495
  that existed and how many were open, reconstructed from the audit trail.
496
+ - `report(handle, { filter? })` โ€” a grouped rollup (`byKind`/`byPriority`/
497
+ `byStatus`/`avgAgeDays`) composed from live reads in the same call.
343
498
 
344
- Reach them by importing `@adhd/backlog` in-process. The same three are also
345
- mounted as operations โ€” `priority-matrix` / `part-of-rollup` / `open-curve` on
346
- the CLI (`backlog_priority_matrix` / `backlog_part_of_rollup` /
347
- `backlog_open_curve` on MCP); see the command surface above.
499
+ Reach them by importing `@adhd/backlog` in-process. The same four are also
500
+ mounted as operations โ€” `priority-matrix` / `part-of-rollup` / `open-curve` /
501
+ `report` on the CLI (`backlog_priority_matrix` / `backlog_part_of_rollup` /
502
+ `backlog_open_curve` / `backlog_report` on MCP); see the command surface above.
348
503
 
349
504
  ## Configuration
350
505
 
@@ -361,7 +516,7 @@ into a narrower scope.
361
516
  | Write busy timeout | `ADHD_BACKLOG_DATABASE_BUSY_TIMEOUT_MS` | `5000` | How long a write waits on a contended lock before giving up |
362
517
  | Log level | `ADHD_BACKLOG_LOG_LEVEL` | `info` | `trace`\|`debug`\|`info`\|`warn`\|`error`\|`fatal`\|`silent` |
363
518
  | Scope | `ADHD_BACKLOG_SCOPE` (falls back to `ADHD_ENV_SCOPE`) | `global` | Which store root to resolve against |
364
- | Namespace | `--namespace <value>` CLI flag (no env var) | `production` | Which path segment under the scope root to resolve against (`<root>/backlog/<namespace>/โ€ฆ`) โ€” one of `production`\|`test`\|`sandbox`. `--namespace sandbox` ALSO mints a fresh throwaway root and writes a real `config.yaml` there with `embedding.enabled: false`, so a sandboxed invocation is isolated along two independent axes plus a deliberate config, never an accident of an empty directory |
519
+ | Namespace | `--namespace <value>` CLI flag (no env var) | `production` | Which path segment under the scope root to resolve against (`<root>/backlog/<namespace>/โ€ฆ`) โ€” one of `production`\|`test`\|`sandbox`. `--namespace sandbox` ALSO mints a fresh throwaway root and writes a real `config.yaml` there with `embedding.enabled: false`, so a sandboxed invocation is isolated along two independent axes plus a deliberate config, never an accident of an empty directory. It also **ignores a foreign `ADHD_ROOT`**: if the variable names a path this tool did not mint as a sandbox it warns `โ€ฆ is not a sandbox this tool created โ€” ignoring it โ€ฆ` and mints a fresh store instead, so a stray `ADHD_ROOT` can never redirect a sandboxed run into an unrecognized location. `adhd-backlog sandbox-path` reports the resolved store without opening it; its `adhdRoot` key is present only when a root was explicitly resolved (`ADHD_ROOT` set, or `--namespace sandbox`) and is omitted for the default `production`/`test` namespaces |
365
520
  | Semantic search | `ADHD_BACKLOG_EMBEDDING_ENABLED` | `false` | See below |
366
521
  | Embedding provider | `ADHD_BACKLOG_EMBEDDING_PROVIDER` | `fastembed` | Only consulted when embedding is enabled |
367
522
  | Embedding model | `ADHD_BACKLOG_EMBEDDING_MODEL` | `bge-base-en-v1.5` (768-dim) | Only consulted when embedding is enabled |
package/api.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { IMergeProjectInput, IMergeProjectOutcome, IRmProjectInput } from './write/merge-project.js';
1
2
  import { IUpsertProjectInput, IUpsertProjectOutcome, IUpsertComponentInput, IUpsertComponentOutcome, IUpsertLocationInput, IUpsertLocationOutcome, IRmLocationInput, IRmLocationOutcome } from './write/catalog.js';
2
3
  import { IDeleteIssueInput, IDeleteIssueOutcome } from './write/delete.js';
3
4
  import { IMoveIssueInput, IMoveIssueOutcome } from './write/move.js';
@@ -7,6 +8,12 @@ import { ITransitionInput, ITransitionOutcome } from './write/transition.js';
7
8
  import { IUpdateIssueInput, IUpdateIssueOutcome } from './write/update.js';
8
9
  import { ICreateIssueInput, ICreateIssueResult } from './write/create-issue.js';
9
10
  import { IIssueGetInput, IIssueGetResult, IIssueQueryInput, IIssueQueryResult, ILookupResult } from './query/types.js';
11
+ import { ISpecCheckInput, ISpecCheckOutcome } from './query/spec-staleness.js';
12
+ import { ISpecAppendInput, ISpecAppendOutcome } from './write/spec-revision.js';
13
+ import { IObligateInput, IObligateOutcome, IUnobligateInput, IUnobligateOutcome } from './write/obligation.js';
14
+ import { IAttestInput, IAttestOutcome, IRecheckInput, IRecheckOutcome } from './write/attestation.js';
15
+ import { IAddCitationInput, IAddCitationOutcome, IRemoveCitationInput, IRemoveCitationOutcome } from './write/citation.js';
16
+ import { IReportInput, IReportResult } from './query/views/report.js';
10
17
  import { IPriorityMatrixInput, IPriorityMatrixResult, IPartOfRollupInput, IPartOfRollupResult, IOpenCurveInput, IOpenCurveResult } from './query/views/stats.js';
11
18
  import { EmbeddingLiveConfig } from './write/embedding-config.js';
12
19
  import { IOutcomeEnvelope } from './envelope.js';
@@ -93,6 +100,17 @@ export declare function partOfRollup(ctx: BacklogCtx, input: IPartOfRollupInput)
93
100
  * `queryHandle` gates.
94
101
  */
95
102
  export declare function openCurve(ctx: BacklogCtx, input: IOpenCurveInput): Promise<IOutcomeEnvelope<IOpenCurveResult>>;
103
+ /**
104
+ * SPEC.md ยง5 / DESIGN ยง5 AC7's grouped rollup โ€” counts by kind, by priority
105
+ * (composed from `priorityMatrix`), and by status, plus the average age of
106
+ * open in-scope items, every number computed from the store in THIS call.
107
+ *
108
+ * A read op, NOT a `query.view` member: it returns a named-scope aggregate
109
+ * (`statusScope` names what was counted) keyed by no list cursor. Uses only
110
+ * `handle.graph` โ€” see {@link priorityMatrix}'s note on the `queryHandle`
111
+ * gates.
112
+ */
113
+ export declare function report(ctx: BacklogCtx, input: IReportInput): Promise<IOutcomeEnvelope<IReportResult>>;
96
114
  /** The `embedding_status` read op's payload (see {@link embeddingStatus}). */
97
115
  export interface IEmbeddingStatusResult {
98
116
  /** The on-disk `embedding.enabled` (observed from the config layers). */
@@ -136,6 +154,7 @@ export declare function embeddingStatus(ctx: BacklogCtx): Promise<IOutcomeEnvelo
136
154
  */
137
155
  export declare function lookup(ctx: BacklogCtx, input: {
138
156
  q: string;
157
+ kind?: string;
139
158
  }): Promise<IOutcomeEnvelope<ILookupResult>>;
140
159
  /** File a new issue, minting its `uid` and linking it to a project component. */
141
160
  export declare function create(ctx: BacklogCtx, input: ICreateIssueInput): Promise<IOutcomeEnvelope<ICreateIssueResult>>;
@@ -147,8 +166,60 @@ export declare function create(ctx: BacklogCtx, input: ICreateIssueInput): Promi
147
166
  * use `transition`.
148
167
  */
149
168
  export declare function update(ctx: BacklogCtx, input: IUpdateIssueInput): Promise<IOutcomeEnvelope<IUpdateIssueOutcome>>;
169
+ /**
170
+ * Add one first-class citation to an existing issue.
171
+ *
172
+ * Mints a `citation` node + its `has_citation` edge + an audit row, atomically.
173
+ * The citation may pin a git `revision` to cite evidence that exists only on an
174
+ * unmerged branch. `update` carries citations as a diff; this is the single-add
175
+ * form.
176
+ */
177
+ export declare function addCitation(ctx: BacklogCtx, input: IAddCitationInput): Promise<IOutcomeEnvelope<IAddCitationOutcome>>;
178
+ /**
179
+ * Soft-remove one citation by the citation's OWN `uid` (never a
180
+ * `(target,line)` re-specification). Bi-temporally invalidates the citation
181
+ * node and its `has_citation` edge; the issue's uid is untouched.
182
+ */
183
+ export declare function removeCitation(ctx: BacklogCtx, input: IRemoveCitationInput): Promise<IOutcomeEnvelope<IRemoveCitationOutcome>>;
150
184
  /** Move an issue to a new status, recording the transition in its audit trail. */
151
185
  export declare function transition(ctx: BacklogCtx, input: ITransitionInput): Promise<IOutcomeEnvelope<ITransitionOutcome>>;
186
+ /**
187
+ * Create a first-class attestation โ€” a separate record keyed to an issue by
188
+ * `{id, revision}`, never written into the issue. The subject's `uid` and
189
+ * revision are unchanged.
190
+ */
191
+ export declare function attest(ctx: BacklogCtx, input: IAttestInput): Promise<IOutcomeEnvelope<IAttestOutcome>>;
192
+ /**
193
+ * Re-run an attestation's anchor ladder and APPEND the new check to its
194
+ * append-only `checks[]` history (never overwriting earlier checks).
195
+ */
196
+ export declare function recheck(ctx: BacklogCtx, input: IRecheckInput): Promise<IOutcomeEnvelope<IRecheckOutcome>>;
197
+ /**
198
+ * Declare a typed requirement on an issue.
199
+ *
200
+ * The requirement is drawn from a closed predicate core
201
+ * (`evidence`/`blockers_terminal`/`relation`/`all_of`/`any_of`/`not`);
202
+ * `applies_to.to` is required. Nothing is evaluated here โ€” the C5 gate reads
203
+ * it at a transition and the C6 verdict predicts it on read.
204
+ */
205
+ export declare function obligate(ctx: BacklogCtx, input: IObligateInput): Promise<IOutcomeEnvelope<IObligateOutcome>>;
206
+ /** Retire an obligation (bi-temporal; never a hard delete). */
207
+ export declare function unobligate(ctx: BacklogCtx, input: IUnobligateInput): Promise<IOutcomeEnvelope<IUnobligateOutcome>>;
208
+ /**
209
+ * Append a spec REVISION to a ticket (C10, DESIGN ยง12). A work product is a
210
+ * revision of its ticket: this mints a NEW immutable `SPEC` node holding the
211
+ * fragment and advances the ticket's `meta.spec_revision` pointer IN PLACE โ€”
212
+ * the ticket's uid is preserved and no prior revision is rewritten. The
213
+ * required `base_revision` is a CAS token: a stale value is refused with
214
+ * `precondition_failed` and nothing is written.
215
+ */
216
+ export declare function specAppend(ctx: BacklogCtx, input: ISpecAppendInput): Promise<IOutcomeEnvelope<ISpecAppendOutcome>>;
217
+ /**
218
+ * Check whether a spec-revision `token` is still current for a ticket (C10).
219
+ * An ABSENT token is `stale` (`method:'none'`, `reason:'no-token-supplied'`),
220
+ * never `fresh`.
221
+ */
222
+ export declare function specCheck(ctx: BacklogCtx, input: ISpecCheckInput): Promise<IOutcomeEnvelope<ISpecCheckOutcome>>;
152
223
  /** Take, renew or release an exclusive working lease on an issue. */
153
224
  export declare function claim(ctx: BacklogCtx, input: IClaimInput): Promise<IOutcomeEnvelope<IClaimOutcome>>;
154
225
  /** Create or remove a typed relationship between two issues. */
@@ -180,6 +251,22 @@ export declare function upsertComponent(ctx: BacklogCtx, input: IUpsertComponent
180
251
  export declare function upsertLocation(ctx: BacklogCtx, input: IUpsertLocationInput): Promise<IOutcomeEnvelope<IUpsertLocationOutcome>>;
181
252
  /** Soft-remove a location by `uid`. */
182
253
  export declare function rmLocation(ctx: BacklogCtx, input: IRmLocationInput): Promise<IOutcomeEnvelope<IRmLocationOutcome>>;
254
+ /**
255
+ * Merge duplicate project `fromUid` into the canonical survivor `toUid` (C1).
256
+ *
257
+ * One `BEGIN IMMEDIATE` transaction re-points every component the duplicate
258
+ * owned onto the survivor, soft-retires the duplicate with a one-hop
259
+ * `meta.redirectTo`, and audits. Idempotent on a repeat `(from,to)` call.
260
+ */
261
+ export declare function mergeProject(ctx: BacklogCtx, input: IMergeProjectInput): Promise<IOutcomeEnvelope<IMergeProjectOutcome>>;
262
+ /**
263
+ * Soft-retire a project by `uid` WITHOUT a survivor (C1): stamps `t_invalid`
264
+ * with a reason and audits; no `redirectTo` is written.
265
+ */
266
+ export declare function rmProject(ctx: BacklogCtx, input: IRmProjectInput): Promise<IOutcomeEnvelope<{
267
+ uid: string;
268
+ retired: true;
269
+ }>>;
183
270
  /**
184
271
  * SPEC ยง6.7 names this verb `delete` on every mount (`backlog_delete`,
185
272
  * `backlog delete`, `DELETE /issue`). `export async function delete` is a