@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.
Files changed (61) hide show
  1. package/CHANGELOG.md +93 -44
  2. package/README.md +332 -81
  3. package/api.d.ts +146 -0
  4. package/cli.d.ts +45 -18
  5. package/env.d.ts +23 -3
  6. package/envelope.d.ts +163 -0
  7. package/index.d.ts +11 -10
  8. package/index.js +531 -173
  9. package/index.mjs +29814 -15647
  10. package/install-skill.d.ts +23 -0
  11. package/package.json +50 -15
  12. package/query/card.d.ts +31 -0
  13. package/query/get.d.ts +11 -0
  14. package/query/index.d.ts +67 -0
  15. package/query/markdown.d.ts +11 -0
  16. package/query/query.d.ts +131 -0
  17. package/query/resolve.d.ts +123 -0
  18. package/query/types.d.ts +450 -0
  19. package/query/views/registry.d.ts +43 -0
  20. package/query/views/semantic.d.ts +101 -0
  21. package/query/views/stats.d.ts +109 -0
  22. package/search-shortcut.d.ts +79 -0
  23. package/serve.d.ts +18 -0
  24. package/server.d.ts +139 -4
  25. package/skill/SKILL.md +619 -138
  26. package/store/graph-backlog-store.d.ts +80 -17
  27. package/store/immediate-retry.d.ts +24 -13
  28. package/store/type-policy.d.ts +4 -0
  29. package/store/vocabulary-guard.d.ts +52 -0
  30. package/version-info.d.ts +15 -0
  31. package/write/audit.d.ts +36 -0
  32. package/write/bootstrap.d.ts +123 -0
  33. package/write/catalog.d.ts +351 -0
  34. package/write/claim-lease.d.ts +21 -0
  35. package/write/claim.d.ts +80 -0
  36. package/write/create-issue.d.ts +250 -0
  37. package/write/delete.d.ts +39 -0
  38. package/write/embed-drain.d.ts +68 -0
  39. package/write/embedding-observer.d.ts +80 -0
  40. package/write/errors.d.ts +303 -0
  41. package/write/issue-status.d.ts +10 -0
  42. package/write/move.d.ts +70 -0
  43. package/write/relate.d.ts +52 -0
  44. package/write/transition.d.ts +60 -0
  45. package/write/tx.d.ts +344 -0
  46. package/write/update.d.ts +81 -0
  47. package/client.d.ts +0 -169
  48. package/markdown.d.ts +0 -75
  49. package/migration-admin.d.ts +0 -26
  50. package/model.d.ts +0 -437
  51. package/store/audit-log.d.ts +0 -16
  52. package/store/claim.d.ts +0 -24
  53. package/store/crud.d.ts +0 -62
  54. package/store/ids.d.ts +0 -24
  55. package/store/lifecycle.d.ts +0 -36
  56. package/store/mapping.d.ts +0 -101
  57. package/store/mutate-metadata.d.ts +0 -8
  58. package/store/query.d.ts +0 -68
  59. package/store/repo-migration.d.ts +0 -51
  60. package/store/serve-lock.d.ts +0 -42
  61. package/store/structure.d.ts +0 -66
package/README.md CHANGED
@@ -1,103 +1,354 @@
1
1
  # @adhd/backlog
2
2
 
3
- A structured, queryable, multi-agent-safe **graph store** for backlog items (bugs,
4
- debt, features, investigations, plans) — a replacement for ad-hoc `BACKLOG.md`
5
- editing that stays compatible with the existing markdown convention this repo
6
- already uses.
3
+ A structured, queryable, concurrent-safe backlog for bugs, debt, features, and
4
+ investigations — served from one graph store over a CLI, an MCP server, an HTTP
5
+ API, and an in-process client.
7
6
 
8
- Built on `@adhd/sox-graph-store` (bi-temporal nodes/edges over SQLite) and mounted
9
- live via `@adhd/apigen-core-client` (no code generation — `extract()` →
10
- `composeSchemas()` → `plugin.run()`).
7
+ ## Why
11
8
 
12
- See `SPEC.md` (functional spec: personas, data model, status vocabulary,
13
- operation surface) and `DESIGN.md` (technical design: graph mapping, claim
14
- protocol, env/apigen wiring) in this package for the full contract.
9
+ The problem this solves: a plain `BACKLOG.md` file has no way to represent
10
+ "someone is already working on this," no way to enforce that a closed item
11
+ cites the fix that closed it, no structured way to ask "what's open in this
12
+ component," and no safe way for two agents to edit it at once. `@adhd/backlog`
13
+ replaces that file with a real store: issues are addressed by a stable `uid`,
14
+ every mutation is audited, claims are leases with staleness detection, and
15
+ queries are structured filters instead of `grep`.
16
+
17
+ ## Install
15
18
 
