@anokye-labs/kbexplorer-engine 0.1.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.
@@ -0,0 +1,9 @@
1
+ import { createRequire } from 'module';
2
+
3
+ // src/store/node-wasm.ts
4
+ function nodeLocateFile() {
5
+ const require2 = createRequire(import.meta.url);
6
+ return (file) => require2.resolve(`sql.js/dist/${file}`);
7
+ }
8
+
9
+ export { nodeLocateFile };
@@ -0,0 +1,2 @@
1
+ export { loadExternalProviders } from './chunk-ASLGNOYV.js';
2
+ import './chunk-JNQVSNLC.js';
@@ -0,0 +1,461 @@
1
+ import { KBNode, Source } from '@anokye-labs/kbexplorer-core';
2
+
3
+ /**
4
+ * Minimal GitHub API shapes needed by `parser.ts`'s `issueToNode` /
5
+ * `treeToNodes`. These are NOT the full GitHub REST client types — just the
6
+ * fields those two functions actually read. The full client (`fetchFile`,
7
+ * `fetchTree`, `fetchFiles`, `fetchIssues`) lives in kbexplorer-template's
8
+ * `src/api/github.ts`, which has not moved to this package yet (slice 4).
9
+ */
10
+ /** A GitHub issue or pull request, as returned by the Issues API. */
11
+ interface GHIssue {
12
+ number: number;
13
+ title: string;
14
+ body: string | null;
15
+ state: string;
16
+ html_url: string;
17
+ created_at: string;
18
+ updated_at: string;
19
+ labels: Array<{
20
+ name: string;
21
+ color?: string;
22
+ }>;
23
+ assignees?: Array<{
24
+ login: string;
25
+ }> | null;
26
+ /** The GitHub user who opened the issue/PR (author). Added in slice 2 for PersonProvider/WorkProvider's author-attribution logic — absent from slice 1's parser-only subset. */
27
+ user?: {
28
+ login: string;
29
+ };
30
+ /** Present (and truthy) only when the issue is actually a pull request. */
31
+ pull_request?: unknown;
32
+ }
33
+ /** A single entry in a GitHub repo's git tree, as returned by the Git Trees API. */
34
+ interface GHTreeItem {
35
+ path: string;
36
+ type: 'blob' | 'tree';
37
+ size?: number;
38
+ /** Git file mode (e.g. `'100644'`, `'040000'`). Not read by `treeToNodes` itself. */
39
+ mode?: string;
40
+ /** Blob/tree SHA. Not read by `treeToNodes` itself. */
41
+ sha?: string;
42
+ /** API URL for this tree entry. Not read by `treeToNodes` itself. */
43
+ url?: string;
44
+ }
45
+ /**
46
+ * A GitHub release as returned by the releases API.
47
+ * Drafts are excluded; prerelease flag is preserved.
48
+ */
49
+ interface GHRelease {
50
+ tag_name: string;
51
+ name: string;
52
+ body: string;
53
+ html_url: string;
54
+ published_at: string;
55
+ prerelease: boolean;
56
+ }
57
+ /**
58
+ * A single commit as returned by the Commits API. Added in slice 3 for
59
+ * `sources/repo-data.ts`'s `RepoData.commits` field (consumed by `WorkProvider`).
60
+ */
61
+ interface GHCommit {
62
+ sha: string;
63
+ commit: {
64
+ message: string;
65
+ author: {
66
+ name: string;
67
+ date: string;
68
+ };
69
+ };
70
+ html_url: string;
71
+ files?: Array<{
72
+ filename: string;
73
+ status: string;
74
+ }>;
75
+ }
76
+
77
+ interface EngineEnv {
78
+ [key: string]: string | boolean | undefined;
79
+ }
80
+
81
+ /**
82
+ * Content-model ingestion types (F2 — issue #149).
83
+ *
84
+ * The content model is **schema-driven**: identity/authority comes from
85
+ * `teamops.yaml`, storage layout from `schema/conventions.yaml`, relationships
86
+ * from `schema/edges.yaml`, lifecycle bands from `schema/lifecycle.yaml`, and
87
+ * URN bases from `index/context.jsonld`. The engine reads these files rather
88
+ * than hardcoding any org's structure, so a second org adopts the platform by
89
+ * changing config — not engine code.
90
+ *
91
+ * Hard rules encoded here:
92
+ * - A node's **kind comes from its `@type`**, never from the file path.
93
+ * - **URN bases come from the JSON-LD context only** — never hardcoded.
94
+ */
95
+ /** Lifecycle band from `schema/lifecycle.yaml` (open — custom bands allowed). */
96
+ type LifecycleBand = 'durable' | 'per-cycle' | 'per-event' | (string & {});
97
+ /** Foreign-key resolution flavor from `schema/edges.yaml`. */
98
+ type FkFlavor = 'scalar' | 'array' | 'composite' | 'alias';
99
+ /** One org declared in `teamops.yaml`. */
100
+ interface OrgDef {
101
+ id: string;
102
+ name?: string | undefined;
103
+ /** Whether this is the home/default org (its entities are stored flat). */
104
+ default?: boolean;
105
+ }
106
+ /** `teamops.yaml` — maps identity to an authority + a default (home) org. */
107
+ interface TeamOps {
108
+ /** Authority host, e.g. `xbox.com` — the URN authority segment. */
109
+ authority: string;
110
+ /** Default/home org id; entities of org-scoped kinds default to it when flat. */
111
+ defaultOrg: string;
112
+ /** All known orgs. */
113
+ orgs: OrgDef[];
114
+ }
115
+ /** Storage + mapping convention for a single kind (from `schema/conventions.yaml`). */
116
+ interface KindConvention {
117
+ /** Kind id (matches the entity's `@type`). */
118
+ kind: string;
119
+ /** Storage root relative to the content-model root (e.g. `squads`). */
120
+ path: string;
121
+ /**
122
+ * Whether this kind is org-scoped. Org-scoped kinds carry an `/{org}` segment
123
+ * in their URN and store the **default org flat** / **non-default orgs nested**
124
+ * in a per-org subdirectory. Authority-scoped kinds omit the org segment.
125
+ */
126
+ orgScoped: boolean;
127
+ /** Field carrying this kind's alias handle (the target of an `alias` FK). */
128
+ aliasField?: string | undefined;
129
+ /**
130
+ * Fields copied verbatim into `data`. When omitted, **all** fields pass
131
+ * through (the default), which keeps the field→data mapping reversible.
132
+ */
133
+ passthrough?: string[] | undefined;
134
+ /** Sibling-file extension whose content is merged as the node body (e.g. `.md`). */
135
+ companionExt?: string | undefined;
136
+ }
137
+ /** `schema/conventions.yaml` — per-kind storage + mapping. */
138
+ interface Conventions {
139
+ /** Field carrying the kind discriminator. Default `@type`; never path-derived. */
140
+ typeField: string;
141
+ /** Field carrying the entity id. Default `id`. */
142
+ idField: string;
143
+ /** kind → convention. */
144
+ kinds: Record<string, KindConvention>;
145
+ }
146
+ /** One leg of a composite FK (`<a>:<b>` → two edges). */
147
+ interface CompositeLeg {
148
+ /** Target kind for this leg. */
149
+ to: string;
150
+ /** Relation taxonomy label for this leg. */
151
+ relation: string;
152
+ }
153
+ /** A foreign-key edge rule from `schema/edges.yaml`. */
154
+ interface EdgeRule {
155
+ id: string;
156
+ /** Source kind. */
157
+ from: string;
158
+ /** Field on the source entity carrying the foreign key. */
159
+ field: string;
160
+ /** Target kind (omitted for `composite`, which uses `composite[]`). */
161
+ to?: string;
162
+ fk: FkFlavor;
163
+ /** Relation taxonomy label applied to resolved edges. */
164
+ relation: string;
165
+ /** Legs for a `composite` FK, in the order the `<a>:<b>` parts appear. */
166
+ composite?: CompositeLeg[];
167
+ description?: string;
168
+ }
169
+ /**
170
+ * A derived edge rule — computed from existing edges, not stored on entities.
171
+ * `shared-target`: sources that point at the **same** target of the referenced
172
+ * FK rule (`via`) are linked to each other. Deduped so a pair is stored once.
173
+ */
174
+ interface DerivedRule {
175
+ id: string;
176
+ type: 'shared-target';
177
+ /** Id of the {@link EdgeRule} whose targets define the grouping. */
178
+ via: string;
179
+ relation: string;
180
+ description?: string;
181
+ }
182
+ /** `schema/edges.yaml` — FK edges + derived + deprecated. */
183
+ interface EdgesSpec {
184
+ edges: EdgeRule[];
185
+ derived: DerivedRule[];
186
+ /** Edge rules whose resolved edges are tagged `deprecated`. */
187
+ deprecated: EdgeRule[];
188
+ }
189
+ /** `schema/lifecycle.yaml` — band → kinds. */
190
+ interface Lifecycle {
191
+ bands: Record<string, string[]>;
192
+ }
193
+ /** Parsed `index/context.jsonld` — CURIE prefix → URN base. */
194
+ interface JsonLdContext {
195
+ /** The `@base` keyword, when present. */
196
+ base?: string | undefined;
197
+ /** prefix → URN base (full, e.g. `kg://xbox.com/squads/`). */
198
+ prefixes: Record<string, string>;
199
+ }
200
+ /**
201
+ * Cross-repo vocabulary / synonym layer (F-cross-repo — issue #153).
202
+ *
203
+ * Maps a per-repo **alias term** (a word one repo uses, e.g. `cell` / `crew`)
204
+ * to a **canonical term** — a kind / CURIE prefix already declared in the
205
+ * JSON-LD {@link JsonLdContext} (e.g. `squad`). It lets the graph unify concepts
206
+ * across repos that use different words while each repo keeps its native label.
207
+ *
208
+ * The map is **data-driven** (declared in `index/vocabulary.jsonld` and/or
209
+ * supplied as a shared overlay independent of any single repo's context) and is
210
+ * a **safe no-op** when empty — output is byte-identical to a build without it.
211
+ */
212
+ interface Vocabulary {
213
+ /**
214
+ * alias term → canonical term. A self-mapping (alias === canonical) is never
215
+ * stored, so an alias is always a *rename* to some other canonical kind.
216
+ */
217
+ aliases: Record<string, string>;
218
+ }
219
+ /**
220
+ * An overlay vocabulary supplied independently of a repo's own files — the
221
+ * cross-repo synonym layer. Either raw `vocabulary.jsonld` content (a string),
222
+ * an already-parsed {@link Vocabulary}, or nothing.
223
+ */
224
+ type VocabularyOverlay = string | Vocabulary | null | undefined;
225
+ /** The fully-parsed content-model schema. */
226
+ interface ContentModelSchema {
227
+ teamops: TeamOps;
228
+ conventions: Conventions;
229
+ edges: EdgesSpec;
230
+ lifecycle: Lifecycle;
231
+ context: JsonLdContext;
232
+ /** Cross-repo synonym layer; `aliases` is empty when none is declared. */
233
+ vocabulary: Vocabulary;
234
+ }
235
+ /** Severity of a build/schema diagnostic. */
236
+ type DiagnosticLevel = 'info' | 'warn' | 'error';
237
+ /** A diagnostic raised while reading the schema or building the graph. */
238
+ interface Diagnostic {
239
+ level: DiagnosticLevel;
240
+ /** Stable machine code, e.g. `unresolved-ref`, `unknown-prefix`. */
241
+ code: string;
242
+ message: string;
243
+ /** Related id / path / URN for context. */
244
+ ref?: string;
245
+ }
246
+ /**
247
+ * A flat content-model source: schema files + entity files keyed by path
248
+ * relative to {@link ContentModelSource.root}. This abstraction lets the same
249
+ * builder run against build-time fixtures, the local manifest, and live fetch.
250
+ */
251
+ interface ContentModelSource {
252
+ /** Root dir name within the repo (e.g. `content-model`). */
253
+ root: string;
254
+ /** path (relative to `root`) → raw file content. */
255
+ files: Record<string, string>;
256
+ }
257
+
258
+ /**
259
+ * Node-type registry — the open, data-driven core of the node-type engine.
260
+ *
261
+ * Each node type declares how it participates in the graph: its layer, default
262
+ * cluster, the relations it tends to emit, and which viewer renders it. The
263
+ * registry is consulted by {@link getNodeLayer} (via {@link resolveNodeLayer})
264
+ * and by cluster/legend logic, so adding a brand-new node type requires only a
265
+ * `registerType` call — no edits to the core discriminated unions or render
266
+ * switches.
267
+ *
268
+ * Implementation note: this module imports `KBNode` from
269
+ * `@anokye-labs/kbexplorer-core` **type-only**, so there is no runtime import
270
+ * cycle even though the module augmentation below adds a `layer` field to it.
271
+ */
272
+
273
+ /**
274
+ * Graph layer taxonomy. Not part of `@anokye-labs/kbexplorer-core` (it's an
275
+ * engine-internal concern, not a core domain type) — defined here since this
276
+ * is the module that resolves it. content-model/register.ts (slice 2) will
277
+ * import it from here too once it migrates.
278
+ */
279
+ type NodeLayer = 'file' | 'content' | 'work';
280
+ /**
281
+ * Module augmentation: `@anokye-labs/kbexplorer-core`'s `KBNode` does not
282
+ * declare a `layer` field, but `buildGraph()` (`src/graph.ts`, via
283
+ * {@link resolveNodeLayer}) stamps one onto every node at build time, and
284
+ * later-slice consumers (content-model/register.ts) read it back. Augmenting
285
+ * the core type in place — rather than introducing a parallel `EngineNode`
286
+ * type that every call site would need to swap to — keeps `KBNode` the single
287
+ * node shape used throughout this package and its consumers. Flagged upstream
288
+ * (anokye-labs/kbexplorer-template#472) in case `@anokye-labs/kbexplorer-core`
289
+ * should grow this field natively in a future version.
290
+ */
291
+ declare module '@anokye-labs/kbexplorer-core' {
292
+ interface KBNode {
293
+ /** Graph layer this node belongs to, stamped by `buildGraph()`. */
294
+ layer?: NodeLayer;
295
+ }
296
+ }
297
+ /** A registered node type and how it participates in the graph. */
298
+ interface NodeTypeDefinition {
299
+ /** Type id — matches a node's `entityType` or, for built-ins, its `source.type`. */
300
+ id: string;
301
+ /** Human-readable label (defaults to a humanized id). */
302
+ label?: string;
303
+ /** Graph layer this type belongs to. Defaults to `'file'` when unset. */
304
+ layer?: NodeLayer;
305
+ /** Default cluster id for nodes of this type (used when a node omits its cluster). */
306
+ cluster?: string;
307
+ /** Relation kinds this type commonly emits (informational; surfaced to the legend). */
308
+ relations?: string[];
309
+ /**
310
+ * Viewer key used to resolve a renderer from the viewer registry. Defaults to
311
+ * the type `id`. The viewer registry falls back to the generic viewer when no
312
+ * component is registered for the key.
313
+ */
314
+ viewer?: string;
315
+ /** Short description of the type (documentation aid). */
316
+ description?: string;
317
+ /**
318
+ * Optional discovery hook — given raw upstream records, decide which become
319
+ * nodes of this type. Reserved for content-model ingestion (F2/F3); the
320
+ * foundation only stores it.
321
+ */
322
+ discover?: (records: unknown[]) => unknown[];
323
+ /**
324
+ * Optional mapping hook — turn a single upstream record into a partial node.
325
+ * Reserved for ingestion (F2/F3); the foundation only stores it.
326
+ */
327
+ map?: (record: unknown) => Partial<KBNode>;
328
+ }
329
+ /** Register (or replace) a node type. */
330
+ declare function registerType(def: NodeTypeDefinition): void;
331
+ /** Resolve a node type by id. Returns `undefined` for unknown ids. */
332
+ declare function resolveType(id: string | undefined): NodeTypeDefinition | undefined;
333
+ /** Whether a node type id is registered. */
334
+ declare function hasType(id: string): boolean;
335
+ /** All registered node types. */
336
+ declare function getRegisteredTypes(): NodeTypeDefinition[];
337
+ /** (Re)register the built-in source types. Idempotent. */
338
+ declare function registerBuiltInNodeTypes(): void;
339
+ /** Clear the registry back to just the built-ins. Intended for tests. */
340
+ declare function resetNodeTypeRegistry(): void;
341
+ /**
342
+ * Resolve a node's graph layer via the registry.
343
+ *
344
+ * Precedence: the node's `entityType` definition → its `source.type`
345
+ * definition → `'file'`. This preserves the historical mapping for built-in
346
+ * source types while letting registered entity types override the layer.
347
+ */
348
+ declare function resolveNodeLayer(node: KBNode): NodeLayer;
349
+ /**
350
+ * Resolve the default cluster for a node from its type, when the node does not
351
+ * already declare one. Returns `undefined` when nothing is registered.
352
+ */
353
+ declare function resolveTypeCluster(node: Pick<KBNode, 'entityType'> & {
354
+ source: {
355
+ type: string;
356
+ };
357
+ }): string | undefined;
358
+
359
+ /**
360
+ * Normalized repo-data bundle (Phase 4 / F4 #318).
361
+ *
362
+ * The local and remote loaders historically differed only in *how* they
363
+ * obtained raw repository data — a pre-built manifest import vs live GitHub API
364
+ * calls — and then wired the *same* providers from it. {@link RepoData} is the
365
+ * normalized superset both acquisition paths produce, so a single
366
+ * `loadKnowledgeBase(source, config)` can wire providers once.
367
+ *
368
+ * A {@link RepoSource} is the system-of-record adapter: it both implements the
369
+ * pure {@link Source} contract from `@anokye-labs/kbexplorer-core` (self-
370
+ * describing, situationally-afforded {@link Resource}s) and exposes
371
+ * {@link RepoSource.getRepoData} — the engine-facing accessor the loader
372
+ * consumes to build the graph.
373
+ */
374
+
375
+ /** Pull request shape consumed by the work + person providers (superset). */
376
+ interface RepoPullRequest {
377
+ number: number;
378
+ title: string;
379
+ body: string;
380
+ state: string;
381
+ labels: Array<{
382
+ name: string;
383
+ color: string;
384
+ }>;
385
+ html_url: string;
386
+ created_at: string;
387
+ updated_at: string;
388
+ head_branch?: string;
389
+ /** GitHub user who opened the PR (present from the API; absent from a manifest). */
390
+ user?: {
391
+ login: string;
392
+ };
393
+ /** Assignees (present from the API; absent from a manifest). */
394
+ assignees?: Array<{
395
+ login: string;
396
+ }>;
397
+ }
398
+ /** Repository metadata consumed by the work provider. */
399
+ interface RepoMetadata {
400
+ name: string;
401
+ description: string;
402
+ html_url: string;
403
+ /** Repo homepage URL (blank when unset). Matches the old generator's 12-key `fetchRepoMetadata` shape. */
404
+ homepage: string;
405
+ default_branch: string;
406
+ stargazers_count: number;
407
+ forks_count: number;
408
+ private: boolean;
409
+ topics: string[];
410
+ primary_language: string;
411
+ languages: Array<{
412
+ name: string;
413
+ size: number;
414
+ }>;
415
+ owner: {
416
+ login: string;
417
+ avatar_url: string;
418
+ };
419
+ }
420
+ /**
421
+ * The normalized raw-data bundle a {@link RepoSource} hands the loader. Optional
422
+ * fields degrade gracefully: a source that cannot supply them (e.g. the API
423
+ * source has no `nodemapRaw` yet) leaves them empty/null and the matching
424
+ * provider becomes a safe no-op — keeping output byte-identical per source.
425
+ */
426
+ interface RepoData {
427
+ /** owner/name slug for file-node identity. */
428
+ repo: string;
429
+ tree: GHTreeItem[];
430
+ authoredContent: Record<string, string>;
431
+ nodemapRaw: string | null;
432
+ nodemapFiles?: Record<string, string>;
433
+ nodemapDirs?: Record<string, GHTreeItem[]>;
434
+ /** Glob lookup over authored/nodemap files (empty for sources without one). */
435
+ listFiles: (pattern: string) => Promise<string[]>;
436
+ issues: GHIssue[];
437
+ pullRequests: RepoPullRequest[];
438
+ commits: GHCommit[];
439
+ branches: Array<{
440
+ name: string;
441
+ protected: boolean;
442
+ }>;
443
+ repoMetadata: RepoMetadata | null;
444
+ releases: GHRelease[];
445
+ structuralFiles: Record<string, string>;
446
+ structuredNodeMapRaw: string | null;
447
+ contentModel: ContentModelSource | null;
448
+ readme: string | null;
449
+ themeFileRaw?: string | null;
450
+ }
451
+ /**
452
+ * A system-of-record adapter. Implements the pure {@link Source} contract (the
453
+ * navigable, situationally-afforded resource surface) and exposes the
454
+ * engine-facing {@link getRepoData} the loader consumes.
455
+ */
456
+ interface RepoSource extends Source {
457
+ /** Produce the normalized data bundle the provider pipeline consumes. */
458
+ getRepoData(): Promise<RepoData>;
459
+ }
460
+
461
+ export { type ContentModelSchema as C, type Diagnostic as D, type EngineEnv as E, type FkFlavor as F, type GHIssue as G, type JsonLdContext as J, type KindConvention as K, type Lifecycle as L, type NodeLayer as N, type OrgDef as O, type RepoSource as R, type TeamOps as T, type VocabularyOverlay as V, type ContentModelSource as a, type GHTreeItem as b, type GHRelease as c, type RepoData as d, type CompositeLeg as e, type Conventions as f, type DerivedRule as g, type DiagnosticLevel as h, type EdgeRule as i, type EdgesSpec as j, type GHCommit as k, type LifecycleBand as l, type NodeTypeDefinition as m, type Vocabulary as n, getRegisteredTypes as o, hasType as p, registerType as q, registerBuiltInNodeTypes as r, resetNodeTypeRegistry as s, resolveNodeLayer as t, resolveType as u, resolveTypeCluster as v, type RepoMetadata as w, type RepoPullRequest as x };