@adhd/backlog 0.1.2 → 0.1.4

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/model.d.ts CHANGED
@@ -90,6 +90,41 @@ export declare class DependencyCycleError extends Error {
90
90
  readonly cycle: string[];
91
91
  constructor(cycle: string[]);
92
92
  }
93
+ /**
94
+ * BUG-BACKLOG-HUMANID-COLLISION-001 (fix #1 — write-time guard):
95
+ * `createItemNode` rejects a `family` that is missing/empty/whitespace-only
96
+ * UNLESS `idOverride` is also given (SPEC.md §5.1's `CreateItemInput.family`
97
+ * contract: "required unless idOverride given"). Thrown BEFORE
98
+ * `allocateHumanIdAndInsert`/`computeNextHumanId` ever run, so a caller that
99
+ * omits `family` (previously silently coerced to the literal string
100
+ * `"undefined"` by `computeNextHumanId`'s template literal, producing
101
+ * `humanId: "undefined-001"` and colliding with every other item that hit
102
+ * the same bug) now fails loudly instead of minting a collision. This is
103
+ * defense in depth: it must hold regardless of whether an upstream caller's
104
+ * own input-schema validation (e.g. apigen-core-client's extracted
105
+ * `CreateItemInput` schema, BUG-APIGEN-CORE-CLIENT-001) enforces `family` as
106
+ * required — the store's own write path must never trust the caller alone.
107
+ */
108
+ export declare class InvalidArgumentError extends Error {
109
+ readonly argument: string;
110
+ constructor(argument: string, message: string);
111
+ }
112
+ /**
113
+ * BUG-BACKLOG-HUMANID-COLLISION-001 (fix #2 — read-time guard): every
114
+ * `(repo, humanId)`-keyed lookup used to silently resolve to "whichever
115
+ * live node is found first" when more than one live node shared the same
116
+ * key (the exact shape of the pre-existing `"undefined-001"` collisions,
117
+ * and the root cause of a real mis-transition this session — see the
118
+ * backlog item's body). Any lookup that finds >1 live match now throws this
119
+ * instead of guessing, listing every colliding `nodeId` so a caller can
120
+ * disambiguate (there is no tool-level nodeId-addressed path yet — the
121
+ * caller must go through the store's own repair primitives, e.g.
122
+ * `renameHumanId`, to resolve the collision).
123
+ */
124
+ export declare class AmbiguousHumanIdError extends Error {
125
+ readonly nodeIds: number[];
126
+ constructor(repo: string, humanId: string, nodeIds: number[]);
127
+ }
93
128
  export interface DedupeScanInput {
94
129
  symbol?: string;
95
130
  path?: string;
package/package.json CHANGED
@@ -1,24 +1,25 @@
1
1
  {
2
2
  "name": "@adhd/backlog",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "bin": {
5
5
  "backlog": "index.js"
6
6
  },
7
7
  "dependencies": {
8
+ "@adhd/apigen-core-client": "^0.3.0",
9
+ "@adhd/apigen-engine-naming": "^0.2.2",
10
+ "@adhd/apigen-plugin-api-fastify": "^0.2.3",
11
+ "@adhd/apigen-plugin-batch": "^0.2.2",
12
+ "@adhd/apigen-plugin-cli-output": "^0.2.3",
13
+ "@adhd/apigen-plugin-ir-cache": "^0.1.0",
14
+ "@adhd/apigen-plugin-mcp": "^0.2.3",
15
+ "@adhd/apigen-plugin-openapi": "^0.2.2",
16
+ "@adhd/environment": "^0.1.3",
17
+ "@adhd/environment-base-spec": "^0.1.0",
8
18
  "@adhd/sox-graph-store": "^0.3.0",
9
19
  "better-sqlite3": "^12.10.0",
10
- "@adhd/environment": "^0.1.2",
11
- "@adhd/environment-base-spec": "^0.1.0",
12
- "@adhd/apigen-core-client": "^0.2.1",
13
- "@adhd/apigen-plugin-api-fastify": "^0.2.1",
14
- "@adhd/apigen-plugin-openapi": "^0.2.1",
15
- "@adhd/apigen-plugin-mcp": "^0.2.1",
16
- "@adhd/apigen-plugin-batch": "^0.2.0",
17
- "@adhd/apigen-plugin-cli-output": "^0.2.1",
18
- "@adhd/apigen-engine-naming": "^0.2.1",
19
- "yaml": "1.10.3",
20
20
  "pino": "10.3.1",
21
- "pino-pretty": "13.1.3"
21
+ "pino-pretty": "13.1.3",
22
+ "yaml": "1.10.3"
22
23
  },
23
24
  "assets": [
24
25
  "skill"
package/store/crud.d.ts CHANGED
@@ -1,6 +1,39 @@
1
1
  import { GraphBacklogStore } from './graph-backlog-store.js';
2
2
  import { BacklogItem, CreateItemInput, CreateItemResult, UpdateItemInput } from '../model.js';
3
3
 
4
+ /**
5
+ * Minimum fraction of the NEW item's meaningful title tokens that must also
6
+ * appear in a CANDIDATE's title for the candidate to count as a duplicate
7
+ * (BUG-BACKLOG-DEDUPE-FTS-WEAK-MATCH-001). Exported so callers/tests can
8
+ * tune it without touching this file's internals.
9
+ *
10
+ * Why title-to-title overlap instead of the FTS hit's bm25 `score`:
11
+ * `store.graph.searchNodes()` (`@adhd/sox-graph-store`) DOES return a
12
+ * per-hit `score` (`-fts_node.rank`, i.e. a positive, higher-is-better bm25
13
+ * score) — but bm25 is corpus- and document-length-relative, not an absolute
14
+ * similarity measure, and empirically it does NOT discriminate this bug's
15
+ * failure mode: a live probe reproducing the exact reported case (a short
16
+ * generic new title vs. a long, verbose, unrelated document that happens to
17
+ * repeat a couple of the new title's common words many times) scored
18
+ * `~0.0000109`, while a genuine short-title-vs-short-title near-duplicate
19
+ * scored `~0.0000113` — the SAME order of magnitude, with no clean
20
+ * separating threshold between them. A long document's sheer token count
21
+ * inflates its bm25 term-frequency component enough to rival a real
22
+ * duplicate's score. Title-to-title token overlap has no such document-
23
+ * length confound: a new item's title can only ever match against a
24
+ * candidate's (typically similarly short) title, so incidental repetition
25
+ * buried in a long candidate BODY can no longer count toward "looks like a
26
+ * duplicate" at all.
27
+ *
28
+ * 0.5 (at least half the new title's meaningful tokens must recur in the
29
+ * candidate's title) was chosen because DESIGN.md §2.4's own worked example
30
+ * ("same bug, different words") is a near-total title rewrite that still
31
+ * shares most of its content words ("database connection pool leaks under
32
+ * load" -> "the database connection pool leaks under load" is 6/6); dropping
33
+ * to a small minority match (e.g. 1-2 shared generic words out of 5) is
34
+ * exactly the false-positive shape this bug reports and must NOT pass.
35
+ */
36
+ export declare const TITLE_OVERLAP_MIN_FRACTION = 0.5;
4
37
  declare function dedupeScan(store: GraphBacklogStore, repo: string, input: CreateItemInput): BacklogItem[];
5
38
  export declare function createItemNode(store: GraphBacklogStore, input: CreateItemInput): CreateItemResult;
6
39
  export declare function getItemNode(store: GraphBacklogStore, repo: string, humanId: string): BacklogItem | null;
package/store/query.d.ts CHANGED
@@ -6,6 +6,20 @@ import { NodeRecord } from '@adhd/sox-graph-store';
6
6
  /** Raw NodeRecord query — used internally where the full node (not just the mapped BacklogItem) is needed. */
7
7
  export declare function queryItemNodes(store: GraphBacklogStore, filter?: BacklogFilter): NodeRecord[];
8
8
  export declare function listItems(store: GraphBacklogStore, filter?: BacklogFilter): BacklogItem[];
9
+ /**
10
+ * BUG-BACKLOG-HUMANID-COLLISION-001 fix #2: this is THE shared `(repo,
11
+ * humanId) -> NodeRecord` lookup every store module funnels through
12
+ * (`crud.ts`/`lifecycle.ts`/`structure.ts`/`client.ts`'s `requireItem*`
13
+ * helpers all call this, directly or via `buildNotFoundError`'s sibling
14
+ * miss path). It used to silently resolve to "whichever live node happens
15
+ * to match `name`, else whichever is first" when more than one live node
16
+ * shared the same `(repo, humanId)` key — the exact shape of the
17
+ * pre-existing `"undefined-001"` collisions, and the root cause of a real
18
+ * mis-transition (see the backlog item body: a `resolveItem` call intended
19
+ * for one node silently landed on a different, unrelated one). Any lookup
20
+ * that finds >1 live match now throws `AmbiguousHumanIdError` instead of
21
+ * guessing.
22
+ */
9
23
  export declare function findItemNode(store: GraphBacklogStore, repo: string, humanId: string): NodeRecord | null;
10
24
  /**
11
25
  * Finds every LIVE node carrying this `humanId`, across ALL repos (no
@@ -33,3 +33,32 @@ export declare function mergeItemsNode(store: GraphBacklogStore, repo: string, k
33
33
  export declare function setPriorityNode(store: GraphBacklogStore, repo: string, humanId: string, priority: Priority): BacklogItem;
34
34
  export declare function attachToPlanNode(store: GraphBacklogStore, repo: string, humanId: string, planSlug: string): void;
35
35
  export declare function assignItemNode(store: GraphBacklogStore, repo: string, humanId: string, to: string, by: string): BacklogItem;
36
+ /**
37
+ * BUG-BACKLOG-HUMANID-COLLISION-001 fix #3 (repair primitive): re-ids a
38
+ * single, `nodeId`-scoped live backlog item to a new `humanId` within the
39
+ * same `repo`. `nodeId`-scoped (not `(repo, oldHumanId)`-keyed) so this is
40
+ * unambiguous EVEN under the exact collision it exists to repair — every
41
+ * other `(repo, humanId)`-keyed lookup in this file would throw
42
+ * `AmbiguousHumanIdError` on a colliding key (fix #2, `findItemNode`), so a
43
+ * repair tool needs a way in that doesn't go through that same lookup.
44
+ *
45
+ * There is no tool-level rename/re-id operation exposed anywhere in this
46
+ * store today (the backlog item's fix direction #5 explicitly calls this
47
+ * gap out) — this is that primitive, added as part of this fix, kept
48
+ * store-internal (not wired to `client.ts`/the MCP surface) since it is a
49
+ * narrow one-off repair tool, not a general-purpose end-user operation.
50
+ *
51
+ * Guards:
52
+ * - the node at `nodeId` must be live and its CURRENT `metadata.humanId`
53
+ * must equal `oldHumanId` (sanity check — refuses to rename the wrong
54
+ * node out from under a caller who mis-copied a nodeId).
55
+ * - `newHumanId` must not already resolve to a DIFFERENT live node in this
56
+ * `repo` (refuses to rename INTO a fresh collision).
57
+ * - re-derives `kind`/`family` from `newHumanId`, rebuilds `tags` (swapping
58
+ * the old kind/family tags for the new ones, preserving every other
59
+ * user tag) and the node `name`/`content`/`content_hash` (which both bake
60
+ * in `repo::humanId` — DESIGN.md §2.2, mapping.ts's `buildNodeName`/
61
+ * `buildNodeContent`) so the renamed node is indistinguishable from one
62
+ * that was always minted under `newHumanId`.
63
+ */
64
+ export declare function renameHumanIdNode(store: GraphBacklogStore, repo: string, nodeId: number, oldHumanId: string, newHumanId: string): BacklogItem;