16
19
  ```bash
17
- pnpm add @adhd/backlog
20
+ pnpm add -g @adhd/backlog
21
+ adhd-backlog --help
18
22
  ```
19
23
 
20
- ## Usage
24
+ This installs the `adhd-backlog` CLI binary. The same package also runs as an
25
+ MCP server, an HTTP API, and an importable client library — see
26
+ [Transports](#transports).
21
27
 
22
- ```ts
23
- import { createItem, listItems, claimItem, transitionStatus } from '@adhd/backlog';
24
- import { buildBacklogEnv } from '@adhd/backlog';
25
- import { openGraphBacklogStore } from '@adhd/backlog';
28
+ ## Quickstart
29
+
30
+ Issues live under a **project** (and, optionally, a **component**), both
31
+ resolved by name or `uid`. File the project first — `create` never mints one.
32
+ File every issue with the correct project **and component**: a component-less
33
+ item lands on the project's reserved `(root)` component and is invisible to
34
+ component-scoped queries. The agent-facing filing rules and the real misfiling
35
+ hazards live in [`skill/SKILL.md` §4](skill/SKILL.md).
26
36
 
27
- const env = buildBacklogEnv();
28
- env.ensureDirs();
29
- const store = openGraphBacklogStore(env.files.db);
30
- const ctx = { store, env };
37
+ ```bash
38
+ adhd-backlog upsert-project --input '{
39
+ "name": "demo-project",
40
+ "path": "/tmp/demo",
41
+ "by": "agent:worker-1"
42
+ }'
43
+ ```
31
44
 
32
- const { item } = await createItem(ctx, {
33
- family: 'BUG-EXAMPLE',
34
- title: 'Example bug',
35
- body: 'Something is broken.',
36
- repo: 'PseudoSky/adhd',
37
- });
45
+ ```json
46
+ {"ok":true,"data":{"uid":"49ec673b-…","created":true,"project":{"uid":"49ec673b-…","name":"demo-project","path":"/tmp/demo"}}}
47
+ ```
38
48
 
39
- await claimItem(ctx, item.repo, item.humanId, 'implementer:abc123');
40
- await transitionStatus(ctx, item.repo, item.humanId, 'FIXED', {
41
- by: 'implementer:abc123',
42
- citations: [{ file: 'entrypoint/backlog/src/client.ts' }],
43
- });
49
+ File an issue against it:
44
50
 
45
- const open = await listItems(ctx, { repo: item.repo, status: 'open' });
51
+ ```bash
52
+ adhd-backlog create --input '{
53
+ "title": "Query pagination drops the last page under offset paging",
54
+ "body": "offset+limit near the end of a result set silently returns fewer rows than `total` implies.",
55
+ "project": "demo-project",
56
+ "priority": "HIGH",
57
+ "gitContext": "feat/backlog-hard-replacement @ 4bf902fc",
58
+ "by": "agent:worker-1"
59
+ }'
46
60
  ```
47
61
 
48
- ## Running as a live server (no codegen)
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"}}}
64
+ ```
49
65
 
