@msareen/knowledge-hub-builder 0.1.3

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 (57) hide show
  1. package/.agents/skills/catalog/SKILL.md +7 -0
  2. package/.agents/skills/export/SKILL.md +7 -0
  3. package/.agents/skills/ingest/SKILL.md +7 -0
  4. package/.agents/skills/lint/SKILL.md +7 -0
  5. package/.agents/skills/new-bundle/SKILL.md +7 -0
  6. package/.agents/skills/query/SKILL.md +7 -0
  7. package/.agents/skills/visualize/SKILL.md +7 -0
  8. package/.bundle_template/index.md +9 -0
  9. package/.bundle_template/log.md +10 -0
  10. package/.bundle_template/raw/.gitkeep +15 -0
  11. package/.bundle_template/refs.md +6 -0
  12. package/.bundle_template/sources.yaml +13 -0
  13. package/.claude/skills/catalog/SKILL.md +7 -0
  14. package/.claude/skills/export/SKILL.md +7 -0
  15. package/.claude/skills/ingest/SKILL.md +7 -0
  16. package/.claude/skills/lint/SKILL.md +7 -0
  17. package/.claude/skills/new-bundle/SKILL.md +7 -0
  18. package/.claude/skills/query/SKILL.md +7 -0
  19. package/.claude/skills/visualize/SKILL.md +7 -0
  20. package/AGENTS.md +167 -0
  21. package/CLAUDE.md +13 -0
  22. package/README.md +289 -0
  23. package/SPEC.md +354 -0
  24. package/document/faq.md +156 -0
  25. package/package.json +52 -0
  26. package/scripts/cli.ts +66 -0
  27. package/scripts/export.ts +42 -0
  28. package/scripts/ingest/acquire.ts +189 -0
  29. package/scripts/ingest/exts.ts +29 -0
  30. package/scripts/ingest/files.ts +29 -0
  31. package/scripts/ingest/folder.ts +44 -0
  32. package/scripts/ingest/index.ts +125 -0
  33. package/scripts/ingest/protect.ts +42 -0
  34. package/scripts/ingest/web.ts +54 -0
  35. package/scripts/init.ts +93 -0
  36. package/scripts/lib/args.ts +8 -0
  37. package/scripts/lib/extract.ts +384 -0
  38. package/scripts/lib/graph-page.ts +477 -0
  39. package/scripts/lib/graph.ts +117 -0
  40. package/scripts/lib/ledger.ts +136 -0
  41. package/scripts/lib/log.ts +53 -0
  42. package/scripts/lib/paths.ts +55 -0
  43. package/scripts/lib/scaffold.ts +56 -0
  44. package/scripts/lib/util.ts +154 -0
  45. package/scripts/lint.ts +165 -0
  46. package/scripts/new-bundle.ts +17 -0
  47. package/scripts/visualize.ts +104 -0
  48. package/skills/catalog/SKILL.md +164 -0
  49. package/skills/export/SKILL.md +31 -0
  50. package/skills/ingest/SKILL.md +229 -0
  51. package/skills/lint/SKILL.md +69 -0
  52. package/skills/new-bundle/SKILL.md +25 -0
  53. package/skills/query/SKILL.md +114 -0
  54. package/skills/visualize/SKILL.md +33 -0
  55. package/templates/hub/gitattributes +12 -0
  56. package/templates/hub/gitignore +12 -0
  57. package/templates/hub/outer.index.md +13 -0
