@adhd/backlog 0.1.9 → 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 (60) hide show
  1. package/CHANGELOG.md +80 -47
  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 +30039 -15879
  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/write/audit.d.ts +36 -0
  31. package/write/bootstrap.d.ts +123 -0
  32. package/write/catalog.d.ts +351 -0
  33. package/write/claim-lease.d.ts +21 -0
  34. package/write/claim.d.ts +80 -0
  35. package/write/create-issue.d.ts +250 -0
  36. package/write/delete.d.ts +39 -0
  37. package/write/embed-drain.d.ts +68 -0
  38. package/write/embedding-observer.d.ts +80 -0
  39. package/write/errors.d.ts +303 -0
  40. package/write/issue-status.d.ts +10 -0
  41. package/write/move.d.ts +70 -0
  42. package/write/relate.d.ts +52 -0
  43. package/write/transition.d.ts +60 -0
  44. package/write/tx.d.ts +344 -0
  45. package/write/update.d.ts +81 -0
  46. package/client.d.ts +0 -174
  47. package/markdown.d.ts +0 -75
  48. package/migration-admin.d.ts +0 -26
  49. package/model.d.ts +0 -437
  50. package/store/audit-log.d.ts +0 -16
  51. package/store/claim.d.ts +0 -24
  52. package/store/crud.d.ts +0 -62
  53. package/store/ids.d.ts +0 -24
  54. package/store/lifecycle.d.ts +0 -36
  55. package/store/mapping.d.ts +0 -101
  56. package/store/mutate-metadata.d.ts +0 -8
  57. package/store/query.d.ts +0 -68
  58. package/store/repo-migration.d.ts +0 -51
  59. package/store/serve-lock.d.ts +0 -42
  60. package/store/structure.d.ts +0 -66
@@ -7,6 +7,29 @@ export declare const ALL_HOSTS: readonly SkillHost[];
7
7
  * re-derives — and risks desyncing from — this resolution rule. */
8
8
  export declare function resolveCodexHomeDir(home: string, homeOverride?: string): string;
9
9
  export declare function hostSkillsDir(host: SkillHost, scope: SkillScope, cwd: string, homeOverride?: string): string;