50
- ```ts
51
- import { startBacklogServer } from '@adhd/backlog';
66
+ > `project` and `component` in the result are `uid`s, not names. `gitContext`
67
+ > is described under [Citations & git context](#citations--git-context).
68
+
69
+ Query for it:
70
+
71
+ ```bash
72
+ adhd-backlog query --input '{"filter":{"project":"demo-project","status":"open"}}'
73
+ ```
74
+
75
+ ```json
76
+ {"ok":true,"data":{"view":"list","items":[{"uid":"b3b2da0e-…","title":"Query pagination drops the last page under offset paging","kind":"issue","status":"open","priority":"HIGH"}],"hasMore":false},"meta":{"total":1,"returned":1,"limit":50}}
77
+ ```
78
+
79
+ Move it forward — a `transition` records the status change in the issue's audit
80
+ trail. A non-empty `note` is required by default
81
+ (`project_policy.transition_requires_note`); a terminal status additionally
82
+ requires verifiable `citations` **only if** the project's policy turns citation
83
+ enforcement on (`citationRequired`, default `false`):
84
+
85
+ ```bash
86
+ adhd-backlog transition --input '{
87
+ "uid": "b3b2da0e-…",
88
+ "toStatus": "in-progress",
89
+ "note": "reproduced with limit:10, offset:95 against a 100-row set",
90
+ "by": "agent:worker-1"
91
+ }'
92
+ ```
93
+
94
+ ```json
95
+ {"ok":true,"data":{"uid":"b3b2da0e-…","fromStatus":"open","toStatus":"in-progress","transitionUid":"9bcb9844-…"}}
96
+ ```
97
+
98
+ A failed call returns the same envelope with `ok:false` instead of throwing:
99
+
100
+ ```json
101
+ {"ok":false,"error":{"code":"item_not_found","message":"No live issue found for uid \"does-not-exist\"","details":{"retryable":false}}}
102
+ ```
103
+
104
+ Every output above was captured from the built binary
105
+ (`entrypoint/backlog/dist/index.js`); `uid`s are truncated with `…`. See
106
+ [Which build these docs describe](#which-build-these-docs-describe).
107
+
108
+ ## Key features
109
+
110
+ ### 1. One shared graph store, concurrent-safe by design
111
+
112
+ The store is not a file you edit — it is a graph database that many processes
113
+ read and write at once. There is no single-writer lock; concurrency is bounded
114
+ by real transactions and leases. File the same item from two agents and one
115
+ wins cleanly, with correct audit rows. (SPEC.md §3/§4; `src/write/tx.ts`.)
116
+
117
+ ### 2. Leased claims for multi-agent work
118
+
119
+ `claim` is a lease, not a flag: it is idempotent for the same claimant
120
+ (`renew`), self-healing when stale, and it protects the item — a competing
121
+ `transition` by another agent is refused with `conflict` until the lease is
122
+ released or goes stale.
123
+
124
+ ```bash
125
+ adhd-backlog claim --input '{"uid":"b3b2da0e-…","by":"agent:worker-1","action":"claim"}'
126
+ ```
127
+
128
+ ```json
129
+ {"ok":true,"data":{"uid":"b3b2da0e-…","status":"claimed","claimedBy":"agent:worker-1","claimedAt":"2026-09-24T00:14:39.978Z"}}
130
+ ```
131
+
132
+ ### 3. Structured, verifiable citations
133
+
134
+ A citation is `{ file, lines?, context?, symbol? }`, not hand-typed markdown.
135
+ When a project enforces citations, a terminal transition is rejected with
136
+ `precondition_failed` unless every cited file resolves inside the project's own
137
+ registered path. Name a `symbol` and the store shells out to
138
+ `gitnexus impact <symbol>` at write time to stamp a best-effort `blastRadius`
139
+ on the citation.
140
+
141
+ ### 4. A registry that answers "where does this live?"
142
+
143
+ `lookup` resolves a tool name, file path, or URL to its owning
144
+ project/component — before you reach for `find`. `query` also lists the
145
+ registry directly:
146
+
147
+ ```bash
148
+ adhd-backlog get --input '{"registry":"project","name":"demo-project"}'
149
+ ```
52
150
 
53
- const abort = new AbortController();
54
- await startBacklogServer({ transport: 'both', port: 3400, signal: abort.signal });
151
+ ```json
152
+ {"ok":true,"data":{"uid":"49ec673b-…","name":"demo-project","path":"/tmp/demo","components":[{"name":"(root)"}],"locations":[]}}
55
153
  ```
56
154
 
57
- - `POST /backlog/client-d/create-item`, `GET /backlog/client-d/get-item`, ... —
58
- every `client.ts` export, mounted live via `@adhd/apigen-plugin-api-fastify`.
59
- (The `client-d` route segment is not a typo — see `DESIGN.md` §7's CLI
60
- section / `SPEC.md` §7's transport table for why it's there.)
61
- - Every export is also available as an MCP tool (e.g. `backlog_client_d_get_item`)
62
- via `@adhd/apigen-plugin-mcp` (stdio transport by default).
155
+ ### 5. Optional semantic search, optional Markdown rendering
156
+
157
+ With embeddings off (the default), keyword filtering (`filter.grep`),
158
+ exact/registry lookup, and every write verb work fully. Turn them on and
159
+ `query`'s natural-language `text` routes to semantic ranking; ask for a semantic
160
+ read with no backend and you get a precise `rag_not_configured` error rather
161
+ than a silent empty result. `query --input '{"format":"markdown", …}'` renders
162
+ every item-list view as Markdown.
163
+
164
+ ### 6. N-way fan-out with `batch action`
63
165
 
64
- ## CLI (`backlog`, live apigen mount — no codegen)
166
+ Run one operation over many items instead of N one-at-a-time calls. `operation`
167
+ is the mounted id (`backlog/create`, …); each item wraps its payload under
168
+ `input`; `mode` (`parallel`/`serial`/`chained`), `onItemError`, `concurrency`,
169
+ and `itemTimeoutMs` govern the run.
65
170
 
66
171
  ```bash
