@adhd/backlog 0.0.1 → 0.0.2
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/README.md +40 -4
- package/dist/cli.d.ts +82 -0
- package/{client.d.ts → dist/client.d.ts} +32 -1
- package/{env.d.ts → dist/env.d.ts} +4 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +191 -0
- package/dist/index.mjs +17408 -0
- package/dist/install-skill.d.ts +28 -0
- package/{markdown.d.ts → dist/markdown.d.ts} +12 -1
- package/dist/migration-admin.d.ts +26 -0
- package/{model.d.ts → dist/model.d.ts} +80 -0
- package/dist/package.json +34 -0
- package/dist/serve.d.ts +12 -0
- package/dist/server.d.ts +54 -0
- package/dist/store/audit-log.d.ts +16 -0
- package/{store → dist/store}/crud.d.ts +13 -6
- package/dist/store/graph-backlog-store.d.ts +20 -0
- package/dist/store/ids.d.ts +24 -0
- package/dist/store/immediate-retry.d.ts +27 -0
- package/dist/store/mapping.d.ts +101 -0
- package/package.json +25 -11
- package/skill/SKILL.md +148 -0
- package/index.d.ts +0 -11
- package/index.js +0 -179
- package/index.mjs +0 -14842
- package/server.d.ts +0 -31
- package/store/graph-backlog-store.d.ts +0 -11
- package/store/ids.d.ts +0 -3
- package/store/mapping.d.ts +0 -49
- /package/{store → dist/store}/claim.d.ts +0 -0
- /package/{store → dist/store}/lifecycle.d.ts +0 -0
- /package/{store → dist/store}/mutate-metadata.d.ts +0 -0
- /package/{store → dist/store}/query.d.ts +0 -0
- /package/{store → dist/store}/structure.d.ts +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type SkillHost = 'claude' | 'codex' | 'opencode';
|
|
2
|
+
export type SkillScope = 'user' | 'project';
|
|
3
|
+
export interface InstallSkillResult {
|
|
4
|
+
host: SkillHost;
|
|
5
|
+
scope: SkillScope;
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Copies the packaged `SKILL.md` into every requested host's skill
|
|
10
|
+
* directory, under a `backlog/` subdirectory (matching the `memory-usage`
|
|
11
|
+
* precedent's per-skill-named-directory shape). Also drops a thin
|
|
12
|
+
* `extension.json` (`{ name, version, type: "skill", entrypoint: "SKILL.md"
|
|
13
|
+
* }`) alongside it, matching that same precedent's shape for hosts that
|
|
14
|
+
* consume it — additive, never required for Claude Code's own
|
|
15
|
+
* description-based auto-surfacing to work.
|
|
16
|
+
*
|
|
17
|
+
* `homeOverride` is a TEST-ISOLATION ESCAPE HATCH ONLY (mirrors
|
|
18
|
+
* `BuildBacklogEnvOptions.adhdRoot`/`BacklogCtx.adhdRoot` elsewhere in this
|
|
19
|
+
* package) — never passed by `runInstallSkillCommand`/the real CLI, which
|
|
20
|
+
* always resolves the genuine machine home directory for `--scope user`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function installSkill(argv: string[], cwd?: string, homeOverride?: string): InstallSkillResult[];
|
|
23
|
+
/** CLI entry — parses argv, runs the install, prints the same
|
|
24
|
+
* `console.log(JSON.stringify(...))` shape every other CLI command uses
|
|
25
|
+
* (BUG-APIGEN-015 parity), so scripting `backlog install-skill` composes
|
|
26
|
+
* with the rest of the CLI's output convention despite bypassing apigen
|
|
27
|
+
* dispatch entirely. */
|
|
28
|
+
export declare function runInstallSkillCommand(argv: string[]): Promise<void>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BacklogFilter, BacklogItem, BacklogStatus, Priority } from './model.js';
|
|
1
|
+
import { BacklogFilter, BacklogItem, BacklogStatus, MalformedHeaderInfo, Priority } from './model.js';
|
|
2
2
|
|
|
3
3
|
declare const LEGACY_TERMINAL_DONE: string[];
|
|
4
4
|
declare const LEGACY_TERMINAL_WORKAROUND: string[];
|
|
@@ -36,8 +36,19 @@ export interface ParsedMarkdownItem {
|
|
|
36
36
|
priority: string;
|
|
37
37
|
body: string;
|
|
38
38
|
}
|
|
39
|
+
export interface ParseWithDiagnosticsResult {
|
|
40
|
+
items: ParsedMarkdownItem[];
|
|
41
|
+
malformedHeaders: MalformedHeaderInfo[];
|
|
42
|
+
}
|
|
39
43
|
/** Ported (structure preserved) from tools/util/backlog.mjs:106-155 (`parse`). */
|
|
40
44
|
export declare function parseBacklogMarkdown(text: string): ParsedMarkdownItem[];
|
|
45
|
+
/**
|
|
46
|
+
* Same parse as {@link parseBacklogMarkdown}, plus `malformedHeaders` — every
|
|
47
|
+
* `##`/`###` line that looks like a corrupted/typo'd id header and was
|
|
48
|
+
* dropped instead of parsed into an item (DEBT-BACKLOG-IMPORT-SILENT-DROP-001).
|
|
49
|
+
* `importFromMarkdown` (client.ts) uses this to populate `ImportResult.malformedHeaders`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseBacklogMarkdownWithDiagnostics(text: string): ParseWithDiagnosticsResult;
|
|
41
52
|
/**
|
|
42
53
|
* SPEC.md §5.6 `renderToMarkdown` — one `###` block per item. Archived-item
|
|
43
54
|
* exclusion (SPEC.md §5.4 `archiveResolved`'s "renderToMarkdown's default
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { MigrationPhase } from './model.js';
|
|
2
|
+
import { BacklogConfig } from './env.js';
|
|
3
|
+
import { Environment } from '@adhd/environment';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mirrors `@adhd/environment-builder`'s own internal `roots.ts` global-root
|
|
7
|
+
* formula (`~/.<orgNamespace>/<project>/<namespace>/config.yaml`) using ONLY
|
|
8
|
+
* the public fields the `@adhd/environment` `Environment` instance exposes
|
|
9
|
+
* (`orgNamespace`/`project`/`namespace`) — `@adhd/backlog` does not depend on
|
|
10
|
+
* the internal `environment-builder` package directly, so this is a
|
|
11
|
+
* deliberate, narrow re-derivation, not an import of a private module.
|
|
12
|
+
* `adhdRootOverride` mirrors `EnvironmentOptions.adhdRoot`'s own test-isolation
|
|
13
|
+
* escape hatch (see `BuildBacklogEnvOptions`) for tests that must never touch
|
|
14
|
+
* the real machine-global `~/.adhd`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function globalConfigPath(env: Environment<BacklogConfig>, adhdRootOverride?: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Reads the existing global `config.yaml` (if any), deep-merges in
|
|
19
|
+
* `migration.phase`, and writes it back — preserving every OTHER key already
|
|
20
|
+
* in the file (e.g. a previously-set `db.busyTimeoutMs` override) rather than
|
|
21
|
+
* clobbering the whole file. A missing or malformed existing file is treated
|
|
22
|
+
* as empty (never fatal for a write), mirroring
|
|
23
|
+
* `@adhd/environment-builder`'s own `readLayerFile` tolerance for a corrupt
|
|
24
|
+
* layer. Returns the absolute path written, for caller confirmation.
|
|
25
|
+
*/
|
|
26
|
+
export declare function writeMigrationPhase(env: Environment<BacklogConfig>, phase: MigrationPhase, adhdRootOverride?: string): string;
|
|
@@ -48,6 +48,8 @@ export interface BacklogItem {
|
|
|
48
48
|
projectPath?: string;
|
|
49
49
|
/** Plan slug this item is attached to, if any — e.g. "agent-registry-schema". */
|
|
50
50
|
plan?: string;
|
|
51
|
+
/** Source markdown path this item was imported from, if any (DEBT-BACKLOG-IMPORT-PLAN-PROVENANCE-001). */
|
|
52
|
+
importedFrom?: string;
|
|
51
53
|
/** Durable ownership — who this item is assigned to (may differ from the active claimant). */
|
|
52
54
|
assignee?: string;
|
|
53
55
|
/** Ephemeral claim lease — see SPEC.md §5. */
|
|
@@ -92,6 +94,8 @@ export interface CreateItemInput {
|
|
|
92
94
|
priority?: Priority;
|
|
93
95
|
tags?: string[];
|
|
94
96
|
plan?: string;
|
|
97
|
+
/** Source markdown path this item is being imported from, if any (DEBT-BACKLOG-IMPORT-PLAN-PROVENANCE-001). */
|
|
98
|
+
importedFrom?: string;
|
|
95
99
|
dedupeScan?: DedupeScanInput;
|
|
96
100
|
/** Skip the dedupe gate and file anyway (planner override after reviewing candidates). */
|
|
97
101
|
force?: boolean;
|
|
@@ -106,6 +110,14 @@ export interface UpdateItemInput {
|
|
|
106
110
|
body?: string;
|
|
107
111
|
tags?: string[];
|
|
108
112
|
projectPath?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Provenance owner of this item (the source-file path that authored it).
|
|
115
|
+
* Only ever set to BACKFILL a legacy row whose `importedFrom` was never
|
|
116
|
+
* stamped (created before the provenance field existed) — see
|
|
117
|
+
* `importFromMarkdown`'s owning-import branch. An already-stamped owner is
|
|
118
|
+
* immutable and must never be reassigned via this patch.
|
|
119
|
+
*/
|
|
120
|
+
importedFrom?: string;
|
|
109
121
|
}
|
|
110
122
|
export interface BacklogFilter {
|
|
111
123
|
repo?: string;
|
|
@@ -119,6 +131,30 @@ export interface BacklogFilter {
|
|
|
119
131
|
claimedBy?: string;
|
|
120
132
|
tags?: string[];
|
|
121
133
|
grep?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Exact-match on `BacklogItem.importedFrom` — the sourcePath that OWNS an
|
|
136
|
+
* item's canonical content (DEBT-BACKLOG-IMPORT-SCOPE-CROSSFILE-001).
|
|
137
|
+
* Needed for a root-level `BACKLOG.md` projection: filtering by bare
|
|
138
|
+
* `{repo}` alone would also surface every item cross-referenced FROM root
|
|
139
|
+
* by a plan/package file (which correctly carries a `plan`/`projectPath`
|
|
140
|
+
* of its own once ownership-gating lands) — `importedFrom` is the only
|
|
141
|
+
* field that reliably answers "does THIS file own this item's content",
|
|
142
|
+
* independent of which OTHER files also cite the same id.
|
|
143
|
+
*/
|
|
144
|
+
importedFrom?: string;
|
|
145
|
+
/**
|
|
146
|
+
* Repo-level projection selector (MIGRATION.md §2.2 "root BACKLOG.md =
|
|
147
|
+
* repo-only, no projectPath/plan"). When true, returns only items that carry
|
|
148
|
+
* NEITHER a `projectPath` NOR a `plan` — i.e. items owned by the repo root
|
|
149
|
+
* rather than a package or plan projection. Unlike the `importedFrom`
|
|
150
|
+
* workaround it does not depend on provenance, so a freshly tool-created
|
|
151
|
+
* repo-level item (which has no `importedFrom`) still appears in the root
|
|
152
|
+
* projection — the Phase-3 DoD ("a fresh create-item appears in the
|
|
153
|
+
* regenerated BACKLOG.md") requires this. Cross-referenced items that carry a
|
|
154
|
+
* `plan`/`projectPath` render in that plan/package projection instead, never
|
|
155
|
+
* duplicated into root.
|
|
156
|
+
*/
|
|
157
|
+
rootLevel?: boolean;
|
|
122
158
|
limit?: number;
|
|
123
159
|
offset?: number;
|
|
124
160
|
}
|
|
@@ -194,16 +230,45 @@ export interface ImportMarkdownInput {
|
|
|
194
230
|
path: string;
|
|
195
231
|
repo: string;
|
|
196
232
|
projectPath?: string;
|
|
233
|
+
/** Plan slug to attach every imported item to (DEBT-BACKLOG-IMPORT-PLAN-PROVENANCE-001) — replaces the post-import attachToPlan-per-id workaround. */
|
|
234
|
+
plan?: string;
|
|
235
|
+
/**
|
|
236
|
+
* Provenance path recorded on each imported node's `importedFrom` field
|
|
237
|
+
* (DEBT-BACKLOG-IMPORT-PLAN-PROVENANCE-001). Defaults to `path` when
|
|
238
|
+
* omitted — the file actually read IS the source, so a caller only needs
|
|
239
|
+
* to set this explicitly when it differs (e.g. importing from a scratch
|
|
240
|
+
* copy but wanting the ORIGINAL path recorded).
|
|
241
|
+
*/
|
|
242
|
+
sourcePath?: string;
|
|
197
243
|
dryRun?: boolean;
|
|
198
244
|
}
|
|
245
|
+
/** A `##`/`###` header line that failed the strict `HEADER_RE` id pattern but looks like an attempted id (DEBT-BACKLOG-IMPORT-SILENT-DROP-001). */
|
|
246
|
+
export interface MalformedHeaderInfo {
|
|
247
|
+
/** 1-based line number in the source file. */
|
|
248
|
+
line: number;
|
|
249
|
+
headerLine: string;
|
|
250
|
+
}
|
|
199
251
|
export interface ImportResult {
|
|
200
252
|
parsed: number;
|
|
201
253
|
created: number;
|
|
202
254
|
skippedDuplicates: number;
|
|
255
|
+
/**
|
|
256
|
+
* Of `skippedDuplicates` (an already-existing humanId), how many had a
|
|
257
|
+
* title/body/priority/status that DIFFERED from the graph's current copy
|
|
258
|
+
* and were refreshed to match the re-imported source
|
|
259
|
+
* (BUG-BACKLOG-IMPORT-INSERT-ONLY-NO-UPDATE-001 — re-importing used to be
|
|
260
|
+
* pure insert-only: a status/content change made directly in a
|
|
261
|
+
* `BACKLOG.md` file after the first import was silently never reflected
|
|
262
|
+
* in the graph on a later re-import). An unchanged existing item is a
|
|
263
|
+
* true no-op — never counted here.
|
|
264
|
+
*/
|
|
265
|
+
updated: number;
|
|
203
266
|
errors: Array<{
|
|
204
267
|
humanId: string;
|
|
205
268
|
message: string;
|
|
206
269
|
}>;
|
|
270
|
+
/** Headers that look like a corrupted/typo'd id and were dropped instead of parsed — never silent (DEBT-BACKLOG-IMPORT-SILENT-DROP-001). */
|
|
271
|
+
malformedHeaders: MalformedHeaderInfo[];
|
|
207
272
|
}
|
|
208
273
|
export interface AuditTrailEntry {
|
|
209
274
|
at: string;
|
|
@@ -218,3 +283,18 @@ export interface AuditTrailResult {
|
|
|
218
283
|
supersededBy?: string;
|
|
219
284
|
};
|
|
220
285
|
}
|
|
286
|
+
export type MigrationPhase = 'not-started' | 'phase-1' | 'phase-2' | 'phase-3' | 'phase-4' | 'phase-5' | 'complete';
|
|
287
|
+
export interface MigrationStatusResult {
|
|
288
|
+
phase: MigrationPhase;
|
|
289
|
+
/** One-line human-readable meaning of `phase`, e.g. "phase-2: BACKLOG.md is still authoritative; the tool is shadow-running in parity-check mode." */
|
|
290
|
+
description: string;
|
|
291
|
+
/** True once the graph (not hand-edited markdown) is authoritative — phase-3 and later. */
|
|
292
|
+
toolIsAuthoritative: boolean;
|
|
293
|
+
}
|
|
294
|
+
/** `setMigrationPhase`'s result — `MigrationStatusResult` plus the absolute
|
|
295
|
+
* path of the GLOBAL `config.yaml` the new phase was persisted to (so a
|
|
296
|
+
* caller can confirm this was a durable, cross-process write, not merely an
|
|
297
|
+
* in-memory value). */
|
|
298
|
+
export interface SetMigrationPhaseResult extends MigrationStatusResult {
|
|
299
|
+
configPath: string;
|
|
300
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adhd/backlog",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"bin": {
|
|
5
|
+
"backlog": "./dist/index.js"
|
|
6
|
+
},
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@adhd/sox-graph-store": "^0.3.0",
|
|
9
|
+
"better-sqlite3": "^12.10.0",
|
|
10
|
+
"@adhd/environment": "0.0.2",
|
|
11
|
+
"@adhd/environment-base-spec": "0.0.3",
|
|
12
|
+
"@adhd/apigen-core-client": "^0.1.1",
|
|
13
|
+
"@adhd/apigen-plugin-api-fastify": "^0.1.2",
|
|
14
|
+
"@adhd/apigen-plugin-openapi": "^0.1.3",
|
|
15
|
+
"@adhd/apigen-plugin-mcp": "^0.1.2",
|
|
16
|
+
"@adhd/apigen-plugin-cli-output": "^0.1.3",
|
|
17
|
+
"@adhd/apigen-engine-naming": "^0.1.3",
|
|
18
|
+
"yaml": "1.10.3"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/better-sqlite3": "^7.6.13"
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"module": "./dist/index.mjs",
|
|
25
|
+
"typings": "./dist/index.d.ts",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"CHANGELOG.md",
|
|
32
|
+
"skill"
|
|
33
|
+
]
|
|
34
|
+
}
|
package/dist/serve.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Scope } from '@adhd/environment-base-spec';
|
|
2
|
+
|
|
3
|
+
export interface RunServeCommandOpts {
|
|
4
|
+
scope?: Scope;
|
|
5
|
+
/** Test-only override — see `buildBacklogEnv`'s `BuildBacklogEnvOptions`. */
|
|
6
|
+
adhdRoot?: string;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Runs until the process receives SIGTERM/SIGINT (the normal way a host
|
|
10
|
+
* process manager — or `.mcp.json`'s own stdio transport lifecycle — stops
|
|
11
|
+
* a long-lived MCP/HTTP server), then resolves cleanly. */
|
|
12
|
+
export declare function runServeCommand(argv: string[], opts?: RunServeCommandOpts): Promise<void>;
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { BacklogCtx } from './client.js';
|
|
2
|
+
import { composeSchemas, Operation, OutputPlugin, RunInput } from '@adhd/apigen-core-client';
|
|
3
|
+
import { Scope } from '@adhd/environment-base-spec';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Guards a live-mount `plugin.run()` call. Exported (not local to this file)
|
|
7
|
+
* because `cli.ts`'s `runBacklogCli` — the third transport, mounting
|
|
8
|
+
* `@adhd/apigen-plugin-cli-output` the exact same way this file mounts
|
|
9
|
+
* fastify/mcp — needs the identical guard; duplicating a 3-line assertion
|
|
10
|
+
* across two files in the SAME package isn't worth a new `packages/`
|
|
11
|
+
* extraction (CLAUDE.md's "Two-Use Refactor Rule" targets logic reusable
|
|
12
|
+
* ACROSS packages, not an in-package private helper), so it's shared via a
|
|
13
|
+
* plain re-export instead.
|
|
14
|
+
*/
|
|
15
|
+
export declare function requireRun(plugin: OutputPlugin): (input: RunInput) => Promise<void>;
|
|
16
|
+
export interface StartOpts {
|
|
17
|
+
transport: 'http' | 'mcp' | 'both';
|
|
18
|
+
port?: number;
|
|
19
|
+
host?: string;
|
|
20
|
+
scope?: Scope;
|
|
21
|
+
/** Test-only override — see `buildBacklogEnv`'s `BuildBacklogEnvOptions`. */
|
|
22
|
+
adhdRoot?: string;
|
|
23
|
+
cwd?: string;
|
|
24
|
+
signal: AbortSignal;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Builds the composed, apigen-ready package descriptor for `client.ts`'s
|
|
28
|
+
* exports. `ctx` may be a plain `BacklogCtx` (the store is already open —
|
|
29
|
+
* `startBacklogServer`'s case, where a long-lived server needs its store
|
|
30
|
+
* immediately regardless of what request comes first) OR a LAZY `() =>
|
|
31
|
+
* BacklogCtx` thunk (`runBacklogCli`'s case) — `createClient` only calls it
|
|
32
|
+
* when a dispatched command actually reaches the real function, which never
|
|
33
|
+
* happens for `--help`/no-args/an unknown command. This is what closes
|
|
34
|
+
* DEBT-BACKLOG-CLI-EAGER-STORE-OPEN-001: `operations`/`schemas` below are
|
|
35
|
+
* computed purely from the built `client.d.ts` (via `extractClientOperations`)
|
|
36
|
+
* and never touch `ctx` at all, so a lazy caller can defer opening the real
|
|
37
|
+
* backing store until a command that actually needs it is dispatched.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildBacklogApigenPackage(ctx: BacklogCtx | (() => BacklogCtx)): Promise<{
|
|
40
|
+
pkg: {
|
|
41
|
+
id: string;
|
|
42
|
+
schemas: ReturnType<typeof composeSchemas>;
|
|
43
|
+
importPath: string;
|
|
44
|
+
fns: Record<string, (...args: unknown[]) => unknown>;
|
|
45
|
+
createClient: () => Promise<BacklogCtx>;
|
|
46
|
+
};
|
|
47
|
+
operations: Operation[];
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Opens (or reuses) the backlog store + env, mounts every `client.ts` export
|
|
51
|
+
* live via `@adhd/apigen-plugin-api-fastify` and/or `@adhd/apigen-plugin-mcp`
|
|
52
|
+
* — no code generation.
|
|
53
|
+
*/
|
|
54
|
+
export declare function startBacklogServer(opts: StartOpts): Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GraphBacklogStore } from './graph-backlog-store.js';
|
|
2
|
+
import { AuditTrailEntry } from '../model.js';
|
|
3
|
+
|
|
4
|
+
export declare const BACKLOG_AUDIT_EVENT_TAG = "backlog-audit-event";
|
|
5
|
+
/**
|
|
6
|
+
* Records one `transition`/`claim` event for the item at `itemNodeId`.
|
|
7
|
+
* `content` is a uniqueness-marker string (mirrors `buildNodeContent()`'s
|
|
8
|
+
* own reasoning, `mapping.ts`) — `@adhd/sox-graph-store`'s global
|
|
9
|
+
* content-hash dedup would otherwise be free to collapse two
|
|
10
|
+
* byte-identical events (e.g. two items independently transitioning
|
|
11
|
+
* `OPEN`→`IN_PROGRESS` with no `by`/`reason` at all) into ONE node.
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeAuditEvent(store: GraphBacklogStore, itemNodeId: number, repo: string, humanId: string, kind: AuditTrailEntry['kind'], detail: Record<string, unknown>): void;
|
|
14
|
+
/** Every persisted event for `itemNodeId`, oldest first — ready to merge
|
|
15
|
+
* straight into `auditTrail()`'s `history` array. */
|
|
16
|
+
export declare function queryAuditEvents(store: GraphBacklogStore, itemNodeId: number): AuditTrailEntry[];
|
|
@@ -5,15 +5,22 @@ declare function dedupeScan(store: GraphBacklogStore, repo: string, input: Creat
|
|
|
5
5
|
export declare function createItemNode(store: GraphBacklogStore, input: CreateItemInput): CreateItemResult;
|
|
6
6
|
export declare function getItemNode(store: GraphBacklogStore, repo: string, humanId: string): BacklogItem | null;
|
|
7
7
|
/**
|
|
8
|
-
* DEVIATION: `@adhd/sox-graph-store`
|
|
9
|
-
*
|
|
8
|
+
* DEVIATION (mitigated — DEBT-BACKLOG-CONTENT-IMMUTABLE-001): `@adhd/sox-graph-store`
|
|
9
|
+
* exposes no PUBLIC primitive to update a node's `content` column after
|
|
10
|
+
* creation (`touch()`'s `Partial<NodeMeta>` covers
|
|
10
11
|
* name/summary/topic/tags/importance/confidence/tExpires/metadata — never
|
|
11
12
|
* `content`; verified against the real source). `title` updates the `summary`
|
|
12
13
|
* column (source of truth for the API) AND `metadata.title`; `body` updates
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* `metadata.body` (source of truth for the API). Below, a title/body change
|
|
15
|
+
* ALSO re-synchronizes the FTS-indexed `content`/`content_hash` columns
|
|
16
|
+
* directly via raw SQL on the store-owned `db` handle — the same DESIGN.md
|
|
17
|
+
* §14-sanctioned escape hatch `structure.ts`'s `removeDependencyNode` already
|
|
18
|
+
* uses for the one other gap (`edge` deletion) the `GraphBackend` API lacks.
|
|
19
|
+
* This is safe specifically because `fts_node_au` (the real schema's `AFTER
|
|
20
|
+
* UPDATE ON node` trigger — `~/dev/ai/sox-ecosystem/libs/data/graph/graph-store/
|
|
21
|
+
* src/index.ts`'s `FTS_TRIGGERS`) re-indexes `fts_node` automatically on
|
|
22
|
+
* ANY write to `node.content`/`name`/`summary`, so no separate FTS statement
|
|
23
|
+
* is needed here.
|
|
17
24
|
*/
|
|
18
25
|
export declare function updateItemNode(store: GraphBacklogStore, repo: string, humanId: string, patch: UpdateItemInput): BacklogItem;
|
|
19
26
|
export declare function softDeleteItemNode(store: GraphBacklogStore, repo: string, humanId: string, reason: string): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { GraphBackend } from '@adhd/sox-graph-store';
|
|
2
|
+
import { default as Database } from 'better-sqlite3';
|
|
3
|
+
|
|
4
|
+
export interface GraphBacklogStore {
|
|
5
|
+
/** Raw handle — ONLY for the CAS transaction wrapper (mutate-metadata.ts / ids.ts). */
|
|
6
|
+
readonly db: Database.Database;
|
|
7
|
+
/** All non-CAS reads/writes go through this. */
|
|
8
|
+
readonly graph: GraphBackend;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* @param busyTimeoutMs SQLite `busy_timeout` (ms) — how long a blocked
|
|
12
|
+
* `.immediate()` waits for a contended lock before throwing `SQLITE_BUSY`
|
|
13
|
+
* (DEBT-BACKLOG-CONCURRENCY-BUSY-RETRY-001). Callers reading from
|
|
14
|
+
* `BacklogConfig` should pass `env.config.db.busyTimeoutMs`; the default
|
|
15
|
+
* here (5000) matches that config field's own default, for callers (tests,
|
|
16
|
+
* ad-hoc scripts) that open a store directly without going through
|
|
17
|
+
* `buildBacklogEnv`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function openGraphBacklogStore(dbPath: string, busyTimeoutMs?: number): GraphBacklogStore;
|
|
20
|
+
export declare function closeGraphBacklogStore(store: GraphBacklogStore): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { GraphBacklogStore } from './graph-backlog-store.js';
|
|
2
|
+
import { NodeRecord } from '@adhd/sox-graph-store';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the humanId to insert under (either `idOverride`, re-verified for
|
|
6
|
+
* an already-live node, or the next auto-allocated `family-NNN`) and invokes
|
|
7
|
+
* `insert(humanId, existing)` — ALL inside one retried `.immediate()`
|
|
8
|
+
* transaction, so no other concurrent `.immediate()`-wrapped write can
|
|
9
|
+
* interleave between "the id was resolved" and "a node claiming it landed".
|
|
10
|
+
* `existing` is the already-live node under `idOverride` (re-checked HERE,
|
|
11
|
+
* not just by an earlier, racy caller-side check) — `insert` is expected to
|
|
12
|
+
* short-circuit on a non-null `existing` exactly like `createItemNode`'s
|
|
13
|
+
* documented idempotent-reimport behavior, but now race-free.
|
|
14
|
+
*/
|
|
15
|
+
export declare function allocateHumanIdAndInsert<T>(store: GraphBacklogStore, repo: string, family: string, idOverride: string | undefined, insert: (humanId: string, existing: NodeRecord | null) => T): T;
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated kept ONLY as a standalone id-generator for any caller that does
|
|
18
|
+
* not need an atomic insert alongside it. `createItemNode`/
|
|
19
|
+
* `supersedeItemNode` no longer use this (see `allocateHumanIdAndInsert`'s
|
|
20
|
+
* doc comment for why splitting allocate-then-insert-later is unsafe under
|
|
21
|
+
* concurrency). Still correct in isolation — just NOT TOCTOU-safe when the
|
|
22
|
+
* caller's own insert happens in a separate, later transaction.
|
|
23
|
+
*/
|
|
24
|
+
export declare function allocateHumanId(store: GraphBacklogStore, repo: string, family: string): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* immediate-retry.ts — bounded, jittered exponential-backoff retry wrapper
|
|
3
|
+
* around a `db.transaction(fn).immediate()` call (DEBT-BACKLOG-CONCURRENCY-
|
|
4
|
+
* BUSY-RETRY-001). `mutate-metadata.ts` / `ids.ts` are the ONLY two write
|
|
5
|
+
* paths that call `.immediate()` directly (DESIGN.md §3/§4.3) and both funnel
|
|
6
|
+
* through this wrapper — retrying ONLY the specific `SQLITE_BUSY`/
|
|
7
|
+
* `SQLITE_BUSY_TIMEOUT`/`SQLITE_BUSY_SNAPSHOT` error `better-sqlite3` throws
|
|
8
|
+
* when a `BEGIN IMMEDIATE`'s wait exceeds `busy_timeout`. Any other thrown
|
|
9
|
+
* error (including `NotFoundError`, `ClaimContentionError`) propagates
|
|
10
|
+
* immediately, unretried — and the semantic `'held'` claim-contention RESULT
|
|
11
|
+
* (claim.ts) is a normal RETURN VALUE, never an exception, so it is never
|
|
12
|
+
* touched by this wrapper either.
|
|
13
|
+
*/
|
|
14
|
+
export interface ImmediateRetryOpts {
|
|
15
|
+
/** Total attempts (first try + retries). Default 5. */
|
|
16
|
+
maxAttempts?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Runs `attempt()` (expected to be `() => db.transaction(fn).immediate()`),
|
|
20
|
+
* retrying up to `maxAttempts` times ONLY when it throws a SQLITE_BUSY-shaped
|
|
21
|
+
* error, with jittered exponential backoff between attempts (20ms, 40ms,
|
|
22
|
+
* 80ms, 160ms, capped at 500ms). Any other error propagates immediately.
|
|
23
|
+
* After the final attempt still fails, the last SQLITE_BUSY error is
|
|
24
|
+
* re-thrown (a genuine, sustained pileup is still a real failure — this
|
|
25
|
+
* bounds the wait, it doesn't hide contention forever).
|
|
26
|
+
*/
|
|
27
|
+
export declare function withImmediateRetry<T>(attempt: () => T, opts?: ImmediateRetryOpts): T;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { BacklogItem, BacklogStatus, Citation, Note, Priority } from '../model.js';
|
|
2
|
+
import { NodeRecord } from '@adhd/sox-graph-store';
|
|
3
|
+
|
|
4
|
+
export declare const BACKLOG_ITEM_TAG = "backlog-item";
|
|
5
|
+
export declare const BACKLOG_PLAN_TAG = "backlog-plan";
|
|
6
|
+
export declare const BACKLOG_ASSIGNEE_TAG = "backlog-assignee";
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors `@adhd/sox-graph-store`'s PRIVATE (unexported) `hashContent()`
|
|
9
|
+
* (`sha256(content.trim().toLowerCase())`) exactly. Needed only by
|
|
10
|
+
* `updateItemNode` (crud.ts), which writes `node.content`/`node.content_hash`
|
|
11
|
+
* directly via raw SQL (DESIGN.md §14's sanctioned escape hatch for the one
|
|
12
|
+
* gap `touch()` doesn't cover — DEBT-BACKLOG-CONTENT-IMMUTABLE-001) and must
|
|
13
|
+
* keep `content_hash` consistent with the `content` it just wrote, exactly as
|
|
14
|
+
* `writeNode()` would have. If upstream ever changes its normalization, this
|
|
15
|
+
* copy must change with it — there is no shared primitive to import instead.
|
|
16
|
+
*/
|
|
17
|
+
export declare function computeContentHash(content: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* `@adhd/sox-graph-store`'s `searchNodes(query)` binds `query` DIRECTLY as an
|
|
20
|
+
* FTS5 `MATCH` argument, parsed by FTS5's own boolean/column-filter query
|
|
21
|
+
* grammar — NOT a plain-text search. `searchNodes`'s own `query.replace(/"/g,
|
|
22
|
+
* '""')` cannot make this safe for arbitrary text: doubling an EXISTING `"`
|
|
23
|
+
* always yields an EVEN number of quote characters in the output, so a
|
|
24
|
+
* caller can never end up with a validly single-quoted phrase this way
|
|
25
|
+
* (verified empirically — wrapping a title in quotes before calling
|
|
26
|
+
* `searchNodes` still throws). A title containing a bare `-`, `:`, `(`, `)`,
|
|
27
|
+
* or `"` (all real English titles — "off-by-one", "fix: the thing" — not
|
|
28
|
+
* edge cases) crashes `createItemNode`'s dedupe scan outright with a raw
|
|
29
|
+
* `SqliteError`, since `force:true` (the only path that SKIPS the dedupe
|
|
30
|
+
* scan) is not the default. `dedupeScan` (crud.ts) uses this to strip every
|
|
31
|
+
* FTS5-syntax-significant character to a space before searching — this is
|
|
32
|
+
* BEHAVIOR-PRESERVING for the already-working case (a title with no special
|
|
33
|
+
* characters passes through unchanged, still an implicit-AND bareword
|
|
34
|
+
* query), and merely prevents the CRASH for the common case that hits one.
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* Strips every FTS5-syntax-significant character to a space before a title/
|
|
38
|
+
* grep term ever reaches `searchNodes`'s raw MATCH bind (dedupeScan and
|
|
39
|
+
* `queryItemNodes`'s grep path, BUG-BACKLOG-DEDUPE-FTS-SYNTAX-CRASH-001).
|
|
40
|
+
*
|
|
41
|
+
* The original fix (2026-07) only stripped the small set of characters found
|
|
42
|
+
* in the reported crash: `"():^*-`. `#` (an entirely ordinary character in a
|
|
43
|
+
* real title, e.g. "Fixes #123" or this file's own regression test) was
|
|
44
|
+
* discovered independently to ALSO crash `searchNodes` with `fts5: syntax
|
|
45
|
+
* error near "#"` — and probing FTS5's actual grammar directly (not
|
|
46
|
+
* guessing) turned up a much longer list that crashes the same way: `. { }
|
|
47
|
+
* ~ [ ] @ ! $ % & = < > ? / \ ; ,` — `{` is the most dangerous of these
|
|
48
|
+
* (`no such column: create` — it gets parsed as column-filter syntax rather
|
|
49
|
+
* than merely erroring, a correctness risk beyond a crash). A second
|
|
50
|
+
* whack-a-mole char-by-char addition would leave the same class of gap open
|
|
51
|
+
* for whatever punctuation mark comes up next, so this strips every
|
|
52
|
+
* character that is NOT a Unicode letter, number, underscore, or whitespace
|
|
53
|
+
* — the complete, non-enumerable-by-hand safe set for an FTS5 bareword
|
|
54
|
+
* query — rather than continuing to enumerate a denylist.
|
|
55
|
+
*/
|
|
56
|
+
export declare function sanitizeFtsQuery(text: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* The JSON shape persisted in `node.meta` (DESIGN.md §2.2/§4.1). Every
|
|
59
|
+
* mutating store operation reads the CURRENT full object, computes a new
|
|
60
|
+
* COMPLETE object, and writes it back via `mutateMetadata` — `touch()`
|
|
61
|
+
* replaces `meta` wholesale (verified, DESIGN.md §14 point 4), so a partial
|
|
62
|
+
* write here would silently drop every other field.
|
|
63
|
+
*/
|
|
64
|
+
export interface BacklogNodeMeta {
|
|
65
|
+
humanId: string;
|
|
66
|
+
kind: string;
|
|
67
|
+
family: string;
|
|
68
|
+
title: string;
|
|
69
|
+
body: string;
|
|
70
|
+
status: BacklogStatus;
|
|
71
|
+
priority?: Priority;
|
|
72
|
+
repo: string;
|
|
73
|
+
projectPath?: string;
|
|
74
|
+
plan?: string;
|
|
75
|
+
/** Source markdown path this item was imported from, if any (DEBT-BACKLOG-IMPORT-PLAN-PROVENANCE-001). */
|
|
76
|
+
importedFrom?: string;
|
|
77
|
+
assignee?: string;
|
|
78
|
+
claimedBy?: string;
|
|
79
|
+
claimedAt?: string;
|
|
80
|
+
citations: Citation[];
|
|
81
|
+
notes: Note[];
|
|
82
|
+
createdAt: string;
|
|
83
|
+
updatedAt: string;
|
|
84
|
+
/** Set by archiveResolved — excludes the item from renderToMarkdown's default view. */
|
|
85
|
+
archivedAt?: string;
|
|
86
|
+
/** Dedupe-scan exact-match fields (DESIGN.md §2.4). */
|
|
87
|
+
dedupeSymbol?: string;
|
|
88
|
+
dedupePath?: string;
|
|
89
|
+
dedupeErrorText?: string;
|
|
90
|
+
}
|
|
91
|
+
export declare function humanIdKind(humanId: string): string;
|
|
92
|
+
export declare function humanIdFamily(humanId: string): string;
|
|
93
|
+
/** See the file-level DEVIATION doc comment for why the marker is appended. */
|
|
94
|
+
export declare function buildNodeContent(repo: string, humanId: string, title: string, body: string): string;
|
|
95
|
+
export declare function buildNodeName(repo: string, humanId: string): string;
|
|
96
|
+
/** DESIGN.md §2.2 — importance derived deterministically from priority. */
|
|
97
|
+
export declare function importanceForPriority(priority: Priority | undefined): number;
|
|
98
|
+
export declare function buildTags(kind: string, family: string, userTags?: readonly string[]): string[];
|
|
99
|
+
/** A node counts as a live backlog item iff it carries the tag AND is not superseded (DESIGN.md §14). */
|
|
100
|
+
export declare function isLiveBacklogItemNode(node: NodeRecord): boolean;
|
|
101
|
+
export declare function toBacklogItem(node: NodeRecord): BacklogItem;
|
package/package.json
CHANGED
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/backlog",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"bin": {
|
|
5
|
+
"backlog": "./dist/index.js"
|
|
6
|
+
},
|
|
4
7
|
"dependencies": {
|
|
5
8
|
"@adhd/sox-graph-store": "^0.3.0",
|
|
6
9
|
"better-sqlite3": "^12.10.0",
|
|
7
|
-
"@adhd/environment": "
|
|
8
|
-
"@adhd/environment-base-spec": "
|
|
9
|
-
"@adhd/apigen-core-client": "^0.1.
|
|
10
|
-
"@adhd/apigen-plugin-api-fastify": "^0.1.
|
|
11
|
-
"@adhd/apigen-plugin-openapi": "^0.1.
|
|
12
|
-
"@adhd/apigen-plugin-mcp": "^0.1.
|
|
10
|
+
"@adhd/environment": "0.0.2",
|
|
11
|
+
"@adhd/environment-base-spec": "0.0.3",
|
|
12
|
+
"@adhd/apigen-core-client": "^0.1.1",
|
|
13
|
+
"@adhd/apigen-plugin-api-fastify": "^0.1.2",
|
|
14
|
+
"@adhd/apigen-plugin-openapi": "^0.1.3",
|
|
15
|
+
"@adhd/apigen-plugin-mcp": "^0.1.2",
|
|
16
|
+
"@adhd/apigen-plugin-cli-output": "^0.1.3",
|
|
17
|
+
"@adhd/apigen-engine-naming": "^0.1.3",
|
|
18
|
+
"yaml": "1.10.3"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/better-sqlite3": "^7.6.13"
|
|
13
22
|
},
|
|
14
|
-
"main": "./index.js",
|
|
15
|
-
"module": "./index.mjs",
|
|
16
|
-
"typings": "./index.d.ts",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"module": "./dist/index.mjs",
|
|
25
|
+
"typings": "./dist/index.d.ts",
|
|
17
26
|
"publishConfig": {
|
|
18
27
|
"access": "public"
|
|
19
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"CHANGELOG.md",
|
|
32
|
+
"skill"
|
|
33
|
+
]
|
|
20
34
|
}
|