How it works
-
From clone to live docs in three steps
-
No framework expertise required. The template is designed for teams who want to focus on content, not tooling.
+
From repo to portal in three steps
+
The same path works for a Markdown file or a full docs site.
1
-
Clone and configure
-
Clone this repo, update kb-docs.json with your slug and name, and set site_name in mkdocs.yml. That's the only configuration you need.
+
Pick a type
+
Add one workflow for single-page docs, or make your site's build headless and describe it in kb-docs.json.
2
-
Write your content
-
Replace the starter Markdown files in docs/ with your own content. Add pages by creating .md files — navigation is generated automatically from frontmatter.
+
Publish a release
+
The publish action validates your docs against the contract rules and attaches kb-docs.tar.gz to the release.
3
-
Tag a release
-
Push a version tag (e.g. v1.0.0). The GitHub Actions workflow builds your docs, packages them, and attaches the artefact to the release — ready for the knowledge base to consume.
+
Register
+
Open a PR adding { "repo": "owner/name" } to the deployment's registry. Everything else is read from your manifest.
@@ -132,20 +133,20 @@ content: |
Ready to publish your documentation?
- Read the template docs to get started, or visit the full user guide for setup and registration.
+ Start with the onboarding guide, or read how to run and embed the Knowledge Base.
diff --git a/docs/index.md b/docs/index.md
index 77d42c2..cd16380 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -20,15 +20,19 @@ At build time it:
4. Produces a single static `dist/`, served by nginx in Docker or embedded into a
host application as a web fragment.
-The output makes no third-party requests at runtime.
+The output makes no third-party requests at runtime. This guide describes release
+**v1.3.1**.
## Who this guide is for
| You want to… | Start with |
|---|---|
-| Publish your team's documentation to the Knowledge Base | Publishing your docs |
-| Run your own Knowledge Base deployment | Running a Knowledge Base |
-| Embed the Knowledge Base inside a host application | Embedding |
+| Understand how the portal works and what readers see | [How it works](kb/how-it-works.md) |
+| Publish your team's documentation to the Knowledge Base | [Choosing how to onboard](kb/onboarding.md) |
+| Check your docs against the contract | [Contract rules and the checker](kb/rules.md) |
+| Run your own Knowledge Base deployment | [Registry](kb/registry.md), [Building](kb/building.md), [Deployment](kb/deployment.md) |
+| Embed the Knowledge Base inside a host application | [Web fragment embedding](kb/embedding.md) |
+| Let an AI agent onboard a repo | [Agent skill](kb/agent-skill.md) |
| Start a new docs site quickly | [Using the Docs Template](template.md) |
## About this site
diff --git a/docs/kb/actions.md b/docs/kb/actions.md
new file mode 100644
index 0000000..c98adf7
--- /dev/null
+++ b/docs/kb/actions.md
@@ -0,0 +1,102 @@
+---
+title: GitHub Actions
+order: 25
+section: Publish Your Docs
+---
+
+# GitHub Actions
+
+Three reusable composite actions live in `AbsaOSS/knowledge-base/actions/`. They share
+one pinned dependency tree, so your repo needs no toolchain, `package.json` or lockfile
+for them. They run on Node 20.
+
+All three also take `npm-registry`, `npm-token`, `node-mirror` and `node-mirror-token`
+for runners without public internet; see [Private networks](private-networks.md).
+
+## publish-docs
+
+Validates a built headless site against the [contract](rules.md), packs
+`kb-docs.tar.gz`, and attaches it to a release (replacing an existing asset of that
+name).
+
+```yaml
+- uses: AbsaOSS/knowledge-base/actions/publish-docs@v1
+ with:
+ manifest: kb-docs.json
+ dist: dist
+```
+
+| Input | Required | Default | Description |
+|---|---|---|---|
+| `manifest` | no | `kb-docs.json` | Path to the manifest, relative to the workspace. |
+| `dist` | no | `dist` | The built headless output. With one app, that app's directory; with several, a directory holding one subdirectory per slug. |
+| `release-tag` | no | triggering release, else the latest | Release to attach to. |
+| `github-token` | no | `{% raw %}${{ github.token }}{% endraw %}` | Needs `contents: write`. |
+| `notify-repo` | no | empty | `owner/name` of a deployment repo to notify after publishing. Needs `notify-token`; skipped silently if either is unset. |
+| `notify-token` | no | empty | Token with `contents: write` on `notify-repo` only. The default token cannot dispatch to another repository. |
+| `npm-registry`, `npm-token`, `node-mirror`, `node-mirror-token` | no | empty | See [Private networks](private-networks.md). |
+
+| Output | Description |
+|---|---|
+| `slugs` | Comma-separated slugs published. |
+| `count` | Number of apps in the artifact. |
+| `artifact` | Absolute path of the packed `kb-docs.tar.gz`. |
+| `release-tag` | Tag of the release it was uploaded to. |
+
+With `notify-repo` and `notify-token` set, a successful publish fires a
+`repository_dispatch` of type `kb-docs-published` carrying `{ repo, tag, slugs }` at the
+deployment repo, so it rebuilds without waiting for its schedule. A failed notify never
+fails the publish. See [Deployment](deployment.md#rebuilding-when-a-docs-repo-publishes).
+
+## check-docs
+
+Runs the same checks on a pull request without packing or uploading anything. Findings
+are annotated by rule ID and summarised on the run. Add it next to the publish workflow
+with the same build step and `dist`.
+
+```yaml
+on: pull_request
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: npm ci && npm run build -- --headless
+ - uses: AbsaOSS/knowledge-base/actions/check-docs@v1
+ with:
+ manifest: kb-docs.json
+ dist: dist
+```
+
+| Input | Required | Default | Description |
+|---|---|---|---|
+| `manifest` | no | `kb-docs.json` | Path to the manifest. |
+| `dist` | no | `dist` | The built headless output, as for `publish-docs`. |
+| `strict` | no | `false` | `"true"` also fails on warnings. Errors always fail. |
+| `npm-registry`, `npm-token`, `node-mirror`, `node-mirror-token` | no | empty | See [Private networks](private-networks.md). |
+
+| Output | Description |
+|---|---|
+| `errors` | Number of error findings. |
+| `warnings` | Number of warning findings. |
+
+## publish-single-page-docs
+
+Renders Markdown into headless HTML and publishes it. See [Single-page docs](single-page.md).
+
+| Input | Required | Default | Description |
+|---|---|---|---|
+| `docs` | yes | — | YAML or JSON list of doc definitions (`md`, `title`, `description`, `slug`, optional `icon`, `tags`). |
+| `release-tag` | no | triggering release, else the latest | Release to attach to. |
+| `github-token` | no | `{% raw %}${{ github.token }}{% endraw %}` | Needs `contents: write`. |
+| `npm-registry`, `npm-token`, `node-mirror`, `node-mirror-token` | no | empty | See [Private networks](private-networks.md). |
+
+Outputs: `slugs`, `count`, `artifact`, `release-tag`, as for `publish-docs`.
+
+## Versioning
+
+Pin to the floating major tag (`@v1`), not a branch. Pushing a `vMAJOR.MINOR.PATCH`
+tag moves the major tag to it. A breaking contract change is a new major (`@v2`) with a
+`kbVersion` bump, so repos pinned to `@v1` keep publishing against the contract they
+were written for.
diff --git a/docs/kb/agent-skill.md b/docs/kb/agent-skill.md
new file mode 100644
index 0000000..3772706
--- /dev/null
+++ b/docs/kb/agent-skill.md
@@ -0,0 +1,51 @@
+---
+title: Agent skill
+order: 27
+section: Publish Your Docs
+---
+
+# The `kb-docs-add` agent skill
+
+An agent skill that onboards a repo's docs for you. It follows the open `SKILL.md`
+format, so it works in Claude Code, GitHub Copilot and other readers.
+
+## Install
+
+```bash
+# interactive
+npx skills add AbsaOSS/knowledge-base --skill kb-docs-add
+
+# non-interactive, project scope, named agents
+npx skills add AbsaOSS/knowledge-base --skill kb-docs-add -a claude-code -a github-copilot -y
+
+# user scope
+npx skills add AbsaOSS/knowledge-base --skill kb-docs-add -g
+```
+
+Or copy the directory by hand:
+
+| Agent | Project | Personal |
+|---|---|---|
+| Claude Code | `.claude/skills/kb-docs-add/` | `~/.claude/skills/kb-docs-add/` |
+| GitHub Copilot | `.github/skills/kb-docs-add/` | `~/.copilot/skills/kb-docs-add/` |
+| Open spec | `.agents/skills/kb-docs-add/` | `~/.agents/skills/kb-docs-add/` |
+
+Then ask the agent to "add this repo's docs to the knowledge base".
+
+## Modes
+
+**Onboard.** It classifies the repo and writes the minimum:
+
+| It finds | Type | It writes |
+|---|---|---|
+| Markdown files, no site generator | single-page | one workflow file |
+| A docs site (MkDocs, Starlight, Docusaurus, Jekyll…) | packaged | `kb-docs.json`, the smallest change to make the build headless, one workflow file |
+| Docs hosted elsewhere | iframe | nothing; it explains the registry entry |
+
+It pins actions to `@v1`, checks slugs and descriptions against the limits, and tells
+you the two steps only you can do: publish a release and register the repo.
+
+**Audit.** For a repo that is already onboarded, it runs the contract checker from a
+scratch clone of the Knowledge Base, fixes each finding by [rule ID](rules.md) at its
+source (never in built output, never in a third-party theme), and reports what the
+checker cannot see, such as URLs built at runtime.
diff --git a/docs/kb/building.md b/docs/kb/building.md
new file mode 100644
index 0000000..48694a9
--- /dev/null
+++ b/docs/kb/building.md
@@ -0,0 +1,87 @@
+---
+title: Building
+order: 41
+section: Run a Knowledge Base
+---
+
+# Building
+
+Requires **Node.js 24 or newer**. A GitHub fetch needs `GITHUB_TOKEN` (or an
+authenticated `gh` CLI).
+
+```bash
+npm install
+npm run build:headless # hermetic: builds from the committed registry fixtures
+```
+
+## Build modes
+
+| Command | Source of apps |
+|---|---|
+| `npm run build` | Fetch GitHub Release artifacts |
+| `npm run build:headless` | Same, with web-fragment output |
+| `npm run build:local` | Build `localPath` entries from local checkouts |
+| `npm run build:local:headless` | Local, headless |
+| `npm run preview` | Serve the built `dist/` (Astro preview, port 4321) |
+| `npm run dev` | Astro dev server; needs artifacts in `apps/` already |
+| `npm run dev:fetch` | Fetch artifacts, then start dev |
+
+| Flag | Environment variable | Effect |
+|---|---|---|
+| `--headless` | `KB_HEADLESS=true` | Web-fragment output: `data-kb-headless="true"` on ``. Anything else, including unset, is standalone. A per-app `headless` in the registry wins. |
+| `--strict` | `KB_STRICT=true` | Production mode; see below. |
+| `--local` | — | A `repo` entry without a `localPath` is skipped with a warning. |
+
+Other environment variables:
+
+| Variable | Description |
+|---|---|
+| `GITHUB_TOKEN` | Auth for fetching Release artifacts. |
+| `KB_REGISTRY` | Registry file to build from. Default `apps.json`. See [Registry](registry.md). |
+
+The URL prefix `knowledge-base` is **not** configurable. nginx and the gateway route
+patterns hard-code the same string.
+
+### Strict mode
+
+`--strict` / `KB_STRICT=true` is for production. It rejects `prebuilt`, `localPath` and
+`optional` entries, an empty registry, any entry that yields no apps, and any artifact
+with an `error` finding from the [contract checker](rules.md). Warnings never fail a
+build. The registry committed in the Knowledge Base repository is a fixture and fails
+strict mode by design.
+
+## Pipeline
+
+1. Stage each artifact and install its apps into `apps/{slug}/`. Archives are extracted
+ safely: no traversal, absolute paths or links ([archive rules](manifest.md#the-archive)).
+2. Run the contract checks and log findings grouped by rule.
+3. Hoist inline `
+
+
+```
+
+`initializeWebFragments()` must run before anything renders a `
`. With
+piercing on, an earlier element fails with `portalHost is not a function` and falls
+back to client rendering.
+
+## Host router and fragment history
+
+`src` on `` decides how the fragment's history relates to the host's.
+
+| | `src` set — **unbound** | no `src` — **bound** |
+|---|---|---|
+| Fragment history | private stack inside the reframed iframe | the host's `window.history` |
+| Address bar | never moves; the host router hears nothing | moves to `/knowledge-base/…` per page; the host router gets a `popstate` |
+| Deep links, back/forward | none | work |
+| Host route needed | the one route the element sits on | a wildcard covering every fragment page: `/knowledge-base/**` |
+
+**Unbound** is the safe default when the host router must own the URL. `src` is read once
+when the element connects; re-create the element to show a different page.
+
+**Bound** gives fragment pages real URLs. The host route config must cover the whole
+prefix, otherwise the first click lands on the host's fallback route and destroys the
+fragment. In Angular:
+
+```ts
+// app.routes.ts
+{ path: 'knowledge-base', children: [{ path: '**', component: KnowledgeBasePage }] }
+
+// main.ts — before bootstrapApplication()
+import { initializeWebFragments } from 'web-fragments';
+initializeWebFragments();
+```
+
+Guards and resolvers on that route run on every fragment page. Document requests for
+`/knowledge-base/…` reach the host, so Angular SSR must render the shell for them; the
+wildcard route does that. An Angular host also needs `"outputMode": "server"` and
+`"externalDependencies": ["web-fragments/gateway", "web-fragments/gateway/node", "htmlrewriter"]`
+in its build target.
+
+## What to know when you publish docs
+
+Pages are re-hosted inside the host document, so [contract rules](rules.md) such as
+KB-HTML-008 (links to non-HTML files) and KB-JS-001 (page-relative `fetch()` URLs) exist
+specifically for embedded use. Navigation between pages is by Astro's client router, with
+view transitions.
diff --git a/docs/kb/how-it-works.md b/docs/kb/how-it-works.md
new file mode 100644
index 0000000..4186054
--- /dev/null
+++ b/docs/kb/how-it-works.md
@@ -0,0 +1,75 @@
+---
+title: How it works
+order: 10
+section: Knowledge Base
+---
+
+# How the Knowledge Base works
+
+The Knowledge Base is a build-time aggregator. A **registry** (`apps.json`) lists where
+each team's documentation comes from; the build collects it and produces one static
+`dist/`.
+
+```
+Browser ──► /knowledge-base/ landing catalog
+ /knowledge-base/{slug}/… each documentation app
+ /__wf/knowledge-base/… assets when embedded as a web fragment
+ │
+ ▼
+ nginx (Docker) ─or─ web-fragments gateway (embedded)
+ │
+ ▼
+ dist/ (static)
+```
+
+## The build, step by step
+
+1. **Obtain** each registered artifact: a GitHub Release asset (`kb-docs.tar.gz`), a
+ local checkout, or a prebuilt tarball or directory. See [Registry](registry.md).
+2. **Read the manifest** (`kb-docs.json`) inside each artifact. It declares one or
+ more apps with their slug, name, description, icon, tags and pages. See
+ [The manifest](manifest.md).
+3. **Check** every artifact against the [contract rules](rules.md) and log the findings.
+4. **Move inline scripts into files** so the site can be served with `script-src 'self'`.
+5. **Re-host every page**: rewrite URLs, wrap CSS, wrap the page in the masthead.
+6. **Generate the catalog** landing page.
+
+The output makes no third-party requests at runtime: the Inter typeface is self-hosted
+and mermaid is vendored into each single-page artifact.
+
+## Reading the portal
+
+- **Masthead** — shown on every page. It carries the branding and a *Library* link
+ plus the current app's navigation.
+- **Catalog** — the landing page at `/knowledge-base/`, one card per app showing its
+ name, description, icon and tags. Apps registered as an `iframe` entry carry an
+ **External** badge.
+- **App pages** — each app keeps its own sidebar, routing and internal navigation.
+ Single-page docs render in a centred reading column with no sidebar.
+- **Light only** — there is no dark mode and no theme toggle.
+
+## What happens to your HTML
+
+Artifacts come from other repositories, so their HTML is treated as untrusted input.
+When a page is re-hosted:
+
+- Every `href`, `src`, `action`, `formaction` and `poster` URL (and `srcset`, `url()`
+ in styles, and URL-bearing `` content) is rewritten to an absolute
+ `/knowledge-base/{slug}/…` path. A `` element is removed.
+- Each sub-app stylesheet is wrapped in the `kb-app` CSS cascade layer, below the
+ Knowledge Base's own rules, so a docs theme cannot restyle the masthead or catalog.
+ `!important` defeats this (KB-CSS-001 in [Contract rules](rules.md)).
+- Inline `on*` event handlers are removed, and so are a theme bootstrap script and a
+ `dark` class on ``/``.
+- Inline `