67
- pnpm add -g @adhd/backlog # installs the `backlog` bin
68
- backlog --help # live-derived command listing
69
- backlog create-item --input '{"family":"BUG-EXAMPLE","title":"t","body":"b","repo":"org/repo"}'
70
- backlog get-item --repo org/repo --human-id BUG-EXAMPLE-001
71
- backlog list-items --filter '{"status":"OPEN"}'
72
- ```
73
-
74
- - Same architecture as the HTTP/MCP transports above — `entrypoint/backlog/src/cli.ts`'s
75
- `runBacklogCli()` reuses `buildBacklogApigenPackage()` and hands it straight
76
- to `@adhd/apigen-plugin-cli-output`'s `run()`. No `apigen generate`, no
77
- bespoke argument parsing — routing, flag parsing, validation, dispatch, and
78
- exit codes all come from that plugin.
79
- - **You type `backlog <command>`, never the internal `client-d` segment.**
80
- `runBacklogCli` derives the real command-table prefix from the live
81
- `operations` list at runtime (`resolveCommandPrefix`) and prepends it before
82
- dispatch — so `backlog get-item …` resolves even though the plugin's real
83
- command table is keyed `backlog client-d get-item` internally (same
84
- `client-d` artifact as the HTTP/MCP routes above; the CLI is the one
85
- transport that hides it from the caller).
86
- - Flags are the schema's domain params, kebab-cased (`humanId` → `--human-id`);
87
- object/array-typed params take a JSON string (`--input '{...}'`,
88
- `--filter '{...}'`).
89
- - Exit codes follow `@adhd/apigen-base-errors`'s `CLI_EXIT_CODE` table: `0`
90
- success, `2` invalid argument (bad/unknown flag, failed validation), `4`
91
- unknown command, etc. Result is printed as JSON to stdout; errors as JSON to
92
- stderr.
93
- - Honors the same `ADHD_BACKLOG_SCOPE`/`ADHD_ENV_SCOPE` scope env vars as the
94
- library API (see Scope below) — there is no separate CLI-only config.
95
- - `runBacklogCli(argv?, opts?)` is also exported for in-process programmatic
96
- use (e.g. a test harness), symmetric with `startBacklogServer`.
97
-
98
- ## Scope
99
-
100
- Resolved via `@adhd/environment` (see `env.ts`): `global` (default —
101
- `~/.adhd/backlog/<namespace>/data/backlog.db`, spans every repo on the
102
- machine), `project` (`<projectRoot>/.adhd/backlog/<namespace>/data/backlog.db`,
103
- one repo), or `system`. See `SPEC.md` §3 for the full resolution order.
172
+ adhd-backlog batch action --input '{
173
+ "operation": "backlog/get",
174
+ "items": [{"input": {"uid": "does-not-exist"}}]
175
+ }'
176
+ ```
177
+
178
+ ### 7. Supersede-safe history
179
+
180
+ A `body` edit does not mutate in place — it mints a successor issue and carries
181
+ the previous node's edges (claims, citations, notes, transitions, relations)
182
+ forward to it. The old `uid` remains queryable history: addressing it returns
183
+ `conflict` and names the live successor, so a stale uid can never silently
184
+ resolve to the wrong record.
185
+
186
+ ```json
187
+ {"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
+ ```
189
+
190
+ ## Command surface
191
+
192
+ Every verb takes a single `--input` flag carrying one JSON object; there are no
193
+ per-field flags. Eighteen operations (the 17 verbs plus `batch`):
194
+
195
+ | Verb | CLI | MCP tool |
196
+ | ----------------- | ------------------------------- | -------------------------- |
197
+ | `get` | `adhd-backlog get` | `backlog_get` |
198
+ | `query` | `adhd-backlog query` | `backlog_query` |
199
+ | `priorityMatrix` | `adhd-backlog priority-matrix` | `backlog_priority_matrix` |
200
+ | `partOfRollup` | `adhd-backlog part-of-rollup` | `backlog_part_of_rollup` |
201
+ | `openCurve` | `adhd-backlog open-curve` | `backlog_open_curve` |
202
+ | `lookup` | `adhd-backlog lookup` | `backlog_lookup` |
203
+ | `create` | `adhd-backlog create` | `backlog_create` |
204
+ | `update` | `adhd-backlog update` | `backlog_update` |
205
+ | `transition` | `adhd-backlog transition` | `backlog_transition` |
206
+ | `claim` | `adhd-backlog claim` | `backlog_claim` |
207
+ | `relate` | `adhd-backlog relate` | `backlog_relate` |
208
+ | `move` | `adhd-backlog move` | `backlog_move` |
209
+ | `delete` | `adhd-backlog delete` | `backlog_delete` |
210
+ | `upsertProject` | `adhd-backlog upsert-project` | `backlog_upsert_project` |
211
+ | `upsertComponent` | `adhd-backlog upsert-component` | `backlog_upsert_component` |
212
+ | `upsertLocation` | `adhd-backlog upsert-location` | `backlog_upsert_location` |
213
+ | `rmLocation` | `adhd-backlog rm-location` | `backlog_rm_location` |
214
+ | `batch` | `adhd-backlog batch action` | `batch_action` |
215
+
216
+ `--help` prints these as `backlog <verb>`; the CLI also accepts the bare
217
+ `adhd-backlog <verb>` form shown above, and accepts the explicit namespace
218
+ prefix at any position. `get`/`query`/`lookup` are reads.
219
+ `create`/`update`/`transition`/`claim`/`relate`/`move`/`delete` mutate one
220
+ issue. The four `upsert*`/`rmLocation` verbs manage the **registry** —
221
+ projects, components, and locations. `batch action` fans any one of them out
222
+ over many items.
223
+
224
+ ## Transports
225
+
226
+ `adhd-backlog` mounts one set of operation descriptors onto every host — there
227
+ is no per-transport reimplementation, so a change to a verb's behavior is
228
+ simultaneously true everywhere:
229
+
230
+ - **CLI** — `adhd-backlog <verb> --input '<json>'`
231
+ - **MCP** — tools named `backlog_<verb>` (e.g. `backlog_create`, `backlog_query`)
232
+ - **HTTP** — a REST API generated from the same descriptors, with an OpenAPI document
233
+ - **In-process** — the package's exported client library, for embedding the store in a Node process
234
+
235
+ ## Envelope
236
+
237
+ Every verb call returns one of two shapes, on every transport:
238
+
239
+ ```ts
240
+ { ok: true, data: T, warnings?: string[], meta?: { total, returned, limit?, offset?, truncated? } }
241
+ { ok: false, error: { code, message, details? }, warnings?: string[] }
242
+ ```
243
+
244
+ `meta` is present on list-shaped reads (`query`) and its `total` is the true
245
+ match count _before_ `limit`/`offset` are applied; a truncated page sets
246
+ `data.hasMore: true` and `data.nextCursor`, so a short result never silently
247
+ looks complete.
248
+
249
+ ### Error codes
250
+
251
+ | Code | Meaning | CLI exit code |
252
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------- |
253
+ | `not_found` | A referenced catalog entry (project/component/kind/status/priority) doesn't exist | 4 |
254
+ | `item_not_found` | The addressed issue doesn't exist | 1 |
255
+ | `invalid_argument` | Malformed flag or parameter shape | 2 |
256
+ | `validation` | Schema rejection — unknown filter key, unknown projection field, over-limit | 2 |
257
+ | `store_busy` | Store contention (a lease or a write conflict); `details.retryable` and `details.retryAfterMs` indicate whether/how to retry | 1 |
258
+ | `rag_not_configured` | A semantic/similarity read was requested but no embedding backend is configured, or the vector space is empty | 1 |
259
+ | `conflict` | Someone else holds the claim, a single-valued relation is already taken, or a supersede raced | 1 |
260
+ | `precondition_failed` | A gate refused the write — a terminal transition missing its required citation/note, or a citation that couldn't be verified against a known project path | 1 |
261
+ | `internal` | Unclassified server-side failure | 1 |
262
+
263
+ Success always exits 0. `item_not_found` and `internal` are deliberately
264
+ distinct codes even though they share exit code 1 — a caller distinguishes
265
+ "this uid doesn't exist" from "something broke" by `error.code`, not by exit
266
+ code alone.
267
+
268
+ ### Citations & git context
269
+
270
+ A citation is `{ file, lines?, context?, symbol? }`. Pass `citations` on
271
+ `create`, or on the `transition` that moves an issue into a terminal status
272
+ when the project's policy requires it.
273
+
274
+ The item-level **`gitContext`** is separate from a citation's own `context`:
275
+ it records the repo disclosure contract's `<active git context>` — the first
276
+ element of a `Citations:` block (`Citations: [<active git context>, …]`).
277
+ Pass it on `create`, or on a `transition` to update it. It is stored on the
278
+ issue itself, and a `format:'markdown'` query renders it once at the head of
279
+ the item's `Citations:` block. Omit it and nothing is stored.
280
+
281
+ ### Which build these docs describe
282
+
283
+ These docs describe `entrypoint/backlog/dist/index.js` built from revision
284
+ `9df2a5c7`, whose `create`/`transition` inputs include `gitContext`. A
285
+ **globally installed** `adhd-backlog` may be an older build (it is whatever was
286
+ last published/installed); on such a build `gitContext` is not in the schema
287
+ and is rejected with `invalid_argument`, and the stats/rollup ops
288
+ (`priority-matrix` / `part-of-rollup` / `open-curve`) are absent. Run
289
+ `adhd-backlog --help` and compare the `backlog create` / `backlog
290
+ priority-matrix` lines against this page if a documented field or verb is
291
+ refused.
292
+
293
+ ## Library API
294
+
295
+ Beyond the CLI/MCP/HTTP surface, the package exports its query layer
296
+ (`src/query/index.ts`), including three rollup/stats read views that are **not**
297
+ members of `query.view`:
298
+
299
+ - `priorityMatrix(handle, { filter? })` — a status-aware per-priority count
300
+ breakdown (defaults to open work; `filter.status` may be `'open'`, `'closed'`,
301
+ `'all'`, or a status name).
302
+ - `partOfRollup(handle, { uid })` — transitive `part_of` descendants of an
303
+ issue, counted once each regardless of chain depth.
304
+ - `openCurve(handle, { filter?, at })` — per-sampled-instant counts of issues
305
+ that existed and how many were open, reconstructed from the audit trail.
306
+
307
+ Reach them by importing `@adhd/backlog` in-process. The same three are also
308
+ mounted as operations — `priority-matrix` / `part-of-rollup` / `open-curve` on
309
+ the CLI (`backlog_priority_matrix` / `backlog_part_of_rollup` /
310
+ `backlog_open_curve` on MCP); see the command surface above.
311
+
312
+ ## Configuration
313
+
314
+ Configuration cascades over `@adhd/environment`, prefixed `ADHD_BACKLOG_`
315
+ (later layers override earlier: built-in default → config file → environment
316
+ variable). The store defaults to a single shared **global** scope — one backlog
317
+ spanning every project on the machine, not one per repository — so an agent
318
+ working across repos sees the same graph everywhere unless it explicitly opts
319
+ into a narrower scope.
320
+
321
+ | Setting | Env var | Default | Notes |
322
+ | ------------------ | ----------------------------------------------------- | -------------------------- | ------------------------------------------------------------ |
323
+ | Database path | `ADHD_BACKLOG_DATABASE_PATH` | resolved under the scope root | Where the graph store's data file lives |
324
+ | Write busy timeout | `ADHD_BACKLOG_DATABASE_BUSY_TIMEOUT_MS` | `5000` | How long a write waits on a contended lock before giving up |
325
+ | Log level | `ADHD_BACKLOG_LOG_LEVEL` | `info` | `trace`\|`debug`\|`info`\|`warn`\|`error`\|`fatal`\|`silent` |
326
+ | Scope | `ADHD_BACKLOG_SCOPE` (falls back to `ADHD_ENV_SCOPE`) | `global` | Which store root to resolve against |
327
+ | 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 |
328
+ | Semantic search | `ADHD_BACKLOG_EMBEDDING_ENABLED` | `false` | See below |
329
+ | Embedding provider | `ADHD_BACKLOG_EMBEDDING_PROVIDER` | `fastembed` | Only consulted when embedding is enabled |
330
+ | Embedding model | `ADHD_BACKLOG_EMBEDDING_MODEL` | `bge-base-en-v1.5` (768-dim) | Only consulted when embedding is enabled |
331
+
332
+ **Embedding (semantic search) is entirely optional.** With it left at its
333
+ default (`false`), `@adhd/backlog` works fully — every verb, keyword filtering
334
+ (`filter.grep`), and exact/registry lookup all function with no embedding
335
+ backend at all. Turning `filter.semantic`, `filter.anchor`, `view:"similar"`,
336
+ `sort:"relevance"`, or `fields:["_vector"]` on without an embedding backend
337
+ configured doesn't crash anything — it returns a `rag_not_configured` error
338
+ that says exactly that, so a caller can tell "not available" apart from "no
339
+ matches." Enabling embedding requires two optional dependencies to be
340
+ installed alongside the package and a store backend that supports native
341
+ vector storage; if either is missing, backlog logs the reason and leaves
342
+ semantic search unconfigured rather than failing to start.
343
+
344
+ ## Further docs
345
+
346
+ - [`skill/SKILL.md`](skill/SKILL.md) — the full agent-facing command surface, worked examples, and filing rules.
347
+ - [`SPEC.md`](SPEC.md) — the functional specification: operation surface, status vocabulary, acceptance criteria.
348
+ - [`DATA_MODEL.md`](DATA_MODEL.md) — the node/edge model this package reads and writes.
349
+ - [`CHANGELOG.md`](CHANGELOG.md) — version history.
350
+
351
+ ## Contributing
352
+
353
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md). This package is MIT-licensed under
354
+ [`LICENSE`](LICENSE); security policy: [`SECURITY.md`](../../SECURITY.md).
package/api.d.ts ADDED
@@ -0,0 +1,146 @@
1
+ import { IUpsertProjectInput, IUpsertProjectOutcome, IUpsertComponentInput, IUpsertComponentOutcome, IUpsertLocationInput, IUpsertLocationOutcome, IRmLocationInput, IRmLocationOutcome } from './write/catalog.js';
2
+ import { IDeleteIssueInput, IDeleteIssueOutcome } from './write/delete.js';
3
+ import { IMoveIssueInput, IMoveIssueOutcome } from './write/move.js';
4
+ import { IRelateInput, IRelateOutcome } from './write/relate.js';
5
+ import { IClaimInput, IClaimOutcome } from './write/claim.js';
6
+ import { ITransitionInput, ITransitionOutcome } from './write/transition.js';
7
+ import { IUpdateIssueInput, IUpdateIssueOutcome } from './write/update.js';
8
+ import { ICreateIssueInput, ICreateIssueResult } from './write/create-issue.js';
9
+ import { IIssueGetInput, IIssueGetResult, IIssueQueryInput, IIssueQueryResult, ILookupResult } from './query/types.js';
10
+ import { IPriorityMatrixInput, IPriorityMatrixResult, IPartOfRollupInput, IPartOfRollupResult, IOpenCurveInput, IOpenCurveResult } from './query/views/stats.js';
11
+ import { IOutcomeEnvelope } from './envelope.js';
12
+ import { GraphBacklogStore } from './store/graph-backlog-store.js';
13
+ import { BacklogConfig } from './env.js';
14
+ import { Environment } from '@adhd/environment';
15
+
16
+ /** The one type apigen special-cases via the `ctx-name-only` invariant. */
17
+ export interface BacklogCtx {
18
+ store: GraphBacklogStore;
19
+ env: Environment<BacklogConfig>;
20
+ /**
21
+ * Test-isolation escape hatch ONLY — mirrors `BuildBacklogEnvOptions.adhdRoot`
22
+ * (the same value passed to `buildBacklogEnv({ adhdRoot })` when constructing
23
+ * `env`). NEVER set this in production code (`server.ts`/`cli.ts` never do).
24
+ */
25
+ adhdRoot?: string;
26
+ }
27
+ /**
28
+ * Fetch one issue by `uid`, projected to the requested `fields`.
29
+ *
30
+ * Defaults to the same five-field card `query` returns
31
+ * (`uid`, `kind`, `title`, `status`, `priority`).
32
+ */
33
+ export declare function get(ctx: BacklogCtx, input: IIssueGetInput): Promise<IOutcomeEnvelope<IIssueGetResult>>;
34
+ /**
35
+ * Search, filter, group and paginate issues.
36
+ *
37
+ * Supports field projection, keyset and offset pagination, the `view`/`groupBy`
38
+ * aggregate axes, and `grep`/`semantic` text filters.
39
+ */
40
+ export declare function query(ctx: BacklogCtx, input: IIssueQueryInput): Promise<IOutcomeEnvelope<IIssueQueryResult>>;
41
+ /**
42
+ * SPEC.md §5's status-aware priority matrix (BUG-023) — a per-priority
43
+ * breakdown of issue counts, scoped by `project`/`component`/`kind`/`status`.
44
+ *
45
+ * An omitted `input.filter.status` scopes to OPEN work (a deliberate
46
+ * divergence from `query`'s `list` default, where an omitted status means "no
47
+ * restriction"); the applied scope is echoed on `data.statusScope`, so a
48
+ * default-scoped result can never be mistaken for an all-status one. `{}` is a
49
+ * valid input.
50
+ *
51
+ * A read op, NOT a `query.view` member: it returns a matrix
52
+ * (`{rows, unassigned, statusScope}`) rather than a `{view, items}` list
53
+ * permutation, and mounting it as a view would force `IIssueQueryInput` to
54
+ * carry axes (`at`, an issue root) the other views must reject. Uses only
55
+ * `handle.graph` — hence `needsSemantic:false`/`probeSpace:false`, so this op
56
+ * never pays the cold semantic-backend bootstrap (`queryHandle`'s own doc
57
+ * comment).
58
+ */
59
+ export declare function priorityMatrix(ctx: BacklogCtx, input: IPriorityMatrixInput): Promise<IOutcomeEnvelope<IPriorityMatrixResult>>;
60
+ /**
61
+ * SPEC.md §5's `part_of` hierarchy rollup (FEAT-005) — every TRANSITIVE
62
+ * descendant of the root issue `input.uid` via `part_of` (issue → issue,
63
+ * `n:1`), counted exactly once each regardless of chain depth, split into
64
+ * `childrenOpen`/`childrenClosed` (plus the open descendants' uids).
65
+ *
66
+ * A read op, NOT a `query.view` member: it is rooted at an issue (`uid`), a
67
+ * per-op input no list view carries. Uses only `handle.graph` — see
68
+ * {@link priorityMatrix}'s note on the `queryHandle` gates.
69
+ */
70
+ export declare function partOfRollup(ctx: BacklogCtx, input: IPartOfRollupInput): Promise<IOutcomeEnvelope<IPartOfRollupResult>>;
71
+ /**
72
+ * SPEC.md §5's `validAt` cumulative-open curve — for each sampled ISO-8601
73
+ * instant in `input.at`, how many in-scope issues EXISTED then and, of those,
74
+ * how many were reconstructed as OPEN then (never the issue's current status;
75
+ * see `openCurve`'s own doc comment for the reconstruction rule).
76
+ *
77
+ * A read op, NOT a `query.view` member: it returns a time series
78
+ * (`{points}`) keyed by a caller-given instant list, an axis no list view has.
79
+ * Uses only `handle.graph` — see {@link priorityMatrix}'s note on the
80
+ * `queryHandle` gates.
81
+ */
82
+ export declare function openCurve(ctx: BacklogCtx, input: IOpenCurveInput): Promise<IOutcomeEnvelope<IOpenCurveResult>>;
83
+ /**
84
+ * Resolve a free-text reference to a project, component or location in the
85
+ * registry.
86
+ */
87
+ export declare function lookup(ctx: BacklogCtx, input: {
88
+ q: string;
89
+ }): Promise<IOutcomeEnvelope<ILookupResult>>;
90
+ /** File a new issue, minting its `uid` and linking it to a project component. */
91
+ export declare function create(ctx: BacklogCtx, input: ICreateIssueInput): Promise<IOutcomeEnvelope<ICreateIssueResult>>;
92
+ /**
93
+ * Edit an existing issue.
94
+ *
95
+ * A `body` change supersedes the issue, minting a fresh `uid`; every other
96
+ * change edits the existing node in place. `status` is not editable here —
97
+ * use `transition`.
98
+ */
99
+ export declare function update(ctx: BacklogCtx, input: IUpdateIssueInput): Promise<IOutcomeEnvelope<IUpdateIssueOutcome>>;
100
+ /** Move an issue to a new status, recording the transition in its audit trail. */
101
+ export declare function transition(ctx: BacklogCtx, input: ITransitionInput): Promise<IOutcomeEnvelope<ITransitionOutcome>>;
102
+ /** Take, renew or release an exclusive working lease on an issue. */
103
+ export declare function claim(ctx: BacklogCtx, input: IClaimInput): Promise<IOutcomeEnvelope<IClaimOutcome>>;
104
+ /** Create or remove a typed relationship between two issues. */
105
+ export declare function relate(ctx: BacklogCtx, input: IRelateInput): Promise<IOutcomeEnvelope<IRelateOutcome>>;
106
+ /** Re-file an issue under a different project component. */
107
+ export declare function move(ctx: BacklogCtx, input: IMoveIssueInput): Promise<IOutcomeEnvelope<IMoveIssueOutcome>>;
108
+ /**
109
+ * Soft-delete an issue.
110
+ *
111
+ * The node is closed off bi-temporally, never physically removed — its audit
112
+ * trail and every edge pointing at it remain readable.
113
+ */
114
+ declare function remove(ctx: BacklogCtx, input: IDeleteIssueInput): Promise<IOutcomeEnvelope<IDeleteIssueOutcome>>;
115
+ /**
116
+ * Create or update a project by `name`.
117
+ *
118
+ * On first creation, also mints the project's reserved default component
119
+ * `(root)`. A repeat call against an existing project is idempotent.
120
+ */
121
+ export declare function upsertProject(ctx: BacklogCtx, input: IUpsertProjectInput): Promise<IOutcomeEnvelope<IUpsertProjectOutcome>>;
122
+ /** Create or update a component by `(project, name)`. */
123
+ export declare function upsertComponent(ctx: BacklogCtx, input: IUpsertComponentInput): Promise<IOutcomeEnvelope<IUpsertComponentOutcome>>;
124
+ /**
125
+ * Create or find a location by `(component, locType, value)`.
126
+ *
127
+ * A component referenced by bare name (rather than uid) requires `project`
128
+ * to disambiguate it.
129
+ */
130
+ export declare function upsertLocation(ctx: BacklogCtx, input: IUpsertLocationInput): Promise<IOutcomeEnvelope<IUpsertLocationOutcome>>;
131
+ /** Soft-remove a location by `uid`. */
132
+ export declare function rmLocation(ctx: BacklogCtx, input: IRmLocationInput): Promise<IOutcomeEnvelope<IRmLocationOutcome>>;
133
+ /**
134
+ * SPEC §6.7 names this verb `delete` on every mount (`backlog_delete`,
135
+ * `backlog delete`, `DELETE /issue`). `export async function delete` is a
136
+ * syntax error — `delete` is a reserved word — but an export CLAUSE may alias
137
+ * to any IdentifierName, reserved words included, and apigen resolves the
138
+ * mounted name from `sf.getExportedDeclarations()` (ts-morph's own
139
+ * rename/re-export resolver, which extract.ts documents as covering "named
140
+ * exports — local, renamed, AND re-exported"). So the operation mounts as
141
+ * `delete` while the implementation keeps a legal identifier.
142
+ *
143
+ * Do NOT "simplify" this to `export async function remove`: that silently
144
+ * renames the tool to `backlog_remove` on all four transports.
145
+ */
146
+ export { remove as delete };