10
+ /**
11
+ * A USAGE error — the caller typed something wrong — as opposed to a genuine
12
+ * internal fault. BUG-BACKLOG-INSTALLSKILL-UX-001: `install-skill`/`install`
13
+ * are special-cased in `cli.ts` BEFORE the apigen command table is built, so
14
+ * they never get the outcome-envelope error handling the six mounted verbs
15
+ * get. Their argument errors used to propagate to the bin entry-guard, which
16
+ * prints `err.stack` — burying a perfectly good one-line message under ten
17
+ * frames of minified dist. Distinguishing usage errors by TYPE (rather than
18
+ * catching everything) keeps real faults loud and stack-bearing while giving
19
+ * typos the short, actionable output the mounted verbs already produce.
20
+ */
21
+ export declare class BacklogUsageError extends Error {
22
+ constructor(message: string);
23
+ }
24
+ /**
25
+ * Prints a usage error the SAME way the apigen-mounted verbs and every
26
+ * other CLI command's own rejection path already do — a machine-readable
27
+ * `{"code":"invalid_argument","message":…}` as the last stderr line, exit
28
+ * code 2 (`CLI_EXIT_CODE['invalid_argument']`) — plus the human-readable
29
+ * usage text, so a reader gets both the parse and the fix in one screen.
30
+ */
31
+ export declare function failUsage(err: BacklogUsageError, helpText: string): void;
32
+ export declare const INSTALL_SKILL_HELP_TEXT = "backlog install-skill [--host claude|codex|opencode|all] [--scope user|project]\n\nCopies the packaged backlog SKILL.md into an agent host's skills directory.\n(`backlog install` is the richer successor \u2014 it also registers the MCP server.)\n\n --host <name> claude | codex | opencode | all (default: all)\n --scope <name> user | project (default: user)\n \"user\" is the machine-wide install (~/.claude/skills/...);\n there is no \"global\" \u2014 that is spelled \"user\" here.\n\nExamples:\n backlog install-skill\n backlog install-skill --host opencode --scope user\n backlog install-skill --host claude --scope project\n";
10
33
  export interface InstallSkillResult {
11
34
  host: SkillHost;
12
35
  scope: SkillScope;
package/package.json CHANGED
@@ -1,27 +1,43 @@
1
1
  {
2
2
  "name": "@adhd/backlog",
3
- "version": "0.1.9",
3
+ "version": "1.0.0",
4
4
  "bin": {
5
- "backlog": "index.js"
5
+ "adhd-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.5",
17
- "@adhd/environment-base-spec": "^0.1.0",
18
- "@adhd/sox-graph-store": "^0.8.6",
19
- "@adhd/sox-store-adapter": "^0.7.0",
20
- "@adhd/sox-telemetry": "^0.2.1",
8
+ "@adhd/apigen-base-logical": "^0.1.2",
9
+ "@adhd/apigen-core-client": "^0.3.1",
10
+ "@adhd/apigen-engine-naming": "^0.2.3",
11
+ "@adhd/apigen-plugin-api-fastify": "^0.2.4",
12
+ "@adhd/apigen-plugin-batch": "^0.2.3",
13
+ "@adhd/apigen-plugin-cli-output": "^0.2.4",
14
+ "@adhd/apigen-plugin-ir-cache": "^0.1.1",
15
+ "@adhd/apigen-plugin-mcp": "^0.2.4",
16
+ "@adhd/apigen-plugin-openapi": "^0.2.3",
17
+ "@adhd/environment": "^0.1.6",
18
+ "@adhd/environment-base-spec": "^0.1.1",
19
+ "@adhd/sox-graph-store": "^0.10.1",
20
+ "@adhd/sox-hybrid-search": "^0.4.6",
21
+ "@adhd/sox-semantic": "^0.1.2",
22
+ "@adhd/sox-store-adapter": "^0.9.2",
23
+ "@adhd/sox-telemetry": "^0.3.0",
24
+ "@modelcontextprotocol/sdk": "^1.29.0",
25
+ "ajv": "^8.20.0",
26
+ "ajv-formats": "2.1.1",
27
+ "commander": "^14.0.3",
28
+ "fastify": "^4.0.0",
21
29
  "pino": "10.3.1",
22
30
  "pino-pretty": "13.1.3",
31
+ "ts-json-schema-generator": "^2.3.0",
32
+ "ts-morph": "^23.0.0",
33
+ "typescript": "^5.5.0",
34
+ "vite": "~5.0.13",
23
35
  "yaml": "1.10.3"
24
36
  },
37
+ "optionalDependencies": {
38
+ "@adhd/sox-embedding-provider": "^0.5.3",
39
+ "@adhd/sox-vector-store": "^0.7.0"
40
+ },
25
41
  "assets": [
26
42
  "skill"
27
43
  ],
@@ -30,5 +46,24 @@
30
46
  "typings": "./index.d.ts",
31
47
  "publishConfig": {
32
48
  "access": "public"
49
+ },
50
+ "description": "Graph-store backlog for multi-agent teams - queryable, claimable, multi-agent-safe issue tracking with CLI and MCP",
51
+ "keywords": [
52
+ "backlog",
53
+ "issue-tracking",
54
+ "graph",
55
+ "cli",
56
+ "mcp",
57
+ "multi-agent"
58
+ ],
59
+ "license": "MIT",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "git+https://github.com/PseudoSky/adhd.git",
63
+ "directory": "entrypoint/backlog"
64
+ },
65
+ "homepage": "https://github.com/PseudoSky/adhd/tree/main/entrypoint/backlog#readme",
66
+ "bugs": {
67
+ "url": "https://github.com/PseudoSky/adhd/issues"
33
68
  }
34
69
  }
