Expand cascade-knowledge-base plugin with new skills and richer workflows

Add ckb-ingest, ckb-lint, ckb-retrieve, ckb-teach-me, ckb-upgrade, and
ckb-index-external skills; update ckb-init's default skill set and
ckb-sync-changes to handle missing origin remotes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Michał Kopeć 2026-08-06 14:28:29 +02:00
parent e38a95aa5c
commit 0351a412fa
14 changed files with 2046 additions and 54 deletions

View file

@ -134,8 +134,8 @@ the report rather than silently patching the export.
### Step 7 — Validate the output bundle ### Step 7 — Validate the output bundle
Before reporting done, re-check the *generated* `outputs/okf/` bundle Before reporting done, re-check the *generated* `outputs/okf/` bundle
against OKF's own conformance criteria (the same shape of check `CLAUDE.md` against OKF's own conformance criteria (the same shape of conformance check
§5 step 1 runs against the source wiki): `ckb-lint` runs against the source wiki):
- Every non-reserved `.md` file has frontmatter with a non-empty `type`. - Every non-reserved `.md` file has frontmatter with a non-empty `type`.
- The root `index.md`'s frontmatter contains only `okf_version` (or is - The root `index.md`'s frontmatter contains only `okf_version` (or is
@ -168,7 +168,7 @@ Tell the user:
spec-valid, don't skip the directory entirely. spec-valid, don't skip the directory entirely.
- **A wiki page missing `type`:** per Step 3, use `type: unknown` and flag - **A wiki page missing `type`:** per Step 3, use `type: unknown` and flag
it in the report — this indicates the source wiki itself failed lint's it in the report — this indicates the source wiki itself failed lint's
conformance check (see `CLAUDE.md` §5 step 1), which is worth surfacing conformance check, which is worth surfacing
to the user rather than quietly masking it in the export. to the user rather than quietly masking it in the export.
- **A future `wiki/archived/` directory:** export it like any other - **A future `wiki/archived/` directory:** export it like any other
subdirectory (mirror the structure, apply the same per-file rules) — subdirectory (mirror the structure, apply the same per-file rules) —
@ -179,3 +179,8 @@ Tell the user:
arbitrary ordering when listing directory entries), sort filenames arbitrary ordering when listing directory entries), sort filenames
alphabetically wherever you're generating a bullet list or walking a alphabetically wherever you're generating a bullet list or walking a
directory, so re-runs are stable. directory, so re-runs are stable.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -145,3 +145,8 @@ The script prints, and you should summarize back to the user:
- **A wiki page with no H1:** falls back to a slugified filename as the - **A wiki page with no H1:** falls back to a slugified filename as the
title (e.g. `foo-bar.md` → "Foo Bar"), same fallback rule `ckb-export-okf` title (e.g. `foo-bar.md` → "Foo Bar"), same fallback rule `ckb-export-okf`
uses. uses.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -1,4 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Copyright 2026 Michał Kopeć
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Export wiki/ into an Astro + Starlight-consumable form at outputs/starlight/. """Export wiki/ into an Astro + Starlight-consumable form at outputs/starlight/.
Usage: Usage:

View file

@ -0,0 +1,437 @@
---
name: ckb-index-external
description: Walk every connector-backed libs/<name>/ (identified by a source.yaml declaring a live external source — SharePoint, Google Drive, a plain URL, or another connector) and build/refresh a self-contained generated index for it — index.md/entities/graph/log.md, mirroring wiki/'s own shape but scoped entirely to that one connector. Indexing goes beyond one page per document: for every document found, it also extracts the people, organizations, projects, decisions, systems, and processes that document actually discusses into their own thin, pointer-style entity pages (evidence back to the specific documents that mention them, not a second copy of wiki/'s synthesis), so the index supports "what do we know about X / what's the process for Y" lookups, not just "what documents exist at this source". The index can optionally be published to (and fetched from) a shared location — a git repo or a shared resource — declared in source.yaml, so most users just read an already-built index instead of re-scanning the live source. Read vs. write access to a given source is a per-user, local-only setting (libs/<name>/source.local.yaml, gitignored) — read-only by default, so one or two designated admins can maintain a source for everyone else. Never touches wiki/, never touches source.yaml, never touches a git-copy lib. Use when the user says "index external sources", "index libs", "refresh the external index", or "scan the SharePoint/Drive folder". This is the on-demand external-source workflow routed by CLAUDE.md/AGENTS.md, distinct from `ckb-ingest` (which processes raw/inbox/ into wiki/) and from a plain git-copy libs/<name>/ (a frozen clone, never touched by any skill).
---
# Index external sources skill
## Purpose
Give `libs/<name>/` a second population mode alongside the existing
git-copy one: a `libs/<name>/source.yaml` declares a *live* external
source — a SharePoint folder, a Google Drive folder, a plain URL, or
another connector — that the user doesn't want to fully mirror locally.
This skill walks that source and builds a small, self-contained index of
what's there, using the same `index.md`/`graph`/`log.md` shape `wiki/`
already uses, rooted at `libs/<name>/` instead.
The index holds two kinds of pages, and both matter for retrieval:
- **Document pages** — one per source item found (one per email, one per
file, one per SharePoint page), the original design. These answer "what
exists at this source".
- **Entity and process pages** — one per person, organization, project,
decision, system, or named process that those documents actually
*discuss*, extracted the same way `ckb-ingest` extracts entities into
`wiki/`, but kept deliberately thin here: a `tldr` plus a list of which
document(s) mention it and what they say, not a full synthesis. These
answer "what do we know about X" without forcing whoever's asking to
read every document's `tldr` by hand looking for a name. A document-only
index is fine for browsing a source but too narrow for actually
retrieving information out of it — that's what this second pass fixes.
This is deliberately **not** blended into the main `wiki/entities/` or
`wiki/graph/edges.json` — the generated index lives entirely inside its own
`libs/<name>/`, at the lowest cascade layer, the same way a git-cloned
KB's own files would. If something in it needs to override or correct what
the connector says, that's what writing the corrected version into `wiki/`
is for (cascade priority already covers that — `wiki/` always wins). Where
an extracted entity already has a full page in `wiki/entities/`, this
skill's own page for it stays thin and points there rather than
re-synthesizing — see Step 6.
Two refinements on top of that base design:
- **Shared, pre-built indexes.** `source.yaml` can declare *where the
already-built index lives* (a git repo, or a shared resource such as a
network path or another connector-reachable location). Every run checks
that location: if it already has an index, fetch it; if it doesn't yet,
a write-access user's run is what creates it there for the first time.
Either way, a read-only user just gets whatever's already there — they
never have to build the index from scratch themselves.
- **Read vs. write access, per user, per source.** Building/refreshing an
index from the live connector is gated by a **local, per-user** setting
(`libs/<name>/source.local.yaml`, never committed) — read-only by
default. This lets a team designate one or two people as the "admin" of
a given external source, while everyone else just consumes whatever
index the admin has already published, without needing their own
connector authorization or triggering redundant/conflicting rebuilds.
This skill only ever writes within a connector-backed `libs/<name>/` (never
`source.yaml` itself, never a git-copy lib, never anything under `wiki/`)
— and it never touches this repo's own git remote; pushing the resulting
changes is a separate, explicit step (see Step 8).
## Trigger phrases
Use this skill when the user says:
- "Index external sources"
- "Index libs"
- "Refresh the external index"
- "Scan the SharePoint/Drive folder" (or similarly names a specific
connector)
- "Make me the admin for `<source>`" / "give me write access to `<source>`"
/ "I don't want write access to `<source>` anymore" — a lighter-weight
request that only touches that one source's `source.local.yaml` (see
Configuration below) and doesn't necessarily trigger a full re-index in
the same turn unless the user also asks for one.
Do **not** use this skill for "Ingest" / "Sync the wiki" (that's
`ckb-ingest` — raw material into `wiki/`, unrelated to `libs/`), for
"Lint" (that's `ckb-lint`, which now also health-checks a connector's own
generated index, but doesn't build it in the first place), or for
answering a question against an index that already exists (that's
`ckb-retrieve`, which reads what this skill built and always verifies
against the underlying source before answering — this skill never answers
questions, only builds/refreshes the index).
## Configuration
### `libs/<name>/source.yaml` (shared, tracked in git, same for everyone)
```yaml
connector: sharepoint # sharepoint | google_drive | web | custom — open string, not a fixed enum
location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
description: "Finance team's shared reports folder" # optional
refresh_interval_days: 7 # optional — how often a write-access run should rebuild this source,
# and the freshness_window_days stamped on its generated pages.
# Omit to use the default of 30. Tune per source: a busy folder
# that changes daily deserves a shorter window than a quarterly
# reports archive that barely moves.
index: # optional — omit entirely for the original default: the index lives only
# here, in this repo, tracked by this repo's own git (nothing to fetch/publish)
store: git # git | shared — open string, where the built index is published to / fetched from
location: "https://github.com/org/finance-index-cache.git" # a git repo URL (store: git), or a
# filesystem/network path or connector-reachable location (store: shared)
ref: main # optional — branch, tag, or subpath hint within that store
```
### `libs/<name>/source.local.yaml` (per-user, per-machine, **never committed**)
```yaml
access: write # write | read — defaults to read if this file is absent or the field is omitted
```
This file is intentionally **not** carved out of `.gitignore` — it stays
caught by the existing `libs/*/*` catch-all, exactly like any other
unlisted file inside a connector-backed `libs/<name>/`. It's local
machine/user state, the same category as a personal `.env` — never synced,
never reviewed by anyone else, and it's what makes "read vs. write" a
per-user decision rather than a shared one baked into `source.yaml`.
Unlike `source.yaml`, this skill *may* create or edit `source.local.yaml`
on this user's behalf — but only when they explicitly ask to become (or
stop being) a given source's admin (e.g. "make me the admin for the
finance reports source" / "I don't want write access to X anymore"), never
as a side effect of just running "index external sources."
## How to run this skill
### Step 1 — Find connector-backed libs
Scan `libs/*/` for a `source.yaml`. A `libs/<name>/` with one is
connector-backed and in scope; a `libs/<name>/` without one is a git-copy
lib and stays completely untouched by this skill.
If no `libs/<name>/source.yaml` exists anywhere, tell the user there's
nothing to index yet and how to add one (a `source.yaml` with `connector`
and `location`), and stop — don't fabricate an index from nothing.
### Step 2 — Read the local access setting
Read `libs/<name>/source.local.yaml` if it exists. `access: write` means
this user is an admin for this one source; anything else (the field is
`read`, or the file doesn't exist at all) means read-only. This check is
**per connector-backed lib** — a user can be write-access for one source
and read-only for another.
### Step 3 — Check the shared index store, and fetch it if it exists
If `source.yaml` has an `index:` block, check whether `index.location`
already has a published index (per `index.store`: a `git` remote with
commits/a reachable ref, or a filesystem/network/`shared` location with
files already in it):
- **It exists** → fetch/pull it (`git pull`/clone for `git`, a plain file
copy for a filesystem/network `shared` path, or the matching connector
tool for a `shared` location that needs one) and use it to refresh
`libs/<name>/{index.md,entities/,graph/,log.md}`. Do this **regardless
of this user's access level** — reading the published index never
requires write access, and it's exactly what lets a read-only user skip
building anything themselves.
- **It's empty / nothing published yet** → there's nothing to fetch. Don't
treat this as an error; it's the normal first-time state before anyone
with write access has run this. Continue to Step 4 — whether that turns
into "create it" or "nothing to report" depends entirely on this user's
access level.
If no `index:` block is configured at all, skip this check — the index (if
any exists yet) already lives directly in `libs/<name>/`, same as before.
### Step 4 — Decide whether to build or refresh from the live connector
This is the one place access level actually changes behavior:
- **Read-only** (the common case, and the default for anyone who hasn't
set `access: write` locally): stop here, regardless of what Step 3
found. Never touch the live connector on a read-only user's behalf.
- If Step 3 fetched something (or one already lived in `libs/<name>/`
with no `index:` block): report what it covers.
- If nothing exists anywhere yet (Step 3 found the shared store empty,
or there's no `index:` block and `libs/<name>/` is empty too): say so
plainly, and suggest asking whoever administers this source to run it,
or setting `access: write` locally if this user is meant to be that
admin.
- **Write access**: always continue to Step 5, whether Step 3 fetched an
existing index (refresh it) or found the store empty (build the very
first version from scratch) — the two cases are handled identically
from here on; Step 8 is what actually creates the remote copy either
way.
Either way, compare the existing index's newest `last_updated` against
this source's `refresh_interval_days` (default 30) and say where it
stands. A read-only user needs to know they're reading a copy that's three
weeks past due so they can go ask the admin rather than quietly trusting
it; a write-access user is about to rebuild anyway, but "this was 40 days
stale" is worth saying, because a source that's routinely overdue is
either configured with too tight an interval or has nobody actually
maintaining it. Both are worth surfacing rather than silently correcting.
### Step 5 — Resolve the connector and enumerate/summarize documents
Resolve `connector` to whatever live tool is actually available this
session:
- `sharepoint` / a Microsoft-hosted location → search (`ToolSearch`) for a
connected Microsoft 365 MCP tool.
- `google_drive` → search for a connected Google Drive MCP tool.
- `web` / `custom` / anything else with a plain URL → `WebFetch`.
If the needed connector isn't authorized in this session (per the current
MCP auth state), **don't fail the whole run** — tell the user which
connector needs authorizing (via claude.ai connector settings, or `claude
mcp`/`/mcp` for non-claude.ai servers), skip that source, and continue with
any others.
For each document found at the location:
- Read enough content to write a real 1-2 sentence `tldr` when the
connector allows fetching content.
- If only metadata is available (listing only, no read access), say so
honestly in the `tldr` (e.g. "Metadata only — content not readable") and
set `confidence` low (e.g. 0.3) rather than fabricating a summary.
Create/update one entity page per document at
`libs/<name>/entities/<doc-slug>.md`:
```yaml
---
type: document
resource: <document's own URL/path>
tldr: ...
confidence: 0.30.9 # per the rule above
quality: ...
last_updated: YYYY-MM-DD
freshness_window_days: 30 # this source's refresh_interval_days, or 30 if unset — shorter than a
# typical wiki page, because external sources change without notice
retention: medium
---
```
### Step 6 — Extract entities and processes mentioned within each document
A document-only index tells a reader *what exists*, but not *what's known
about a given person, topic, or procedure* without reading every `tldr` by
hand. Close that gap the same way `ckb-ingest` extracts entities into
`wiki/`, but keep the result scoped and thin — this connector's own layer
is a retrieval index into its source documents, not a second knowledge
base competing with `wiki/`'s synthesis.
For each document processed in Step 5, identify what it actually
*discusses*, not just what it *is*: named people, organizations, projects,
decisions, systems, and processes/procedures (e.g. "VDI access request
process", "candidate profile approval") mentioned in its content. Skip
this for a document that's purely about itself with nothing else
extractable (a bare calendar acceptance with no discussion, for instance)
— not every document yields additional pages.
For each distinct entity/process found across the source's documents,
create or update one page at `libs/<name>/entities/<entity-slug>.md`,
using the same slug convention `wiki/entities/` would use for that name
(so `damien-gultig.md`, not a date-prefixed slug — this is what keeps
entity-page filenames from colliding with document-page filenames, which
are always date-prefixed per Step 5):
```yaml
---
type: person | organization | project | decision | system | concept | ... # same open field as wiki/'s schema
resource: /wiki/entities/<slug>.md # OPTIONAL — only if a full page for this entity already exists in wiki/
tldr: One sentence — who/what this is, and what these documents specifically show (not a full biography;
if wiki/ already has a full page, this tldr should say what these documents add, not restate it)
confidence: 0.30.9
quality: ...
last_updated: YYYY-MM-DD
freshness_window_days: 30 # or this source's refresh_interval_days, if set
retention: medium
---
```
Body: a short "Mentioned in" list, one bullet per document that discusses
this entity, linking to that document's own page
(`entities/<doc-slug>.md`) with a one-line note of what that specific
document says — enough for `ckb-retrieve` (or a human) to know exactly
which document to open for the full context, without needing to re-derive
it from scratch.
**If this entity already has a full page in `wiki/entities/`** (check the
cascade first, same as `ckb-ingest`'s Step 2), set `resource:` to that
page's path and keep this page's body to just the "Mentioned in" list —
don't re-synthesize what the wiki page already says. The wiki page remains
authoritative (cascade priority); this page's only job is pointing back to
*these specific documents* as additional evidence, which the wiki page may
not have cited yet.
Record real relationships surfaced by a document between two entities
(`uses`, `depends_on`, `caused`, `contradicts`) as edges in
`libs/<name>/graph/edges.json`, same edge vocabulary `wiki/` uses. Also add
a `mentioned_in` edge from each entity to every document that discusses
it — this is what makes the graph a genuine index into the source
documents rather than just a bag of loose pages.
### Step 7 — Update the hub page, entities index, graph, and log
Create/update `libs/<name>/index.md` — the root routing page for this
connector, mirroring `wiki/index.md`'s own role:
```yaml
---
type: external-source
resource: <location, from source.yaml>
tldr: One sentence — what this source is, how many documents were found, and how many entities/processes were extracted from them
last_updated: YYYY-MM-DD
---
```
followed by a short routing table pointing to `entities/index.md` and
`graph/index.md`.
Update `libs/<name>/entities/index.md` as two sections, so both kinds of
page stay easy to find without conflating them:
```markdown
## Documents
- [<doc-slug>](<doc-slug>.md) — <tldr>
...
## Entities & Processes
- [<entity-slug>](<entity-slug>.md) — <tldr>
...
```
Same flat-bullet, no-frontmatter convention as `wiki/entities/index.md`
otherwise — this is just a two-heading variant of it, not a new format.
Update `libs/<name>/graph/index.md` (same convention as
`wiki/graph/index.md`) to summarize both the `mentioned_in` document
coverage and any real entity-to-entity edges found. Log every
created/updated page in `libs/<name>/log.md`, same reverse-chronological
format as Rule B in `CLAUDE.md`/`AGENTS.md` — this log is independent of
`wiki/log.md`; **nothing under `wiki/` is touched by this skill at all.**
### Step 8 — Publish, if this source has both write access and a configured index store
If this user has `access: write` **and** `source.yaml` has an `index:`
block, push the refreshed `libs/<name>/{index.md,entities/,graph/,log.md}`
back out to `index.location` (`git push` for `store: git`, a file copy
back for a filesystem/network `store: shared` path, or the matching
connector tool for a `shared` location that needs one) — **every single
run**, not just the first one, so every other user's next fetch (Step 3)
picks up the update. This is exactly how a shared index gets created in
the first place: if Step 3 found the store empty, this step's push is what
populates it for the first time; there's no separate "initialize" action.
If no `index:` block is configured, there's nothing external to publish;
the refreshed files staying inside this repo's own `libs/<name>/` (tracked
by this repo's own git) *is* the publish step, same as the original
default behavior.
If this user is **read-only**, this step never runs — nothing is ever
pushed or copied to `index.location` (or, in the no-`index:`-block case,
nothing is even rebuilt in `libs/<name>/` to begin with). A read-only user
has, by construction, nothing of their own to save back — Step 4 already
stopped them before anything was built.
### Step 9 — Remind to review and sync
This is always the last step, every time this skill makes any change at
all. Close with a short reminder — do not sync or push anything yourself
here:
> "External source indexing complete for `libs/<name>/` — see
> `libs/<name>/log.md` for the details. Please review the changes, and
> once you're happy with them, say 'sync changes' to commit and push them
> to `origin`."
If nothing was actually indexed or fetched (no connector-backed libs
found, every source was skipped for lack of authorization, or every source
was read-only with nothing new to fetch), skip this reminder.
## Edge cases
- **No connector-backed `libs/<name>/` exists** — report that and explain
how to add one; don't touch anything.
- **`source.local.yaml` doesn't exist yet for a source** — treat as
read-only; this is the correct default, not a misconfiguration worth
flagging loudly. Mention once, briefly, how to become the admin
(`access: write` in that file) if the user seems to expect a rebuild.
- **Everyone is read-only and nothing has ever been published** — report
that clearly rather than silently doing nothing; this is the one case
worth flagging as a real gap (nobody can currently index this source).
- **Connector not authorized this session** — report which one and how to
authorize it (claude.ai connector settings / `claude mcp` / `/mcp`),
skip that source, keep going with the rest. Never ask the user for
tokens or callback URLs.
- **Fetching a published index (Step 3) fails or the remote is
unreachable** — report it and fall back to whatever's already cached
locally in `libs/<name>/` from a previous fetch, rather than blocking
the whole run; don't let a transient network issue block a read-only
user from seeing the last-known index.
- **Re-running against an already-indexed source** — refresh existing
document *and* entity/process pages in place (update
`tldr`/`last_updated`/`confidence`, add newly-seen `mentioned_in` edges);
never duplicate a document's or entity's page.
- **A previously-indexed document is no longer found at the source**
don't delete its page. Update its `tldr` with a note ("No longer found
at source as of YYYY-MM-DD") and set `retention: low`, so the existing
`ckb-lint` retention sweep archives it naturally on a later pass — no new
archival mechanism needed. Leave any entity pages that cite it alone;
the citation is still historically accurate even if the document itself
is gone.
- **An extracted entity already has a full page in `wiki/entities/`**
keep this connector-side page thin (evidence/`mentioned_in` pointers
only, `resource:` set to the wiki page) rather than re-deriving
everything the wiki page already says; that duplication is exactly what
the cascade design is meant to avoid.
- **A document is dense with names/topics and extracting every one would
produce dozens of near-duplicate pages** — extract what's clearly named
and substantively discussed (not every passing mention), and prefer
updating an existing entity page's "Mentioned in" list over creating a
marginal new one. Don't force artificial granularity just to maximize
page count.
- **A document yields no extractable entity beyond itself** — that's fine;
its Step 5 document page is the only page it produces. Not every
document needs to feed Step 6.
- **A source is very large** — cap what gets fully fetched/summarized in
one run and explicitly report what was skipped (e.g. "42 of ~300
documents summarized this pass — re-run to continue"). Never silently
truncate while implying full coverage.
- **Write access but publishing (Step 8) conflicts with a newer version
someone else already pushed** — this is why Step 3 (fetch) always runs
first, even for write-access users: rebuild on top of the latest fetched
state rather than blindly overwriting it. If a real conflict still shows
up at publish time, report it rather than force-pushing over it.
- **`libs/<name>/` has both real content files and a `source.yaml`** —
ambiguous and out of scope for this skill to resolve; report it and stop
for that folder (this is the same case `ckb-lint`'s external-source
config check flags).
- **`source.yaml` is missing `connector` or `location`** — report it as
malformed and skip that source; don't guess at intent.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -0,0 +1,195 @@
---
name: ckb-ingest
description: Process raw/inbox/ (or raw/ directly if the inbox is empty) into the structured wiki/ — consult the cascade, extract typed entities and relationships, synthesize frontmatted pages, cross-link them, update the index and log, then remind the user to review and sync to origin. Use when the user says "Ingest", "Sync the wiki", or "Update the Wiki". This is the content-level workflow routed by CLAUDE.md/AGENTS.md, distinct from the git-level `ckb-sync-changes` skill (which reconciles this repo's own history with its `origin` remote and does no wiki synthesis at all).
---
# Ingest skill
## Purpose
Turn raw, unstructured material in `raw/inbox/` (notes, links, pasted text,
scratch files) into the local, mutable, structured `wiki/` — the top layer
of the cascade that overlays read-only upstream KBs in `linked/` and
`libs/`. This is the primary way the wiki grows. It is implemented as a
skill (rather than living inline in `CLAUDE.md`/`AGENTS.md`) so the full
ingestion procedure only loads into context when actually invoked.
This skill only ever writes to `wiki/` (and moves processed files within
`raw/`). It never touches `linked/` or `libs/` — those are immutable
upstream sources of truth — and it never touches this repo's own git
remote; pushing the resulting changes is a separate, explicit step (see
Step 7).
## Trigger phrases
Use this skill when the user says:
- "Ingest"
- "Sync the wiki"
- "Update the wiki"
Do **not** use this skill for "sync changes", "sync with git/origin", or
"reconcile git" — those are git-level operations with nothing to do with
wiki content, handled by the `ckb-sync-changes` skill instead (see
`.agents/skills/ckb-sync-changes/SKILL.md`). This skill and that one are
deliberately separate: this one turns raw material into wiki pages; that
one commits/pulls/pushes whatever is currently on disk, wiki changes
included.
## How to run this skill
### Step 1 — Process the inbox
Scan `raw/inbox/` for new material. After ingesting each item, move it to
`raw/archive/<YYYY-MM-DD>/`, where the date is today's ingestion date
(create the dated folder if it doesn't exist yet).
If `raw/inbox/` is empty, scan `raw/` directly instead — excluding
`raw/archive/`, which holds material already processed by a previous
ingest.
### Step 2 — Consult the cascade
Before writing anything, check whether the entity already exists: local
`wiki/` first, then each `linked/<name>/`, then each `libs/<name>/`
(alphabetical within each layer). The local `wiki/` always wins — upstream
content is informative context, but it can be overridden locally rather
than treated as final.
### Step 3 — Extract entities
Identify typed entities in the source material — people, projects,
libraries, concepts, systems. Create entity pages at
`wiki/entities/<entity-name>.md` if they don't already exist. Record typed
relationships between entities — `uses`, `depends_on`, `caused`,
`contradicts`, `supersedes` — as edge data in `wiki/graph/edges.json`.
Also record `has_expertise_in` and `owns` edges when the material actually
evidences them: `has_expertise_in` when a person demonstrably answers
questions or explains decisions on a topic, `owns` when they hold
declared responsibility for a system, area, or decision. These are what
make "who knows about X" and "who owns X" answerable as a direct graph
lookup instead of a full-text guess (see `ckb-retrieve` Step 3). Record
them only from demonstrated evidence — someone being present in a meeting
or cc'd on a thread is not expertise, and don't infer ownership from job
title alone. Both are optional, like every other edge type; an absent
edge is better than a fabricated one.
For recurring teams, clients, systems, or initiatives, consider whether a
plain project scope page under `wiki/projects/<name>.md` would make future
queries easier to route. A scope page should list when to use it, included
wiki/entity/source paths, exclusions, and refresh hints. Create one only
when the source material shows a real repeated scope; don't manufacture
scopes for one-off facts.
### Step 4 — Synthesize pages
Convert the core knowledge into clean, modular Markdown files. Every page
gets frontmatter with:
- A `tldr:` — one sentence, optimised for LLM reading.
- A `confidence:` score (0.01.0, based on source corroboration).
- A `quality:` self-score (0.01.0).
- A `last_updated:` timestamp.
- A `freshness_window_days:` appropriate to the topic.
- A `retention:` level.
`type` is also required (per the page frontmatter schema in
`CLAUDE.md`/`AGENTS.md` page schema) — set it once, based on the entity/content
kind (person, project, concept, library, decision, playbook, ...).
For long conversations, meeting notes, transcripts, or chat exports, use a
structured distillation before writing the final page:
- `Question` or searchable problem statement, when there is one.
- `Summary` of the thread/note.
- `Resolution` or `Decision`, if the material contains one.
- `Systems and code references` mentioned.
- `People involved` or apparent owners/experts.
- `High-signal excerpts` for dense technical paragraphs or consecutive
messages that would be lost in a single summary.
"High-signal" needs an acceptance test, or every excerpt looks worth
keeping and the page becomes a second copy of the transcript. Promote a
run of text to its own section or linked page only when it clears all
three:
- **It carries a rare term.** Something specific enough that a future
search would use it — a config flag, an error string, a hostname, a
contract clause, a version number. Check with `rg -c` across `wiki/`:
if the term already appears on many pages it isn't a distinguishing
handle, and the excerpt adds no findability the summary doesn't have.
- **It's substantial.** Roughly 200 characters or more, or a few
consecutive paragraphs/messages from one author. A one-line "yes, do
that" is a resolution to fold into `Resolution`, not an excerpt.
- **Something corroborates it.** It was agreed with, acted on, corrected,
or referred back to later in the material. An unanswered assertion is a
claim, not a settled fact — keep it in the summary with that ambiguity
intact rather than promoting it.
Fail any one of the three and the content still belongs in the page, just
inside `Summary`/`Resolution` rather than as its own retrievable unit.
When you do promote an excerpt, carry its parent topic with it — the
thread question or section heading it sat under. An excerpt that reads
unambiguously on its own is the entire point; one that needs the
surrounding transcript to make sense hasn't been extracted, only moved.
### Step 5 — Link and cross-reference
Use **both** `[[Wikilinks]]` (Obsidian-compatible) and standard
`[markdown](path.md)` links on every cross-reference, so the wiki works in
Obsidian, GitHub, and CLI tools alike. Where useful, reference upstream
files directly at `linked/<name>/...` or `libs/<name>/...`.
### Step 6 — Update index and log
Add new pages to the routing table in `wiki/index.md` with a **Use when**
description. If the page lives in a subdirectory, also add it to that
subdirectory's own `index.md`. Append a log entry to the most specific
applicable log — the subdirectory's `log.md` if it has one, otherwise the
root `wiki/log.md` — following the format in Rule B (`CLAUDE.md`/
`AGENTS.md` Rule B).
If this step creates a brand-new `wiki/<topic>/` subdirectory, immediately
create that subdirectory's `index.md` per the Recursive Index & Log
Convention.
If this step creates or updates a project scope under `wiki/projects/`,
also update `wiki/projects/index.md`. If ingest closes a previously
recorded question in `wiki/query-gaps.md`, move that entry from Open to
Resolved and mention the page or source that now answers it.
### Step 7 — Remind to review and sync
This is always the last step, every time this skill runs and made any
change at all. Close with a short reminder — do not sync or push
anything yourself here, and do not skip this even if the changes look
small or obviously correct:
> "Ingest complete — `wiki/` has been updated (see `wiki/log.md` for the
> details). Please review the changes, and once you're happy with them,
> say 'sync changes' to commit and push them to `origin`."
If nothing was actually ingested (empty inbox and empty `raw/`, nothing to
process), skip this reminder — there's nothing to review or sync.
## Edge cases
- **Inbox and `raw/` both empty** — report that there's nothing to
ingest. Don't touch `wiki/`, and skip the Step 7 reminder.
- **Item in inbox doesn't yield a clear entity or page** (too vague, pure
scratch note with no durable fact) — don't force a page into existence.
Still move the item to `raw/archive/<YYYY-MM-DD>/` since it's been
considered, but note in the ingest report that it produced no wiki
change.
- **Conflicting information vs. an existing local wiki page** — this is a
supersession case handled consistently with `ckb-lint`, not a
silent overwrite: update the existing page if the new source is clearly
more current/corroborated, and link `supersedes`/`superseded_by` if an
older version is worth preserving rather than edited in place.
- **Material duplicates an upstream (`linked/`/`libs/`) page with nothing
new to add** — don't create a redundant local page just to have one;
the cascade already surfaces the upstream page. Only write locally when
overriding, correcting, or adding to what upstream says.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -1,6 +1,6 @@
--- ---
name: ckb-init name: ckb-init
description: Bootstrap a brand-new Cascade Knowledge Base - the same directory structure, AGENTS.md/CLAUDE.md system prompt, and empty wiki/ scaffold as this project - inside a target folder (typically empty, or a new project that doesn't have one yet). Use when the user asks to "set up a new wiki like this one", "initialize a new cascade KB", "bootstrap a wiki here", "create a knowledge base with this schema", or calls it a "wiki initializer". Do not confuse with a generic `init` skill that documents an existing codebase - this one creates the Cascade KB pattern itself, empty, ready for its first ingest. description: Bootstrap a brand-new Cascade Knowledge Base - the same directory structure, AGENTS.md/CLAUDE.md system prompt, full default skill set, LICENSE/VERSION, and empty wiki/ scaffold as this project - inside a target folder (typically empty, or a new project that doesn't have one yet). Use when the user asks to "set up a new wiki like this one", "initialize a new cascade KB", "bootstrap a wiki here", "create a knowledge base with this schema", or calls it a "wiki initializer". Do not confuse with a generic `init` skill that documents an existing codebase - this one creates the Cascade KB pattern itself, empty, ready for its first ingest. Distinct from `ckb-upgrade`, which catches an *existing* populated KB up with template changes rather than bootstrapping a new one.
--- ---
# Cascade KB init skill # Cascade KB init skill
@ -9,16 +9,21 @@ description: Bootstrap a brand-new Cascade Knowledge Base - the same directory s
Copy this project's Cascade Knowledge Base *schema* - not its content - into Copy this project's Cascade Knowledge Base *schema* - not its content - into
a new target folder: the directory structure, the `AGENTS.md`/`CLAUDE.md` a new target folder: the directory structure, the `AGENTS.md`/`CLAUDE.md`
system prompt that defines how the KB behaves, and the empty `wiki/` system prompt that defines how the KB behaves, the full default skill set,
scaffold (routing table, overview, log, error book, entity/graph indexes). `LICENSE` and `VERSION`, the generic `README`/`MANUAL` docs, and the empty
The result is a new, empty KB that behaves exactly like this one, ready for `wiki/` scaffold (routing table, overview, log, error book, entity/graph
its first `raw/inbox/` drop and "Ingest." indexes). The result is a new, empty KB that behaves exactly like this one,
ready for its first `raw/inbox/` drop and "Ingest."
This is a one-way copy from this repo's own `AGENTS.md`/`wiki/` template This is a one-way copy from this repo's own template files into a
files into a different folder. It never reads or writes anything in this different folder. It never reads or writes anything in this repo's `raw/`,
repo's `raw/`, `wiki/entities/`, `wiki/graph/edges.json`, or `outputs/` - `wiki/entities/`, `wiki/graph/edges.json`, or `outputs/` - those hold this
those hold this project's actual accumulated knowledge, which is exactly project's actual accumulated knowledge, which is exactly what should *not*
what should *not* travel into a fresh KB. travel into a fresh KB. (For catching an *already-populated* KB up with
newer template files without losing its accumulated content, see
`ckb-upgrade` instead - that skill does the same source-of-truth copying
this one does, but merges it into an existing KB rather than a blank
folder.)
## Trigger phrases ## Trigger phrases
@ -57,19 +62,39 @@ specifically.
### Step 3 - Default skill set (no need to ask) ### Step 3 - Default skill set (no need to ask)
The bare scaffold (directory structure + `AGENTS.md`/`CLAUDE.md` + empty The bare scaffold (directory structure + `AGENTS.md`/`CLAUDE.md` + empty
`wiki/` templates) is always included, and so is the reusable KB skill set `wiki/` templates) is always included, and so is the full reusable KB
- these operate purely on the `wiki/` structure, so they carry over skill set - these operate purely on the `wiki/` structure (or, for
cleanly and are part of "the schema" as far as this skill is concerned: `ckb-init` and `ckb-upgrade` themselves, on the schema layer), so they
`ckb-export-okf`, `ckb-export-starlight`, `ckb-sync-changes`, `extract-transcript`, carry over cleanly and are part of "the schema" as far as this skill is
`ckb-project-summary`. Don't ask about these - just include them. concerned. As of this writing, that's every skill under this repo's
`.agents/skills/`:
`ghost-writer` and `clouddrift-docx` are not part of the default set (a - `ckb-init` (this skill - a new KB can bootstrap further KBs of its own)
general writing tool and a brand-specific export skill respectively, not - `ckb-upgrade` (lets the new KB catch up with template changes later)
KB-schema-native) - only include either if the user explicitly asks for it, - `ckb-ingest`
e.g. "also bring over ghost-writer." - `ckb-lint`
- `ckb-sync-changes`
- `ckb-project-summary`
- `ckb-export-okf`
- `ckb-export-starlight`
- `ckb-onboard-me`
- `ckb-teach-me`
- `cbk-quiz`
Record the final skill list (default five, plus anything explicitly added) Don't ask about any of these - just include them.
- this affects Steps 6 and 7.
If, at the time this skill runs, `.agents/skills/` also contains skills
*not* in the list above, they're either a newer schema-native addition
this document hasn't been updated to mention yet (safe to fold into the
default set the same way - they follow the same "operates purely on the
KB structure" test) or a project-specific/general-purpose skill someone
added for this project only (e.g. a brand-specific export tool or a
general writing aid, unrelated to the Cascade KB pattern itself) - in
that case, only include it if the user explicitly asks for it by name,
e.g. "also bring over `<skill-name>`."
Record the final skill list (the defaults above, plus anything explicitly
added) - this affects Steps 8 and 9.
### Step 4 - Create the directory structure ### Step 4 - Create the directory structure
@ -87,11 +112,20 @@ wiki/graph/
workload/ workload/
``` ```
`libs/`, `linked/`, and `tmp/` are gitignored per the schema (Step 6) and Git doesn't track empty directories, so every one of these - plus
stay empty. `raw/inbox/`, `raw/archive/`, and `workload/` are meant to be `.agents/`, `.agents/skills/`, and `.claude/` created in Step 9 - needs a
tracked but start empty - add a placeholder `.gitkeep` file to each so they placeholder to survive a fresh `git init` and first commit. This repo's
survive a fresh `git init` + first commit rather than vanishing as empty own convention is an empty file named `.gitadd` in each directory (not
directories. `.gitkeep`) - match that convention exactly, so a new KB's directory
listing looks identical to this one's.
`libs/` and `tmp/` are gitignored per the schema (Step 8) except for their
own `.gitadd` placeholder, so they stay effectively empty. `linked/` gets
a `.gitadd` too but is *not* gitignored - it's meant to hold real
symlinks, which git tracks natively (as a small blob storing the link
target), so nothing extra is needed there. `raw/inbox/`, `raw/archive/`,
and `workload/` are meant to be tracked and start genuinely empty aside
from their placeholder.
### Step 5 - Write `AGENTS.md` and the `CLAUDE.md` symlink ### Step 5 - Write `AGENTS.md` and the `CLAUDE.md` symlink
@ -101,7 +135,30 @@ schema definition). Then create `CLAUDE.md` in the target as a symlink to
`AGENTS.md`, matching this repo's own convention (one source of truth, `AGENTS.md`, matching this repo's own convention (one source of truth,
readable under either filename). readable under either filename).
### Step 6 - Write the empty `wiki/` scaffold ### Step 6 - Write `LICENSE`, `VERSION`, and the generic docs
Copy these verbatim from this repo - they're already fully generic (no
project-specific content, confirmed by having zero references to any
actual project entity):
- `README.md` and `README.pl.md` - the technical feature overview, including
the "source repo" pointer at the top. Leave that pointer as-is (pointing
at this template's own canonical repo) unless the user says the new KB
should point somewhere else - it's what lets the new KB's own
`ckb-upgrade` find template updates later.
- `MANUAL.md` and `MANUAL.pl.md` - the task-oriented user guide.
- `VERSION` - copy the exact current value; the new KB starts life on the
same template version it was just cloned from.
For `LICENSE`, copy the Apache License 2.0 text, but **ask the user first**
whether to keep the copyright line as-is (appropriate if this new KB is
still effectively part of the same umbrella/organization) or update it to
their own name/organization (appropriate if this is a genuinely separate
project that will accumulate its own content under its own ownership) -
don't silently carry over a copyright attribution that may not apply to
what the new KB is about to collect.
### Step 7 - Write the empty `wiki/` scaffold
Create these files in the target, using this repo's current versions as Create these files in the target, using this repo's current versions as
the template and stripping every reference to this project's actual the template and stripping every reference to this project's actual
@ -129,28 +186,53 @@ Do not carry over any entity pages, graph edges, log entries, or overview
content specific to this project - the whole point is an empty KB with the content specific to this project - the whole point is an empty KB with the
same shape. same shape.
### Step 7 - Write `.gitignore` ### Step 8 - Write `.gitignore`
Base rules (always): `libs/`, `linked/`, `tmp/`, `.DS_Store`. Since Copy this repo's actual current `.gitignore` verbatim rather than
`ckb-export-starlight` and `ckb-export-okf` are in the default skill set (Step 3), reconstructing it from memory - it uses a `<dir>/*` + `!<dir>/.gitadd`
also always add `outputs/starlight` and `outputs/okf` - both exist to be pairing (not a bare `<dir>/` line) for directories that should exist as a
gitignored precisely because those two skills are present by default. tracked shell but have their real contents ignored:
### Step 8 - Copy the skill set from Step 3 ```
libs/*
!libs/.gitadd
tmp/*
!tmp/.gitadd
outputs/starlight
outputs/okf
outputs/teaching
.env
```
Since `ckb-export-starlight`, `ckb-export-okf`, and `ckb-teach-me` are all
in the default skill set (Step 3), their `outputs/` subfolders
(`outputs/starlight`, `outputs/okf`, `outputs/teaching`) are gitignored by
default too - each exists to be ignored precisely because its skill is
present by default. If a future skill set change ever drops one of those
three skills from the default set, drop its matching ignore line too;
if the user explicitly adds a skill with its own `outputs/<x>` convention,
ask whether it needs a similar ignore line.
### Step 9 - Copy the skill set from Step 3
Copy each skill's folder from this repo's `.agents/skills/<name>/` into the Copy each skill's folder from this repo's `.agents/skills/<name>/` into the
target's `.agents/skills/<name>/` unchanged - the default five, plus target's `.agents/skills/<name>/` unchanged (including each skill's own
anything explicitly added. Then create `.claude/skills` in the target as a license footer, and any support files like
symlink to `../.agents/skills`, matching this repo's convention - do this `ckb-export-starlight/scripts/export_starlight.py`) - the full default set
once, after copying the whole set, not per-skill. from Step 3, plus anything explicitly added. Then create `.claude/skills`
in the target as a symlink to `../.agents/skills`, matching this repo's
convention - do this once, after copying the whole set, not per-skill.
### Step 9 - Report ### Step 10 - Report
Tell the user: Tell the user:
- The resolved target path. - The resolved target path.
- The directory tree created. - The directory tree created.
- Whether `AGENTS.md`/`CLAUDE.md` were written or (per Step 2) skipped/merged. - Whether `AGENTS.md`/`CLAUDE.md` were written or (per Step 2) skipped/merged.
- Which skills were copied (the default five, plus anything explicitly added). - Which skills were copied (the full default set, plus anything explicitly
added).
- The `VERSION` the new KB starts on, and what was decided for `LICENSE`'s
copyright line.
- Next step: "Drop material into `raw/inbox/` and say 'Ingest' to populate the wiki for the first time." - Next step: "Drop material into `raw/inbox/` and say 'Ingest' to populate the wiki for the first time."
## Edge cases ## Edge cases
@ -168,3 +250,8 @@ Tell the user:
drifted from each other** (e.g. one mentions a directory the other drifted from each other** (e.g. one mentions a directory the other
doesn't) - fix the drift in *this* repo first if noticed, then copy the doesn't) - fix the drift in *this* repo first if noticed, then copy the
corrected version. Don't propagate a known inconsistency into a new KB. corrected version. Don't propagate a known inconsistency into a new KB.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -0,0 +1,194 @@
---
name: ckb-lint
description: Health-check the wiki/ — conformance, freshness, confidence decay, retention sweep, supersession detection, orphan detection, graph consistency, index/log consistency, and error-book entries — auto-fixing what it safely can and reporting the rest, then reminding the user to review and sync to origin. Also runs the same checks against each connector-backed libs/<name>/'s own generated index (see ckb-index-external), plus a source.yaml validity check, without ever touching a git-copy lib or a connector's source.yaml itself. Use when the user says "Lint", "health-check the wiki", "check the wiki", or asks for a periodic/scheduled wiki health check. This is the maintenance workflow routed by CLAUDE.md/AGENTS.md, distinct from `ckb-ingest` (which adds new content) and `ckb-sync-changes` (a pure git-level operation with no wiki synthesis at all).
---
# Lint skill
## Purpose
Health-check the local, mutable `wiki/` for structural rot: missing or
malformed frontmatter, stale pages, decayed confidence, orphaned or
archivable content, broken graph edges, and index/log drift. This is
maintenance, not growth — it never adds new knowledge the way `ckb-ingest`
does; it only checks and repairs the shape of what's already there.
Implemented as a skill (rather than living inline in
`CLAUDE.md`/`AGENTS.md`) so the full checklist only loads into context when
actually invoked — on demand, or on a schedule if the user has set one up.
This skill only ever writes within `wiki/` (including moves into
`wiki/archived/`) and, for any connector-backed `libs/<name>/` (one with a
`source.yaml` — see `CLAUDE.md`/`AGENTS.md` directory contract) where this user has
local `access: write` (see `ckb-index-external`), within that connector's
own agent-owned generated index (`index.md`/`entities/`/`graph/`/`log.md`).
For a connector-backed `libs/<name>/` where this user is read-only (the
default), findings against that index are reported only, never auto-fixed
— the same access gate `ckb-index-external` applies to rebuilding applies
here too. It never touches `linked/`, a git-copy `libs/<name>/`, or a
connector's `source.yaml`/`source.local.yaml` — those stay immutable or
user-owned — and it never touches this repo's own git remote; pushing the
resulting changes is a separate, explicit step (see the final step below).
## Trigger phrases
Use this skill when the user says:
- "Lint"
- "health-check the wiki" / "check the wiki"
- "run the periodic/scheduled wiki check"
Do **not** use this skill for "Ingest" / "Sync the wiki" (that's
`ckb-ingest` — turning raw material into new wiki pages) or for "sync
changes" / "sync with git" (that's `ckb-sync-changes` — reconciling this
repo's own commit history with `origin`, no wiki content involved).
## How to run this skill
Work through each check in order. A malformed page breaks every later
check's ability to reason about it, so conformance comes first.
Checks 1-9 below are written against `wiki/`, but the same checks also run
a second time against each connector-backed `libs/<name>/`'s own generated
index — scoped entirely to that one connector's `index.md`/`entities/`/
`graph/`/`log.md`. Auto-fixes there only happen where this user has local
`access: write` for that source (see `ckb-index-external`); everywhere
read-only, findings are reported but not applied. Findings and fixes there
are logged in that connector's own `log.md`, never in `wiki/log.md`. A
git-copy `libs/<name>/` (no `source.yaml`) and any `source.yaml`/
`source.local.yaml` are never touched by any of these checks.
### 1 — Conformance check
Verify every non-reserved `.md` file under `wiki/` (i.e. excluding
`index.md` and `log.md`) has parseable YAML frontmatter with a non-empty
`type` field. Flag violations first, and treat flagged pages as
unreliable input for the checks below rather than guessing at their
intended type/content.
### 2 — Freshness check
Scan every page whose `last_updated` exceeds its `freshness_window_days`.
Flag as stale; suggest the user confirm or update the content — don't
silently rewrite stale content yourself.
### 3 — Confidence decay
Reduce `confidence` on pages not reinforced by a new source since the last
check. Pages that fall below 0.3 confidence get flagged for re-review.
### 4 — Retention sweep
Move `retention: low` pages older than 2× their `freshness_window_days`
into `wiki/archived/`. Never delete — always move, and log the move (see
Rule B in `CLAUDE.md`/`AGENTS.md`) with a note explaining why.
### 5 — Supersession detection
When two pages appear to cover the same entity, check for contradictions.
If one is clearly newer/better corroborated, add `supersedes` /
`superseded_by` links between them. Preserve the older page rather than
deleting it, but mark it stale.
### 6 — Orphan detection
Find pages with no inbound links (`[[wikilinks]]` or
`[markdown](path.md)` references from elsewhere in the wiki). Either add
backlinks from relevant pages where an obvious connection exists, or move
the orphan to `wiki/archived/` with a log note if no natural backlink
exists.
### 7 — Graph consistency
Verify every edge in `wiki/graph/edges.json` points to an existing entity
page. Remove or fix broken edges; note what was removed rather than
silently dropping entries.
### 8 — Index/log consistency
Verify every subdirectory under `wiki/` that contains pages has an
`index.md` listing all of them, and that no single change is recorded in
both a subdirectory `log.md` and the root `wiki/log.md` (per the
Recursive Index & Log Convention). Fix missing index entries and
duplicate log entries directly.
### 9 — Error Book entry
Record any systemic issue found above (a repeated broken pattern, a
recurring format mismatch, the same kind of orphan appearing again) in
`wiki/error-book.md` with its root cause, the fix applied, and the derived
constraint that should prevent recurrence. A one-off, isolated issue
doesn't need an Error Book entry — this is for patterns, not incidents.
### 10 — External source config check
For each `libs/<name>/source.yaml`, verify it has a non-empty `connector`
and `location` — report only, this file is never edited by any skill. If
an `index:` block is present, verify it has a non-empty `store` and
`location` too. If `refresh_interval_days` is present, verify it's a
positive integer. Also flag (report only) a `libs/<name>/` that
ambiguously has both real content files and a `source.yaml` — that's a
configuration conflict for the user to resolve, not something to guess at.
Report any connector-backed source whose generated index is overdue:
newest `last_updated` in `libs/<name>/` older than its
`refresh_interval_days` (default 30). Report it the same way whether or
not this user has write access — a read-only user can't fix it, but
knowing which source to chase the admin about is the actionable part. Name
how overdue it is rather than just "stale," since a source two days past a
7-day interval is a different situation from one six months past a 30-day
one. Never re-index here; that's `ckb-index-external`'s job, and suggesting
it is as far as this check goes.
Don't flag a missing `libs/<name>/source.local.yaml` as an issue — its
absence is the correct, read-only default (see `ckb-index-external`), not
a gap to report or fix.
### Auto-fix vs. report
Auto-fix what can be done safely and mechanically: broken links, missing
backlinks, stale flags, missing index entries, duplicate log entries,
dangling graph edges. Report anything that needs a judgment call
(supersession decisions, low-confidence content, ambiguous orphans) rather
than guessing on the user's behalf.
### Final step — Remind to review and sync
This is always the last step, every time this skill makes any change at
all to `wiki/`. Close with a short reminder — do not sync or push
anything yourself here:
> "Lint complete — see `wiki/log.md` for what changed (auto-fixed: ...;
> flagged for your review: ...). Please review the changes, and once
> you're happy with them, say 'sync changes' to commit and push them to
> `origin`."
If any connector-backed `libs/<name>/` was also touched, mention it
separately (its own `log.md`, not `wiki/log.md`) rather than folding it
into the same sentence.
If the lint pass made no changes at all (clean bill of health, nothing to
fix or flag), skip this reminder — there's nothing to review or sync.
## Edge cases
- **Wiki is empty or near-empty** — report that there's little to lint
yet; don't fabricate findings to look thorough.
- **A page's frontmatter is unparseable** (not just missing `type`, but
invalid YAML) — flag it prominently in the conformance check and skip
it in every later numbered check rather than letting a parse error
crash or silently mis-handle downstream logic.
- **Retention sweep would archive a page that's clearly still in active
use** (e.g. linked from a very recent `workload/` entry) — flag it for
the user to confirm rather than auto-archiving; recency of use can
override a stale `last_updated`/`retention` combination.
- **Supersession is ambiguous** (two pages disagree and neither is
clearly newer/better corroborated) — report the conflict rather than
guessing which one wins.
- **Repeated run with nothing changed since the last lint** — should
produce essentially the same clean report each time; don't invent
variation just to seem active.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -141,3 +141,8 @@ Close with a short offer: to go deeper on any single step, to widen the tour
- **Upstream-only topic** — if the anchor lives in `linked/`/`libs/` with no - **Upstream-only topic** — if the anchor lives in `linked/`/`libs/` with no
local overlay, build the tour from the upstream KB's own index/links and mark local overlay, build the tour from the upstream KB's own index/links and mark
every step as upstream/read-only. every step as upstream/read-only.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -175,3 +175,8 @@ whatever the wiki says at that moment, not what it said today.
- **Re-running with no wiki changes since the last run** - should produce - **Re-running with no wiki changes since the last run** - should produce
essentially the same content each time; don't introduce random variation essentially the same content each time; don't introduce random variation
in section content or ordering between runs. in section content or ordering between runs.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -196,3 +196,8 @@ Wait for the answer. If yes, go back to Step 2 with the new parameters. If no, c
**No wiki content.** If the topic has no coverage in the wiki at all, say: "That topic does not appear in the wiki yet. Choose a different topic, or ingest a source on it first." **No wiki content.** If the topic has no coverage in the wiki at all, say: "That topic does not appear in the wiki yet. Choose a different topic, or ingest a source on it first."
**Ambiguous open answers.** When an open answer is on the border — partially right — mark it correct, note what was right and what the full answer includes. Do not leave the user unsure whether they got credit. **Ambiguous open answers.** When an open answer is on the border — partially right — mark it correct, note what was right and what the full answer includes. Do not leave the user unsure whether they got credit.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -0,0 +1,284 @@
---
name: ckb-retrieve
description: Governs how a question actually gets answered from the knowledge base. An index entry — a wiki page's `tldr`, a connector-index document/entity page — is deliberately compressed so lazy-loading stays cheap; that compression also means it can be incomplete, paraphrased loosely, or stale relative to the real source. This skill makes "go verify against the source before answering" a standing step, not an optional last resort: every time a page surfaced by the index looks relevant enough to actually ground part of the answer, follow it to its underlying source material — a wiki page's own `## Sources` citations into `raw/archive/`/`outputs/`, or a connector-index page's `resource:` pointer back to the live connector item — before treating its content as settled. Use whenever answering a question or researching a topic from `wiki/`, `linked/<name>/`, or a connector-backed `libs/<name>/` — this is the retrieval workflow routed by CLAUDE.md/AGENTS.md, always in play, not something the user needs to name explicitly. Distinct from `ckb-onboard-me` (produces a reading-order tour, not an answer to a specific question) and from the write-side skills `ckb-ingest`/`ckb-index-external`/`ckb-lint` (build or repair the index; this skill only ever reads it, plus the sources behind it).
---
# Retrieve (source-verified query) skill
## Purpose
The whole point of `tldr`/lazy-loading (CLAUDE.md/AGENTS.md index-first
navigation and skill routing) is that most of the wiki never has to enter
context — a one-sentence summary decides
whether a page is worth opening at all. That's the right trade for
*deciding relevance*. It's the wrong trade for *grounding an answer*: a
`tldr` is a compression of whatever the page's author judged important at
write time, a connector-index entity page is (per `ckb-index-external`)
deliberately a thin pointer rather than a synthesis, and either can have
drifted from the primary material since — a source updated, a nuance
dropped, a paraphrase that's subtly wrong.
This skill closes that gap: once a page looks relevant enough to actually
use in an answer, don't stop at its `tldr` or even its full synthesized
body — follow it to the source material that page was built from, and
answer from there. It's the difference between citing what the index
*says about* the evidence and citing the evidence.
This skill is **read-only**. It never writes to `wiki/`, `libs/<name>/`,
or anywhere else — it only reads what already exists (the index, and the
source material behind it) to answer the question in front of it. If
verification surfaces a real gap or contradiction worth fixing in the
wiki, say so and suggest `ckb-lint`/`ckb-ingest` rather than editing
anything mid-answer.
## Trigger phrases
This is the default retrieval path for **any** question answered from the
knowledge base — the user does not need to name this skill. Typical
prompts that should route here:
- A direct question answerable from the wiki ("what do we know about X",
"what's the status of Y", "who owns Z").
- "Look up X" / "check the wiki for X" / "search for X".
- Mid-conversation moments where CLAUDE.md/AGENTS.md routes a KB question
to retrieval — this skill *is* that workflow's implementation.
Do **not** use this skill for:
- Building or refreshing an index in the first place — that's
`ckb-ingest` (raw material → `wiki/`) or `ckb-index-external`
(connector → `libs/<name>/`). This skill only ever consumes what those
produced.
- A guided reading order across many pages on a topic — that's
`ckb-onboard-me`. This skill answers one question with verified
evidence; it doesn't produce a curriculum.
- Health-checking the index's own structure (frontmatter, staleness,
broken links) — that's `ckb-lint`. Verification failures found here
(Edge cases, below) are worth mentioning to the user as a possible lint
finding, but this skill doesn't run the lint checklist itself.
## How to run this skill
### Step 1 — Read the index
Same cascade order as CLAUDE.md/AGENTS.md, first match wins:
1. `wiki/index.md` — match the question against the **Use when** column.
2. `wiki/projects/index.md` — if a project scope matches the question,
use that project's listed pages, entity pages, raw/archive sources,
connector-backed libs, outputs, and graph areas as the first search
boundary. A project scope narrows the first pass only; it never hides
the rest of the cascade.
3. `wiki/entities/index.md` — match against entity titles/`tldr`.
4. If nothing local matches: each `linked/<name>/` index (alphabetical),
then each connector-backed `libs/<name>/entities/index.md` — for a
connector-backed lib this means its *generated* index (both the
Documents and the Entities & Processes sections `ckb-index-external`
produces), never the live source directly. If it isn't built yet,
suggest "index external sources" rather than querying the live
connector ad hoc from inside this skill.
### Step 2 — Shortlist every page that looks relevant
Read the `tldr` of every page the index match surfaced — don't stop at
the first plausible hit. A question is often best answered by
triangulating two or three pages (e.g. a concept page plus the specific
person/decision page that qualifies it), and a page whose `tldr` looks
only tangential can still be hiding the exact fact needed in its body or
its Sources. Keep the shortlist to what's plausibly relevant; this isn't
"open everything," it's "don't stop at one."
If index/TLDR matching is not enough, run a local hybrid pass before
giving up: use `rg` for exact tokens across `wiki/`, `raw/archive/`,
`outputs/`, `raw/inbox/`, and readable upstream indexes; combine those
hits with project-scope matches, entity/title/TLDR matches, graph
proximity, freshness, confidence, and quality. Prefer exact text matches
for error strings, commands, flags, filenames, hostnames, IDs, and other
literals; prefer entity/semantic matches for paraphrased questions.
Sweep `raw/inbox/` even though nothing there has been ingested yet.
Material dropped an hour ago can already hold the answer, and finding it
there is also the clearest possible signal that an `ckb-ingest` run is
overdue — mention that. Say plainly when an answer rests on un-ingested
inbox material rather than on a synthesized page.
Keep each signal's hits as its own ranked list rather than merging them by
eye as you go. Step 4 needs the separate orderings.
### Step 3 — Walk the graph for anything the shortlist missed
If a shortlisted page has edges in `wiki/graph/edges.json` (or a
connector's own `graph/edges.json`), follow `depends_on`/`uses`/`caused`
edges one hop out to catch a connected page the index text match alone
wouldn't have surfaced.
For "who knows about X" and "who owns X" questions — both advertised in
the trigger phrases above — the graph is the primary lookup rather than a
fallback. Read the `has_expertise_in` and `owns` edges pointing at the
topic entity and answer from the people or teams on the other end,
ordered by how many distinct sources evidence each edge. Where no such
edge exists yet, fall back to authorship evidence: who the `## Sources`
material actually shows answering questions on that topic. Say which of
the two grounded the answer, since an inferred expert is a weaker claim
than a recorded one.
### Step 4 — Fuse the signals, dedupe, then rerank
Signals disagree, and none of them is trustworthy alone: an exact `rg` hit
can sit in a page that answers a different question, and a confident TLDR
match can be a loose paraphrase of something stale. Don't settle that by
picking a favourite signal — fuse the ranked lists from Step 2.
**Fuse.** For every candidate, sum `weight / (k + rank)` across each list
it appears in, where `rank` is its 1-based position in that list. Use
`k = 10` and a default `weight` of 1.0. A page ranked third in three
different lists beats one ranked first in a single list; consensus is the
whole point.
`k = 10` is deliberately smaller than the `k = 60` rank fusion is usually
quoted with. 60 is tuned for retrievers returning hundreds of candidates,
and against the dozen-or-so a local wiki produces it flattens every score
into near-identical values. Raise a list's weight when the question
warrants it — for a pasted error string, command, flag, or ID, weight the
exact-match list around 2.0, because no amount of title/TLDR similarity
should outrank a literal match on the token the user actually pasted.
**Dedupe.** Collapse candidates carrying the *same claim* into one entry
before ranking further. Step 6 actively manufactures these: a `wiki/`
page, the `raw/archive/` file it cites, and a connector-index page whose
`resource:` points back at that same wiki page are three hits for one
fact. Keep whichever sits closest to the primary material and record the
others as corroboration, not as independent evidence. Three views of one
claim are not three sources.
**Rerank.** Score each surviving candidate 010 on how well it answers
*the literal question asked*, not on how well it matches the query's
vocabulary, then keep the best handful and drop the rest. This is the same
agent making a deliberate second pass, not a separate model. The point is
that relevance judgment happens explicitly, over the shortlist, in one
place — rather than being folded silently into how the final answer gets
drafted.
### Step 5 — Expand local context and build an evidence packet
For every result that survived Step 4, keep a small evidence packet with:
- source path or connector resource
- matched claim or short excerpt
- source date or `last_updated`
- freshness/confidence/quality signals, when available
- project-scope or graph relationship hints, when relevant
- which signals it was fused from, its rerank score, and anything it
absorbed during dedupe — this is what Step 7 draws caveats from
When a match is a section, heading, or snippet inside a larger Markdown
file, include nearby headings/paragraphs before deciding what it means.
Avoid answering from an isolated fragment when the neighboring context
changes the interpretation.
### Step 6 — Follow every page on the shortlist to its source before answering from it
This is the step this skill exists to enforce. For each page on the
shortlist that will actually ground part of the answer:
- **A `wiki/` page** — every page synthesized via `ckb-ingest` carries a
`## Sources` section citing the exact `raw/archive/<date>/...` or
`outputs/...` file(s) it was built from. Open the cited file(s) — or
the specific section of a large one — and confirm the wiki's claim
matches what the primary material actually says. This also often
surfaces adjacent detail the synthesis compressed away that's directly
useful for the current question.
- **A connector-index page in `libs/<name>/`** (a document page, or an
entity/process page from the `ckb-index-external` extraction) — read
its `resource:` field. If the connector is authorized this session (per
`ToolSearch`/MCP auth state), re-fetch the live item for current,
complete content rather than trusting the cached `tldr` — connector
pages use a deliberately short `freshness_window_days` (30, vs. a
typical wiki page's 60-90) precisely because external sources drift
without notice. If the connector isn't authorized, or the item is a
calendar acceptance / metadata-only page with nothing more to fetch,
say plainly that the answer relies on the cached index rather than a
live re-check, so the user can weigh that.
- **A `linked/<name>/` page** — this is already full mirrored content,
not a summary; reading the page itself already is reading the source.
No extra fetch needed unless *that* page itself cites something further
outside the mirror.
Target the specific claim, not the whole file — a large transcript or
deck doesn't need a full read every time, just enough (search for the
name/topic, read the surrounding context) to confirm the point actually
being used.
### Step 7 — Reconcile and answer
If the source confirms the index, answer normally — but note what
grounded it (e.g. "per the kickoff transcript cited in
`gt-fde-access-requirements.md`") rather than presenting the answer as if
sourced from the `tldr` alone.
If the source contradicts, extends, or is more precise than what the
index said, prefer the source for the answer and say so explicitly — this
is exactly the kind of drift `ckb-lint`'s freshness/confidence checks
exist to eventually catch, so mention it's worth a lint pass if the gap
looks like more than a one-off, but don't rewrite the wiki page yourself
mid-answer unless the user asks for that separately.
State caveats in the answer itself, not only in the metadata you read to
build it. When a page grounding the answer is past its
`freshness_window_days`, carries a low `confidence` or `quality`, rests on
un-ingested `raw/inbox/` material, or was checked against a cached
connector index rather than a live re-fetch, say so in a short clause next
to the claim it qualifies. Surface a conflict between two live pages the
same way, even when neither is marked `superseded_by` yet. The metadata
already exists and Step 4 already put it in front of you; the failure mode
is answering confidently *from* a stale or contested page without passing
that on, which leaves the reader no way to weigh it.
If no page anywhere is even plausibly relevant, say so — don't verify
against a source that has nothing to do with the question just to appear
thorough, and don't fabricate an answer either. For durable missing
knowledge, add or propose a short `wiki/query-gaps.md` entry with the
question, date, search areas tried, and the smallest missing source/page
that would close the gap. If you edit `wiki/query-gaps.md`, update
`wiki/log.md` immediately.
## Edge cases
- **Only one signal returned anything** — fusion is a no-op, so don't run
the arithmetic for show. The rerank still applies (it's the check that
the one list actually answers the question rather than merely matching
its wording), and dedupe still applies as soon as Step 6 pulls in a
page's own cited source.
- **A cited source file no longer exists** (moved, renamed, or cleaned
up) — say so plainly rather than silently falling back to the `tldr` as
if it had been verified. Flag it as a likely `ckb-lint` finding (a
broken `Sources` reference) rather than treating it as a dead end.
- **Connector item unreachable or connector not authorized this
session** — state clearly that the answer is based on the cached
connector index, not a live re-check, and name which connector would
need authorizing to go further.
- **Question is low-stakes and the matched page is high-confidence,
recently updated, and well within its `freshness_window_days`** — still
open the source at least once to ground the answer; skip only a *second*
redundant read if the same source has already been opened earlier in
the same retrieval pass for another part of the same question.
- **Many pages are relevant and reading every cited source in full would
be excessive** — prioritize the sources that actually carry the
specific fact the question needs, not every source any shortlisted page
has ever cited. Say what was skipped rather than silently narrowing
without a note.
- **The "source" is itself a generated artifact** (e.g. an
`outputs/emails/*.md` draft cited as a wiki page's Source) rather than
raw external material — that's still the source relative to the wiki
page; no further hop beyond it is required.
- **A connector-index entity/process page's `resource:` points at a full
`wiki/entities/` page** (per `ckb-index-external`'s thin-page
convention) — treat the wiki page as the real source to verify against;
the connector page is just the pointer that got you there.
- **Nothing in the index is even plausibly relevant** — say the knowledge
base has nothing on this yet, and suggest `ckb-ingest` (for new raw
material) or `ckb-index-external` (for a connector-backed source) if
that seems like the actual gap. For durable gaps, record or propose a
`wiki/query-gaps.md` entry. Don't stretch a weak match into an answer
just to have one.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -31,7 +31,7 @@ Use this skill when the user says things like:
- "sync the repo" - "sync the repo"
Do **not** use this skill for bare "Sync" or "Sync the wiki" — those trigger Do **not** use this skill for bare "Sync" or "Sync the wiki" — those trigger
the content-level Ingestion Workflow in `CLAUDE.md`/`AGENTS.md` §3 instead the content-level ingest workflow routed by `CLAUDE.md`/`AGENTS.md` instead
(processing `raw/inbox/` into `wiki/`), which this skill has nothing to do (processing `raw/inbox/` into `wiki/`), which this skill has nothing to do
with. with.
@ -63,7 +63,35 @@ ask the user via `AskUserQuestion` whether to include or skip each one
*before* staging anything in Step 3 — never silently commit or silently *before* staging anything in Step 3 — never silently commit or silently
drop a flagged file. drop a flagged file.
### Step 2 — Detect the first-run / unrelated-histories case ### Step 2 — Ensure `origin` is configured
```bash
git remote get-url origin
```
- **Succeeds** → go to Step 3.
- **Fails** (`fatal: No such remote 'origin'`, or `git remote` lists nothing
at all) → this repo has no remote to sync with yet. Ask the user directly
(a plain question is fine here — it's a URL to paste, not a choice between
options):
> "This repo has no `origin` remote configured. Paste the URL of the
> remote repository (e.g. `https://github.com/org/repo.git` or
> `git@github.com:org/repo.git`) and I'll add it as `origin`."
Once given:
```bash
git remote add origin <url>
```
Then continue to Step 3 with `origin` now configured. If `git remote add`
itself errors (malformed URL, etc.), report the raw error and stop — don't
guess at a corrected URL or retry with a modified one.
- **A differently-named remote already exists** (e.g. `upstream`) that looks
like it might be the intended remote — don't assume. Ask the user whether
that's the one to sync with (in which case, whether to alias/rename it to
`origin` since the rest of this skill assumes that name) or whether to add
a separate, new `origin`.
### Step 3 — Detect the first-run / unrelated-histories case
```bash ```bash
git fetch origin git fetch origin
@ -71,10 +99,10 @@ git rev-parse HEAD # fails with "unknown revision" if local has no co
git rev-parse origin/main # fails if the remote branch doesn't exist/is empty git rev-parse origin/main # fails if the remote branch doesn't exist/is empty
``` ```
- **Local `HEAD` exists** → go to Step 3, regardless of remote state. - **Local `HEAD` exists** → go to Step 4, regardless of remote state.
- **Local `HEAD` doesn't exist, and `origin/main` doesn't exist or is empty** - **Local `HEAD` doesn't exist, and `origin/main` doesn't exist or is empty**
→ this is a plain first publish, not a reconciliation. Proceed → this is a plain first publish, not a reconciliation. Proceed
automatically to Step 3 (it will just commit and push with nothing to automatically to Step 4 (it will just commit and push with nothing to
merge). merge).
- **Local `HEAD` doesn't exist, but `origin/main` already has commits** - **Local `HEAD` doesn't exist, but `origin/main` already has commits**
**hard stop, every time this is detected.** Show the user: **hard stop, every time this is detected.** Show the user:
@ -90,14 +118,14 @@ git rev-parse origin/main # fails if the remote branch doesn't exist/is empt
Do **not** offer a fourth "discard remote, force local to become main" Do **not** offer a fourth "discard remote, force local to become main"
option — that requires a force-push and is out of scope for this skill; option — that requires a force-push and is out of scope for this skill;
if the user wants that, tell them it needs to be done manually. Execute if the user wants that, tell them it needs to be done manually. Execute
only the option chosen, then continue to Step 3 with whichever git state only the option chosen, then continue to Step 4 with whichever git state
results. results.
### Step 3 — Steady-state flow ### Step 4 — Steady-state flow
Order matters: **commit local changes first, then fetch/merge, then push.** Order matters: **commit local changes first, then fetch/merge, then push.**
Committing first turns any overlap into an ordinary merge conflict (which Committing first turns any overlap into an ordinary merge conflict (which
Step 4 already knows how to present), rather than a stash-pop conflict with Step 5 already knows how to present), rather than a stash-pop conflict with
no commit boundary to fall back on. no commit boundary to fall back on.
```bash ```bash
@ -113,7 +141,7 @@ git merge origin/main # merge, never rebase — rebase would rewrit
Outcomes of the merge: Outcomes of the merge:
- `Already up to date.` or a clean auto-merge → continue below. - `Already up to date.` or a clean auto-merge → continue below.
- `CONFLICT` → go to Step 4, then come back here once every conflict is - `CONFLICT` → go to Step 5, then come back here once every conflict is
resolved and committed. resolved and committed.
```bash ```bash
@ -125,7 +153,7 @@ fetch and push), retry the fetch → merge → push cycle **exactly once**. If
it fails again, stop and report the raw error to the user — never force, it fails again, stop and report the raw error to the user — never force,
never retry more than once. never retry more than once.
### Step 4 — Present each conflict to the user ### Step 5 — Present each conflict to the user
```bash ```bash
git diff --name-only --diff-filter=U git diff --name-only --diff-filter=U
@ -162,15 +190,16 @@ staged, commit:
```bash ```bash
git commit -m "Merge origin/main (conflicts resolved with user input)" git commit -m "Merge origin/main (conflicts resolved with user input)"
``` ```
Then return to Step 3's push. Then return to Step 4's push.
### Step 5 — Report ### Step 6 — Report
End with a structured summary: End with a structured summary:
``` ```
## Sync report ## Sync report
**Remote:** origin already configured (or "origin added: <url>")
**Pulled from origin/main:** <N> commit(s) — <oneline log, or "none, already up to date"> **Pulled from origin/main:** <N> commit(s) — <oneline log, or "none, already up to date">
**Committed locally:** <N> file(s) — <paths>, commit <short-hash> (or "no local changes to commit") **Committed locally:** <N> file(s) — <paths>, commit <short-hash> (or "no local changes to commit")
**Conflicts encountered:** <N> (or "none") **Conflicts encountered:** <N> (or "none")
@ -195,5 +224,16 @@ blocker.
push." push."
- **Push rejected twice in a row** — stop, report the raw git error, do not - **Push rejected twice in a row** — stop, report the raw git error, do not
force and do not retry a third time. force and do not retry a third time.
- **No remote configured, or fetch/push fails on auth/network** — report - **No `origin` remote configured** — handled proactively in Step 2 (ask
the user for the URL and add it), not treated as a failure.
- **User pastes an invalid or unreachable URL**`git remote add` itself
usually still succeeds (it doesn't validate reachability); the failure
surfaces at the `git fetch origin` in Step 3. Report that raw error and
ask the user to confirm the URL rather than guessing a correction.
- **Fetch/push fails on auth/network once a remote is configured** — report
the error clearly. This skill does not manage git credentials. the error clearly. This skill does not manage git credentials.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -0,0 +1,327 @@
---
name: ckb-teach-me
description: Turn the wiki knowledge base into a personalised teaching curriculum — plan a single session or a spaced series (duration, frequency, optional calendar .ics reminders), teach one portion per session using varied techniques (Socratic questioning, analogies, worked examples, teach-back, mini knowledge checks), and track progress against the plan across calls, re-teaching weak spots before moving on. Use when the user asks to "teach me the wiki", "teach me about X", "run a teaching session", "continue my lessons", or wants a structured course built from the knowledge base rather than a one-off quiz (for that, use the `quiz` skill instead).
---
# Teach me (wiki curriculum) skill
## Purpose
Turn the wiki into a taught course, not just a lookup table or a one-off
quiz. This skill has two modes that always run in this order for a given
topic:
1. **Plan mode** (first call for a topic) — scope the material, ask
scheduling questions, chunk the knowledge into session-sized portions,
present the plan, save it once accepted.
2. **Teach mode** (every later call for that topic) — compare the saved
plan against saved progress, teach the next portion using a technique
different from last time, spot-check retention of earlier sessions, and
update progress.
The wiki stays authoritative and read-only from this skill's point of
view — it never writes back to `wiki/`. Curricula, plans, and progress live
under `outputs/teaching/`, one subfolder per topic.
## Trigger phrases
- "teach me the wiki" / "teach me everything in the wiki"
- "teach me about X" / "I want to learn X" / "run me a course on X"
- "run a teaching session" / "continue my lessons" / "next lesson"
- "where am I up to in my lessons on X"
Do **not** use this skill for a one-off knowledge test with no curriculum
or progress tracking — that's the `quiz` skill.
---
## How to run this skill
### Step 0 — Resolve topic and existing state
1. Determine the topic from the user's phrasing. "Teach me about X" scopes
to X; a bare "teach me the wiki" scopes to the whole KB — confirm this
is really what they want given it may be large, and offer to narrow it
first (e.g. "the whole wiki is quite broad — want me to scope this to a
part of it, or build a multi-session course covering all of it?").
2. Slugify the topic (kebab-case) and look for
`outputs/teaching/<slug>/plan.md`.
- **Exists** → this is a **Teach mode** call. Skip to Step 5.
- **Doesn't exist** → this is a **Plan mode** call. Continue to Step 1.
- **Several folders look like plausible matches** for a vague topic
(e.g. user says "continue my lessons" with more than one curriculum
in progress) → list them (topic, sessions done / total) and ask which
one to continue.
### Step 1 — Scheduling questions (Plan mode only)
Ask one at a time, waiting for each answer:
**Q1 — Single session or a series?**
> "Do you want this as one session, or a series of shorter sessions spread
> over time?"
**Q2 — Duration and frequency**
- If single session: "How long should the session be? (e.g. 20, 45, 60
minutes)"
- If series: "How long should each session be, and how often would you
like them? (e.g. '20 minutes, twice a week' or '30 minutes every
weekday')" Also ask for a start date if they want the calendar file
(Q3) — otherwise it's optional and can default to "starting next
session request."
**Q3 — Calendar reminders**
> "Would you like a calendar file (.ics) you can import to get reminders
> for these sessions?"
If yes and it's a series, also ask for a preferred time of day and start
date if not already given, so events can be scheduled concretely. If it's
a single session, offer a one-off reminder event instead.
### Step 2 — Gather the material
1. Read `wiki/index.md` (and `wiki/entities/index.md`,
`wiki/graph/index.md` if present) to find every page in scope. For a
narrow topic, use the cascade (local `wiki/` first, then `linked/`,
then `libs/`) same as any query. For "whole wiki", scope is every page
under `wiki/` plus its graph.
2. Read each in-scope page in full (`tldr` first — skip the body only if
the page turns out irrelevant on inspection).
3. **Optional web supplement** — if the wiki is thin on a subtopic that's
clearly needed for a coherent course, you may search the web to fill
the gap. Mark any such material distinctly as *external, supplementary*
in the plan and during teaching — it does not carry the wiki's
cascade authority. If the gap is substantial enough to be worth
keeping permanently, tell the user afterwards it would be a good
candidate for `raw/inbox/` and a proper Ingest (per the Demand-Driven
Context rule in `CLAUDE.md`) rather than silently folding it into the
wiki yourself.
4. Use `wiki/graph/edges.json` (or link-following if edges are sparse — same
fallback as the onboarding-tour skill) to work out dependency order:
foundations (`depends_on`/`uses` targets) before the concepts that build
on them.
### Step 3 — Chunk into sessions
Order the material pedagogically (foundations → core → things that build
on it, same logic as a guided tour), then cut it into session-sized
portions against the duration from Step 1.
- Rule of thumb: roughly one wiki page or tightly-scoped subtopic per
10-15 minutes of session time, adjusted for how dense the material
actually is (a short stub page is quick; a long entity page with many
facts takes longer).
- **When in doubt, add a session rather than overfill one.** A session
that runs long or crams two unrelated topics together teaches worse than
two clean, focused sessions. Never let a single session's plan exceed
its stated duration on paper.
- Each session should have one clear theme/title, not a grab-bag.
- Reserve implicit room in later sessions for retention spot-checks (Step
6) — don't plan every minute as new material.
### Step 4 — Present the plan and get acceptance
Show the session breakdown before saving anything:
```markdown
## Proposed course: [Topic]
**Format:** [N session(s) of ~M minutes] [ · every X, starting YYYY-MM-DD if series]
**Calendar file:** [Yes — will generate reminders.ics / No]
1. **Session 1 — [title]** — [1-line: what it covers, sourced from which pages]
2. **Session 2 — [title]** — ...
...
Sound good, or would you like the sessions rebalanced (more/fewer,
different split)?
```
Wait for explicit acceptance before writing anything. If the user wants
changes, adjust and re-present — don't save a rejected plan.
### Step 5 — Save the plan (Plan mode only, after acceptance)
Create `outputs/teaching/<slug>/plan.md`:
```markdown
---
type: teaching-plan
topic: <topic as stated>
scope: <"whole wiki" | narrower description>
session_mode: single | series
session_duration_minutes: <M>
frequency: <e.g. "twice a week" | null for single>
calendar_file: outputs/teaching/<slug>/reminders.ics | null
created: YYYY-MM-DD
last_updated: YYYY-MM-DD
---
# Teaching plan: <topic>
## Overview
[2-3 sentences: what this course covers and why the sessions are ordered
this way.]
## Sessions
### Session 1 — <title>
- **Covers:** [pages/subtopics, as links, e.g. `wiki/entities/foo.md`]
- **Learning goals:** [what the user should be able to explain/do after]
- **External supplements:** [none | list, marked as such]
### Session 2 — <title>
...
```
Create `outputs/teaching/<slug>/progress.md` alongside it, empty of
sessions so far:
```markdown
---
type: teaching-progress
topic: <topic as stated>
plan_file: outputs/teaching/<slug>/plan.md
last_updated: YYYY-MM-DD
---
# Teaching progress: <topic>
## Session log
_No sessions taught yet._
## Next up
Session 1 — <title>.
```
If a calendar file was requested, generate
`outputs/teaching/<slug>/reminders.ics` (see Calendar file generation
below). Report the paths written and that the course is ready to start
whenever the user says so — do not start teaching Session 1 automatically
in this same turn unless the user asks for it explicitly.
### Step 6 — Teach mode: pick up where progress left off
1. Read `plan.md` and `progress.md` for the resolved topic.
2. Determine the next unit of work:
- If the previous session's knowledge check (see below) showed weak
spots, open this session with a short, targeted recap of just those
weak points before moving on — don't re-teach the whole prior
session.
- If the previous check was very poor (roughly under half recalled),
dedicate this whole session to reinforcing that material instead of
advancing the plan, and note this as an inserted repetition session
in `progress.md` (it doesn't consume a slot from the numbered plan).
- Otherwise, teach the next not-yet-completed session from `plan.md`.
3. Pick a teaching technique **different from the one used last session**
(rotate through the list below) so the course doesn't feel repetitive.
4. Teach the portion: pull the actual facts from the relevant wiki pages
(plus any marked external supplements), and deliver them via the
chosen technique. Keep it interactive — this is a taught session, not
a wall of text.
5. Close the session with a short, informal knowledge check (2-4
questions, open or quick-recall style) on **today's** material only.
Judge generously, same spirit as the `quiz` skill: if the core fact
came through even imperfectly, count it as understood.
6. Update `progress.md`: append a dated entry for this session (technique
used, brief note on what was taught, knowledge-check result, any weak
spots flagged for next time), update `Next up`, bump `last_updated` on
both `progress.md` and — only if the plan's own shape changed (e.g. an
inserted repetition session) — `plan.md`.
7. If this was the final session in the plan, say so, give a short
overall verdict, and offer either a full-course review (can point at
the `quiz` skill for a proper scored test) or planning a new topic.
**Teaching technique rotation** (pick one not used last time; introduce
variety, don't always default to plain explanation):
- **Socratic questioning** — lead the user to the fact via guided
questions rather than stating it outright.
- **Analogy / storytelling** — frame the concept through a relatable
comparison or short narrative.
- **Worked example / case walkthrough** — walk through a concrete instance
from the wiki step by step.
- **Compare-and-contrast** — teach a concept against something it's
commonly confused with.
- **Teach-back** — give the user the raw facts, then ask them to explain
the concept back in their own words; correct gently.
- **Mnemonic / memory device** — for lists or sequences, build a memory
aid together with the user.
- **Real-world scenario / role-play** — apply the concept to a
hypothetical situation the user reasons through.
---
## Calendar file generation
Only when requested. Write a standard RFC 5545 `.ics` file to
`outputs/teaching/<slug>/reminders.ics`:
```
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Cascade KB//Teach Me//EN
BEGIN:VEVENT
UID:<slug>-session-1@ckb-teach-me
DTSTART:<YYYYMMDDTHHMMSS>
DTEND:<start + session_duration_minutes>
RRULE:<only if series, e.g. FREQ=WEEKLY;BYDAY=TU,TH>
SUMMARY:Lesson: <topic> — Session 1
DESCRIPTION:<title of session 1>
BEGIN:VALARM
TRIGGER:-PT30M
ACTION:DISPLAY
DESCRIPTION:Lesson reminder
END:VALARM
END:VEVENT
...one VEVENT per session (or a single RRULE'd VEVENT if the sessions are
evenly spaced and identical in duration)...
END:VCALENDAR
```
- For a series with a regular cadence, prefer one recurring `VEVENT` with
an `RRULE` (and a `COUNT=<N>` matching the number of sessions) over N
separate events — simpler for the user's calendar app to manage.
- For a single session or irregular cadence, emit one `VEVENT` per
session with explicit dates.
- Use the date/time the user gave in Step 1; if no time of day was given,
ask before generating rather than guessing.
- Tell the user the file path and that importing it is a manual step in
their own calendar app — this skill only produces the file.
---
## Edge cases
- **No wiki content in scope.** Tell the user the topic isn't covered
yet, same as the `quiz` skill would — suggest a narrower topic or an
Ingest first. Don't fabricate a curriculum from nothing.
- **Topic scope changes between Plan and Teach calls** (user asks to
teach something broader/narrower than the saved plan). Treat this as a
new topic — create a new `outputs/teaching/<new-slug>/` rather than
overwriting the existing plan, since the old one may still be in
progress.
- **User wants to abandon or restart a course.** Ask for confirmation,
then either delete the folder (only with explicit confirmation — this
is a destructive action) or archive `progress.md` with a note and start
a fresh `plan.md` if they want to redo the scoping.
- **Plan and progress disagree** (e.g. plan.md was hand-edited, or a
session number in progress.md doesn't exist in plan.md). Point it out
to the user and ask how to reconcile rather than silently guessing.
- **Course fully completed and user asks to "teach me" again on the same
topic.** Don't silently restart — tell them the course is complete and
ask whether they want a fresh, deeper course, a review/quiz pass, or a
different topic.
- **Very long-running series** (many sessions already logged) — no need
to re-read every past `progress.md` entry in full each time; the `Next
up` line and the most recent 1-2 session entries are enough context to
continue. Older entries are history, not required reading.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*

View file

@ -0,0 +1,390 @@
---
name: ckb-upgrade
description: Check the canonical Cascade KB template repo (https://git.wierzbowa.cloud/michal/ckb) for a newer schema/tooling version, and if one exists, upgrade this KB's template layer (AGENTS.md/CLAUDE.md, .agents/skills/, LICENSE, VERSION, README/MANUAL docs, base .gitignore rules) in place. Also checks wiki/index.md's own kb_schema_version for compatibility (including the case of an unversioned, pre-schema wiki) independently of the template layer, and — only with explicit confirmation — can migrate existing wiki/ content (backfilling missing frontmatter, adding missing scaffold files) up to the current schema while preserving every fact already collected. Use when the user asks to "upgrade the wiki", "upgrade this KB", "check for a newer template version", "update the KB schema", "pull in the latest skills", or "is there a new version of ckb". Distinct from ckb-sync-changes (reconciles this repo's OWN origin remote with its own history) and ckb-init (bootstraps a brand-new, empty KB) — this skill updates an EXISTING, populated KB's tooling layer (and, if asked, its content's schema conformance) from a separate upstream template source.
---
# Upgrade skill
## Purpose
A Cascade KB, once created, tends to drift from the canonical template as
the template gains new skills, fixes, or schema refinements. This skill
answers "am I behind, and if so, can I catch up?" without risking the
knowledge collected since. It treats a KB as having two layers, and checks
each **independently** — a KB can be behind on one, the other, both, or
neither:
- **Template layer** — the schema/tooling that defines *how* the KB
behaves: `AGENTS.md`/`CLAUDE.md`, `.agents/skills/*`, `LICENSE`,
`VERSION`, `README.md`/`README.pl.md`/`MANUAL.md`/`MANUAL.pl.md`, and the
base `.gitignore` rules. Compared via the root `VERSION` file. Freely
upgradable — these files hold no project-specific content.
- **Content layer** — everything the KB has actually learned:
`wiki/entities/`, `wiki/graph/edges.json`, `wiki/index.md`'s entity rows,
`wiki/log.md`, `wiki/overview.md`, `wiki/error-book.md`, plus `raw/`,
`outputs/`, `workload/`. Compared via `wiki/index.md`'s own
`kb_schema_version` field (a KB predating that convention entirely
counts as "unversioned" and behind by default). This skill **never**
touches any of it automatically — a migration only ever runs after
explicit confirmation (see Step 3 and Step 7), and even then it only
ever adds/backfills structure, never rewrites the substance of what a
page says.
It's entirely possible to be behind on the content layer while current on
the template layer, or vice versa: a KB might already have the latest
`AGENTS.md`/skills but still carry `wiki/` pages ingested years ago under
an older (or no) schema version, or the reverse. This skill checks and
offers to fix both, separately.
## Schema versioning policy
`wiki/index.md` is the only wiki file that carries `kb_schema_version`.
Treat it as the version of the wiki content contract, not as a per-page
field.
Use schema versions as `MAJOR.MINOR`:
- Bump the **minor** version for backward-compatible additions: optional
frontmatter fields, optional reserved wiki scaffolds, new optional
subdirectories such as `wiki/projects/`, or additional optional
index/log conventions.
- Bump the **major** version for breaking changes: removing or renaming a
required field, changing the meaning of an existing required field,
removing an existing reserved filename convention, or changing cascade
lookup semantics in a way older tooling cannot safely interpret.
The root `VERSION` file tracks the template/tooling release, not just the
wiki schema. When a schema convention changes in the template repo, update
the relevant docs/skills and `VERSION` in the same template-layer change.
When migrating an existing KB's content layer, bump `wiki/index.md`'s
`kb_schema_version` only after the confirmed migration has actually
completed.
This is different from `ckb-sync-changes` (reconciles *this* repo's own
commit history with *its own* `origin` remote — same content, no template
comparison) and from `ckb-init` (bootstraps a brand-new, empty KB from the
template — nothing to preserve because there's no content yet). This skill
assumes you already have a populated KB and want to catch it up with
template improvements made elsewhere, without disturbing anything you've
collected unless you specifically ask it to reshape that content's
structure too.
## Trigger phrases
- "upgrade the wiki" / "upgrade this KB"
- "check for a newer template version" / "is there a new version of ckb"
- "update the KB schema" / "pull in the latest skills"
Do **not** use this for "sync changes" (that's `ckb-sync-changes` — this
repo's own `origin`) or for setting up a brand-new KB (that's `ckb-init`).
## How to run this skill
### Step 1 — Fetch the canonical template for comparison
Clone the canonical repo shallowly into a scratch location under `tmp/`
never touch this repo's own git remotes or history to do this comparison:
```bash
rm -rf tmp/ckb-upgrade-src
git clone --depth 1 https://git.wierzbowa.cloud/michal/ckb.git tmp/ckb-upgrade-src
```
If the clone fails (network, auth, unreachable host), report the raw
error and stop — this skill does not fall back to a cached or partial
check, and does not retry silently.
### Step 2 — Compare template (tooling) versions
Read `VERSION` at the repo root (treat a missing file as `0.0.0` — an
older KB that predates this convention) and
`tmp/ckb-upgrade-src/VERSION`. Compare as dotted-integer tuples
(`1.2.0` > `1.1.9`), not as plain strings — e.g.:
```bash
python3 -c "
local_v = tuple(int(x) for x in open('VERSION').read().strip().split('.'))
remote_v = tuple(int(x) for x in open('tmp/ckb-upgrade-src/VERSION').read().strip().split('.'))
print('newer' if remote_v > local_v else 'same-or-older')
"
```
Record the result (`template_upgrade_available`: yes/no) and continue to
Step 3 regardless — a "no" here does **not** end the skill, because the
wiki content schema (Step 3) is checked independently and may still be
behind.
### Step 3 — Compare the wiki content's own schema version
This is a **separate** check from Step 2, and can find something to do
even when Step 2 didn't. Read the `kb_schema_version` frontmatter field
from local `wiki/index.md`, and from
`tmp/ckb-upgrade-src/wiki/index.md` (the version new KBs are created with
today). Compare as `(major, minor)` tuples, e.g. `"1.2"` > `"1.1"`.
- **Local `wiki/index.md` has no `kb_schema_version` at all** — this is an
**unversioned** wiki: either it predates the convention entirely, or it
was hand-built without the standard frontmatter. Treat it as behind by
default, same as version `"0.0"`.
- **Local version is older than the template's** — behind.
- **Local version is the same or newer** — not behind; nothing to do for
content.
Record the result (`wiki_schema_upgrade_available`: yes/no, plus the two
version strings for reporting).
**If both `template_upgrade_available` and `wiki_schema_upgrade_available`
are "no"** → report "already up to date — template v`X`, wiki schema
v`Y`," clean up the scratch clone (Step 8), and stop.
### Step 4 — Diff the template layer only (if `template_upgrade_available`)
Skip this step entirely if Step 2 found no template upgrade — go straight
to Step 5 with just the wiki-schema finding to present, if any.
The template layer is exactly these paths — never anything under `wiki/`,
`raw/`, `outputs/`, `workload/`, `tmp/`, `libs/`, or `linked/`:
- `AGENTS.md` (and the `CLAUDE.md` symlink — recreate it if it's missing
or isn't a symlink to `AGENTS.md`; don't diff its content, it's just a
symlink)
- `LICENSE`
- `VERSION`
- `README.md`, `README.pl.md`, `MANUAL.md`, `MANUAL.pl.md` — whichever
exist upstream (an older local KB may not have some of these yet)
- `.agents/skills/<name>/**` for every skill folder present in
`tmp/ckb-upgrade-src/.agents/skills/`
- The base ignore rules in `.gitignore` (`libs/`, `linked/`, `tmp/`,
regenerated `outputs/` subfolders, `.env`, etc.) — merged, never
replaced wholesale (see Step 6)
For every path in that list, diff the local copy against the scratch
clone's copy:
- **Doesn't exist locally, exists upstream** (e.g. a skill or doc added
since this KB was created) — no local content at risk; stage it as an
addition.
- **Identical** — nothing to do.
- **Differs** — this needs care. The difference might be a template
improvement worth taking, or it might be a deliberate local
customization — nothing here can tell those apart automatically,
especially for `AGENTS.md`, which the [manual](../../../MANUAL.md)
explicitly tells users they're free to edit. Never silently overwrite;
queue it for Step 5.
### Step 5 — Present findings and get decisions
Present whichever of the two findings apply (one or both):
```markdown
## Upgrade available
### Template: v<local> → v<remote> (only if template_upgrade_available)
**New files** (nothing local to lose, will be added):
- <path>
- ...
**Changed files** (local content differs from the template):
- <path><one-line summary of the change, e.g. "Step 7 reworded" or
"trigger phrases extended">
- ...
### Wiki content schema: v<local-or-"unversioned"> → v<remote> (only if wiki_schema_upgrade_available)
Your `wiki/` content was built against an older (or no) schema version.
This doesn't stop anything from working, but some pages may be missing
frontmatter fields the current schema expects (`tldr`, `confidence`,
`quality`, `retention`, etc.), or missing structural pieces (e.g. no
`wiki/graph/` folder yet). Nothing factual would be rewritten — only
structure and metadata would be backfilled.
```
For each **changed template file**, ask via `AskUserQuestion` — batched
into one question if the user wants to move fast, or one at a time if
they'd rather review each — with options **"Take the template version"**,
**"Keep my local version"**, or **"Show me the full diff first."**
Default recommendation for template files:
- Skill files (`.agents/skills/**`), `LICENSE`, `VERSION` — lean toward
"take the template version." These are rarely hand-edited and are
exactly the kind of thing that should stay in sync.
- `AGENTS.md`, `README*`, `MANUAL*` — don't default silently. Say plainly
that local customization is common in these files and ask explicitly
rather than nudging toward either choice.
If `wiki_schema_upgrade_available`, ask a **separate**, explicit question
— don't fold it into the template-file batch, since it's a different kind
of decision with different risk (it touches `wiki/`, which this skill
otherwise never does):
> "Would you like me to also upgrade all wiki-related folders and data to
> the new standard? I'll review every page under `wiki/`, add any missing
> scaffold files, and backfill missing frontmatter fields — without
> rewriting any of the actual knowledge you've collected. For a large
> wiki this can take a while. Options: **Yes, rebuild to the new
> standard** / **No, just the template files** / **Show me what's
> missing first, then decide.**"
These two decisions are independent — a user can accept the template
upgrade and decline the wiki migration, or vice versa.
### Step 6 — Apply the accepted template-layer changes
Only if `template_upgrade_available` and at least one item was accepted:
- Copy accepted **new** and **changed** files from `tmp/ckb-upgrade-src/`
over their local counterparts.
- For `.gitignore`, don't overwrite it — append any upstream base rules
not already present locally (line-by-line comparison), preserving every
existing local line, including custom ones the user added for their own
project.
- If `CLAUDE.md` isn't a symlink to `AGENTS.md` (or is missing), recreate
it as one.
- Leave every path outside the template layer completely untouched at
this step — `wiki/` changes, if any, happen only in Step 7.
- Update local `VERSION` to the template's new version last, only once
every other accepted change has been applied successfully.
### Step 7 — Rebuild wiki content to the new schema (only if confirmed in Step 5)
This is the one part of this skill that writes under `wiki/` — it only
ever runs after the explicit confirmation from Step 5, never
automatically, and never partially without saying so. Warn the user
before starting if `wiki/entities/` has many pages, since this is a
per-page pass, not instant.
**a. Fill structural gaps first.** Check for the standard `wiki/` scaffold
pieces and create any that are missing, using the same empty-template
shape `ckb-init` writes for a fresh KB (never overwrite one that already
exists, even if it looks sparse):
- `wiki/entities/index.md`, `wiki/graph/index.md`, `wiki/graph/edges.json`
- `wiki/error-book.md`, `wiki/overview.md`
- Any subdirectory under `wiki/entities/`-style groupings that's missing
its own `index.md` per the Recursive Index & Log Convention.
**b. Backfill frontmatter on every existing page**, per the schema in
`AGENTS.md` page schema. For each `.md` file under `wiki/` (excluding the reserved
`index.md`/`log.md`/`error-book.md`), check its frontmatter against the
schema and fix only what's missing — never touch a field that's already
present, and never alter the page's body text:
- **Missing `type`** — infer conservatively from the page's own content
and location (e.g. a page under `wiki/entities/` describing a person,
project, library, or concept). Don't silently guess: batch every
inferred `type` into one list and confirm it with the user before
writing, since `type` is the field lint treats as most load-bearing.
- **Missing `tldr`** — synthesize a one-sentence summary from the page's
actual content. Low-risk, mechanical — no need to ask per-page.
- **Missing `confidence` / `quality`** — don't invent a placeholder
number. Re-read the page fresh and score it the same way `ckb-ingest`
would score a newly-synthesized page (source corroboration for
confidence, structure/citations/consistency for quality).
- **Missing `last_updated`** — set to today, and note in the log entry
(Step 7d) that the date reflects this migration, not original
authorship.
- **Missing `freshness_window_days` / `retention`** — set a reasonable
default appropriate to the page's apparent topic, the same judgment call
`ckb-ingest` makes for newly-written pages.
- **Missing dual-linking** on an existing cross-reference (only a
`[[wikilink]]` where a `[markdown](path.md)` link should also exist, or
vice versa) — add the missing form. Don't invent new cross-references
that weren't already there in some form.
**c. Bump `wiki/index.md`'s `kb_schema_version`** to the template's
current value, once every page has been checked.
**d. Log every touched page** in `wiki/log.md`, per Rule B's format — this
is the one legitimate exception to this skill's normal "never writes to
`wiki/log.md`" behavior, and should say clearly in each entry that the
change was a schema-migration backfill, not new knowledge:
```markdown
## [YYYY-MM-DD HH:MM] - UPDATE (schema migration)
- **File Affected:** `wiki/entities/foo.md`
- **Description:** Backfilled missing `tldr`, `confidence`, `retention` during schema migration to v<new>. No content changes.
- **Source:** ckb-upgrade schema migration
---
```
**e. Suggest running "Lint" afterward** as an independent verification
pass, rather than re-implementing lint's own conformance checks here —
the two skills should agree on what "conformant" means.
### Step 8 — Clean up and report
```bash
rm -rf tmp/ckb-upgrade-src
```
Report a summary covering whichever tracks actually ran:
```markdown
## Upgrade report
**Template version:** v<old> → v<new> (or "already current")
**Added:** <N> file(s) — <paths, or "none">
**Updated:** <N> file(s) — <paths, or "none">
**Kept local (declined template version):** <N> file(s) — <paths, or "none">
**Wiki content schema:** v<old-or-"unversioned"> → v<new> (or "already current" / "declined")
**Scaffold files added:** <paths, or "none">
**Pages backfilled:** <N><paths, or "none">
**Types inferred and confirmed:** <list, or "none">
```
If anything actually changed, close with the same reminder pattern as
`ckb-ingest`/`ckb-lint`: review the diff, then say "sync changes" to
commit and push it to *this KB's own* `origin` — not the template repo
just checked against, which is typically a different remote for any KB
other than the template project itself.
## Edge cases
- **No `VERSION` file locally** — treat local version as `0.0.0`; any real
template version counts as newer. Mention in the report that this KB
predates version tracking.
- **No `kb_schema_version` in local `wiki/index.md` at all** — treat as
unversioned/behind by default (Step 3), and say so plainly in the
report rather than silently treating it as "version 0.0."
- **This KB's own `origin` *is* the template repo itself** (i.e. this is
the template project's own working copy) — the checks still work
(they'll usually report "already up to date" on both fronts since
you're comparing against yourself), but note this in the report so it's
clear why an "upgrade" against your own remote looks like a no-op.
- **Network/auth failure cloning the template repo** — report the raw
error, make no changes on either track, and clean up any partial
scratch clone.
- **A "changed" template file's difference looks trivial** — still
surface it for a decision; don't invent a heuristic for "safe to
auto-take," since the cost of a wrong guess (silently discarding a real
customization) is worse than one extra question.
- **User declines every changed template file** — still add any pure-new
files (nothing lost by doing so) and bump `VERSION`, since the KB
genuinely is now aware it's on the newer template version even if it
kept its own customized copies of specific files.
- **User confirms the wiki migration but the wiki is very large** — process
it in batches with progress updates rather than promising instant
completion; it's fine for this to be the longest-running part of the
skill.
- **User declines the wiki migration** — still bump nothing under `wiki/`,
including `kb_schema_version` — leave it exactly as found, so a future
run of this skill still detects it as behind rather than silently
considering it handled.
- **A page's inferred `type` is ambiguous even after inspection** — ask
the user directly for that page rather than guessing between two
plausible types; don't let one uncertain page block backfilling the
rest.
- **Uncommitted local changes already present before running this skill**
— proceed anyway (nothing here is destructive; every change lands as an
ordinary working-tree diff, reversible with `git checkout`), but mention
in the report that there was pre-existing uncommitted work, so the user
knows the diff they're about to review isn't only from this upgrade.
- **A skill folder was renamed or removed upstream** — don't delete the
corresponding local folder automatically (it might still be in active
use, or further customized locally); mention it in the report as
"removed upstream, left in place locally" and let the user decide
separately.
---
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*