package/SPEC.md ADDED
@@ -0,0 +1,354 @@
1
+ # KHB — Specification
2
+
3
+ **KHB — Knowledge Hub Builder.** Distributed as
4
+ `@msareen/knowledge-hub-builder`. Install the tooling once; run `khb init` wherever the knowledge should
5
+ live (OneDrive, a shared drive, a private repo) to create a **hub**. KHB supplies the
6
+ rules and tooling and holds no knowledge; the hub holds all of it and is yours.
7
+
8
+ A personal knowledge system built as a **bundle of bundles**: independent knowledge bundles
9
+ joined by a thin router, navigable by any coding/knowledge agent (Claude, Codex, or other).
10
+ The common contract and canonical workflows are agent-agnostic. Runtime-specific shims
11
+ only handle discovery: Codex reads `AGENTS.md` and `.agents/skills/`; Claude imports the
12
+ contract through `CLAUDE.md` and discovers `.claude/skills/`.
13
+
14
+ Lineage: the operating model (immutable raw sources → LLM-curated wiki → schema, driven
15
+ by ingest/query/lint) is [Karpathy's LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f);
16
+ the file format is [Google's OKF](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) v0.1.
17
+ KHB's own contribution is the bundle-of-bundles layer over both — see §1.
18
+
19
+ ## 1. Core ideas
20
+
21
+ 1. **Bundle** — a self-contained *logical* unit owning its content and ingestion sources.
22
+ What makes a bundle is a human decision about ownership, not a classification of subject
23
+ matter: a person, a team, a client, a project. One bundle holds as many topics as its
24
+ owner has — a team bundle carries its roadmap, its incidents and its vendor notes
25
+ together, grouped by subdirectory, because they share a custodian and a context.
26
+ Bundles are never split automatically; splitting is an explicit instruction, never a
27
+ default behavior of ingest, catalog or query.
28
+ Bundles are *lean*: the common rules (AGENTS.md and the `skills/` protocols) live once
29
+ at the hub root, not duplicated per bundle. When a bundle must travel alone,
30
+ `khb export <bundle>` produces a standalone folder with the common patterns
31
+ injected — independence on demand rather than boilerplate everywhere.
32
+ 2. **Bundle of bundles** — the hub root is itself a bundle whose content is *routing*, not
33
+ knowledge. `outer.index.md` is the router: it points at bundles and larger topics, and
34
+ details route downward into a bundle's own `index.md`.
35
+ 3. **No collation by copying** — when two topics must be combined, content is never merged
36
+ across bundles. Instead: the requesting bundle records a **reference** (`refs.md`), and the
37
+ query resolves by *querying into* the other bundle. This keeps bundles independent and
38
+ collision-free. Two agents can work the two bundles in parallel and join results at the end.
39
+ 4. **Ingest, don't wiki-sprawl** — external material (folders on disk, Confluence, Azure
40
+ DevOps, internet links) is pulled *into* a bundle by ingesters rather than being linked as
41
+ living parts of the wiki. The wiki stays canonical; sources are provenance.
42
+ 5. **Acquisition and interpretation are separate steps.** `khb ingest` converts bytes to
43
+ markdown in `raw/` and stops. `catalog` — an agent pass, no command — turns that text
44
+ into concepts. Splitting them means a bad extraction is a re-run, not a re-think, and
45
+ every concept traces back through a provenance header to the original file.
46
+ 6. **Extract to markdown, locally** — binary/opaque formats (PDF, DOCX, XLSX, images,
47
+ audio) are converted to markdown so every bundle's knowledge is plain, greppable text.
48
+ Every extractor is local and deterministic: pure-JS libraries, tesseract WASM, whisper.
49
+ Lossy routes (OCR, ASR) are marked `quality: low` rather than hidden.
50
+ 7. **Bun** is the scripting language for all tooling and third-party interfaces.
51
+
52
+ ## 2. Two directories, one of them yours
53
+
54
+ KHB is two things that must not be confused: an installed **package** (tooling, no
55
+ knowledge) and a **hub** (knowledge, no tooling). Nothing you write ever lands in the
56
+ package, and the package is never a place to keep bundles.
57
+
58
+ ### 2a. The hub — created by `khb init`, lives wherever you want
59
+
60
+ ```
61
+ my-knowledge/ # ~/OneDrive/my-knowledge, a private repo, a share…
62
+ ├── khb.json # THE MARKER — how `khb` finds this hub; records its version
63
+ ├── outer.index.md # THE router — bundle-of-bundles index
64
+ ├── bundles/
65
+ │ └── <bundle>/ # OKF bundle, lean: content + routing, no agent boilerplate
66
+ │ ├── index.md # OKF index: progressive disclosure of this bundle
67
+ │ ├── refs.md # cross-bundle references (the ONLY way out of a bundle)
68
+ │ ├── sources.yaml # ingestion sources for this bundle
69
+ │ ├── log.md # ingest ledger: source → sha256 → raw → curated
70
+ │ ├── <group>/ # free-form subdirectories of concept .md files
71
+ │ │ ├── index.md # (each may carry its own index)
72
+ │ │ └── <concept>.md
73
+ │ └── raw/ # ingested/extracted material, pre-curation (gitignored)
74
+ ├── inbox/extracted/ # hub-wide extraction cache, keyed by content hash (gitignored)
75
+
76
+ │ ── below: package-owned copies, refreshed by `khb upgrade`, never hand-edited ──
77
+ ├── AGENTS.md # common contract; Codex discovers this directly
78
+ ├── CLAUDE.md # Claude shim — imports AGENTS.md
79
+ ├── .agents/skills/<name>/ # Codex discovery adapter → canonical skill
80
+ ├── .claude/skills/<name>/ # Claude discovery adapter → canonical skill
81
+ ├── SPEC.md # this file
82
+ └── skills/<name>/SKILL.md # one self-contained workflow protocol per folder:
83
+ # query routing, reading, and query-time learning
84
+ # ingest acquire + extract → raw/ (mechanical)
85
+ # catalog raw/ → concept docs (subagent fan-out)
86
+ # lint structural rules L1–L9
87
+ # new-bundle / export / visualize
88
+ ```
89
+
90
+ The contract docs are *copied into* the hub rather than read from the package because an
91
+ agent is opened on the hub folder and must find its rules there, without knowing where
92
+ `khb` is installed. They are package-owned: `khb upgrade` overwrites them in place and
93
+ leaves `bundles/` and `outer.index.md` alone.
94
+
95
+ ### 2b. The package — `@msareen/knowledge-hub-builder`, installed once
96
+
97
+ > **None of the following is in your hub.** This section describes the *installed tool*,
98
+ > which lives in your global package directory — not in the folder you are working in. A
99
+ > hub contains exactly what §2a lists: `khb.json`, `outer.index.md`, `bundles/`, and the
100
+ > package-owned contract docs. There is no `scripts/` and no `package.json` in a hub, so
101
+ > paths below are package-internal and will not resolve from the hub root. To run the tool,
102
+ > call `khb <command>` — never reach for a file in this tree.
103
+
104
+ ```
105
+ @msareen/knowledge-hub-builder/ # installed once, globally — NOT part of a hub
106
+ ├── package.json # bin: khb → scripts/cli.ts
107
+ ├── scripts/
108
+ │ ├── cli.ts # subcommand dispatch; owns the global --hub flag
109
+ │ ├── init.ts # khb init / khb upgrade
110
+ │ ├── new-bundle.ts # scaffold from .bundle_template, register in outer.index.md
111
+ │ ├── export.ts # bundle + common patterns → standalone shareable folder
112
+ │ ├── lint.ts # enforce skills/lint/SKILL.md across the hub
113
+ │ ├── visualize.ts # serve the live bundle graph from indexes + refs
114
+ │ ├── ingest/ # folder.ts / files.ts / web.ts → acquire.ts → bundle/raw
115
+ │ └── lib/
116
+ │ ├── extract.ts # every local extractor + the content-hash cache
117
+ │ ├── ledger.ts # log.md read/write
118
+ │ ├── paths.ts # package-side paths — importing it never needs a hub
119
+ │ └── util.ts # hub resolution + shared helpers
120
+ ├── .bundle_template/ # copied by `khb new-bundle`
121
+ ├── templates/hub/ # copied by `khb init`
122
+ └── AGENTS.md, skills/, … # the masters that `khb init`/`upgrade` copy into hubs
123
+ ```
124
+
125
+ ### 2c. Hub resolution
126
+
127
+ `khb` locates the hub in this order, and refuses to guess if none is found:
128
+
129
+ 1. `--hub <dir>` on the command line;
130
+ 2. `$KHB_HUB`;
131
+ 3. the nearest ancestor of the working directory containing `khb.json`.
132
+
133
+ Rule 3 is the normal path: `cd` anywhere inside the hub and run `khb lint`. One
134
+ consequence worth stating — a hub is identified by its marker file, not its name, so
135
+ hubs may be renamed or moved freely, and nested hubs resolve to the innermost one.
136
+
137
+ ## 3. Routing model
138
+
139
+ - `outer.index.md` lists every bundle with a one-line scope and "route here when" hints.
140
+ It contains **no knowledge**, only routing. An agent always starts at `AGENTS.md`, which
141
+ sends it to `outer.index.md`, which sends it into exactly one bundle.
142
+ - Inside a bundle, `index.md` routes to concept docs and subdirectory indexes
143
+ (progressive disclosure, OKF §6). Same rule: index = routing only.
144
+ - Keep hops shallow: outer index → bundle index → (subdirectory index →) concept.
145
+ If a bundle needs deep index chains, consider splitting it into two bundles.
146
+
147
+ ## 3b. Concept model (OKF v0.1)
148
+
149
+ Bundle content follows the [Open Knowledge Format (OKF)](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) v0.1:
150
+
151
+ - A **concept** is any non-reserved `.md` file in the bundle — not a special folder.
152
+ Concept ID = its path without `.md`. It may describe a tangible asset (table, API)
153
+ or an abstract idea (metric, decision, playbook).
154
+ - **Front matter**: `type` required (free-form, e.g. `Metric`, `Playbook`); `title`,
155
+ `description`, `resource`, `tags`, `timestamp` recommended; extra keys allowed.
156
+ - **Grouping** is free-form subdirectories chosen per domain (`tables/`,
157
+ `references/metrics/`, `notes/`, …) — structure carries no fixed semantics.
158
+ - **Indexes**: `index.md` may appear in any directory for progressive disclosure
159
+ (`* [Title](path.md) - description`). `log.md` optionally records history.
160
+ - **Links** between concepts are plain markdown links (bundle-root form
161
+ `/tables/x.md` preferred); relationship semantics live in prose, not link types.
162
+ Broken links are tolerated (not-yet-written knowledge).
163
+ - Conventional body headings: `# Schema`, `# Examples`, `# Citations`.
164
+
165
+ KHB additions on top of OKF: `refs.md` (reserved; the only cross-bundle
166
+ pointer), `sources.yaml` (ingestion provenance), `raw/` (uncurated ingested material,
167
+ exempt from OKF conformance), and the outer bundle-of-bundles router.
168
+
169
+ ## 4. Cross-bundle references and collation
170
+
171
+ - A bundle may only reach outside itself through its `refs.md`. Each entry names the target
172
+ bundle, the reason, and optionally the specific note.
173
+ - **Collation protocol** (also in `skills/query/SKILL.md`): to answer a question spanning bundles A and B:
174
+ 1. Resolve in A. Note where A's `refs.md` points to B.
175
+ 2. Open B **through its own index.md** — never jump straight to a B note from A's text.
176
+ 3. Join the two answers in the response, not in the files.
177
+ Parallelizable: agent 1 handles A, agent 2 handles B, results merged by the orchestrator.
178
+ - Inline links from a note in A directly into a note in B are a lint error. Notes may say
179
+ "see refs → <bundle>", nothing more specific. This is what keeps bundles independent.
180
+
181
+ ## 5. Ingestion
182
+
183
+ Each bundle declares its sources in `sources.yaml`:
184
+
185
+ ```yaml
186
+ sources:
187
+ - type: folder # local disk
188
+ path: /abs/path/to/project-x
189
+ - type: web
190
+ urls:
191
+ - https://example.com/design-doc
192
+ # Confluence and ADO are listed for the record but ingested by the agent
193
+ # via MCP/CLI, not by scripts — see "scripting philosophy" below.
194
+ - type: confluence
195
+ space: PROJX
196
+ - type: ado
197
+ org: myorg
198
+ project: ProjectX
199
+ ```
200
+
201
+ `khb ingest <bundle>` is **one flat phase**: it walks every scripted source, extracts what it
202
+ can, and writes `raw/<type>/<file>.md` with a provenance header. It does not interpret
203
+ content — that is §5b. Re-running is incremental: a source whose content hash is unchanged
204
+ and whose `raw/` copy still exists is skipped; `--force` re-acquires.
205
+
206
+ **A source is identified by its bytes, not its path.** When a file turns up at an unseen
207
+ path, khb looks for a row with the same hash whose own path has since vanished; exactly one
208
+ match means the file moved, and that row is re-pointed at the new path — keeping its `raw/`
209
+ file (never renamed, because concepts cite it) and its `curated` value, and correcting the
210
+ raw file's `source:` header. Without this, moving a file reads as a deletion plus an
211
+ unrelated arrival: a duplicate raw file, a duplicate row with an empty `curated`, and
212
+ eventually a duplicate concept for material already cataloged. A source's `raw/` filename is
213
+ likewise fixed for life, so a file that moves and *then* changes re-extracts over the same
214
+ file rather than stranding the one its citations point at.
215
+
216
+ Two look-alikes are deliberately excluded, since both would rewire provenance on a guess: a
217
+ **copy** (the twin's path still exists — two real sources, both ingested) and an **ambiguous**
218
+ match (several vanished rows share the hash). Each is reported and left to judgement.
219
+
220
+ The bundle argument is optional. With none, ingest targets `default` and scaffolds it if the
221
+ hub has no such bundle, so a hub with zero bundles still has a landing path. Only that name
222
+ is auto-created — an explicit name that doesn't resolve is an error, not a scaffold request.
223
+ `default` is a holding area, not a tier: its contents are cataloged like any bundle's. Its
224
+ material moves only when a human says which bundle should own it — a topic emerging inside
225
+ it is not itself a reason to move anything.
226
+
227
+ Every raw file carries its origin, so a lossy extraction is always recoverable:
228
+
229
+ ```yaml
230
+ ---
231
+ source: /abs/path/to/original.pdf
232
+ fetched: 2026-07-23T09:14:02Z
233
+ sha256: db2ee470c95d
234
+ extract_tool: tesseract.js
235
+ quality: low # high = real text; low = OCR or transcript, verify against source
236
+ ---
237
+ ```
238
+
239
+ **Scripting philosophy: script every deterministic conversion, script no API wrappers.**
240
+ Folder, files and web are scripted, as is every extractor (§6). Confluence, ADO and git
241
+ hosts are reached through their MCP servers or official CLIs by the agent, which writes into
242
+ the same `raw/` shape — no API wrapper code to maintain here. If agent-free refresh of one
243
+ of those ever becomes a real need, promote it to a script then.
244
+
245
+ ## 5b. Catalog
246
+
247
+ Turning `raw/` into concept docs is a separate, agent-only step — `skills/catalog/SKILL.md`,
248
+ with no CLI command, because nothing about it is mechanical. Per bundle: read each raw file,
249
+ split it into concepts, give each OKF frontmatter, link them, register them in `index.md`,
250
+ and fill the `curated` column of `log.md`. Parallelized by fanning cheap subagents over the
251
+ raw files, with one hard rule — subagents write concept docs, the orchestrator alone writes
252
+ `index.md`, `log.md` and `refs.md`.
253
+
254
+ ## 6. Extraction
255
+
256
+ Extraction is deterministic, so `khb` owns all of it. Common formats use bundled pure-JS
257
+ libraries with no system install; results are cached hub-wide by content hash
258
+ (`inbox/extracted/<sha256>.md`) and reused across bundles. Nothing here contacts a model:
259
+ tesseract and whisper are local binaries, expensive in CPU but reproducible, which is what
260
+ puts them on the CLI side of the §Division-of-labor line.
261
+
262
+ | Format | Tool | Deps | Quality |
263
+ |---|---|---|---|
264
+ | PDF | `unpdf` (pdf.js), `pdftotext` if present | bundled | high |
265
+ | DOCX | `mammoth`, `pandoc` if present | bundled | high |
266
+ | ODT, PPTX | `fflate` + XML | bundled | high |
267
+ | XLSX | `fflate` → one markdown table per sheet | bundled | high |
268
+ | scanned PDF | `pdfium` + `tesseract.js` (WASM) | opt-in, ~75 MB | low |
269
+ | Images (png/jpg/webp/tif) | `tesseract.js` | opt-in, ~75 MB | low |
270
+ | Audio, video | `whisper` / `faster-whisper` | opt-in, pip | low |
271
+
272
+ Missing optional deps degrade to a ledger row with an empty `raw` and a printed install
273
+ hint — never to a failed run. `quality: low` output is a standing invitation for the catalog
274
+ pass to re-read the original: a vision read of a chart or a scanned table recovers what OCR
275
+ drops, and rewrites the raw file with `extract_tool: claude-vision`.
276
+
277
+ ## 7. Lint
278
+
279
+ `khb lint` enforces (details in `skills/lint/SKILL.md`):
280
+ - every bundle has `index.md`, `refs.md`, `sources.yaml`
281
+ - every bundle is registered in `outer.index.md`; nothing in `outer.index.md` is dangling
282
+ - every concept doc is listed in an index and carries OKF frontmatter (`type` required)
283
+ - `refs.md` targets exist; no cross-bundle inline links from concept docs
284
+ - index files contain links only (routing, not content)
285
+
286
+ ## 8. Visualizer
287
+
288
+ `khb visualize [--port N]` scans `outer.index.md`, every bundle `index.md`/`refs.md`, and
289
+ every concept doc's markdown links, and serves an interactive graph from a local server —
290
+ `aliases: vis, viz` (`-v` is taken by `khb --version`). Two zoom levels: bundles as nodes
291
+ with refs as directed edges (note counts as node size), and — click a bundle — its
292
+ concepts as nodes with the markdown links between them as edges. Click a concept to open
293
+ a panel that fetches its full body from `/api/file`. A refresh button rescans the hub and
294
+ refetches the graph over `/api/graph`, for watching a hub change live during a catalog
295
+ pass.
296
+
297
+ The inner view is **clustered by folder, not free-floating**: each concept is anchored to
298
+ the region of its top-level subdirectory (`tables/`, `notes/`, …), drawn as a labelled
299
+ hull, and links that cross folders pull far more weakly than links inside one. The
300
+ bundle's own organisation is therefore the visible structure, and the graph reads as
301
+ traffic between regions instead of a single hairball. Deeper nesting collapses into its
302
+ top-level folder — a handful of labelled regions beats one region per directory.
303
+
304
+ The canvas **pans and zooms** (wheel to zoom at the cursor, drag the background to pan,
305
+ `fit` / `F` to reframe). The layout is settled before the first paint and then framed by a
306
+ fit pass with a floor on the zoom, so a small hub opens zoomed in rather than as three dots
307
+ in an empty canvas, and a large one opens whole.
308
+
309
+ The outer view seats bundles on a ring **just wide enough to hold them side by side**, with
310
+ short-range repulsion and a firm pull to the centre: the hub opens as one compact group
311
+ that reads at a glance, and zoom — not distance — is what makes it usable.
312
+
313
+ Labels are drawn in **screen space at a fixed size**, not in world space, and any label
314
+ whose box would collide with one already drawn is dropped — larger nodes claim their name
315
+ first, and the hovered node always keeps its own. Text therefore stays legible and sparse
316
+ at every zoom level instead of piling into an unreadable puddle; zooming in reveals the
317
+ labels that were culled. A node's label is its front-matter `title`, clipped short; the
318
+ untruncated title and full path appear in the panel when it is clicked, and in the hover
319
+ strip at the bottom.
320
+
321
+ A concept's `type` is encoded as **colour**, never as sub-text, and is spelled out in words
322
+ in the hover strip and the panel chip — there is no legend and no shape vocabulary to
323
+ learn. Hovering a node dims everything it isn't linked to. The top bar is deliberately
324
+ thin: back, fit, a dark/light toggle remembered in `localStorage`, and refresh. Every
325
+ colour the canvas draws comes from the active theme so the graph and the surrounding chrome
326
+ stay in step.
327
+
328
+ Folder regions are laid out on an **ellipse sized from the folders themselves** — each
329
+ folder's disc grows with its file count, and the ring is only wide enough for neighbouring
330
+ discs to clear each other — so two folders sit side by side rather than a screen and a half
331
+ apart. The fit pass has a zoom ceiling but no floor: a large graph zooms out until it is
332
+ whole rather than opening cropped.
333
+
334
+ Unpinned, it binds a random free port (and picks another if `--port N` is already taken)
335
+ and opens the URL in the default browser; `--no-open` just prints it. The page heartbeats
336
+ the server and beacons on unload, so the server exits on its own once the browser tab
337
+ closes rather than lingering as a background process.
338
+
339
+ ## 9. Agent workflow summary
340
+
341
+ ```
342
+ build: sources.yaml → khb ingest → raw/*.md (+ provenance, + log.md row)
343
+ → catalog (agent) → concept docs + index.md entries + curated column
344
+ → khb lint
345
+
346
+ query: question → AGENTS.md → outer.index.md → bundle/index.md → concept docs
347
+ ↘ (spanning?) refs.md → other bundle via ITS index
348
+ ↘ (durable synthesis?) propose a new concept → on confirm,
349
+ write it, link it both ways, index it, log it
350
+ ```
351
+
352
+ The query arm writing back is what makes the hub denser with use rather than merely larger:
353
+ a question answered by joining two concepts leaves that join behind for the next one. It is
354
+ always proposed and never silent — see `skills/query/SKILL.md`.
@@ -0,0 +1,156 @@
1
+ # Frequently Asked Questions
2
+
3
+ ## What is KHB?
4
+
5
+ KHB (Knowledge Hub Builder) is a local, markdown-based system for building and maintaining
6
+ a bundle-of-bundles knowledge base with an AI agent.
7
+
8
+ The `khb` CLI is the supporting tool. It handles deterministic work such as extraction,
9
+ file plumbing, validation, visualization, and export. The bundled workflow skills tell an
10
+ AI agent—such as Claude, Codex, Gemini, or another compatible agent—how to ingest, catalog,
11
+ query, and maintain the knowledge. The agent follows those skills and orchestrates the
12
+ overall workflow.
13
+
14
+ The hub itself remains an ordinary folder of markdown files, indexes, source declarations,
15
+ and provenance records. The CLI holds no knowledge and does not call a model.
16
+
17
+ ## What are the main KHB workflow stages?
18
+
19
+ KHB has three main stages:
20
+
21
+ 1. **Ingest** converts declared sources into provenance-bearing markdown under
22
+ `bundles/<bundle>/raw/`.
23
+ 2. **Catalog** interprets one bundle's raw material, creates canonical concept documents,
24
+ and updates that bundle's index and ledger.
25
+ 3. **Query** routes a question through the indexes and answers from curated concept
26
+ documents.
27
+
28
+ Ingest is mechanical and local. Catalog and query require an agent because they involve
29
+ judgement.
30
+
31
+ ## Does cataloging move everything into one main bundle?
32
+
33
+ No. Ingest and catalog operate on the same owner-defined bundle. For example, material
34
+ ingested into `client-a` is cataloged into concepts inside `bundles/client-a/`.
35
+
36
+ Bundles are units of ownership, such as a person, team, project, or client. They are not
37
+ subject categories, and an agent must not create, split, merge, or reorganize them without
38
+ explicit instruction.
39
+
40
+ ## What happens when there are multiple bundles?
41
+
42
+ Cataloging handles exactly one bundle at a time. Name the bundle in the request, such as:
43
+
44
+ > Catalog the `client-a` bundle.
45
+
46
+ If multiple bundles are possible and none was identified, the agent asks which bundle to
47
+ use instead of guessing.
48
+
49
+ During a query, the agent starts at `outer.index.md` and selects one bundle. If the
50
+ question genuinely spans bundles, it follows `refs.md`, enters each additional bundle
51
+ through its own `index.md`, and combines the results only in the answer.
52
+
53
+ ## What does `khb ingest [bundle] [--force]` do?
54
+
55
+ The command reads the selected bundle's `sources.yaml`, acquires every supported declared
56
+ source, extracts it into markdown under `raw/`, and updates `log.md`.
57
+
58
+ It does not summarize, label, organize, or create concepts. Unchanged content hashes are
59
+ skipped by default; `--force` reacquires everything.
60
+
61
+ When no bundle is named, KHB uses the `default` bundle and creates it if necessary. An
62
+ explicitly named bundle must already exist.
63
+
64
+ ## Should I run ingestion manually or ask an agent?
65
+
66
+ Either works. Running `khb ingest client-a` manually performs the deterministic extraction.
67
+ You can instead ask:
68
+
69
+ > Ingest the `client-a` bundle.
70
+
71
+ The agent invokes the same command, checks its summary, handles supported authenticated
72
+ sources, and reports anything pending. Manual execution is useful in scripts or when only
73
+ extraction is needed.
74
+
75
+ ## Where are ingestion sources declared?
76
+
77
+ Each bundle has its own declaration file:
78
+
79
+ ```text
80
+ bundles/<bundle>/sources.yaml
81
+ ```
82
+
83
+ For example:
84
+
85
+ ```yaml
86
+ sources:
87
+ - type: folder
88
+ path: /absolute/path/to/documents
89
+ - type: files
90
+ paths:
91
+ - /absolute/path/to/one.pdf
92
+ - /absolute/path/to/two.xlsx
93
+ - type: web
94
+ urls:
95
+ - https://en.wikipedia.org/wiki/Example
96
+ - type: confluence
97
+ space: PROJX
98
+ ```
99
+
100
+ The `sources` list may contain one or many source declarations of different types.
101
+
102
+ ## When are sources added to `sources.yaml`?
103
+
104
+ Sources are declared before ingestion. The usual sequence is:
105
+
106
+ 1. Create or select the owner bundle.
107
+ 2. Add its source locations to `sources.yaml`.
108
+ 3. Run ingestion.
109
+ 4. Catalog the resulting raw material.
110
+
111
+ Declaring a source records where material should come from; it does not copy anything by
112
+ itself.
113
+
114
+ ## What if I ask for ingestion without identifying the sources?
115
+
116
+ The ingest workflow inspects the bundle's existing `sources.yaml` and asks which files,
117
+ folders, URLs, or services to ingest. It includes existing declarations so they can be
118
+ confirmed or replaced.
119
+
120
+ The agent does not infer sources from nearby files, change `sources.yaml`, or run ingestion
121
+ until the user answers.
122
+
123
+ ## Can one bundle ingest Wikipedia, Confluence, and Azure DevOps material?
124
+
125
+ Yes. Public pages such as Wikipedia can be declared as `web` sources and fetched by
126
+ `khb ingest`.
127
+
128
+ Authenticated systems such as Confluence and Azure DevOps are also declared in
129
+ `sources.yaml`, but the KHB CLI does not authenticate to them. The agent pulls their data
130
+ through an available MCP connector or official CLI, writes the same provenance-bearing
131
+ markdown shape under `raw/`, and updates `log.md`. If access is unavailable, the agent
132
+ reports the blocked source.
133
+
134
+ ## Does cataloging delete the raw files?
135
+
136
+ No. Cataloging reads raw files and creates canonical concept documents elsewhere in the
137
+ same bundle. The raw material remains derived evidence and is not used directly to answer
138
+ queries.
139
+
140
+ The bundle's `log.md` connects each source and raw file to the concept documents curated
141
+ from it.
142
+
143
+ ## Can a query create or update concepts?
144
+
145
+ Yes, but never silently. If answering produces durable, reusable knowledge that existing
146
+ concepts do not cover, the agent offers to:
147
+
148
+ - fold the knowledge into an existing concept; or
149
+ - create a new concept and link it to the concepts from which it was derived.
150
+
151
+ The agent explains what it would write and where, then waits for confirmation. After
152
+ approval, it updates the concept links, `index.md`, and `log.md`, and runs `khb lint`.
153
+ One-off conversational answers are not saved.
154
+
155
+ If a query reveals that an existing concept is wrong or stale, the agent similarly offers
156
+ to repair it in place before making any change.
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@msareen/knowledge-hub-builder",
3
+ "version": "0.1.3",
4
+ "description": "Knowledge Hub Builder — a bundle-of-bundles knowledge base you build with an agent",
5
+ "keywords": ["knowledge-base", "okf", "agent", "wiki", "bundles", "claude", "codex", "rag"],
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/msareen/knowledge-hub-builder.git"
10
+ },
11
+ "homepage": "https://github.com/msareen/knowledge-hub-builder#readme",
12
+ "type": "module",
13
+ "bin": {
14
+ "khb": "./scripts/cli.ts"
15
+ },
16
+ "files": [
17
+ "scripts/",
18
+ "skills/",
19
+ "templates/",
20
+ ".bundle_template/",
21
+ ".agents/skills/",
22
+ ".claude/skills/",
23
+ "AGENTS.md",
24
+ "CLAUDE.md",
25
+ "SPEC.md",
26
+ "README.md",
27
+ "document/"
28
+ ],
29
+ "engines": {
30
+ "bun": ">=1.0.0"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "khb": "bun scripts/cli.ts",
37
+ "lint": "bun scripts/cli.ts lint",
38
+ "visualize": "bun scripts/cli.ts visualize",
39
+ "new-bundle": "bun scripts/cli.ts new-bundle",
40
+ "export": "bun scripts/cli.ts export",
41
+ "ingest": "bun scripts/cli.ts ingest"
42
+ },
43
+ "dependencies": {
44
+ "@hyzyla/pdfium": "^2.1.13",
45
+ "fflate": "^0.8.3",
46
+ "mammoth": "^1.12.0",
47
+ "sharp": "^0.35.3",
48
+ "tesseract.js": "^7.0.0",
49
+ "unpdf": "^1.6.2",
50
+ "yaml": "^2.4.0"
51
+ }
52
+ }
package/scripts/cli.ts ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bun
2
+ // khb — the CLI. Subcommands are loaded lazily: `init` must run before a hub exists,
3
+ // so nothing that resolves a hub may be imported at module scope.
4
+ import { version } from "./lib/paths";
5
+
6
+ const COMMANDS: Record<string, { load: () => Promise<unknown>; help: string }> = {
7
+ init: { load: () => import("./init"), help: "khb init [dir] create a hub here (or in dir)" },
8
+ upgrade: { load: () => import("./init"), help: "khb upgrade refresh this hub's contract docs" },
9
+ "new-bundle": { load: () => import("./new-bundle"), help: 'khb new-bundle <name> ["scope"] scaffold a bundle + register it' },
10
+ ingest: { load: () => import("./ingest/index"), help: "khb ingest [bundle] [--force] acquire + extract declared sources → raw/ (default: 'default')" },
11
+ lint: { load: () => import("./lint"), help: "khb lint validate the hub against skills/lint/SKILL.md" },
12
+ visualize: { load: () => import("./visualize"), help: "khb visualize [--port N] [--no-open] serve the live bundle graph in your browser; aliases: vis, viz" },
13
+ export: { load: () => import("./export"), help: "khb export <bundle> [dest] standalone copy of one bundle" },
14
+ };
15
+
16
+ // Short forms that just resolve to a canonical command above — kept out of COMMANDS
17
+ // itself so help text lists each command once. `-v` is taken by --version, so
18
+ // `visualize` gets word-shaped aliases instead of a letter one.
19
+ const ALIASES: Record<string, string> = { vis: "visualize", viz: "visualize" };
20
+
21
+ const argv = process.argv.slice(2);
22
+
23
+ // --hub <dir> is global: strip it here so subcommands never see it, and hand it to
24
+ // util.ts through the environment (same channel as $KHB_HUB).
25
+ const hubAt = argv.indexOf("--hub");
26
+ if (hubAt >= 0) {
27
+ const dir = argv[hubAt + 1];
28
+ if (!dir) {
29
+ console.error("--hub needs a directory");
30
+ process.exit(1);
31
+ }
32
+ process.env.KHB_HUB = dir;
33
+ argv.splice(hubAt, 2);
34
+ }
35
+
36
+ const cmd0 = argv.shift();
37
+ const cmd = cmd0 && ALIASES[cmd0] ? ALIASES[cmd0] : cmd0;
38
+
39
+ if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
40
+ console.log(`khb ${version()} — Knowledge Hub Builder\n`);
41
+ console.log(`khb is the supporting tool: it handles deterministic extraction, file plumbing,`);
42
+ console.log(`validation, and export. Your AI agent — Claude, Codex, Gemini, or another`);
43
+ console.log(`compatible agent — follows the workflow skills and orchestrates the knowledge work.\n`);
44
+ for (const c of Object.values(COMMANDS)) console.log(" " + c.help);
45
+ console.log(`\nGlobal: --hub <dir> operate on that hub instead of searching upward from cwd`);
46
+ console.log(`Docs: https://github.com/msareen/knowledge-hub-builder`);
47
+ process.exit(0);
48
+ }
49
+
50
+ if (cmd === "--version" || cmd === "-v") {
51
+ console.log(version());
52
+ process.exit(0);
53
+ }
54
+
55
+ const entry = COMMANDS[cmd];
56
+ if (!entry) {
57
+ console.error(`Unknown command: ${cmd}`);
58
+ console.error(`Try: khb help`);
59
+ process.exit(1);
60
+ }
61
+
62
+ // Subcommand modules parse process.argv.slice(2) themselves — reshape it so they see
63
+ // their own arguments and not the subcommand name.
64
+ process.argv = [process.argv[0], process.argv[1], ...argv];
65
+ process.env.KHB_SUBCOMMAND = cmd;
66
+ await entry.load();
@@ -0,0 +1,42 @@
1
+ // khb export <bundle> [dest] — export a bundle as a standalone, shareable unit.
2
+ // Bundles stay lean in the hub (common patterns live at hub root); export injects those
3
+ // patterns so the exported folder works alone with any agent.
4
+ import { cpSync, writeFileSync, mkdirSync, existsSync, readFileSync } from "node:fs";
5
+ import { HUB, bundleDir, join } from "./lib/util";
6
+ import { detail, totalElapsed } from "./lib/log";
7
+
8
+ const [name, destArg] = process.argv.slice(2);
9
+ if (!name) { console.error("Usage: khb export <bundle> [dest]"); process.exit(1); }
10
+
11
+ const src = bundleDir(name);
12
+ const dest = destArg ?? join(HUB, "export", name);
13
+ if (existsSync(dest)) { console.error(`Destination exists: ${dest}`); process.exit(1); }
14
+
15
+ console.log(`khb export → ${name}`);
16
+ detail(`from: ${src}`);
17
+ detail(`to: ${dest}`);
18
+
19
+ mkdirSync(dest, { recursive: true });
20
+ detail(`copying bundle/ …`);
21
+ cpSync(src, join(dest, "bundle"), { recursive: true });
22
+
23
+ // Inject the hub's package-owned copies (kept current by `khb upgrade`). AGENTS.md and
24
+ // skills/ are canonical; the shims/adapters make them discoverable by Claude and Codex.
25
+ for (const item of ["AGENTS.md", "CLAUDE.md", "skills", ".agents/skills", ".claude/skills"]) {
26
+ const target = join(dest, item);
27
+ mkdirSync(join(target, ".."), { recursive: true });
28
+ cpSync(join(HUB, item), target, { recursive: true });
29
+ detail(`copying ${item}`);
30
+ }
31
+
32
+ // standalone router: one-bundle outer index
33
+ detail("writing outer.index.md + README.md");
34
+ const scope = (readFileSync(join(HUB, "outer.index.md"), "utf8")
35
+ .split("\n").find((l) => l.includes(`[${name}]`)) ?? "").split("|")[2]?.trim() ?? "";
36
+ writeFileSync(join(dest, "outer.index.md"),
37
+ `# outer.index — exported bundle\n\n| Bundle | Scope | Route here when |\n|---|---|---|\n| [${name}](bundle/index.md) | ${scope} | always — single-bundle export |\n`);
38
+
39
+ writeFileSync(join(dest, "README.md"),
40
+ `# ${name} (exported KHB bundle)\n\nExported: ${new Date().toISOString()}\nOrigin: KHB bundle-of-bundles repo.\n\nStandalone unit: start at AGENTS.md → outer.index.md → bundle/index.md.\nWorkflow protocols (query, ingest, lint, …) live in skills/<name>/SKILL.md and are discoverable by Claude and Codex.\nNote: refs.md entries pointing at other bundles will not resolve here.\n`);
41
+
42
+ console.log(`\nExported to ${dest} in ${totalElapsed()} (bundle + agent contracts, skills, single-bundle router)`);