@@ -0,0 +1,31 @@
1
+ import { IIssueAuditEntry, IIssueCard, IIssueField, IIssueRef } from './types.js';
2
+ import { EdgeRecord, GraphBackend, NodeRecord } from '@adhd/sox-graph-store';
3
+
4
+ /** SPEC.md §6.5's `assertKnownFields` — unknown name → `BacklogValidationError` naming it, never a silent drop. */
5
+ export declare function assertKnownIssueFields(fields: readonly string[] | undefined): asserts fields is readonly IIssueField[] | undefined;
6
+ /** `status.meta.metadata.terminal` — the closedness knob (DATA_MODEL.md §3, SPEC.md §2). Defaults to `false` for a status row written before the field existed, never `true` by omission (a closedness knob that defaults closed would silently exclude items from open-item views). */
7
+ export declare function isStatusTerminal(statusRecord: NodeRecord | undefined): boolean;
8
+ /** The full audit trail for an issue — `audits` edges FROM the issue (SPEC.md §3: `audits: * → audit (1:n)`, the subject is the edge SOURCE), sorted oldest-first. */
9
+ export declare function resolveAuditTrail(graph: GraphBackend, issueId: number, outgoing?: EdgeRecord[]): Promise<IIssueAuditEntry[]>;
10
+ /**
11
+ * `blockers` (SPEC.md §5.2's definition, restated onto the `blocks` edge,
12
+ * §6.2): the set of issues that `blocks` this one (incoming
13
+ * `blocks` edges — `e.dst === issueId`) and are NOT YET terminal — "what's
14
+ * actually blocking it right now," never the full historical blocker set.
15
+ */
16
+ export declare function resolveBlockers(graph: GraphBackend, issueId: number): Promise<IIssueRef[]>;
17
+ /** `related` — both directions of `relates_to` (an `n:m` symmetric-in-practice rel, §3), deduplicated. */
18
+ export declare function resolveRelated(graph: GraphBackend, issueId: number, outgoing?: EdgeRecord[]): Promise<IIssueRef[]>;
19
+ export interface IAssembleIssueCardOptions {
20
+ /** Score from a `searchRanked`/`searchNodes` response — populates `_score` when requested (§5a). */
21
+ score?: number;
22
+ /** Pre-fetched outgoing edges (`getEdges({src: issue.id})`) — pass when the caller already has them (e.g. a batch `query` page) to avoid a redundant round trip. */
23
+ outgoingEdges?: EdgeRecord[];
24
+ }
25
+ /**
26
+ * Project `issue` onto the requested `fields` (default: the five-field terse
27
+ * card, SPEC.md §6.5). `uid` is always populated regardless of `fields`.
28
+ */
29
+ export declare function assembleIssueCard(graph: GraphBackend, issue: NodeRecord, fields: readonly IIssueField[], opts?: IAssembleIssueCardOptions): Promise<IIssueCard>;
30
+ /** Batch-assemble cards for a page of issue nodes, sharing one `getNodesByIds`-batched catalog/placement resolution pass where possible. Falls back to per-issue assembly (still batches internally) — a cross-issue batch would require a different, `getEdges`-list API this file's dependency (`GraphBackend`) does not expose. */
31
+ export declare function assembleIssueCards(graph: GraphBackend, issues: NodeRecord[], fields: readonly IIssueField[], scoreByUid?: ReadonlyMap<string, number>): Promise<IIssueCard[]>;
package/query/get.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { IIssueCard, IIssueGetByUidInput } from './types.js';
2
+ import { GraphBackend } from '@adhd/sox-graph-store';
3
+
4
+ /**
5
+ * Fetch one issue by `uid`, projected to the requested `fields` (default:
6
+ * the same five-field card `query` defaults to, SPEC.md §6.3.1/§6.5/AC-13).
7
+ *
8
+ * Errors: `IssueNotFoundError(uid)` (no live node carries `uid`),
9
+ * `BacklogValidationError('fields', ...)` (an unknown field name).
10
+ */
11
+ export declare function getIssue(graph: GraphBackend, input: IIssueGetByUidInput): Promise<IIssueCard>;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * index.ts — the `@adhd/backlog` read/query layer's public surface
3
+ * (SPEC.md §5, §5a, §6.1, §6.3.1, §6.5, §3a).
4
+ *
5
+ * **Layout:**
6
+ * - `types.ts` — every shared TS shape (`IIssueCard`, `IIssueField`,
7
+ * `IIssueQueryInput`/`IIssueFilter`, `IIssuePage`, registry types). Import
8
+ * from here, not from `get.ts`/`query.ts`/`card.ts` directly, to avoid
9
+ * depending on an implementation module's re-export surface.
10
+ * - `resolve.ts` — read-only (non-transactional) uid/name resolution and edge
11
+ * traversal, built directly on the bare `GraphBackend` (see that file's own
12
+ * doc comment for why this is safe, unlike `write/tx.ts`'s hand-composed
13
+ * forms). The six sibling write verbs (`update`/`transition`/`claim`/
14
+ * `relate`/`move`/`delete`) should import from here for any READ they need
15
+ * OUTSIDE their own `immediate` transaction (e.g. resolving a `filter`
16
+ * parameter, or re-reading a node post-commit to build their own outcome's
17
+ * card) — never re-implement uid/name resolution a third time.
18
+ * - `card.ts` — `assembleIssueCard`/`assembleIssueCards`: projects a live
19
+ * `issue` `NodeRecord` onto a requested `IIssueField` list. The six sibling
20
+ * write verbs' own outcome shapes (`IUpdateOutcome`, `ITransitionOutcome`,
21
+ * etc.) embed card-shaped fields (SPEC.md §6.3) — call `assembleIssueCard`
22
+ * post-commit rather than hand-rolling a second field-projection pass.
23
+ * - `get.ts` — the `get` verb (§6.3.1).
24
+ * - `query.ts` — the `query` verb (§5, §5a, §6.5) and its `view` union
25
+ * (`list`/`ready`/`graph`/`order`/`stale`/`similar`/`overlap`).
26
+ * - `views/registry.ts` — §3a's registry read surface (`projects`/`components`/
27
+ * `locations`/`lookup`/registry `get` detail).
28
+ * - `views/stats.ts` — the stats/rollup read views (§5): the status-aware
29
+ * priority matrix (`priorityMatrix`, BUG-023), the `part_of` hierarchy
30
+ * rollup (`partOfRollup`, FEAT-005 — transitive, not one-level), and
31
+ * `validAt` point-in-time cumulative-open curves (`openCurve`).
32
+ * - `views/semantic.ts` — the semantic read views (§5a, FEAT-022):
33
+ * `querySimilarView` (`view:'similar'`) and the shared fused-relevance
34
+ * ranking primitive (`rankByFusedRelevance`). `query.ts`'s `case 'similar':`
35
+ * dispatch calls `querySimilarView` directly — there is no second,
36
+ * embedding-only `view:'similar'` implementation left in `query.ts`.
37
+ * - `markdown.ts` — `query`'s `format:'markdown'` rendering (§6.5/§6.6,
38
+ * DATA_MODEL.md §8): a pure re-serialization of the SAME `IIssueCard[]`
39
+ * `format:'json'` already returns for `list`/`ready`/`stale`/`similar` —
40
+ * never a second query path.
41
+ *
42
+ * **Reconciliation note for the write layer.** `write/create-issue.ts`
43
+ * declares its OWN `ICreateIssueCard` (a narrower, always-fully-populated
44
+ * shape it builds inline from data it already has after a fresh insert — it
45
+ * never needs a field-projected read; named distinctly from this module's
46
+ * `IIssueCard` on purpose — see that file's doc comment for
47
+ * BUG-APIGEN-CORE-CLIENT-BARE-NAME-COLLISION-001, a real apigen-core-client
48
+ * extraction defect the original shared bare name `IIssueCard` triggered
49
+ * once both types became simultaneously reachable from `api.d.ts`). This
50
+ * module's {@link IIssueCard} (from `types.ts`) is the FIELDS-PROJECTED
51
+ * read-layer shape every OTHER verb should use — the two are intentionally
52
+ * not unified in this slice (unifying them means either widening
53
+ * `create-issue.ts`'s always-populated shape to all-optional, or teaching
54
+ * this module's card assembler to skip a post-insert read entirely; both are
55
+ * a `create-issue.ts` edit, out of scope for a read-layer-only task). A
56
+ * future consolidation should re-export `create-issue.ts`'s result through
57
+ * THIS module's `IIssueCard` instead of maintaining two card shapes.
58
+ */
59
+ export * from './types.js';
60
+ export * from './resolve.js';
61
+ export * from './card.js';
62
+ export * from './get.js';
63
+ export * from './query.js';
64
+ export * from './views/registry.js';
65
+ export * from './views/stats.js';
66
+ export * from './views/semantic.js';
67
+ export * from './markdown.js';
@@ -0,0 +1,11 @@
1
+ import { IIssueCard } from './types.js';
2
+
3
+ /** Renders one `IIssueCard` as a markdown section — title header, never the `uid` (DATA_MODEL.md §8). */
4
+ export declare function renderOneIssueCardMarkdown(card: IIssueCard): string;
5
+ /**
6
+ * Renders a full page of issue cards as markdown (`query`'s `format:'markdown'`
7
+ * result) — one `##` section per card, separated by a blank line. An empty
8
+ * page renders a stated "no results" line rather than an empty string, so a
9
+ * caller piping this straight to a file never gets a silently blank document.
10
+ */
11
+ export declare function renderIssueCardsMarkdown(items: readonly IIssueCard[]): string;
@@ -0,0 +1,131 @@
1
+ import { IIssueQueryInput, IIssueQueryResult } from './types.js';
2
+ import { IQueryEnvelopeMeta } from '../envelope.js';
3
+ import { StoreSearchBackend } from '@adhd/sox-hybrid-search';
4
+ import { GraphBackend } from '@adhd/sox-graph-store';
5
+
6
+ /**
7
+ * The dependencies `query`/the view helpers need. `search` is OPTIONAL —
8
+ * `@adhd/sox-embedding-provider`/`@adhd/sox-vector-store` are
9
+ * `optionalDependencies` of this package (package.json), so a store opened
10
+ * without a configured embedding backend simply cannot serve
11
+ * `filter.semantic`/`view:'similar'`; those paths throw
12
+ * `InvalidArgumentError('semantic', ...)` rather than silently degrading to a
13
+ * grep-only result the caller did not ask for.
14
+ */
15
+ export interface IQueryStoreHandle {
16
+ readonly graph: GraphBackend;
17
+ readonly search?: {
18
+ readonly backend: StoreSearchBackend;
19
+ /** Embeds `filter.semantic`'s free text into the SAME vector space `backend`'s vector store was built against. */
20
+ embedQuery(text: string): Promise<Float32Array>;
21
+ /**
22
+ * One-row probe of the REAL vector table: `true` iff at least one vector
23
+ * exists under this backend's own space. Per-query truth, never a
24
+ * process-lifetime latch — see `write/bootstrap.ts`'s
25
+ * {@link isVectorSpacePopulated}. `api.ts`'s `queryHandle` runs this and
26
+ * snapshots the result onto {@link IQueryStoreHandle.spacePopulated}
27
+ * before the (synchronous) routing decision reads it.
28
+ */
29
+ spacePopulated(): Promise<boolean>;
30
+ };
31
+ /**
32
+ * Snapshot of {@link IQueryStoreHandle.search}'s `spacePopulated()` probe,
33
+ * taken by `api.ts`'s `queryHandle` when the caller passed a bare `text:`
34
+ * positional. `resolveTextInput` is synchronous, so the (async) probe
35
+ * result is resolved onto the handle first; a handle built directly by a
36
+ * test or the ETL sets it itself. Absent means "unknown" and is treated as
37
+ * "not populated" — routing then falls back to grep.
38
+ */
39
+ readonly spacePopulated?: boolean;
40
+ /**
41
+ * Fail-loud vocabulary guard (`store/vocabulary-guard.ts`). When present,
42
+ * `queryIssuesWithMeta` awaits it before dispatching any view: a store
43
+ * holding live nodes under a vocabulary this build does not recognize must
44
+ * never read as `{ok:true, total:0}`. OPTIONAL so a hand-built handle
45
+ * (tests, the ETL) is unaffected — `api.ts`'s `queryHandle` wires it for
46
+ * every real host, and it is deliberately re-run per query (not latched at
47
+ * open) so a long-lived process notices a store rewritten under it.
48
+ */
49
+ readonly assertVocabulary?: () => Promise<void>;
50
+ }
51
+ /**
52
+ * Whether a `query` verb's own input touches the semantic channel at all —
53
+ * the need-predicate `api.ts`'s `query` call site passes as
54
+ * `queryHandle(ctx, { needsSemantic: queryNeedsSemanticBackend(input) })`
55
+ * (SPEC.md §5b point 1). Structural, not a maintained list: it names the
56
+ * SAME "semantic inputs" vocabulary `env.ts`'s `embedding.enabled` doc
57
+ * comment already names, plus a bare `text` positional — deciding whether
58
+ * `text` auto-routes to `semantic` or `grep` itself requires the backend to
59
+ * be live, so a `text` query must bootstrap it even though it may end up on
60
+ * the grep route.
61
+ *
62
+ * A verb that returns `false` never derives a semantic backend: no embedding
63
+ * provider construction, no vector-store open, no cold ONNX load.
64
+ */
65
+ export declare function queryNeedsSemanticBackend(input: IIssueQueryInput): boolean;
66
+ /**
67
+ * Normalises `input.text` (the natural-language query shared by every mount —
68
+ * CLI `search`, MCP, HTTP) into `filter.semantic` or `filter.grep`, exactly
69
+ * ONCE, so every caller gets identical routing rather than each transport
70
+ * reimplementing it. Routes to `semantic` when the store handle actually
71
+ * carries a `search` backend AND that backend's space is populated
72
+ * ({@link IQueryStoreHandle.spacePopulated}, the per-query snapshot of
73
+ * {@link IQueryStoreHandle.search}'s `spacePopulated()` probe taken by
74
+ * `api.ts`'s `queryHandle`); otherwise routes to `grep`. A non-empty handle
75
+ * whose space is still empty must NOT route to semantic: over an empty vector
76
+ * table `searchRanked` returns zero candidates, which is an empty page — worse
77
+ * than the grep fallback — so populated-ness is load-bearing, not an
78
+ * optimization. The matching default `sort` (`'relevance'` / `'textMatch'`)
79
+ * is only applied when the caller did not pass `sort` explicitly — an
80
+ * explicit `sort` always wins.
81
+ *
82
+ * Exported (in addition to being called internally by {@link queryIssues})
83
+ * so the sort-precedence rule above can be unit-tested directly: the ranked
84
+ * grep/semantic branch of `queryList` never echoes the resolved `sort` value
85
+ * back in its output (it only gates on relevance/textMatch requiring
86
+ * grep/semantic, then ignores `sort` entirely when ordering ranked results),
87
+ * so there is no way to observe "explicit sort survived" from `queryIssues`'s
88
+ * return value alone — `text-routing.spec.ts` asserts on this function's
89
+ * return value for that one property, and drives every other behaviour
90
+ * through the real `queryIssues`/real store end-to-end.
91
+ */
92
+ export declare function resolveTextInput(handle: IQueryStoreHandle, input: IIssueQueryInput): IIssueQueryInput;
93
+ /** {@link queryIssuesWithMeta}'s return shape. */
94
+ export interface IQueryIssuesOutcome {
95
+ result: IIssueQueryResult;
96
+ /**
97
+ * Present only for `view:'list'` — the only view whose result is a
98
+ * filtered/paginated row set with an honestly countable pre-limit total
99
+ * (`envelope.ts`'s {@link IQueryEnvelopeMeta}). `view:'ready'` computes
100
+ * readiness in-memory and stops enumerating once `limit` candidates are
101
+ * found (`queryReady`'s own doc comment: removing that early exit to count
102
+ * a true pre-limit total would reintroduce the O(candidates) cost its
103
+ * grouped-relation-fetch design exists to avoid), `view:'stale'` applies no
104
+ * limit at all so every row it returns already IS the total, and
105
+ * `view:'similar'`/`'graph'`/`'order'`/`'overlap'` are a ranking, a graph
106
+ * projection, a topological order, and an axis grouping respectively — none
107
+ * of them a filtered row set with a "how many matched" count. Adding a
108
+ * `meta` to any of those would mean inventing a number this module cannot
109
+ * stand behind.
110
+ */
111
+ meta?: IQueryEnvelopeMeta;
112
+ }
113
+ /**
114
+ * `queryIssues` (below) plus the transport-facing `meta` the envelope
115
+ * exposes for a list-shaped read (`api.ts`'s `query` mount is the one caller
116
+ * that needs it). Every other in-process caller keeps calling `queryIssues`
117
+ * itself, which discards `meta` and returns exactly the shape it always has.
118
+ *
119
+ * `format:'markdown'` (SPEC.md §6.5/§6.6, DATA_MODEL.md §8) is handled here,
120
+ * as a POST-PROCESSING step over {@link dispatchQueryView}'s always-`json`
121
+ * result — never a second query path (SPEC.md §6.5: "renders this same page
122
+ * ... never a second code path"). Only the four item-list views
123
+ * ({@link MARKDOWN_CAPABLE_VIEWS}) have a markdown rendering rule at all; any
124
+ * other view (`graph`/`order`/`overlap`/`projects`/`components`/`locations`)
125
+ * rejects `format:'markdown'` outright rather than silently falling back to
126
+ * `json` or inventing an ad hoc rendering for a shape DATA_MODEL.md §8 never
127
+ * describes.
128
+ */
129
+ export declare function queryIssuesWithMeta(handle: IQueryStoreHandle, rawInput?: IIssueQueryInput): Promise<IQueryIssuesOutcome>;
130
+ /** The `query` verb (SPEC.md §5, §6.5) — dispatches on `input.view`, default `'list'`. */
131
+ export declare function queryIssues(handle: IQueryStoreHandle, rawInput?: IIssueQueryInput): Promise<IIssueQueryResult>;
@@ -0,0 +1,123 @@
1
+ import { isUidShaped } from '../write/catalog.js';
2
+ import { EdgeRecord, GraphBackend, NodeRecord } from '@adhd/sox-graph-store';
3
+
4
+ export { isUidShaped };
5
+ /** A resolved catalog/registry row, read-only. */
6
+ export interface IResolvedRef {
7
+ id: number;
8
+ uid: string;
9
+ name: string;
10
+ record: NodeRecord;
11
+ }
12
+ /**
13
+ * Resolve `uid` → the live `issue` node, or throw {@link IssueNotFoundError}
14
+ * (SPEC.md §6.1: "a `uid` with no matching live node throws
15
+ * `IssueNotFoundError(uid)`"). This is the READ-PATH counterpart of
16
+ * `write/tx.ts`'s `getNodeByUidTx` — safe to call standalone because it is
17
+ * not composing a check-then-act write around the result.
18
+ */
19
+ export declare function resolveIssueByUid(graph: GraphBackend, uid: string): Promise<NodeRecord>;
20
+ /**
21
+ * Resolve `ref` (uid or name, disambiguated by shape — SPEC.md §6.1) against
22
+ * `expectedKind`, on the READ path: an unresolved NAME is never an error here
23
+ * (unlike the write-path `mintOrResolveCatalogTx`) — SPEC.md §6.1's own read-
24
+ * path rule: "an unresolved `name` is not an error; it resolves to zero
25
+ * matches." Callers that need read-path "resolve or throw" semantics (e.g.
26
+ * `get`'s `project`/`component` display resolution once an issue's edges are
27
+ * already known to exist) use {@link resolveRefOrThrow} instead.
28
+ */
29
+ export declare function tryResolveRef(graph: GraphBackend, expectedKind: string, ref: string): Promise<IResolvedRef | null>;
30
+ /** Like {@link tryResolveRef}, but throws {@link CatalogNotFoundError} on a miss — for call sites that need "this reference must already exist." */
31
+ export declare function resolveRefOrThrow(graph: GraphBackend, expectedKind: string, ref: string): Promise<IResolvedRef>;
32
+ /** `component`, scoped within `project` (SPEC.md §6.1) — read path, `component.meta.metadata.projectUid` must match. */
33
+ export declare function tryResolveComponentRef(graph: GraphBackend, projectUid: string, ref: string): Promise<IResolvedRef | null>;
34
+ /** The single live edge of `rel` FROM `srcId` (SPEC.md §3's `n:1` rels — `has_kind`/`has_status`/`has_priority`/`authored_by`), or `null` when none exists. */
35
+ export declare function pickSingleEdge(edges: EdgeRecord[], rel: string, srcId: number): EdgeRecord | undefined;
36
+ /**
37
+ * All edges ORIGINATING at `nodeId` — a single `getEdges({src})` call covers
38
+ * every `n:1`/`1:n` outgoing rel an issue carries (`has_kind`/`has_status`/
39
+ * `has_priority`/`authored_by`/`has_note`/`has_citation`/`has_transition`/
40
+ * `audits`/`relates_to`/`supersedes`/`duplicate_of`/`part_of`/`blocks`) —
41
+ * cheaper than one `getEdges` call per rel.
42
+ */
43
+ export declare function getOutgoingEdges(graph: GraphBackend, nodeId: number): Promise<EdgeRecord[]>;
44
+ /** All edges TARGETING `nodeId` — used for the reverse traversals (`owns_component`'s issue→component lookup, `blocks`'s incoming-blocker lookup, `duplicate_of`'s incoming lookup). */
45
+ export declare function getIncomingEdges(graph: GraphBackend, nodeId: number): Promise<EdgeRecord[]>;
46
+ /**
47
+ * The component that owns `issueId` (SPEC.md §3: `owns_component: component →
48
+ * issue (1:n)`) and, one hop further, the project that owns that component
49
+ * (`owns_project: project → component (1:n)`). Every live issue has exactly
50
+ * one of each (§8 AC-23), so this returns `undefined` only for a
51
+ * pre-invariant / corrupted row, never as an expected steady-state case.
52
+ */
53
+ export declare function resolveIssuePlacement(graph: GraphBackend, issueId: number): Promise<{
54
+ component?: NodeRecord;
55
+ project?: NodeRecord;
56
+ }>;
57
+ /**
58
+ * Resolve an edge-scoped filter value (SPEC.md §6.5 rule 3: `kind`/`status`/
59
+ * `priority`/`project`/`component`/`author` live on EDGES, not `NodeFilter`
60
+ * columns) to the set of candidate issue rowids satisfying it.
61
+ *
62
+ * **Direction is looked up, not assumed.** The six edge-scoped dimensions do
63
+ * NOT all run the same way: `has_kind`/`has_status`/`has_priority`/
64
+ * `authored_by` point issue → catalog (the catalog node is the edge TARGET),
65
+ * while `owns_project`/`owns_component` point catalog → child (the catalog
66
+ * node is the edge SOURCE). This function previously hard-coded the first
67
+ * shape — `getEdges({dst: resolved.id, rel})` collecting `src` — for all six,
68
+ * so calling it for `project`/`component` looked for edges INTO a component
69
+ * when every real `owns_component` edge points OUT of it, and it silently
70
+ * returned an EMPTY set for a component that genuinely owned issues. Silent,
71
+ * because an empty candidate set is indistinguishable from "nothing matched."
72
+ *
73
+ * `views/semantic.ts` worked around this by hand-composing its own
74
+ * source-directed traversal for those two dimensions and reusing this
75
+ * function only for the four it got right, flagging in its own header that
76
+ * "a future caller who takes that doc comment at face value for
77
+ * component/project will reproduce this exact miss." That is now fixed at
78
+ * source instead: the `edge_kind` row for `rel` carries `source_kind`/
79
+ * `target_kind` (the same rows `resolveEdgeKindTx` reads on the write path),
80
+ * so the traversal direction is derived from the data rather than assumed,
81
+ * and stays correct for any rel added later.
82
+ *
83
+ * Returns `undefined` (never an empty array) when `ref` does not resolve to
84
+ * any live catalog/registry row — SPEC.md §6.1's read-path rule ("an
85
+ * unresolved name is not an error; it resolves to zero matches") is realized
86
+ * by the CALLER treating `undefined` as "this filter can never match
87
+ * anything," short-circuiting the whole query to an empty page rather than
88
+ * querying with a meaningless empty `ids: []` (which `NodeFilter.ids` would
89
+ * otherwise interpret as "no restriction" on some backends — never rely on
90
+ * that ambiguity here).
91
+ */
92
+ export declare function resolveEdgeScopedCandidates(graph: GraphBackend, input: {
93
+ rel: string;
94
+ expectedKind: string;
95
+ ref: string;
96
+ projectUid?: string;
97
+ }): Promise<Set<number> | undefined>;
98
+ /** Every live catalog row's `name` for a given open-vocabulary catalog kind — used only to VALIDATE a filter value against what genuinely exists, never to constrain what a caller may create. */
99
+ export declare function fetchCatalogNames(graph: GraphBackend, catalogKind: 'kind' | 'status' | 'priority'): Promise<string[]>;
100
+ /**
101
+ * Nearest-name suggestions for an unresolved filter value — case-insensitive
102
+ * edit distance over the catalog's OWN live names, bounded to a distance
103
+ * proportional to the typo'd value's own length so an unrelated short
104
+ * catalog name is never suggested for a long, clearly-different value.
105
+ * Sorted nearest-first, capped at `max`.
106
+ */
107
+ export declare function suggestClosestCatalogNames(value: string, candidates: readonly string[], max?: number): string[];
108
+ /**
109
+ * Resolves a multi-valued `kind`/`status`/`priority` filter dimension
110
+ * (OR-union across `refs`), rejecting with a `BacklogValidationError` naming
111
+ * every ref that matches NO live catalog row of that kind — see this
112
+ * section's top doc comment for the full rationale. Never rejects a ref that
113
+ * resolves to a real row with zero owning issues; that still contributes an
114
+ * empty set exactly as a plain unresolved-name read would.
115
+ */
116
+ export declare function resolveValidatedCatalogFilter(graph: GraphBackend, input: {
117
+ rel: string;
118
+ catalogKind: 'kind' | 'status' | 'priority';
119
+ field: string;
120
+ refs: readonly string[];
121
+ }): Promise<Set<number>>;
122
+ /** Intersect a list of candidate-rowid sets (SPEC.md §6.5 rule 3: "AND semantics — an issue must satisfy every edge-scoped filter given"). An empty input list means "no edge-scoped filter was given" — returns `undefined` (no restriction), never an empty set. */
123
+ export declare function intersectCandidateSets(sets: ReadonlyArray<Set<number>>): Set<number> | undefined;