@adhd/backlog 0.1.8 → 1.0.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.
- package/CHANGELOG.md +93 -44
- package/README.md +332 -81
- package/api.d.ts +146 -0
- package/cli.d.ts +45 -18
- package/env.d.ts +23 -3
- package/envelope.d.ts +163 -0
- package/index.d.ts +11 -10
- package/index.js +531 -173
- package/index.mjs +29814 -15647
- package/install-skill.d.ts +23 -0
- package/package.json +50 -15
- package/query/card.d.ts +31 -0
- package/query/get.d.ts +11 -0
- package/query/index.d.ts +67 -0
- package/query/markdown.d.ts +11 -0
- package/query/query.d.ts +131 -0
- package/query/resolve.d.ts +123 -0
- package/query/types.d.ts +450 -0
- package/query/views/registry.d.ts +43 -0
- package/query/views/semantic.d.ts +101 -0
- package/query/views/stats.d.ts +109 -0
- package/search-shortcut.d.ts +79 -0
- package/serve.d.ts +18 -0
- package/server.d.ts +139 -4
- package/skill/SKILL.md +619 -138
- package/store/graph-backlog-store.d.ts +80 -17
- package/store/immediate-retry.d.ts +24 -13
- package/store/type-policy.d.ts +4 -0
- package/store/vocabulary-guard.d.ts +52 -0
- package/version-info.d.ts +15 -0
- package/write/audit.d.ts +36 -0
- package/write/bootstrap.d.ts +123 -0
- package/write/catalog.d.ts +351 -0
- package/write/claim-lease.d.ts +21 -0
- package/write/claim.d.ts +80 -0
- package/write/create-issue.d.ts +250 -0
- package/write/delete.d.ts +39 -0
- package/write/embed-drain.d.ts +68 -0
- package/write/embedding-observer.d.ts +80 -0
- package/write/errors.d.ts +303 -0
- package/write/issue-status.d.ts +10 -0
- package/write/move.d.ts +70 -0
- package/write/relate.d.ts +52 -0
- package/write/transition.d.ts +60 -0
- package/write/tx.d.ts +344 -0
- package/write/update.d.ts +81 -0
- package/client.d.ts +0 -169
- package/markdown.d.ts +0 -75
- package/migration-admin.d.ts +0 -26
- package/model.d.ts +0 -437
- package/store/audit-log.d.ts +0 -16
- package/store/claim.d.ts +0 -24
- package/store/crud.d.ts +0 -62
- package/store/ids.d.ts +0 -24
- package/store/lifecycle.d.ts +0 -36
- package/store/mapping.d.ts +0 -101
- package/store/mutate-metadata.d.ts +0 -8
- package/store/query.d.ts +0 -68
- package/store/repo-migration.d.ts +0 -51
- package/store/serve-lock.d.ts +0 -42
- package/store/structure.d.ts +0 -66
package/skill/SKILL.md
CHANGED
|
@@ -1,148 +1,629 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backlog-usage
|
|
3
|
-
description:
|
|
3
|
+
description: 'Use whenever filing, reading, claiming, transitioning, relating, or resolving a backlog issue — or registering a project/component/location — in ANY repo on this machine, via the `adhd-backlog` CLI / `mcp__backlog__*` tools, never by hand-editing a `BACKLOG.md` file. Examples: "log this bug", "file a debt item for the flaky test", "claim that issue", "what''s still open in this repo", "where does this tool live".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `@adhd/backlog` usage
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
backlog
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
8
|
+
`backlog` is a graph-backed, multi-agent backlog tool: issues, a project/
|
|
9
|
+
component/location registry, and the relationships between them, all served
|
|
10
|
+
from one store over four transports (CLI, MCP, HTTP, and in-process). This
|
|
11
|
+
skill is the ONLY place the command surface and calling convention are
|
|
12
|
+
documented — `AGENTS.md`/`CLAUDE.md` carry just a pointer to it.
|
|
13
|
+
|
|
14
|
+
Identity is the global `uid` returned by `create`/`upsertProject`/etc. —
|
|
15
|
+
never a family-scoped human-readable id. A `uid` is stable for the life of its
|
|
16
|
+
node, but a `body` edit replaces that node: as §3 below spells out, `update`
|
|
17
|
+
with a `body` mints a successor with a fresh `uid` and joins the two with a
|
|
18
|
+
`SUPERSEDES` edge. A uid you persisted earlier therefore stays _resolvable_ but
|
|
19
|
+
may no longer be the _live_ one — addressing it returns `conflict` and names
|
|
20
|
+
the successor. Never treat a stored uid as immutable across edits.
|
|
21
|
+
|
|
22
|
+
Every example below was run against `entrypoint/backlog/dist/index.js` — the
|
|
23
|
+
baseline examples on revision `9df2a5c7`, the §8 stats/rollup examples on the
|
|
24
|
+
build that first mounted those ops — and its exact output is what is shown. A
|
|
25
|
+
_globally installed_ `adhd-backlog` may be an older build: in particular
|
|
26
|
+
`gitContext` on `create`/`transition` (§6) exists in the `9df2a5c7` build but
|
|
27
|
+
an older installed build rejects it with `invalid_argument`, and the §8 stats
|
|
28
|
+
ops (`priority-matrix`/`part-of-rollup`/`open-curve`) exist only in a build at
|
|
29
|
+
or after the one that mounted them. Compare the `backlog create` and `backlog
|
|
30
|
+
priority-matrix` lines of `adhd-backlog --help` with §1 before relying on a
|
|
31
|
+
field.
|
|
32
|
+
|
|
33
|
+
## 1. Command surface — 17 verbs (plus `batch`), one calling convention
|
|
34
|
+
|
|
35
|
+
**Every verb takes a single `--input` flag carrying one JSON object.** There
|
|
36
|
+
are no per-field flags.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
adhd-backlog backlog get --input '<IIssueGetInput json>'
|
|
40
|
+
adhd-backlog backlog query --input '<IIssueQueryInput json>'
|
|
41
|
+
adhd-backlog backlog priority-matrix --input '<IPriorityMatrixInput json>'
|
|
42
|
+
adhd-backlog backlog part-of-rollup --input '<IPartOfRollupInput json>'
|
|
43
|
+
adhd-backlog backlog open-curve --input '<IOpenCurveInput json>'
|
|
44
|
+
adhd-backlog backlog lookup --input '{"q": "<tool, file path, or URL>"}'
|
|
45
|
+
adhd-backlog backlog create --input '<ICreateIssueInput json>'
|
|
46
|
+
adhd-backlog backlog update --input '<IUpdateIssueInput json>'
|
|
47
|
+
adhd-backlog backlog transition --input '<ITransitionInput json>'
|
|
48
|
+
adhd-backlog backlog claim --input '<IClaimInput json>'
|
|
49
|
+
adhd-backlog backlog relate --input '<IRelateInput json>'
|
|
50
|
+
adhd-backlog backlog move --input '<IMoveIssueInput json>'
|
|
51
|
+
adhd-backlog backlog upsert-project --input '<IUpsertProjectInput json>'
|
|
52
|
+
adhd-backlog backlog upsert-component --input '<IUpsertComponentInput json>'
|
|
53
|
+
adhd-backlog backlog upsert-location --input '<IUpsertLocationInput json>'
|
|
54
|
+
adhd-backlog backlog rm-location --input '<IRmLocationInput json>'
|
|
55
|
+
adhd-backlog backlog delete --input '<IDeleteIssueInput json>'
|
|
56
|
+
adhd-backlog batch action --input '<IBatchActionInput json>'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The `backlog` segment in front of every verb (and `batch` in front of
|
|
60
|
+
`action`) is the CLI namespace each operation is mounted under, and is the
|
|
61
|
+
form `adhd-backlog --help` prints. The leading segment is optional: the CLI
|
|
62
|
+
accepts both `adhd-backlog get --input …` and
|
|
63
|
+
`adhd-backlog backlog get --input …` (identical). Running `adhd-backlog
|
|
64
|
+
--help` (or an unknown command) prints the exact live shape of every input:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
$ adhd-backlog --help
|
|
68
|
+
Available commands:
|
|
69
|
+
|
|
70
|
+
backlog claim { input: { uid: string, by: string, action: 'claim'|'release'|'renew', force?: boolean } }
|
|
71
|
+
backlog create { input: { title: string, body: string, project: string, component?: string, kind?: string, status?: string, priority?: string, citations?: object[], author?: string, assignee?: string, gitContext?: string, by: string, duplicateAction?: 'abort'|'force'|'comment', awaitEmbed?: boolean } }
|
|
72
|
+
backlog delete { input: { uid: string, reason: string, by: string, awaitEmbed?: boolean } }
|
|
73
|
+
backlog get { input: { uid: string, fields?: union[] } | { registry: 'project'|'component'|'location', name: string, filter?: object } }
|
|
74
|
+
backlog lookup { input: { q: string } }
|
|
75
|
+
backlog move { input: { uid: string, toProject?: string, toComponent?: string, by: string } }
|
|
76
|
+
backlog open-curve { input: { filter?: object, at: string[] } }
|
|
77
|
+
backlog part-of-rollup { input: { uid: string } }
|
|
78
|
+
backlog priority-matrix { input: { filter?: object } }
|
|
79
|
+
backlog query { input: { text?: string, filter?: object, fields?: union[], sort?: 'priority'|'updated'|'created'|'relevance'|'textMatch', direction?: 'asc'|'desc', limit?: number, offset?: number, after?: string, view?: 'list'|'ready'|'graph'|'order'|'stale'|'similar'|'overlap'|'projects'|'components'|'locations', format?: 'json'|'markdown', overlapAxis?: 'file'|'project'|'component'|'author', overlapUids?: string[], staleAfterMin?: number } }
|
|
80
|
+
backlog relate { input: { sourceUid: string, targetUid: string, rel: 'relates_to'|'supersedes'|'blocks'|'duplicate_of'|'part_of', action: 'add'|'remove', by: string } }
|
|
81
|
+
backlog rm-location { input: { uid: string, by: string, reason?: string } }
|
|
82
|
+
backlog transition { input: { uid: string, by: string, toStatus: string, note?: string, citations?: object[], gitContext?: string } }
|
|
83
|
+
backlog update { input: { uid: string, by: string, title?: string, body?: string, kind?: string, priority?: string, assignee?: string, author?: string, awaitEmbed?: boolean } }
|
|
84
|
+
backlog upsert-component { input: { project: string, name: string, path?: string, description?: string, by: string } }
|
|
85
|
+
backlog upsert-location { input: { component: string, project?: string, locType: 'path'|'url'|'tool', value: string, by: string } }
|
|
86
|
+
backlog upsert-project { input: { name: string, path?: string, repoUrl?: string, monorepo?: boolean, description?: string, by: string } }
|
|
87
|
+
batch action { input: { operation: 'backlog/get', items: object[], concurrency?: number, mode?: 'parallel'|'serial'|'chained', onItemError?: 'continue'|'abort', itemTimeoutMs?: number } }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Trust that output over anything hardcoded here — it is the live schema, not
|
|
91
|
+
a stale copy of it.
|
|
92
|
+
|
|
93
|
+
### Special commands — no `--input`, and not verbs
|
|
94
|
+
|
|
95
|
+
`serve`, `install-skill` (alias `install`), `search`, `sandbox-path`, and
|
|
96
|
+
`store-check` are handled before the command table:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
adhd-backlog serve [--transport mcp|http|both] [--port N] [--host H]
|
|
100
|
+
adhd-backlog install-skill [--host claude|codex|opencode|all] [--scope user|project]
|
|
101
|
+
adhd-backlog search "<query text>" [--limit n]
|
|
102
|
+
adhd-backlog sandbox-path
|
|
103
|
+
adhd-backlog store-check
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`store-check` reports the store vocabulary this build expects versus the kinds
|
|
107
|
+
actually present in the resolved store, exiting non-zero on a mismatch — useful
|
|
108
|
+
after pointing a build at a store written by a different build.
|
|
109
|
+
|
|
110
|
+
`sandbox-path` prints the resolved store location and exits without opening
|
|
111
|
+
it — `{"namespace":"production"|"test"|"sandbox","adhdRoot":"…","dbPath":"…","embeddingEnabled":bool}`.
|
|
112
|
+
Use it to confirm WHICH store a command would touch before running a write.
|
|
113
|
+
Combined with the global `--namespace sandbox` flag (valid before any
|
|
114
|
+
command) it reports the throwaway store that flag would mint, so you can
|
|
115
|
+
check isolation without creating anything.
|
|
116
|
+
|
|
117
|
+
Two conventions apply to every transcript below. **Uids are truncated with
|
|
118
|
+
`…` for readability** — always pass the FULL value the previous call
|
|
119
|
+
returned, never the ellipsis form. And **every invocation prints warnings on
|
|
120
|
+
stderr** (telemetry, the embedding backend, onnxruntime) whether or not it
|
|
121
|
+
succeeded; stdout carries the JSON envelope alone. Parse stdout, key on the
|
|
122
|
+
exit code, and ignore stderr — it is noise, not a failure signal.
|
|
123
|
+
|
|
124
|
+
`search "x" --limit 2` is the argv-flag shortcut for `backlog query --input
|
|
125
|
+
'{"text":"x","limit":2}'` — same envelope, same exit codes, verified:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
$ adhd-backlog search "auth module" --limit 5
|
|
129
|
+
{"ok":true,"data":{"view":"list","items":[{"uid":"…","title":"Flaky test in auth module","kind":"issue","status":"closed","priority":"CRITICAL"}],"hasMore":false},"meta":{"total":1,"returned":1,"limit":5}}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`--namespace <value>` is a global flag valid before ANY command — it selects
|
|
133
|
+
which declared store instance to resolve against: `production` (default),
|
|
134
|
+
`test` (a persisted, non-ephemeral store), or `sandbox`. `--namespace
|
|
135
|
+
sandbox` additionally diverts the invocation into a fresh throwaway store
|
|
136
|
+
(`mkdtemp` + its own DB) instead of the real one, writes a real `config.yaml`
|
|
137
|
+
there with `embedding.enabled: false` so a sandboxed run never pays a real
|
|
138
|
+
model-load cost, and prints the path so you can pass `ADHD_ROOT=<path>` to
|
|
139
|
+
reuse it across calls. Use it whenever you want to try a command without
|
|
140
|
+
touching production data — verified:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
$ adhd-backlog --namespace sandbox backlog upsert-project --input '{"name":"sandbox-demo","by":"claude:1"}'
|
|
144
|
+
[backlog] --namespace sandbox: isolated store at /var/folders/.../backlog-sandbox-AL1cJH (not auto-deleted — pass ADHD_ROOT=... to reuse it, or remove it yourself when done)
|
|
145
|
+
{"ok":true,"data":{"uid":"3ec19363-cd8c-4479-8d7f-c8e4e9f0948b","created":true,"project":{"uid":"3ec19363-cd8c-4479-8d7f-c8e4e9f0948b","name":"sandbox-demo"}}}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 2. The outcome envelope — every verb, every transport
|
|
149
|
+
|
|
150
|
+
Every verb returns one of exactly two shapes, always the same envelope
|
|
151
|
+
regardless of transport:
|
|
152
|
+
|
|
153
|
+
```jsonc
|
|
154
|
+
{ "ok": true, "data": { /* verb-specific payload */ }, "warnings": [], "meta": {} }
|
|
155
|
+
{ "ok": false, "error": { "code": "item_not_found", "message": "…", "details": {} } }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Never assume an unwrapped payload — always read `envelope.data`. **This holds
|
|
159
|
+
for every error a VERB itself reports** — a validation failure the operation's
|
|
160
|
+
own logic detects (bad `limit`, unknown filter key, a terminal transition
|
|
161
|
+
missing its citation) always comes back as `{"ok":false,"error":{...}}` on
|
|
162
|
+
stdout, exit non-zero.
|
|
163
|
+
|
|
164
|
+
It does NOT hold for a malformed `--input` on the CLI: a request that fails
|
|
165
|
+
the outer ajv schema check — missing a required field, an unknown property,
|
|
166
|
+
invalid JSON — never reaches a verb's own logic at all. That class prints an
|
|
167
|
+
UNWRAPPED `{"code":"invalid_argument","message":"…","details":[...]}` (no
|
|
168
|
+
`ok` key) to **stderr**, not stdout, still with the matching `invalid_argument`
|
|
169
|
+
exit code below. A caller that only reads stdout per the envelope contract
|
|
170
|
+
gets nothing at all for this — the single most common error shape a bad
|
|
171
|
+
caller hits — so read stderr too whenever stdout is empty and the exit code
|
|
172
|
+
is non-zero. There are exactly nine error codes for a verb's own reported
|
|
173
|
+
failures, and the CLI's process exit code is derived from `error.code`:
|
|
174
|
+
|
|
175
|
+
| code | exit | meaning |
|
|
176
|
+
| --------------------- | ---- | ---------------------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| `not_found` | 4 | a referenced catalog entry (project/component/kind/status/priority) does not exist |
|
|
178
|
+
| `item_not_found` | 1 | the addressed issue `uid` does not exist |
|
|
179
|
+
| `invalid_argument` | 2 | malformed flag or parameter shape |
|
|
180
|
+
| `validation` | 2 | schema rejection — unknown filter key, unknown projection field, over-limit |
|
|
181
|
+
| `store_busy` | 1 | store contention (busy/lease) — `details.retryable`/`retryAfterMs` say whether and how to retry; never hot-loop |
|
|
182
|
+
| `rag_not_configured` | 1 | a semantic/similarity read with no embedding backend, or an empty vector space |
|
|
183
|
+
| `conflict` | 1 | someone else holds the claim, a single-valued relation is taken, or a supersede raced |
|
|
184
|
+
| `precondition_failed` | 1 | a gate refused the write — a terminal transition missing its required citation/note, or an unverifiable citation |
|
|
185
|
+
| `internal` | 1 | unclassified server-side failure |
|
|
186
|
+
|
|
187
|
+
Success is always exit `0`.
|
|
188
|
+
|
|
189
|
+
### MCP tool names
|
|
190
|
+
|
|
191
|
+
Each verb is also an MCP tool once `.mcp.json` wires the server, named
|
|
192
|
+
`backlog_<verb>` with the verb's own words snake_cased: `backlog_get`,
|
|
193
|
+
`backlog_query`, `backlog_priority_matrix`, `backlog_part_of_rollup`,
|
|
194
|
+
`backlog_open_curve`, `backlog_lookup`, `backlog_create`, `backlog_update`,
|
|
195
|
+
`backlog_transition`, `backlog_claim`, `backlog_relate`, `backlog_move`,
|
|
196
|
+
`backlog_upsert_project`, `backlog_upsert_component`,
|
|
197
|
+
`backlog_upsert_location`, `backlog_rm_location`, `backlog_delete`, plus the
|
|
198
|
+
un-namespaced `batch_action`.
|
|
199
|
+
|
|
200
|
+
## 3. Issue verbs — worked examples
|
|
201
|
+
|
|
202
|
+
Every mutating verb requires `by` — the acting identity, always
|
|
203
|
+
`${agentName}:${instanceId}`, never a bare role literal like `"agent"`. A
|
|
204
|
+
missing/blank `by` is rejected with `invalid_argument` before any write
|
|
205
|
+
runs.
|
|
206
|
+
|
|
207
|
+
**File a new issue.** `project` is RESOLVE-ONLY — `create` never mints one;
|
|
208
|
+
register it first with `upsert-project` (§4). `component` is also resolve-only
|
|
209
|
+
and defaults to the project's reserved `(root)` component when omitted — pass
|
|
210
|
+
it, or the item is invisible to component-scoped scans (§4, "The filing rule"):
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
$ adhd-backlog backlog create --input '{
|
|
214
|
+
"title": "Flaky test in auth module",
|
|
215
|
+
"body": "The auth integration test times out intermittently.",
|
|
216
|
+
"project": "demo-project",
|
|
217
|
+
"by": "claude:1",
|
|
142
218
|
"priority": "HIGH"
|
|
143
219
|
}'
|
|
220
|
+
{"ok":true,"data":{"created":true,"uid":"a61ff0b6-a0f1-4189-9923-671f6cbacd4e","item":{"uid":"a61ff0b6-a0f1-4189-9923-671f6cbacd4e","title":"Flaky test in auth module","kind":"issue","status":"open","priority":"HIGH","project":"020e87f2-…","component":"dcf134ab-…","createdAt":"2026-09-17T01:22:56.056Z","author":"claude:1"}}}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Filing more than a handful of similar issues in a row? Use `batch action`
|
|
224
|
+
(§5) instead of repeating this call.
|
|
225
|
+
|
|
226
|
+
`create` runs a dedupe scan (FTS + semantic, when embeddings are configured)
|
|
227
|
+
BEFORE writing. `duplicateAction` (default `'abort'`) controls what happens
|
|
228
|
+
when the scan surfaces a candidate at/above the project's dedupe threshold:
|
|
229
|
+
`'abort'` — nothing is written, `{created:false, reason:'duplicate-suppressed',
|
|
230
|
+
duplicateCandidates}`; `'force'` — writes a genuinely new issue anyway,
|
|
231
|
+
still reporting `duplicateCandidates`; `'comment'` — no new issue is
|
|
232
|
+
written, a note is attached to the top-scoring candidate instead. A
|
|
233
|
+
zero-candidate scan proceeds to a normal create regardless of
|
|
234
|
+
`duplicateAction`. **Always inspect `duplicateCandidates` before forcing.**
|
|
235
|
+
|
|
236
|
+
**Read one issue.** `get` returns a terse five-field card
|
|
237
|
+
(`uid`/`kind`/`title`/`status`/`priority`) by default — ask for more
|
|
238
|
+
explicitly:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
$ adhd-backlog backlog get --input '{"uid":"a61ff0b6-…"}'
|
|
242
|
+
{"ok":true,"data":{"uid":"a61ff0b6-…","title":"Flaky test in auth module","kind":"issue","status":"open","priority":"HIGH"}}
|
|
243
|
+
|
|
244
|
+
$ adhd-backlog backlog get --input '{"uid":"a61ff0b6-…","fields":["body","citations"]}'
|
|
245
|
+
{"ok":true,"data":{"uid":"a61ff0b6-…","body":"The auth integration test times out intermittently.","citations":[]}}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`get`'s own `--help` now renders its full union —
|
|
249
|
+
`{ uid, fields? } | { registry, name, filter? }`. The second form reads one
|
|
250
|
+
registry entry by name directly, e.g.
|
|
251
|
+
`{"registry":"project","name":"demo-project"}` returns the project's
|
|
252
|
+
`{uid, name, path, components, locations}` — the same data `query`'s
|
|
253
|
+
`view:"projects"`/`"components"`/`"locations"` list in bulk (§4).
|
|
254
|
+
|
|
255
|
+
The full field vocabulary is `uid, title, kind, status, priority, project,
|
|
256
|
+
component, createdAt, updatedAt, assignee, author, closedAt` (cheap/plain)
|
|
257
|
+
plus `body, citations, notes, auditTrail, blockers, related, _score,
|
|
258
|
+
_vector` (opt-in only — each costs a genuine extra read, so none is in the
|
|
259
|
+
default card).
|
|
260
|
+
|
|
261
|
+
**Search/filter/page issues:**
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
$ adhd-backlog backlog query --input '{"filter":{"project":"demo-project","status":"open"},"limit":10}'
|
|
265
|
+
{"ok":true,"data":{"view":"list","items":[{"uid":"a61ff0b6-…","title":"Flaky test in auth module","kind":"issue","status":"open","priority":"HIGH"}],"hasMore":false},"meta":{"total":1,"returned":1,"limit":10}}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`query.view` (default `'list'`) selects the result shape: `list` · `ready` ·
|
|
269
|
+
`graph` · `order` · `stale` · `similar` · `overlap` · `projects` · `components`
|
|
270
|
+
· `locations` (the last three are the registry LIST views — §4). `text` is the
|
|
271
|
+
natural-language form — routed to `filter.semantic` when a populated vector
|
|
272
|
+
space can rank it, or `filter.grep` (keyword FTS) otherwise; never set
|
|
273
|
+
`text` alongside `filter.semantic`/`filter.grep` yourself. Pagination is
|
|
274
|
+
truthful: `meta.total` is the count before `limit`/`offset`, `meta.returned`
|
|
275
|
+
is `data.items.length`, and a page cut short for any reason other than your
|
|
276
|
+
own `limit` sets `meta.truncated`.
|
|
277
|
+
|
|
278
|
+
**Edit an existing issue.** Every field edits in place **except `body`**: a
|
|
279
|
+
`body` change SUPERSEDES the issue, minting a successor node with a fresh
|
|
280
|
+
`uid` and carrying the old node's edges forward. The response's `uid` is the
|
|
281
|
+
successor; the old `uid` becomes a `SUPERSEDES`-linked history node, and
|
|
282
|
+
addressing it returns `conflict` naming the successor (see the example below).
|
|
283
|
+
A caller that persists uids must follow that pointer after any body edit.
|
|
284
|
+
`status` is not editable here — use `transition`:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
$ adhd-backlog backlog update --input '{"uid":"a61ff0b6-…","by":"claude:1","priority":"CRITICAL"}'
|
|
288
|
+
{"ok":true,"data":{"uid":"a61ff0b6-…","changed":["priority"]}}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
A `body` edit returns the successor's `uid`, and the pre-edit `uid` then
|
|
292
|
+
resolves to a redirect (never to the stale record):
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
$ adhd-backlog backlog update --input '{"uid":"a61ff0b6-…","by":"claude:1","body":"new body"}'
|
|
296
|
+
{"ok":true,"data":{"uid":"e3b32183-…","changed":["body"]}}
|
|
297
|
+
|
|
298
|
+
$ adhd-backlog backlog get --input '{"uid":"a61ff0b6-…"}'
|
|
299
|
+
{"ok":false,"error":{"code":"conflict","message":"Issue \"a61ff0b6-…\" was superseded by a body edit and is no longer the live issue; it now lives under \"e3b32183-…\"","details":{"retryable":false}}}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Move an issue to a new status.** A terminal `toStatus` REQUIRES `citations`
|
|
303
|
+
only when the project's policy turns citation enforcement ON — `citationRequired`
|
|
304
|
+
defaults to `false`, so out of the box no citation is required. When a project
|
|
305
|
+
HAS turned it on, citations must be verifiable against the project's own
|
|
306
|
+
filesystem path — an unverifiable citation is rejected with `precondition_failed`:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
$ adhd-backlog backlog transition --input '{
|
|
310
|
+
"uid": "a61ff0b6-…", "by": "claude:1", "toStatus": "closed",
|
|
311
|
+
"note": "fixed",
|
|
312
|
+
"citations": [{ "file": "packages/auth/src/index.ts", "lines": "1-1" }]
|
|
313
|
+
}'
|
|
314
|
+
{"ok":true,"data":{"uid":"a61ff0b6-…","fromStatus":"open","toStatus":"closed","transitionUid":"8b802b1b-…"}}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**`toStatus` is an open catalog, not a fixed enum.** `open`/`claimed`/
|
|
318
|
+
`closed` are the conventional names, not the permitted set. An unresolved
|
|
319
|
+
NAME is not an error — it MINTS a new status (`terminal:false`) and the
|
|
320
|
+
transition succeeds, exactly as `create`'s own `status` field behaves. Only a
|
|
321
|
+
uid-SHAPED reference resolving to nothing is rejected, with `not_found`:
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
$ adhd-backlog backlog transition --input '{"uid":"a61ff0b6-…","by":"claude:1","toStatus":"awaiting-review","note":"n"}'
|
|
325
|
+
{"ok":true,"data":{"uid":"a61ff0b6-…","fromStatus":"open","toStatus":"awaiting-review","transitionUid":"f52b0f50-…"}}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
So a typo becomes a real status rather than an error, and the issue silently
|
|
329
|
+
leaves the set `filter.status:"open"` returns. Treat the status name as
|
|
330
|
+
load-bearing input: pass one you can spell, or read the catalog first.
|
|
331
|
+
|
|
332
|
+
**Claim / renew / release protocol (multi-agent use).** Claiming is
|
|
333
|
+
idempotent for the SAME claimant — a second `action:"claim"` from the same
|
|
334
|
+
`by` returns `status:"renewed"` rather than a contention error, so retrying
|
|
335
|
+
after a lost response is always safe. A
|
|
336
|
+
long-running task renews periodically; every exit path releases
|
|
337
|
+
unconditionally (a no-op if already unclaimed):
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
$ adhd-backlog backlog claim --input '{"uid":"a61ff0b6-…","by":"claude:1","action":"claim"}'
|
|
341
|
+
{"ok":true,"data":{"uid":"a61ff0b6-…","status":"claimed","claimedBy":"claude:1","claimedAt":"2026-09-17T01:24:31.896Z"}}
|
|
342
|
+
|
|
343
|
+
$ adhd-backlog backlog claim --input '{"uid":"a61ff0b6-…","by":"claude:1","action":"renew"}'
|
|
344
|
+
{"ok":true,"data":{"uid":"a61ff0b6-…","status":"renewed","claimedBy":"claude:1","claimedAt":"2026-09-17T01:24:33.468Z"}}
|
|
345
|
+
|
|
346
|
+
$ adhd-backlog backlog claim --input '{"uid":"a61ff0b6-…","by":"claude:1","action":"release"}'
|
|
347
|
+
{"ok":true,"data":{"uid":"a61ff0b6-…","status":"released"}}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Link two issues.** `rel` is one of `relates_to`, `supersedes`, `blocks`,
|
|
351
|
+
`duplicate_of`, `part_of` — NOT the bare word `"related"`:
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
$ adhd-backlog backlog relate --input '{"sourceUid":"777c5e33-…","targetUid":"a61ff0b6-…","rel":"relates_to","action":"add","by":"claude:1"}'
|
|
355
|
+
{"ok":true,"data":{"sourceUid":"777c5e33-…","targetUid":"a61ff0b6-…","rel":"relates_to","action":"add","noop":false}}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
`noop:true` means `add` found an already-live matching edge, or `remove`
|
|
359
|
+
found none — no edge was written and no audit row produced; never assume
|
|
360
|
+
every call was a fresh write. `targetUid` may belong to a different project
|
|
361
|
+
than `sourceUid`.
|
|
362
|
+
|
|
363
|
+
**Move an issue to a different project/component.** `toProject`/
|
|
364
|
+
`toComponent` are RESOLVE-ONLY, never minted — register the destination
|
|
365
|
+
first with `upsert-project`/`upsert-component` if it doesn't exist yet:
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
$ adhd-backlog backlog move --input '{"uid":"777c5e33-…","toProject":"demo-project","by":"claude:1"}'
|
|
369
|
+
{"ok":true,"data":{"uid":"777c5e33-…","noop":false,"fromProject":"38b9af5d-…","toProject":"020e87f2-…","fromComponent":"22113591-…","toComponent":"dcf134ab-…"}}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Soft-delete an issue.** `reason` is REQUIRED. The node is closed off
|
|
373
|
+
bi-temporally, never physically removed — its audit trail and every edge
|
|
374
|
+
pointing at it remain readable:
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
$ adhd-backlog backlog delete --input '{"uid":"777c5e33-…","reason":"duplicate of tracked work","by":"claude:1"}'
|
|
378
|
+
{"ok":true,"data":{"uid":"777c5e33-…","invalidated":true}}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## 4. Registry — project / component / location
|
|
382
|
+
|
|
383
|
+
The registry answers **"where does this live, and what do I file the bug
|
|
384
|
+
against?"** in one call, before you `rg`/search for it.
|
|
385
|
+
|
|
386
|
+
### The model
|
|
387
|
+
|
|
388
|
+
- **project** — one repo/workspace root, registered with its filesystem `path`
|
|
389
|
+
and/or git `repoUrl`. ONE canonical row per logical repo (`adhd`,
|
|
390
|
+
`sox-ecosystem`). Every issue verb RESOLVES a project by name or `uid` and
|
|
391
|
+
**never mints one** — an unknown project name is `not_found` (exit 4).
|
|
392
|
+
- **component** — a path _within_ that project (`entrypoint/backlog`,
|
|
393
|
+
`tools/nx-plugins/build`), resolved-only within its project. `upsert-project`
|
|
394
|
+
mints exactly ONE reserved component, `(root)`, per project; `create`
|
|
395
|
+
defaults an omitted `component` to it. **No other component is ever
|
|
396
|
+
auto-created** — an unknown component name is `not_found`, never a new row.
|
|
397
|
+
- **location** — a tool name, file path, or URL owned by a component; the thing
|
|
398
|
+
`lookup` resolves.
|
|
399
|
+
|
|
400
|
+
### The filing rule — file every item with the right project AND component
|
|
401
|
+
|
|
402
|
+
A component-less item is not an error: it lands on `(root)` and is then
|
|
403
|
+
**invisible to every component-scoped query** (`filter.component:"…"`), while
|
|
404
|
+
still appearing in a project-scoped one. That is the misfiling signature —
|
|
405
|
+
`get <uid>` finds the item, but the component scan that should list it never
|
|
406
|
+
does. So, before filing:
|
|
407
|
+
|
|
408
|
+
1. Discover the exact registered names:
|
|
409
|
+
`adhd-backlog query --input '{"view":"projects"}'` and
|
|
410
|
+
`adhd-backlog query --input '{"view":"components","filter":{"project":"<p>"}}'`.
|
|
411
|
+
2. Register anything missing with `upsert-project`/`upsert-component` FIRST.
|
|
412
|
+
3. `create` with both `project` and `component`.
|
|
413
|
+
|
|
414
|
+
A component-scoped scan is how a repo's own work is found (e.g.
|
|
415
|
+
`filter.component:"entrypoint/backlog"` for this repo's own items, or project
|
|
416
|
+
`adhd`); an item filed on `(root)` is invisible to it.
|
|
417
|
+
|
|
418
|
+
### When to use each registry verb
|
|
419
|
+
|
|
420
|
+
| verb | use it when | idempotent key |
|
|
421
|
+
| ------------------ | -------------------------------------------------------------------------------- | ----------------------------- |
|
|
422
|
+
| `upsert-project` | registering/updating a repo or workspace root; also mints its `(root)` component | `name` |
|
|
423
|
+
| `upsert-component` | registering/updating a path _inside_ an already-registered project | `(project, name)` |
|
|
424
|
+
| `upsert-location` | pointing a tool/file/URL at its owning component so `lookup` resolves it | `(component, locType, value)` |
|
|
425
|
+
| `rm-location` | retiring a location (soft-invalidate) | `uid` |
|
|
426
|
+
|
|
427
|
+
All four are create-or-update by that key — never a duplicate row — and all
|
|
428
|
+
require `by`.
|
|
429
|
+
|
|
430
|
+
**Register or update a project** (create-or-update by `name`; also mints the
|
|
431
|
+
project's reserved default component `(root)` on first creation):
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
$ adhd-backlog backlog upsert-project --input '{"name":"demo-project","path":"/tmp/demo","by":"claude:1"}'
|
|
435
|
+
{"ok":true,"data":{"uid":"020e87f2-…","created":true,"project":{"uid":"020e87f2-…","name":"demo-project","path":"/tmp/demo"}}}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Register or update a component** (create-or-update by `(project, name)`;
|
|
439
|
+
`project` is resolve-only):
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
$ adhd-backlog backlog upsert-component --input '{"project":"demo-project","name":"auth-service","path":"packages/auth","by":"claude:1"}'
|
|
443
|
+
{"ok":true,"data":{"uid":"41a61c6d-…","created":true,"component":{"uid":"41a61c6d-…","name":"auth-service","projectUid":"020e87f2-…","path":"packages/auth"}}}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Register a location** — a tool name, file path, or URL owned by a
|
|
447
|
+
component. A bare component NAME requires `project` to disambiguate it (a
|
|
448
|
+
`uid` never does):
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
$ adhd-backlog backlog upsert-location --input '{"component":"auth-service","project":"demo-project","locType":"path","value":"packages/auth/src/index.ts","by":"claude:1"}'
|
|
452
|
+
{"ok":true,"data":{"uid":"a4b0dd6b-…","created":true,"location":{"uid":"a4b0dd6b-…","locType":"path","value":"packages/auth/src/index.ts","componentUid":"41a61c6d-…"}}}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Resolve a tool, file, or URL to its owning project/component** — the
|
|
456
|
+
go-to before searching for "which repo owns this?":
|
|
457
|
+
|
|
458
|
+
```
|
|
459
|
+
$ adhd-backlog backlog lookup --input '{"q":"packages/auth/src/index.ts"}'
|
|
460
|
+
{"ok":true,"data":{"project":{"uid":"020e87f2-…","name":"demo-project","path":"/tmp/demo"},"component":{"uid":"41a61c6d-…","name":"auth-service","path":"packages/auth"},"location":{"uid":"a4b0dd6b-…","locType":"path","value":"packages/auth/src/index.ts"}}}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`lookup` classifies `q` automatically as a tool name, file path, or URL —
|
|
464
|
+
it only resolves against LOCATIONS already registered via `upsert-location`,
|
|
465
|
+
never against a bare project/component name. An unregistered value returns
|
|
466
|
+
`not_found` (exit 4); a path miss falls back to a suffix/prefix scan before
|
|
467
|
+
giving up, and reports a `hint` when only a partial match was found — never
|
|
468
|
+
a silent empty result.
|
|
469
|
+
|
|
470
|
+
**Remove a location** (soft-invalidate by `uid`):
|
|
471
|
+
|
|
472
|
+
```
|
|
473
|
+
$ adhd-backlog backlog rm-location --input '{"uid":"a4b0dd6b-…","by":"claude:1","reason":"tool renamed"}'
|
|
474
|
+
{"ok":true,"data":{"uid":"a4b0dd6b-…","invalidated":true}}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
**List every project/component/location in the registry** — the go-to for
|
|
478
|
+
"what does this repo have registered?" without hand-rolling a scan. This is
|
|
479
|
+
`query`'s `view:"projects"`/`"components"`/`"locations"` (not a separate
|
|
480
|
+
verb): every live row of that kind, optionally scoped by `filter.project`
|
|
481
|
+
(and, for `locations`, `filter.component`):
|
|
482
|
+
|
|
483
|
+
```
|
|
484
|
+
$ adhd-backlog backlog query --input '{"view":"projects"}'
|
|
485
|
+
{"ok":true,"data":{"view":"projects","items":[{"uid":"020e87f2-…","name":"demo-project","path":"/tmp/demo"}]}}
|
|
486
|
+
|
|
487
|
+
$ adhd-backlog backlog query --input '{"view":"components","filter":{"project":"demo-project"}}'
|
|
488
|
+
{"ok":true,"data":{"view":"components","items":[{"uid":"41a61c6d-…","name":"auth-service","projectUid":"020e87f2-…","path":"packages/auth"},{"uid":"…","name":"(root)","projectUid":"020e87f2-…"}]}}
|
|
489
|
+
|
|
490
|
+
$ adhd-backlog backlog query --input '{"view":"locations","filter":{"component":"auth-service","project":"demo-project"}}'
|
|
491
|
+
{"ok":true,"data":{"view":"locations","items":[{"uid":"a4b0dd6b-…","locType":"path","value":"packages/auth/src/index.ts","componentUid":"41a61c6d-…"}]}}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Filing hazards (real, observed on this machine)
|
|
495
|
+
|
|
496
|
+
1. **Duplicate project identities.** The same repo can exist as TWO project
|
|
497
|
+
rows — one path-derived, one repo-derived — and an item lands under
|
|
498
|
+
whichever name you pass. Observed split (2026-09-22): `sox-ecosystem` (path)
|
|
499
|
+
vs `PseudoSky/sox-ecosystem` (no path); `claude-agents` (path) vs
|
|
500
|
+
`PseudoSky/claude-agents`; `claude-tools` vs `QuSecure/claude-tools`;
|
|
501
|
+
`dot` vs `id8/dot`. Prefer the row that carries a `path` (and, for an
|
|
502
|
+
active repo, the bulk of the items) — an item under the other row is
|
|
503
|
+
invisible to a query scoped to the first. (`adhd` is already reconciled to
|
|
504
|
+
one row; `PseudoSky/adhd` does not exist.)
|
|
505
|
+
2. **Store/scope confusion — an item can land in a store nobody reads.**
|
|
506
|
+
`adhd-backlog sandbox-path` reports the store a command will touch. Each
|
|
507
|
+
namespace resolves to its OWN file under `~/.adhd/backlog/<namespace>/data/`
|
|
508
|
+
(default `backlog.db`; that namespace's `config.yaml` may pin another name),
|
|
509
|
+
so the `production` store is never the `test` store. `ADHD_BACKLOG_SCOPE`
|
|
510
|
+
only changes the scope ROOT; an absolute `db.path` from any config layer
|
|
511
|
+
still wins (`db.path ?? files.db`) — run `sandbox-path` to confirm. A build
|
|
512
|
+
that writes a per-repo namespace (e.g. `entrypoint/backlog` on
|
|
513
|
+
`main`) files items that are silently absent from production — no error,
|
|
514
|
+
just a missing row (filed as 49ce83b8). Run `sandbox-path` before a write
|
|
515
|
+
you care about, and file through the production CLI only.
|
|
516
|
+
|
|
517
|
+
## 5. Batch — N-way fan-out over one operation
|
|
518
|
+
|
|
519
|
+
`batch action` runs the SAME operation over many items. `operation` is the
|
|
520
|
+
mounted operation id, namespaced as `backlog/<verb>` (not the bare verb
|
|
521
|
+
name), and each entry in `items` wraps its payload under `input`:
|
|
522
|
+
|
|
523
|
+
```
|
|
524
|
+
$ adhd-backlog batch action --input '{
|
|
525
|
+
"operation": "backlog/create",
|
|
526
|
+
"items": [
|
|
527
|
+
{ "input": { "title": "Batch item one", "body": "first", "project": "demo-project", "by": "claude:1" } },
|
|
528
|
+
{ "input": { "title": "Batch item two", "body": "second", "project": "demo-project", "by": "claude:1" } }
|
|
529
|
+
]
|
|
530
|
+
}'
|
|
531
|
+
[{"index":0,"status":"fulfilled","value":{"ok":true,"data":{"created":true,"uid":"874dfa26-…", …}}},
|
|
532
|
+
{"index":1,"status":"fulfilled","value":{"ok":true,"data":{"created":true,"uid":"e071b0b8-…", …}}}]
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Each result is `{index, status:'fulfilled', value}` or `{index,
|
|
536
|
+
status:'rejected', reason}` — `value`/`reason` is the SAME outcome envelope
|
|
537
|
+
`backlog/<verb>` would return standalone, so a batched item's own `ok`/
|
|
538
|
+
`error.code` still applies. `mode` (`'parallel'` default · `'serial'` ·
|
|
539
|
+
`'chained'`), `onItemError` (`'continue'` default · `'abort'`), and
|
|
540
|
+
`concurrency`/`itemTimeoutMs` govern how the fan-out runs. The valid
|
|
541
|
+
`operation` values are exactly the 17 mounted verbs above (the nine issue
|
|
542
|
+
verbs, the four registry verbs, and the three stats reads of §8), each
|
|
543
|
+
prefixed `backlog/` — passing a bare verb name (`"create"`) is rejected with
|
|
544
|
+
`invalid_argument` naming the full list.
|
|
545
|
+
|
|
546
|
+
## 6. Citations — structured, not hand-typed markdown
|
|
547
|
+
|
|
548
|
+
A citation is `{ file, lines?, context?, symbol? }`. Pass `citations` on
|
|
549
|
+
`create` or on the `transition` that moves an issue into a terminal status.
|
|
550
|
+
They are required and enforced only when the project's policy demands it:
|
|
551
|
+
`citationRequired` defaults to `false`, so out of the box a terminal
|
|
552
|
+
transition needs no citation (what IS required by default is a `note` —
|
|
553
|
+
`transitionRequiresNote`). When a project HAS turned `citationRequired` on, a
|
|
554
|
+
terminal transition with no citations, or with an unverifiable one, is
|
|
555
|
+
rejected with `precondition_failed`. "Unverifiable" means the file
|
|
556
|
+
could not be confirmed to exist under the project's own registered path —
|
|
557
|
+
never resolved outside it. The verification gate applies only when the
|
|
558
|
+
project HAS a registered path; a project with no `path` cannot hash any
|
|
559
|
+
citation target at all, so its citations are accepted and recorded with
|
|
560
|
+
`sha: "unverified"`.
|
|
561
|
+
|
|
562
|
+
Name a `symbol` on a citation to get best-effort blast-radius enrichment for
|
|
563
|
+
free — the store shells out to `gitnexus impact <symbol>` at write time
|
|
564
|
+
(bounded timeout, never blocks or fails the write) and stamps the citation's
|
|
565
|
+
`blastRadius` when gitnexus is installed and the repo is indexed. Absence of
|
|
566
|
+
`blastRadius` on a citation that named a `symbol` means "not enriched," never
|
|
567
|
+
"confirmed zero blast radius."
|
|
568
|
+
|
|
569
|
+
The item-level **`gitContext`** is separate from a citation's own `context`.
|
|
570
|
+
Pass `gitContext` on `create` (or on a `transition` to update it) to record
|
|
571
|
+
the repo disclosure contract's `<active git context>` — the FIRST element of a
|
|
572
|
+
`Citations:` block (`Citations: [<active git context>, …]`, per the repo
|
|
573
|
+
`AGENTS.md` "Cite what you read"). It is stored on the issue itself, never per
|
|
574
|
+
citation, and a `format:'markdown'` query renders it once at the head of the
|
|
575
|
+
item's `Citations:` block (`Citations: [<active git context>]`, then the
|
|
576
|
+
citation lines). Omit it and nothing is stored and no output changes. A
|
|
577
|
+
citation's own `context` is free-text prose and is never rendered by the
|
|
578
|
+
markdown projection — it cannot carry the git context.
|
|
579
|
+
|
|
580
|
+
## 7. Verify writes from a NEW process
|
|
581
|
+
|
|
582
|
+
An MCP `backlog_get` served by a long-lived `serve` process can answer out
|
|
583
|
+
of that process's own in-memory/uncheckpointed state. After a write you
|
|
584
|
+
care about, verify by running the `adhd-backlog` CLI in a fresh shell — a
|
|
585
|
+
genuinely new process — rather than re-reading through the same live MCP
|
|
586
|
+
session.
|
|
587
|
+
|
|
588
|
+
## 8. Stats & rollups
|
|
589
|
+
|
|
590
|
+
Three aggregate read views are first-class mounted ops — `backlog
|
|
591
|
+
priority-matrix`, `backlog part-of-rollup`, `backlog open-curve` (MCP:
|
|
592
|
+
`backlog_priority_matrix` / `backlog_part_of_rollup` / `backlog_open_curve`).
|
|
593
|
+
All three are read-only and take no `by`, and each returns its own shape — a
|
|
594
|
+
matrix, a rollup tree, a time series — so none is a `query.view` member.
|
|
595
|
+
|
|
596
|
+
**Priority matrix** — per-priority counts, scoped by
|
|
597
|
+
`project`/`component`/`kind`/`status`. An omitted `filter.status` scopes to
|
|
598
|
+
OPEN work (unlike `list`, where an omitted status means no restriction); the
|
|
599
|
+
applied scope is echoed on `data.statusScope`, and `unassigned` counts in-scope
|
|
600
|
+
issues carrying no priority:
|
|
601
|
+
|
|
602
|
+
```
|
|
603
|
+
$ adhd-backlog backlog priority-matrix --input '{}'
|
|
604
|
+
{"ok":true,"data":{"rows":[{"priority":"HIGH","priorityUid":"fb9d525e-…","rank":0,"count":1}],"unassigned":1,"statusScope":"open"}}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**Part-of rollup** — every TRANSITIVE `part_of` descendant of the root issue
|
|
608
|
+
(not just direct children), counted once each regardless of chain depth, split
|
|
609
|
+
into `childrenOpen`/`childrenClosed` (plus the open descendants' uids):
|
|
610
|
+
|
|
611
|
+
```
|
|
612
|
+
$ adhd-backlog backlog part-of-rollup --input '{"uid":"74c22c35-…"}'
|
|
613
|
+
{"ok":true,"data":{"uid":"74c22c35-…","childrenTotal":1,"childrenOpen":1,"childrenClosed":0,"childrenOpenUids":["db4587ba-…"]}}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Open curve** — for each sampled ISO-8601 instant, how many in-scope issues
|
|
617
|
+
EXISTED then (exact, via `validAt`) and, of those, how many were OPEN then
|
|
618
|
+
(reconstructed from the audit trail, never the issue's current status):
|
|
619
|
+
|
|
620
|
+
```
|
|
621
|
+
$ adhd-backlog backlog open-curve --input '{"at":["2020-01-01T00:00:00.000Z"]}'
|
|
622
|
+
{"ok":true,"data":{"points":[{"at":"2020-01-01T00:00:00.000Z","existed":0,"open":0,"closed":0}]}}
|
|
144
623
|
```
|
|
145
624
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
625
|
+
The same three functions are also exported from the package's query layer
|
|
626
|
+
(`src/query/views/stats.ts`, re-exported by `src/query/index.ts`) for
|
|
627
|
+
in-process consumers — `priorityMatrix(handle, { filter? })`,
|
|
628
|
+
`partOfRollup(handle, { uid })`, `openCurve(handle, { filter?, at })`. That
|
|
629
|
+
in-process surface is described in `README.md` → "Library API".
|