@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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anokye Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # @anokye-labs/kbexplorer-engine
2
+
3
+ The runtime-agnostic engine for kbexplorer: it turns a knowledge-base source
4
+ (a manifest, the GitHub API, or a local directory) into a computed `KBGraph`,
5
+ and provides a small scriptable query API over that graph. The core (`.`) entry
6
+ stays portable across Node and browser-like environments; Node-specific pieces
7
+ (like `FileSystemSource`) live behind the `./sources` subpath.
8
+
9
+ ## Develop
10
+
11
+ ```bash
12
+ npm install
13
+ npm run typecheck
14
+ npm run build
15
+ npm test
16
+ ```
17
+
18
+ ## Package shape
19
+
20
+ - `.` — the engine core: graph building (`buildGraph`), the unified loader
21
+ (`loadKnowledgeBase`), the query helpers, providers, parsing, identity, and
22
+ supporting types.
23
+ - `./sources` — source adapters (`ManifestSource`, `GitHubApiSource`,
24
+ `FileSystemSource`) and the `RepoSource` / `RepoData` contracts.
25
+ - `./store` — the optional sqlite-backed graph store entry point.
26
+
27
+ ## Loading a knowledge base
28
+
29
+ `loadKnowledgeBase` has two call shapes, distinguished by the first argument:
30
+
31
+ ```ts
32
+ import { loadKnowledgeBase } from '@anokye-labs/kbexplorer-engine';
33
+ import { GitHubApiSource, FileSystemSource } from '@anokye-labs/kbexplorer-engine/sources';
34
+
35
+ // Config-first (scripting): returns a bare KBGraph.
36
+ // With no `source`, a GitHubApiSource is built from config.source.
37
+ const graph = await loadKnowledgeBase(config);
38
+ const localGraph = await loadKnowledgeBase(config, { source: new FileSystemSource('./my-kb') });
39
+
40
+ // Positional (advanced): returns { graph, config }. This is the SHA-pinned
41
+ // contract consumed by kbexplorer-template — it is unchanged.
42
+ const { graph: g, config: c } = await loadKnowledgeBase(new GitHubApiSource(config.source), config);
43
+ ```
44
+
45
+ The two forms differ only in ergonomics and return shape; the positional form
46
+ is fully preserved.
47
+
48
+ ## Query API
49
+
50
+ Pure, runtime-agnostic helpers over a computed `KBGraph`:
51
+
52
+ ```ts
53
+ import {
54
+ getNode, findNodes, neighbors, related, subgraph, shortestPath,
55
+ } from '@anokye-labs/kbexplorer-engine';
56
+
57
+ getNode(graph, 'home'); // KBNode | undefined
58
+ findNodes(graph, n => n.cluster === 'engine'); // KBNode[]
59
+ neighbors(graph, 'home', { direction: 'out' }); // KBNode[]
60
+ related(graph, 'home'); // KBNode[] (precomputed index)
61
+ subgraph(graph, 'home', { radius: 2 }); // KBGraph neighborhood
62
+ shortestPath(graph, 'home', 'loader'); // string[] | null
63
+ ```
64
+
65
+ ## Sources
66
+
67
+ - `ManifestSource(manifest, config)` — a frozen, pre-built snapshot.
68
+ - `GitHubApiSource(sourceConfig)` — the live GitHub API.
69
+ - `FileSystemSource(rootDir, options?)` — **Node-only** adapter that walks a
70
+ local directory into the same `RepoData` bundle a manifest yields (authored
71
+ `content/`, `.github` structural files, `content-model/`, README).
72
+
73
+ ## Recipes
74
+
75
+ Runnable examples of the scriptable API against an in-repo fixture live in
76
+ [`docs/recipes.md`](./docs/recipes.md). CI builds the package and runs all eight
77
+ scripts on every pull request.
@@ -0,0 +1,239 @@
1
+ import { SourceConfig } from '@anokye-labs/kbexplorer-core';
2
+ import { E as EngineEnv, k as GHCommit, G as GHIssue, c as GHRelease, b as GHTreeItem, a as ContentModelSource, R as RepoSource } from './repo-data-0JvFdLGv.js';
3
+
4
+ /**
5
+ * GitHub API client for fetching repository content at runtime.
6
+ *
7
+ * Runtime-agnostic, boundary-pure port of kbexplorer-template's
8
+ * `src/api/github.ts` (anokye-labs/kbexplorer-template#472, slice 4/5). The
9
+ * template client wrapped every call in a browser-storage cache and read the
10
+ * API base from a Vite build-time env at module scope. Neither of those belongs
11
+ * in this runtime-agnostic engine package, so this port drops the cache layer
12
+ * entirely (it stays template-side as a thin wrapper) and injects the API base
13
+ * per call via an optional {@link EngineEnv} argument — mirroring the injection
14
+ * idiom already used in `src/store/config.ts`. `resolveImageUrl` (a pure
15
+ * UI/dev-server concern) is intentionally not ported; it stays in template.
16
+ *
17
+ * Supports two modes:
18
+ * - authored: fetches markdown files from a content directory
19
+ * - repo-aware: fetches issues, PRs, README, and file tree
20
+ */
21
+
22
+ /**
23
+ * The distinct GitHub REST endpoint path patterns this client's `ghFetch` call
24
+ * sites hit — one entry per endpoint family. Exported as a single source of
25
+ * truth so an out-of-repo drift-detection test (kbexplorer-template's
26
+ * `twin-coverage.test.ts`) can import it instead of regexing this file's raw
27
+ * source text: adding a new endpoint here fails that test until a matching twin
28
+ * route exists. Keep this in sync with the paths passed to `ghFetch` below.
29
+ */
30
+ declare const GITHUB_ENDPOINT_PATTERNS: readonly ["contents/", "git/trees/", "issues", "pulls", "commits", "releases", "branches", "languages"];
31
+ declare class NotModifiedError extends Error {
32
+ constructor();
33
+ }
34
+ declare class RateLimitError extends Error {
35
+ resetAt?: Date;
36
+ constructor(resetAt?: Date);
37
+ }
38
+ declare class GitHubApiError extends Error {
39
+ status: number;
40
+ constructor(status: number, body: string);
41
+ }
42
+ interface GHFileContent {
43
+ name: string;
44
+ path: string;
45
+ sha: string;
46
+ content: string;
47
+ encoding: string;
48
+ }
49
+ interface CacheStore {
50
+ get<T>(key: string): T | undefined;
51
+ set<T>(key: string, value: T): void;
52
+ }
53
+ /** Fetch and decode a single file from the repo. */
54
+ declare function fetchFile(source: SourceConfig, path: string, env?: EngineEnv, cache?: CacheStore): Promise<string>;
55
+ /** List all files in a directory (recursive via Git Trees API). */
56
+ declare function fetchTree(source: SourceConfig, path?: string, env?: EngineEnv, cache?: CacheStore): Promise<GHTreeItem[]>;
57
+ /** Fetch issues (not PRs) from the repo. */
58
+ declare function fetchIssues(source: SourceConfig, env?: EngineEnv, cache?: CacheStore): Promise<GHIssue[]>;
59
+ /** Fetch pull requests from the repo. */
60
+ declare function fetchPullRequests(source: SourceConfig, env?: EngineEnv, cache?: CacheStore): Promise<GHIssue[]>;
61
+ /** Fetch recent commits from the repo. */
62
+ declare function fetchCommits(source: SourceConfig, count?: number, env?: EngineEnv, cache?: CacheStore): Promise<GHCommit[]>;
63
+ /** Fetch GitHub releases (non-draft, newest-first, capped at 30). */
64
+ declare function fetchReleases(source: SourceConfig, limit?: number, env?: EngineEnv, cache?: CacheStore): Promise<GHRelease[]>;
65
+ /** Fetch multiple files in parallel. */
66
+ declare function fetchFiles(source: SourceConfig, paths: string[], env?: EngineEnv, cache?: CacheStore): Promise<Map<string, string>>;
67
+
68
+ /**
69
+ * RepoManifest — the pre-built snapshot shape a {@link ManifestSource} is
70
+ * constructed from (relocated from kbexplorer-template's `src/engine/
71
+ * local-loader.ts` in anokye-labs/kbexplorer-template#472, slice 4/5).
72
+ *
73
+ * It moves into this package because `ManifestSource`'s constructor takes one.
74
+ * The manifest-generation script and the local (manifest-import) loader remain
75
+ * template-side; only the interface travels here.
76
+ */
77
+
78
+ interface RepoManifest {
79
+ configRaw: string | null;
80
+ authoredContent: Record<string, string>;
81
+ tree: Array<{
82
+ path: string;
83
+ type: 'blob' | 'tree';
84
+ size?: number;
85
+ }>;
86
+ readme: string | null;
87
+ issues: GHIssue[];
88
+ pullRequests: Array<{
89
+ number: number;
90
+ title: string;
91
+ body: string;
92
+ state: string;
93
+ labels: Array<{
94
+ name: string;
95
+ color: string;
96
+ }>;
97
+ html_url: string;
98
+ created_at: string;
99
+ updated_at: string;
100
+ head_branch?: string;
101
+ }>;
102
+ commits: Array<{
103
+ sha: string;
104
+ commit: {
105
+ message: string;
106
+ author: {
107
+ name: string;
108
+ date: string;
109
+ };
110
+ };
111
+ html_url: string;
112
+ }>;
113
+ branches?: Array<{
114
+ name: string;
115
+ protected: boolean;
116
+ }>;
117
+ /**
118
+ * GitHub releases (non-draft, newest-first, capped at 30). Absent/empty in
119
+ * repos without releases — the WorkProvider handles this gracefully (safe no-op).
120
+ */
121
+ releases?: GHRelease[];
122
+ repoMetadata?: {
123
+ name: string;
124
+ description: string;
125
+ html_url: string;
126
+ /** Repo homepage URL (blank when unset). Matches the old generator's 12-key `fetchRepoMetadata` shape. */
127
+ homepage: string;
128
+ default_branch: string;
129
+ stargazers_count: number;
130
+ forks_count: number;
131
+ private: boolean;
132
+ topics: string[];
133
+ primary_language: string;
134
+ languages: Array<{
135
+ name: string;
136
+ size: number;
137
+ }>;
138
+ owner: {
139
+ login: string;
140
+ avatar_url: string;
141
+ };
142
+ } | null;
143
+ nodemapRaw?: string | null;
144
+ nodemapFiles?: Record<string, string>;
145
+ nodemapDirs?: Record<string, Array<{
146
+ path: string;
147
+ type: 'blob' | 'tree';
148
+ size?: number;
149
+ }>>;
150
+ structuredNodeMapRaw?: string | null;
151
+ structuralFiles?: Record<string, string>;
152
+ /**
153
+ * Optional content-model source (F2): schema files + entity files keyed by
154
+ * path relative to `structuredContent.path` (default `content-model/`). Absent
155
+ * (null) in repos without a content model — the ContentModelProvider is then a
156
+ * safe no-op.
157
+ */
158
+ contentModel?: ContentModelSource | null;
159
+ /**
160
+ * Optional raw contents of the dedicated theme file referenced by
161
+ * `config.theme.themesFile` (F5/T5.1). Read at manifest-generation time from
162
+ * the host repo and merged into the theme block in local mode the same way
163
+ * the remote loader fetches it at runtime. Null/absent when no themesFile is
164
+ * configured or the file is missing — a safe no-op.
165
+ */
166
+ themeFileRaw?: string | null;
167
+ generatedAt: string;
168
+ }
169
+
170
+ /**
171
+ * buildManifest() — the manifest PRODUCER (anokye-labs/kbexplorer-engine#17,
172
+ * the anchor child of the thin-CLI/fat-engine epic
173
+ * anokye-labs/kbexplorer-template#463).
174
+ *
175
+ * The engine already owned the manifest **consume** path — `ManifestSource`,
176
+ * the `RepoSource` acquisition adapters (`GitHubApiSource`, `FileSystemSource`),
177
+ * and the `RepoManifest` snapshot shape itself (`./repo-manifest`). What was
178
+ * missing was the **producer**: something that turns a `RepoSource` into a
179
+ * `RepoManifest`. That logic lived duplicated outside the engine, in
180
+ * kbexplorer-template's `scripts/generate-manifest.js` and the CLI's
181
+ * byte-parallel `src/lib/repo-manifest.ts` fork (flagged by cli#232).
182
+ *
183
+ * `buildManifest` closes that loop *without* re-implementing any fetching:
184
+ * acquisition (GitHub REST calls, local filesystem walks) already lives on the
185
+ * injected `RepoSource`. This function drives it through the existing
186
+ * `getRepoData()` seam exactly once and re-shapes the normalized `RepoData`
187
+ * bundle into the serializable `RepoManifest` snapshot — the mechanical,
188
+ * source-agnostic half of what the template script does before it writes
189
+ * `repo-manifest.json` to disk. It is deliberately the mirror image of
190
+ * `ManifestSource.getRepoData()` (which maps `RepoManifest` → `RepoData`);
191
+ * this maps `RepoData` → `RepoManifest`.
192
+ *
193
+ * Signature note (`buildManifest(source, options)`, not `(source, config)`):
194
+ * every `RepoManifest` field this function assembles comes straight off
195
+ * `RepoData` — none of them need a resolved `KBConfig`. The two fields a
196
+ * `RepoSource` cannot uniformly supply are threaded in via `options` instead:
197
+ *
198
+ * - `configRaw` — the raw (pre-YAML-parse) `config.yaml` text. `KBConfig`
199
+ * (what `GitHubApiSource.resolveConfig()` / a local `loadConfig()` caller
200
+ * would have) is already-parsed, so it can't round-trip back to source
201
+ * text. Callers already hold (or can cheaply obtain) the raw text — a local
202
+ * caller reads the same file `FileSystemSource` walks, and a remote caller
203
+ * already has the engine's exported `fetchFile` to hand — so this function
204
+ * accepts it rather than duplicating a second raw-fetch code path.
205
+ * - `generatedAt` — the snapshot timestamp. Defaults to
206
+ * `new Date().toISOString()` but is overridable so callers (and this
207
+ * package's own tests) can produce deterministic output for
208
+ * idempotency/`--check`-drift assertions, matching the issue's requirement
209
+ * that source-derived fields (everything *except* `generatedAt`/live
210
+ * GitHub state) be deterministic.
211
+ *
212
+ * `nodemapFiles`/`nodemapDirs` are passed through as-is from `RepoData` when a
213
+ * source populates them (neither shipped `RepoSource` does yet — both leave
214
+ * `nodemapRaw: null`, matching their existing, unchanged behavior — so
215
+ * `buildManifest` carries whatever future sources supply without
216
+ * re-implementing the template script's nodemap-collection logic itself,
217
+ * which is out of this issue's scope: see the issue's "non-goals").
218
+ */
219
+
220
+ /** Options for fields `RepoData` cannot uniformly supply (see module docs). */
221
+ interface BuildManifestOptions {
222
+ /** Raw (pre-parse) `config.yaml` text to embed, or `null`/omitted when there is none. */
223
+ configRaw?: string | null;
224
+ /**
225
+ * Overrides the snapshot timestamp. Defaults to `new Date().toISOString()`.
226
+ * Pass a fixed value for deterministic/idempotency tests and `--check` drift
227
+ * comparisons, which must ignore this volatile field regardless.
228
+ */
229
+ generatedAt?: string;
230
+ /** When set, overlay the live-GitHub fields from this source onto the primary-source manifest (hybrid: local content + live augmentation). */
231
+ augmentFrom?: RepoSource;
232
+ }
233
+ /**
234
+ * Drive `source.getRepoData()` once and assemble a `RepoManifest` snapshot
235
+ * from the result — the producer half of the `ManifestSource` contract.
236
+ */
237
+ declare function buildManifest(source: RepoSource, options?: BuildManifestOptions): Promise<RepoManifest>;
238
+
239
+ export { type BuildManifestOptions as B, type CacheStore as C, type GHFileContent as G, NotModifiedError as N, type RepoManifest as R, fetchFile as a, buildManifest as b, fetchFiles as c, fetchIssues as d, fetchPullRequests as e, fetchCommits as f, fetchReleases as g, fetchTree as h, GITHUB_ENDPOINT_PATTERNS as i, GitHubApiError as j, RateLimitError as k };
@@ -0,0 +1,239 @@
1
+ import { SourceConfig } from '@anokye-labs/kbexplorer-core';
2
+ import { E as EngineEnv, k as GHCommit, G as GHIssue, c as GHRelease, b as GHTreeItem, a as ContentModelSource, R as RepoSource } from './repo-data-0JvFdLGv.cjs';
3
+
4
+ /**
5
+ * GitHub API client for fetching repository content at runtime.
6
+ *
7
+ * Runtime-agnostic, boundary-pure port of kbexplorer-template's
8
+ * `src/api/github.ts` (anokye-labs/kbexplorer-template#472, slice 4/5). The
9
+ * template client wrapped every call in a browser-storage cache and read the
10
+ * API base from a Vite build-time env at module scope. Neither of those belongs
11
+ * in this runtime-agnostic engine package, so this port drops the cache layer
12
+ * entirely (it stays template-side as a thin wrapper) and injects the API base
13
+ * per call via an optional {@link EngineEnv} argument — mirroring the injection
14
+ * idiom already used in `src/store/config.ts`. `resolveImageUrl` (a pure
15
+ * UI/dev-server concern) is intentionally not ported; it stays in template.
16
+ *
17
+ * Supports two modes:
18
+ * - authored: fetches markdown files from a content directory
19
+ * - repo-aware: fetches issues, PRs, README, and file tree
20
+ */
21
+
22
+ /**
23
+ * The distinct GitHub REST endpoint path patterns this client's `ghFetch` call
24
+ * sites hit — one entry per endpoint family. Exported as a single source of
25
+ * truth so an out-of-repo drift-detection test (kbexplorer-template's
26
+ * `twin-coverage.test.ts`) can import it instead of regexing this file's raw
27
+ * source text: adding a new endpoint here fails that test until a matching twin
28
+ * route exists. Keep this in sync with the paths passed to `ghFetch` below.
29
+ */
30
+ declare const GITHUB_ENDPOINT_PATTERNS: readonly ["contents/", "git/trees/", "issues", "pulls", "commits", "releases", "branches", "languages"];
31
+ declare class NotModifiedError extends Error {
32
+ constructor();
33
+ }
34
+ declare class RateLimitError extends Error {
35
+ resetAt?: Date;
36
+ constructor(resetAt?: Date);
37
+ }
38
+ declare class GitHubApiError extends Error {
39
+ status: number;
40
+ constructor(status: number, body: string);
41
+ }
42
+ interface GHFileContent {
43
+ name: string;
44
+ path: string;
45
+ sha: string;
46
+ content: string;
47
+ encoding: string;
48
+ }
49
+ interface CacheStore {
50
+ get<T>(key: string): T | undefined;
51
+ set<T>(key: string, value: T): void;
52
+ }
53
+ /** Fetch and decode a single file from the repo. */
54
+ declare function fetchFile(source: SourceConfig, path: string, env?: EngineEnv, cache?: CacheStore): Promise<string>;
55
+ /** List all files in a directory (recursive via Git Trees API). */
56
+ declare function fetchTree(source: SourceConfig, path?: string, env?: EngineEnv, cache?: CacheStore): Promise<GHTreeItem[]>;
57
+ /** Fetch issues (not PRs) from the repo. */
58
+ declare function fetchIssues(source: SourceConfig, env?: EngineEnv, cache?: CacheStore): Promise<GHIssue[]>;
59
+ /** Fetch pull requests from the repo. */
60
+ declare function fetchPullRequests(source: SourceConfig, env?: EngineEnv, cache?: CacheStore): Promise<GHIssue[]>;
61
+ /** Fetch recent commits from the repo. */
62
+ declare function fetchCommits(source: SourceConfig, count?: number, env?: EngineEnv, cache?: CacheStore): Promise<GHCommit[]>;
63
+ /** Fetch GitHub releases (non-draft, newest-first, capped at 30). */
64
+ declare function fetchReleases(source: SourceConfig, limit?: number, env?: EngineEnv, cache?: CacheStore): Promise<GHRelease[]>;
65
+ /** Fetch multiple files in parallel. */
66
+ declare function fetchFiles(source: SourceConfig, paths: string[], env?: EngineEnv, cache?: CacheStore): Promise<Map<string, string>>;
67
+
68
+ /**
69
+ * RepoManifest — the pre-built snapshot shape a {@link ManifestSource} is
70
+ * constructed from (relocated from kbexplorer-template's `src/engine/
71
+ * local-loader.ts` in anokye-labs/kbexplorer-template#472, slice 4/5).
72
+ *
73
+ * It moves into this package because `ManifestSource`'s constructor takes one.
74
+ * The manifest-generation script and the local (manifest-import) loader remain
75
+ * template-side; only the interface travels here.
76
+ */
77
+
78
+ interface RepoManifest {
79
+ configRaw: string | null;
80
+ authoredContent: Record<string, string>;
81
+ tree: Array<{
82
+ path: string;
83
+ type: 'blob' | 'tree';
84
+ size?: number;
85
+ }>;
86
+ readme: string | null;
87
+ issues: GHIssue[];
88
+ pullRequests: Array<{
89
+ number: number;
90
+ title: string;
91
+ body: string;
92
+ state: string;
93
+ labels: Array<{
94
+ name: string;
95
+ color: string;
96
+ }>;
97
+ html_url: string;
98
+ created_at: string;
99
+ updated_at: string;
100
+ head_branch?: string;
101
+ }>;
102
+ commits: Array<{
103
+ sha: string;
104
+ commit: {
105
+ message: string;
106
+ author: {
107
+ name: string;
108
+ date: string;
109
+ };
110
+ };
111
+ html_url: string;
112
+ }>;
113
+ branches?: Array<{
114
+ name: string;
115
+ protected: boolean;
116
+ }>;
117
+ /**
118
+ * GitHub releases (non-draft, newest-first, capped at 30). Absent/empty in
119
+ * repos without releases — the WorkProvider handles this gracefully (safe no-op).
120
+ */
121
+ releases?: GHRelease[];
122
+ repoMetadata?: {
123
+ name: string;
124
+ description: string;
125
+ html_url: string;
126
+ /** Repo homepage URL (blank when unset). Matches the old generator's 12-key `fetchRepoMetadata` shape. */
127
+ homepage: string;
128
+ default_branch: string;
129
+ stargazers_count: number;
130
+ forks_count: number;
131
+ private: boolean;
132
+ topics: string[];
133
+ primary_language: string;
134
+ languages: Array<{
135
+ name: string;
136
+ size: number;
137
+ }>;
138
+ owner: {
139
+ login: string;
140
+ avatar_url: string;
141
+ };
142
+ } | null;
143
+ nodemapRaw?: string | null;
144
+ nodemapFiles?: Record<string, string>;
145
+ nodemapDirs?: Record<string, Array<{
146
+ path: string;
147
+ type: 'blob' | 'tree';
148
+ size?: number;
149
+ }>>;
150
+ structuredNodeMapRaw?: string | null;
151
+ structuralFiles?: Record<string, string>;
152
+ /**
153
+ * Optional content-model source (F2): schema files + entity files keyed by
154
+ * path relative to `structuredContent.path` (default `content-model/`). Absent
155
+ * (null) in repos without a content model — the ContentModelProvider is then a
156
+ * safe no-op.
157
+ */
158
+ contentModel?: ContentModelSource | null;
159
+ /**
160
+ * Optional raw contents of the dedicated theme file referenced by
161
+ * `config.theme.themesFile` (F5/T5.1). Read at manifest-generation time from
162
+ * the host repo and merged into the theme block in local mode the same way
163
+ * the remote loader fetches it at runtime. Null/absent when no themesFile is
164
+ * configured or the file is missing — a safe no-op.
165
+ */
166
+ themeFileRaw?: string | null;
167
+ generatedAt: string;
168
+ }
169
+
170
+ /**
171
+ * buildManifest() — the manifest PRODUCER (anokye-labs/kbexplorer-engine#17,
172
+ * the anchor child of the thin-CLI/fat-engine epic
173
+ * anokye-labs/kbexplorer-template#463).
174
+ *
175
+ * The engine already owned the manifest **consume** path — `ManifestSource`,
176
+ * the `RepoSource` acquisition adapters (`GitHubApiSource`, `FileSystemSource`),
177
+ * and the `RepoManifest` snapshot shape itself (`./repo-manifest`). What was
178
+ * missing was the **producer**: something that turns a `RepoSource` into a
179
+ * `RepoManifest`. That logic lived duplicated outside the engine, in
180
+ * kbexplorer-template's `scripts/generate-manifest.js` and the CLI's
181
+ * byte-parallel `src/lib/repo-manifest.ts` fork (flagged by cli#232).
182
+ *
183
+ * `buildManifest` closes that loop *without* re-implementing any fetching:
184
+ * acquisition (GitHub REST calls, local filesystem walks) already lives on the
185
+ * injected `RepoSource`. This function drives it through the existing
186
+ * `getRepoData()` seam exactly once and re-shapes the normalized `RepoData`
187
+ * bundle into the serializable `RepoManifest` snapshot — the mechanical,
188
+ * source-agnostic half of what the template script does before it writes
189
+ * `repo-manifest.json` to disk. It is deliberately the mirror image of
190
+ * `ManifestSource.getRepoData()` (which maps `RepoManifest` → `RepoData`);
191
+ * this maps `RepoData` → `RepoManifest`.
192
+ *
193
+ * Signature note (`buildManifest(source, options)`, not `(source, config)`):
194
+ * every `RepoManifest` field this function assembles comes straight off
195
+ * `RepoData` — none of them need a resolved `KBConfig`. The two fields a
196
+ * `RepoSource` cannot uniformly supply are threaded in via `options` instead:
197
+ *
198
+ * - `configRaw` — the raw (pre-YAML-parse) `config.yaml` text. `KBConfig`
199
+ * (what `GitHubApiSource.resolveConfig()` / a local `loadConfig()` caller
200
+ * would have) is already-parsed, so it can't round-trip back to source
201
+ * text. Callers already hold (or can cheaply obtain) the raw text — a local
202
+ * caller reads the same file `FileSystemSource` walks, and a remote caller
203
+ * already has the engine's exported `fetchFile` to hand — so this function
204
+ * accepts it rather than duplicating a second raw-fetch code path.
205
+ * - `generatedAt` — the snapshot timestamp. Defaults to
206
+ * `new Date().toISOString()` but is overridable so callers (and this
207
+ * package's own tests) can produce deterministic output for
208
+ * idempotency/`--check`-drift assertions, matching the issue's requirement
209
+ * that source-derived fields (everything *except* `generatedAt`/live
210
+ * GitHub state) be deterministic.
211
+ *
212
+ * `nodemapFiles`/`nodemapDirs` are passed through as-is from `RepoData` when a
213
+ * source populates them (neither shipped `RepoSource` does yet — both leave
214
+ * `nodemapRaw: null`, matching their existing, unchanged behavior — so
215
+ * `buildManifest` carries whatever future sources supply without
216
+ * re-implementing the template script's nodemap-collection logic itself,
217
+ * which is out of this issue's scope: see the issue's "non-goals").
218
+ */
219
+
220
+ /** Options for fields `RepoData` cannot uniformly supply (see module docs). */
221
+ interface BuildManifestOptions {
222
+ /** Raw (pre-parse) `config.yaml` text to embed, or `null`/omitted when there is none. */
223
+ configRaw?: string | null;
224
+ /**
225
+ * Overrides the snapshot timestamp. Defaults to `new Date().toISOString()`.
226
+ * Pass a fixed value for deterministic/idempotency tests and `--check` drift
227
+ * comparisons, which must ignore this volatile field regardless.
228
+ */
229
+ generatedAt?: string;
230
+ /** When set, overlay the live-GitHub fields from this source onto the primary-source manifest (hybrid: local content + live augmentation). */
231
+ augmentFrom?: RepoSource;
232
+ }
233
+ /**
234
+ * Drive `source.getRepoData()` once and assemble a `RepoManifest` snapshot
235
+ * from the result — the producer half of the `ManifestSource` contract.
236
+ */
237
+ declare function buildManifest(source: RepoSource, options?: BuildManifestOptions): Promise<RepoManifest>;
238
+
239
+ export { type BuildManifestOptions as B, type CacheStore as C, type GHFileContent as G, NotModifiedError as N, type RepoManifest as R, fetchFile as a, buildManifest as b, fetchFiles as c, fetchIssues as d, fetchPullRequests as e, fetchCommits as f, fetchReleases as g, fetchTree as h, GITHUB_ENDPOINT_PATTERNS as i, GitHubApiError as j, RateLimitError as k };