Compare commits
No commits in common. "main" and "feature/external-source-connectors" have entirely different histories.
main
...
feature/ex
49 changed files with 494 additions and 6770 deletions
|
|
@ -1,68 +0,0 @@
|
||||||
# `software` module
|
|
||||||
|
|
||||||
Opt-in. Install with "install the software module"; remove with "uninstall the
|
|
||||||
software module". Nothing in here is active until installed — the skills below
|
|
||||||
live in this folder, not in `.agents/skills/`, precisely so their descriptions
|
|
||||||
stay out of context for knowledge bases that don't document software.
|
|
||||||
|
|
||||||
## What it assumes
|
|
||||||
|
|
||||||
You are building software, and this KB holds the knowledge *about* it. The code
|
|
||||||
itself lives in `src/<repo>/` as plain clones — **gitignored**, each with its own
|
|
||||||
remote and its own history. One KB can hold several.
|
|
||||||
|
|
||||||
The code is not part of the KB's history and may be absent entirely from a fresh
|
|
||||||
clone. That is deliberate, and it is why the module writes a `type: repository`
|
|
||||||
entity page for every repo: remote URL, default branch, language, build command,
|
|
||||||
path, owner. **That page is the durable artifact; the clone is a convenience.**
|
|
||||||
|
|
||||||
## What it adds
|
|
||||||
|
|
||||||
| Skill | Does |
|
|
||||||
|---|---|
|
|
||||||
| `ckb-code-map` | Reads a `src/<repo>` clone and writes it into `wiki/entities/` as a repository page plus component pages, stamped with the commit it was generated from so staleness is detectable. |
|
|
||||||
| `ckb-spec` | Owns KB-root specs, delegates per-repo specs to OpenSpec, and bridges both into `wiki/` — archived changes become decision records, current specs become `type: spec` pages. |
|
|
||||||
|
|
||||||
Plus: `src/` and `openspec/` scaffold, a routing block in `AGENTS.md`, `.gitignore`
|
|
||||||
rules for `src/*`, and the `repository`/`component`/`spec` types.
|
|
||||||
|
|
||||||
## Two spec levels
|
|
||||||
|
|
||||||
Specs live at **both** levels, and the split is the point:
|
|
||||||
|
|
||||||
- **`openspec/` at the KB root** — what and why, across repos. Cross-cutting
|
|
||||||
contracts that no single repo owns. Served by `ckb-spec` directly, because a
|
|
||||||
root `openspec/` is not a normal OpenSpec install: there is no code beneath it.
|
|
||||||
- **`src/<repo>/openspec/`** — how this repo implements it. OpenSpec's native
|
|
||||||
case: travels with the code, rides along in the repo's own PRs. Served by
|
|
||||||
OpenSpec's own instructions; `ckb-spec` defers to them and says so plainly when
|
|
||||||
the CLI isn't installed rather than improvising a replacement.
|
|
||||||
|
|
||||||
The levels are linked by `implements:` / `implemented_by:` frontmatter, which
|
|
||||||
becomes graph edges. This exists so that two spec levels produce a **lint signal**
|
|
||||||
when they disagree, instead of two silently divergent truths. A KB-root spec with
|
|
||||||
no implementer, or a repo-level spec whose parent was archived, is a finding.
|
|
||||||
|
|
||||||
## What it does not do
|
|
||||||
|
|
||||||
- **`src/` is not a cascade layer.** It is primary evidence, like `raw/archive/`.
|
|
||||||
`ckb-retrieve` may open and cite it to verify a claim; it never answers "what is
|
|
||||||
entity X", and `ckb-ingest` does not treat code as inbox material. Without this
|
|
||||||
rule every ingest would turn your codebase into wiki pages.
|
|
||||||
- **It does not commit or push `src/` repos.** They are separate repos with
|
|
||||||
separate remotes. `ckb-sync-changes` never `git add`s under `src/`.
|
|
||||||
|
|
||||||
## Setting up OpenSpec
|
|
||||||
|
|
||||||
See [OPENSPEC.md](../../../OPENSPEC.md) ([Polski](../../../OPENSPEC.pl.md)) for
|
|
||||||
install steps, the daily propose → apply → archive loop, and the reason
|
|
||||||
`openspec init` must never run at the KB root.
|
|
||||||
|
|
||||||
## Design record
|
|
||||||
|
|
||||||
- [D-0001](../../../wiki/decisions/0001-opt-in-file-based-kb-modules.md) — why modules are files in the repo.
|
|
||||||
- [D-0002](../../../wiki/decisions/0002-software-module-design.md) — why gitignored clones, both spec levels, hybrid vendoring.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE).*
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
## MODULE: software
|
|
||||||
|
|
||||||
This KB documents software being built here. Source repos live in `src/<repo>/`
|
|
||||||
as **gitignored plain clones**, each with its own remote and history.
|
|
||||||
|
|
||||||
- `src/` is **evidence, not a cascade layer.** `ckb-retrieve` may open and cite
|
|
||||||
code to verify a claim; it never answers "what is entity X", and `ckb-ingest`
|
|
||||||
never treats code as inbox material.
|
|
||||||
- **Never `git add` under `src/`.** Those are separate repos. `ckb-sync-changes`
|
|
||||||
reports each one's dirty/ahead state separately and commits none of them.
|
|
||||||
- Every repo has a `type: repository` page in `wiki/entities/` — the durable
|
|
||||||
record, since the clone may be absent from a fresh checkout.
|
|
||||||
- Specs live at two levels: `openspec/` at the KB root (cross-cutting, owned by
|
|
||||||
`ckb-spec`) and `src/<repo>/openspec/` (per-repo, owned by OpenSpec itself).
|
|
||||||
They link via `implements:` / `implemented_by:`.
|
|
||||||
|
|
||||||
| User intent | Skill |
|
|
||||||
|---|---|
|
|
||||||
| Map or refresh a `src/` repo into wiki entities | `ckb-code-map` |
|
|
||||||
| Write, sync, or bridge specs (either level) | `ckb-spec` |
|
|
||||||
|
|
||||||
Module-contributed page types: `repository`, `component`, `spec`. Additional
|
|
||||||
optional frontmatter: `repo`, `commit`, `spec_id`, `implements`, `implemented_by`.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# Source repos are independent git clones with their own remotes — never part of
|
|
||||||
# this KB's history. Knowledge about them lives in wiki/entities/ instead.
|
|
||||||
src/*
|
|
||||||
!src/.gitadd
|
|
||||||
!src/README.md
|
|
||||||
# KB-root cross-cutting specs ARE tracked — they are knowledge, not code.
|
|
||||||
!openspec/
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
# Cascade KB module manifest — read by `ckb-module` on install/uninstall.
|
|
||||||
# See wiki/decisions/0001-opt-in-file-based-kb-modules.md for why modules are
|
|
||||||
# files in the repo rather than native agent plugins.
|
|
||||||
name: software
|
|
||||||
title: Software / source-code KB
|
|
||||||
version: 1.0.0
|
|
||||||
requires_kb_schema: "1.5"
|
|
||||||
summary: >
|
|
||||||
Turns this knowledge base into one that documents software you are building:
|
|
||||||
source repos live as gitignored plain clones in src/, and OpenSpec drives
|
|
||||||
iterative development at both KB-root and per-repo level.
|
|
||||||
|
|
||||||
# Skills copied into .agents/skills/ on install, removed on uninstall.
|
|
||||||
# .claude/skills is a symlink to .agents/skills, so there is no second copy.
|
|
||||||
skills:
|
|
||||||
- ckb-code-map
|
|
||||||
- ckb-spec
|
|
||||||
|
|
||||||
# Directories created on install. Never removed on uninstall — they may hold
|
|
||||||
# the user's clones and specs.
|
|
||||||
scaffold:
|
|
||||||
- path: src/
|
|
||||||
from: scaffold/src-README.md
|
|
||||||
as: src/README.md
|
|
||||||
note: Drop zone for source repos, one clone per subdirectory.
|
|
||||||
- path: openspec/
|
|
||||||
from: scaffold/openspec/
|
|
||||||
note: KB-root, cross-cutting specs spanning multiple src/ repos.
|
|
||||||
|
|
||||||
# Appended to AGENTS.md between the module markers, removed on uninstall.
|
|
||||||
agents_fragment: fragments/agents-routing.md
|
|
||||||
|
|
||||||
# Appended to .gitignore between the module markers, removed on uninstall.
|
|
||||||
gitignore_fragment: fragments/gitignore.snippet
|
|
||||||
|
|
||||||
# Frontmatter this module contributes to the page schema. Additive: the base
|
|
||||||
# schema tolerates these whether or not the module is installed.
|
|
||||||
schema_additions:
|
|
||||||
types:
|
|
||||||
- repository # one src/ repo: remote, branch, language, build, owner
|
|
||||||
- component # a module/service/package inside a repo
|
|
||||||
- spec # a capability spec, KB-root or repo-level
|
|
||||||
fields:
|
|
||||||
- repo # /wiki/entities/<repo>.md this page belongs to
|
|
||||||
- commit # commit hash the page was generated from
|
|
||||||
- spec_id # OpenSpec capability id
|
|
||||||
- implements # repo-level spec -> the KB-root spec it implements
|
|
||||||
- implemented_by # KB-root spec -> repo-level specs implementing it
|
|
||||||
|
|
||||||
# External dependency. The module reports its absence rather than substituting
|
|
||||||
# its own workflow — see wiki/decisions/0002-software-module-design.md.
|
|
||||||
depends_on:
|
|
||||||
- name: openspec
|
|
||||||
scope: src-repo-level-only
|
|
||||||
check: "command -v openspec"
|
|
||||||
optional: true
|
|
||||||
note: >
|
|
||||||
Required only for per-repo specs. The KB-root spec layer is served by
|
|
||||||
ckb-spec itself and works without the CLI.
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# KB-root specs
|
|
||||||
|
|
||||||
Cross-cutting capability specs — the ones that span several `src/` repos and that
|
|
||||||
no single repo owns. Managed by the `ckb-spec` skill.
|
|
||||||
|
|
||||||
* `specs/<capability>.md` — current truth. What the system, as a whole, does.
|
|
||||||
* `changes/<change-id>/` — a proposed delta, not yet true.
|
|
||||||
* `changes/archive/` — applied or rejected changes. Immutable; often worth a
|
|
||||||
matching decision record under `wiki/decisions/`.
|
|
||||||
|
|
||||||
Specs for how *one* repo implements a capability belong in that repo, at
|
|
||||||
`src/<repo>/openspec/`, where OpenSpec's own tooling manages them and they travel
|
|
||||||
with the code. Link the two with `implements:` / `implemented_by:` — both sides,
|
|
||||||
always.
|
|
||||||
|
|
||||||
Unlike `src/`, this directory **is** tracked in the KB's git history: it is
|
|
||||||
knowledge, not code.
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# `src/`
|
|
||||||
|
|
||||||
Source repositories for the software this knowledge base documents. One clone
|
|
||||||
per subdirectory, each an independent git repo with its own remote.
|
|
||||||
|
|
||||||
**Everything here except this file is gitignored.** Clones are never committed to
|
|
||||||
the KB. A fresh clone of this KB will have an empty `src/` — that is expected.
|
|
||||||
|
|
||||||
What survives is the knowledge: every repo has a `type: repository` page under
|
|
||||||
`wiki/entities/` recording its remote, default branch, language, build command
|
|
||||||
and owner, so you can re-clone from the wiki alone. Run `ckb-code-map` after
|
|
||||||
adding a repo here to create or refresh that page.
|
|
||||||
|
|
||||||
Per-repo specs live in `src/<repo>/openspec/` and travel with that repo, not with
|
|
||||||
this KB. Cross-cutting specs live in the KB root's `openspec/`.
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
---
|
|
||||||
name: ckb-code-map
|
|
||||||
description: Read a source repository under src/ and write what it IS into wiki/entities/ — a `type: repository` page (remote, default branch, language, build and test commands, owner) plus `type: component` pages for its significant modules/services, cross-linked into the graph and stamped with the commit they were generated from so staleness is detectable. Use when the user says "map the code", "map src/<repo>", "refresh the code map", "index the repo", "add this repo to the wiki", or after cloning a new repo into src/. Distinct from `ckb-ingest` (which processes raw/inbox/ material and never reads code) and from reading code to answer a question (that is `ckb-retrieve` using src/ as evidence). Part of the opt-in `software` module.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Code map skill
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
`src/` clones are gitignored: they are not in this KB's history and may be
|
|
||||||
absent entirely from a fresh checkout. So the *knowledge* about a repository
|
|
||||||
has to live in `wiki/`, or it doesn't survive.
|
|
||||||
|
|
||||||
This skill produces that knowledge. It is deliberately **not** a code-to-prose
|
|
||||||
dump — a wiki page restating what any reader could get by opening the file is
|
|
||||||
pure liability, because it goes stale silently. What this skill records is what
|
|
||||||
you cannot get by reading one file: how to obtain the repo, how to build and
|
|
||||||
test it, what its pieces are, who owns it, and how those pieces connect to
|
|
||||||
entities the wiki already knows about.
|
|
||||||
|
|
||||||
## Trigger phrases
|
|
||||||
|
|
||||||
- "map the code" / "map `src/<repo>`" / "index the repo"
|
|
||||||
- "refresh the code map" / "the code map is stale"
|
|
||||||
- "add this repo to the wiki" / "I cloned a new repo into src"
|
|
||||||
|
|
||||||
## Scope rule
|
|
||||||
|
|
||||||
Reads `src/`. Writes only `wiki/`. Never writes, commits, or pushes anything
|
|
||||||
inside `src/` — those are independent repos with their own remotes.
|
|
||||||
|
|
||||||
## How to run this skill
|
|
||||||
|
|
||||||
### Step 0 — Confirm the module is installed and the repo exists
|
|
||||||
|
|
||||||
Check `ckb.yaml` lists the `software` module. If `src/` is empty or the named
|
|
||||||
repo isn't there, say so and stop — don't guess which repo was meant when
|
|
||||||
several are present, ask.
|
|
||||||
|
|
||||||
### Step 1 — Gather the facts that don't come from reading code
|
|
||||||
|
|
||||||
From inside the repo, cheaply:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git -C src/<repo> remote -v
|
|
||||||
git -C src/<repo> rev-parse --abbrev-ref HEAD
|
|
||||||
git -C src/<repo> rev-parse --short HEAD
|
|
||||||
git -C src/<repo> log -1 --format=%cI
|
|
||||||
git -C src/<repo> status --short
|
|
||||||
```
|
|
||||||
|
|
||||||
Then read the manifest and entry docs — `README`, `package.json`, `pyproject.toml`,
|
|
||||||
`go.mod`, `Cargo.toml`, `Makefile`, CI config. Build and test commands come from
|
|
||||||
here, not from inference.
|
|
||||||
|
|
||||||
**Do not run the build, the tests, or any script from the repo** to find out what
|
|
||||||
it does. Mapping is a read-only activity.
|
|
||||||
|
|
||||||
### Step 2 — Identify components, and be ruthless about what counts
|
|
||||||
|
|
||||||
A component is a part of the system a person would name in conversation: a
|
|
||||||
service, a CLI, a published package, a long-lived subsystem. A directory is not
|
|
||||||
automatically a component. **Ten honest component pages beat a hundred mirroring
|
|
||||||
the folder tree** — the second kind makes the wiki look thorough while making it
|
|
||||||
useless to search.
|
|
||||||
|
|
||||||
If you cannot write a one-sentence `tldr` for a candidate that says what it *does*
|
|
||||||
(not where it lives), it isn't a component. Leave it out.
|
|
||||||
|
|
||||||
### Step 3 — Consult the cascade before creating anything
|
|
||||||
|
|
||||||
Per the cascade rule, check whether pages already exist for this repo or its
|
|
||||||
components — in `wiki/` first, then `linked/`, then `libs/`. Refreshing an
|
|
||||||
existing page is the normal case, not the exception: update it, keep its history,
|
|
||||||
and don't renumber or re-slug it just because a directory was renamed.
|
|
||||||
|
|
||||||
### Step 4 — Write the repository page
|
|
||||||
|
|
||||||
`wiki/entities/<repo-slug>.md`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
type: repository
|
|
||||||
tldr: One sentence on what this software does — not "the repo for X".
|
|
||||||
resource: https://git.example.com/me/thing
|
|
||||||
repo: thing
|
|
||||||
commit: a1b2c3d
|
|
||||||
confidence: 0.9
|
|
||||||
quality: 0.8
|
|
||||||
last_updated: YYYY-MM-DD
|
|
||||||
freshness_window_days: 90
|
|
||||||
retention: high
|
|
||||||
---
|
|
||||||
|
|
||||||
# thing
|
|
||||||
|
|
||||||
**Remote:** `git@git.example.com:me/thing.git` · **Default branch:** `main`
|
|
||||||
· **Local path:** `src/thing` · **Mapped at:** `a1b2c3d` (YYYY-MM-DD)
|
|
||||||
|
|
||||||
## What it is
|
|
||||||
|
|
||||||
Two or three sentences. What problem it solves and for whom.
|
|
||||||
|
|
||||||
## Getting it
|
|
||||||
|
|
||||||
git clone git@git.example.com:me/thing.git src/thing
|
|
||||||
|
|
||||||
## Build and test
|
|
||||||
|
|
||||||
<the actual commands, from the manifest — not invented>
|
|
||||||
|
|
||||||
## Components
|
|
||||||
|
|
||||||
* [[thing-api]] / [thing-api](/wiki/entities/thing-api.md) — <tldr>
|
|
||||||
|
|
||||||
## Specs
|
|
||||||
|
|
||||||
KB-root specs this repo implements, and the repo-level specs that implement them.
|
|
||||||
Written by `ckb-spec`; leave the section here even when empty.
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
`src/thing` at `a1b2c3d`, mapped YYYY-MM-DD. README, `pyproject.toml`, CI config.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
<!-- Yours. Never rewritten by any skill. -->
|
|
||||||
```
|
|
||||||
|
|
||||||
The `commit` field and the **Mapped at** line are what make this page auditable —
|
|
||||||
they let a reader and `ckb-lint` tell how far the page has drifted from the code.
|
|
||||||
Never write them from memory; take them from Step 1. They are this page's
|
|
||||||
fingerprint, serving the same role `source_fingerprint` serves elsewhere: a
|
|
||||||
commit either still matches `HEAD` or it doesn't, which beats guessing from a
|
|
||||||
date.
|
|
||||||
|
|
||||||
`## Notes` is **protected** (page schema, `CLAUDE.md`/`AGENTS.md`). A refresh
|
|
||||||
regenerates everything above it and carries it across byte-for-byte. This
|
|
||||||
matters more here than almost anywhere else: a code map is re-run often, and
|
|
||||||
the things worth knowing about a repo that the repo doesn't say about itself —
|
|
||||||
which build target is abandoned, which service is being decommissioned, who to
|
|
||||||
ask — have nowhere else to live.
|
|
||||||
|
|
||||||
### Step 5 — Write component pages
|
|
||||||
|
|
||||||
Same shape, `type: component`, with `repo: <repo-slug>` pointing home. Keep them
|
|
||||||
thin and link upward to the repository page and sideways to whatever the wiki
|
|
||||||
already knows — a component that talks to a system with an existing entity page
|
|
||||||
should link to it rather than re-describing it.
|
|
||||||
|
|
||||||
### Step 6 — Graph, indexes, log
|
|
||||||
|
|
||||||
- `wiki/graph/edges.json` — `part_of` (component → repository), `depends_on`
|
|
||||||
(repo/component → an external library or a system that has a page), `owns`
|
|
||||||
(person → repo, **only** on stated ownership, never inferred from commit counts).
|
|
||||||
- `wiki/entities/index.md` — add or refresh a row per page.
|
|
||||||
- `wiki/log.md` — one entry per run, per Rule B, naming the repo and commit.
|
|
||||||
|
|
||||||
### Step 7 — Report
|
|
||||||
|
|
||||||
State the repo and commit mapped, pages created vs. refreshed, components
|
|
||||||
deliberately skipped and why, anything you couldn't determine (no build command
|
|
||||||
in the manifest — say so rather than inventing one), and whether the working tree
|
|
||||||
was dirty at map time (a map taken from uncommitted work is fine, but should be
|
|
||||||
labelled as such). Close with the standard reminder: on disk, not committed.
|
|
||||||
|
|
||||||
## Edge cases
|
|
||||||
|
|
||||||
- **Dirty working tree** — map it, but record the commit *plus* a note that
|
|
||||||
uncommitted changes were present. Don't refuse, and don't silently pretend the
|
|
||||||
tree was clean.
|
|
||||||
- **Repo with no remote** (local-only) — record `resource:` as absent and say
|
|
||||||
plainly in **Getting it** that this repo exists only locally. That's a real
|
|
||||||
finding: a gitignored, remote-less repo is one disk failure from gone.
|
|
||||||
- **Monorepo** — one repository page, components per package. Don't create a
|
|
||||||
repository page per package.
|
|
||||||
- **Several repos, user said "map the code"** — ask which, or offer to do all;
|
|
||||||
don't pick one.
|
|
||||||
- **Repo that's mostly vendored/generated code** — map what's authored here. Note
|
|
||||||
the vendored portion once, on the repository page.
|
|
||||||
- **A component page already exists as `type: concept`** from an earlier ingest —
|
|
||||||
don't create a duplicate. Update the existing page and change its type, noting
|
|
||||||
the change in the log entry.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../../LICENSE)
|
|
||||||
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
---
|
|
||||||
name: ckb-spec
|
|
||||||
description: Own the KB-root `openspec/` layer (cross-cutting capability specs spanning several src/ repos), delegate per-repo specs to OpenSpec's own installed workflow inside src/<repo>/openspec/, and bridge both into wiki/ — archived change proposals become numbered decision records, current specs become `type: spec` entity pages, and the two levels link via implements/implemented_by. Use when the user says "write a spec", "propose a change", "what specs do we have", "archive this change", "sync the specs", "which spec covers X", or "wire OpenSpec into the wiki". Part of the opt-in `software` module. Distinct from `ckb-decide` (records decisions directly; this skill feeds it from archived specs) and `ckb-code-map` (records what the code IS; this skill records what it SHOULD DO).
|
|
||||||
---
|
|
||||||
|
|
||||||
# Spec skill
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
A code map says what the software *is*. A spec says what it is *supposed to do* —
|
|
||||||
and a change proposal says what should become true next. This skill owns that
|
|
||||||
axis, across two levels that deliberately differ:
|
|
||||||
|
|
||||||
| Level | Holds | Owned by |
|
|
||||||
|---|---|---|
|
|
||||||
| `openspec/` at the KB root | Cross-cutting capabilities and contracts spanning several `src/` repos. The *what* and *why*. | **This skill.** A root `openspec/` has no code beneath it and no single repo governing it, so OpenSpec's own repo-shaped workflow doesn't serve it. |
|
|
||||||
| `src/<repo>/openspec/` | How one repo implements those capabilities. The *how*. | **OpenSpec itself.** This is its native case: specs travel with the code and ride along in the repo's own PRs. |
|
|
||||||
|
|
||||||
The split is the point, and so is the risk it creates: two places a statement can
|
|
||||||
live is two places it can diverge. The `implements` / `implemented_by` links
|
|
||||||
below exist so divergence becomes a **lint finding** rather than a silent second
|
|
||||||
source of truth. Maintaining those links is not optional bookkeeping — it is the
|
|
||||||
reason the two-level arrangement is safe.
|
|
||||||
|
|
||||||
## Trigger phrases
|
|
||||||
|
|
||||||
- "write a spec" / "add a capability" / "what specs do we have" / "which spec covers X"
|
|
||||||
- "propose a change" / "draft a change proposal" / "archive this change"
|
|
||||||
- "sync the specs" / "wire OpenSpec into the wiki" / "are the specs in sync"
|
|
||||||
|
|
||||||
## Scope rule
|
|
||||||
|
|
||||||
Writes `openspec/` at the KB root, and `wiki/`. Inside `src/<repo>/` it reads
|
|
||||||
freely and writes **only** through OpenSpec's own workflow, on the user's
|
|
||||||
explicit instruction. It never commits or pushes a `src/` repo.
|
|
||||||
|
|
||||||
## KB-root specs
|
|
||||||
|
|
||||||
### Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
openspec/
|
|
||||||
├── README.md # what this layer is for
|
|
||||||
├── specs/
|
|
||||||
│ └── <capability>.md # one capability, current truth
|
|
||||||
└── changes/
|
|
||||||
├── <change-id>/ # proposed delta, not yet true
|
|
||||||
└── archive/ # applied changes, immutable
|
|
||||||
```
|
|
||||||
|
|
||||||
### A KB-root capability spec
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
type: spec
|
|
||||||
spec_id: billing-invoicing
|
|
||||||
tldr: One sentence stating the capability, in the present tense, as a contract.
|
|
||||||
status: active
|
|
||||||
implemented_by: /src/billing/openspec/specs/invoicing.md, /src/portal/openspec/specs/invoice-view.md
|
|
||||||
affects: /wiki/entities/billing.md
|
|
||||||
confidence: 0.9
|
|
||||||
quality: 0.8
|
|
||||||
last_updated: YYYY-MM-DD
|
|
||||||
freshness_window_days: 180
|
|
||||||
retention: high
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
Body: **Purpose** (why this capability exists), **Requirements** (each one
|
|
||||||
testable — "the system SHALL ..." beats "the system should be fast"),
|
|
||||||
**Out of scope**, **Implementations** (which repos, dual-linked), **Sources**.
|
|
||||||
|
|
||||||
Write requirements a reader can disprove. A requirement nobody can fail is not a
|
|
||||||
requirement, and a spec full of them reads as thorough while constraining nothing.
|
|
||||||
|
|
||||||
### Writing one
|
|
||||||
|
|
||||||
1. Check `openspec/specs/` for an existing capability on the subject. Extend it
|
|
||||||
rather than creating a near-duplicate — two overlapping specs is the failure
|
|
||||||
this layer exists to prevent.
|
|
||||||
2. Check the cascade (`wiki/` → `linked/` → `libs/`) for what the KB already
|
|
||||||
knows about the subject, and link to it rather than restating it.
|
|
||||||
3. Write the spec, then a `type: spec` mirror page under `wiki/entities/` (or
|
|
||||||
link the spec file directly from the relevant entity page — prefer one home
|
|
||||||
plus links over two copies of the text).
|
|
||||||
4. Log it per Rule B and update `wiki/entities/index.md`.
|
|
||||||
|
|
||||||
## Per-repo specs
|
|
||||||
|
|
||||||
**Delegate.** If `src/<repo>/openspec/` exists, follow the instructions OpenSpec
|
|
||||||
installed there — do not substitute a workflow of your own, and do not "improve"
|
|
||||||
its file format to match this KB's conventions. That repo's specs are governed by
|
|
||||||
OpenSpec upstream; divergence there breaks its tooling and its PR flow.
|
|
||||||
|
|
||||||
### Never run `openspec init` at the KB root
|
|
||||||
|
|
||||||
`openspec init` writes tool-integration files into `.claude/skills/` and adds
|
|
||||||
marker blocks to `AGENTS.md` / `CLAUDE.md`. At the KB root both are load-bearing:
|
|
||||||
`.claude/skills` is a symlink to `.agents/skills` (so OpenSpec's files would join
|
|
||||||
this KB's skill set), and `CLAUDE.md` is a symlink to `AGENTS.md`, the KB's own
|
|
||||||
system prompt.
|
|
||||||
|
|
||||||
Run it **only** inside `src/<repo>/`, where the repo gets its own `.claude/` and
|
|
||||||
its own `AGENTS.md`. The KB-root `openspec/` is not an OpenSpec install and never
|
|
||||||
needs `init`. If the user asks for `init` at the root, explain this and offer the
|
|
||||||
KB-root spec layer instead — don't run it and don't clean up afterwards.
|
|
||||||
|
|
||||||
Install is `npm install -g @fission-ai/openspec@latest` (Node 20.19.0+), then
|
|
||||||
`openspec init` in the repo; `openspec update` after a CLI upgrade. See
|
|
||||||
[OPENSPEC.md](../../../../OPENSPEC.md) for the user-facing guide.
|
|
||||||
|
|
||||||
If OpenSpec is not installed (`command -v openspec` fails, or the repo has no
|
|
||||||
`openspec/`), **say so plainly and stop** rather than improvising:
|
|
||||||
|
|
||||||
> "This repo has no OpenSpec setup, and the `openspec` CLI isn't on PATH. Per-repo
|
|
||||||
> specs are OpenSpec's own workflow — install it and run `openspec init` in
|
|
||||||
> `src/<repo>`, and I'll take it from there. I can write this as a KB-root
|
|
||||||
> cross-cutting spec instead if it isn't repo-specific."
|
|
||||||
|
|
||||||
That offer is genuine, not a consolation: a requirement that spans repos belongs
|
|
||||||
at the root anyway.
|
|
||||||
|
|
||||||
## The bridge
|
|
||||||
|
|
||||||
Three directions, all owned here.
|
|
||||||
|
|
||||||
### 1. Archived change → decision record
|
|
||||||
|
|
||||||
An applied-and-archived OpenSpec change is a decision that was made and acted on.
|
|
||||||
When the user archives a change (either level), offer to record it via
|
|
||||||
`ckb-decide`: the change's *why* becomes **Context** and **Rationale**, the delta
|
|
||||||
becomes **Decision**, and rejected options in the proposal become
|
|
||||||
**Alternatives considered**.
|
|
||||||
|
|
||||||
Cite the change id and its path in the decision's `## Sources`. Do not
|
|
||||||
auto-record without asking — not every archived change is a decision worth a
|
|
||||||
permanent numbered record, and a decisions log padded with routine changes loses
|
|
||||||
the property that makes it worth reading.
|
|
||||||
|
|
||||||
Both artifacts are append-only, which makes them a natural pair: neither is ever
|
|
||||||
rewritten when the thinking changes later.
|
|
||||||
|
|
||||||
### 2. Spec → entity page
|
|
||||||
|
|
||||||
Current KB-root specs surface in `wiki/entities/` as `type: spec` pages so
|
|
||||||
`ckb-retrieve` can answer "what is this supposed to do" without opening the spec
|
|
||||||
tree. Keep these thin and pointer-style: `tldr`, `spec_id`, status, links. The
|
|
||||||
spec file stays the source of truth — a full copy in `wiki/` is a second thing
|
|
||||||
to keep in sync, and it will lose.
|
|
||||||
|
|
||||||
### 3. Level linking — the drift rule
|
|
||||||
|
|
||||||
- A KB-root spec lists every repo-level spec implementing it in `implemented_by`.
|
|
||||||
- A repo-level spec names its parent in `implements`.
|
|
||||||
- Both become graph edges (`implements`) in `wiki/graph/edges.json`.
|
|
||||||
|
|
||||||
Set **both sides**, every time. A one-sided link is a lint finding, exactly as a
|
|
||||||
one-sided decision supersession is.
|
|
||||||
|
|
||||||
### Sync check
|
|
||||||
|
|
||||||
On "sync the specs" / "are the specs in sync", report:
|
|
||||||
|
|
||||||
1. KB-root specs with empty `implemented_by` — *specified but nobody builds it.*
|
|
||||||
2. Repo-level specs with `implements` pointing at a missing or archived root spec
|
|
||||||
— *building against something no longer true.*
|
|
||||||
3. One-sided links, either direction.
|
|
||||||
4. Root specs whose `last_updated` is older than the mapped commit of every repo
|
|
||||||
implementing them — *possible drift; verify, don't auto-fix.*
|
|
||||||
|
|
||||||
Report all four. **Fix none of them automatically** — each is a statement about
|
|
||||||
intent, and only the user knows which side is right. `ckb-lint` runs the same
|
|
||||||
four checks as part of its sweep when this module is installed.
|
|
||||||
|
|
||||||
## Edge cases
|
|
||||||
|
|
||||||
- **`src/` is empty** — the KB-root layer still works. Cross-cutting specs can
|
|
||||||
precede any code; that's often the point.
|
|
||||||
- **A spec that's really a decision** ("we'll use Postgres") — that's `ckb-decide`.
|
|
||||||
A spec states a standing contract; a decision records a choice at a point in
|
|
||||||
time. If it doesn't constrain future behaviour, it isn't a spec.
|
|
||||||
- **A capability implemented by exactly one repo, forever** — it probably belongs
|
|
||||||
in that repo, not at the root. Say so; don't silently promote it.
|
|
||||||
- **OpenSpec's format changes upstream** — for per-repo specs, follow upstream.
|
|
||||||
This skill's formats govern the KB-root layer only.
|
|
||||||
- **A change proposal that was rejected** — it still archives, and it is still
|
|
||||||
worth a decision record with `status: rejected`. The reasoning is the value.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../../LICENSE)
|
|
||||||
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
name: ckb-quiz
|
name: cbk-quiz
|
||||||
description: Quiz the user on knowledge captured in the wiki. Reads wiki pages, generates questions in the user's chosen format (open questions or multiple choice), runs the quiz one question at a time with score tracking and immediate feedback. Use when the user asks for a quiz, wants to test their knowledge, or says "quiz me on X".
|
description: Quiz the user on knowledge captured in the wiki. Reads wiki pages, generates questions in the user's chosen format (open questions or multiple choice), runs the quiz one question at a time with score tracking and immediate feedback. Use when the user asks for a quiz, wants to test their knowledge, or says "quiz me on X".
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -1,323 +0,0 @@
|
||||||
---
|
|
||||||
name: ckb-decide
|
|
||||||
description: Record a decision into wiki/decisions/ as a numbered decision record — what was decided, by whom, when, why, what it affects, and which earlier decision it supersedes or reverses — and answer questions about decisions already recorded ("what did we decide about X", "show decision 7", "which decisions are still open", "what changed the database choice"). Use when the user says "record a decision", "log a decision", "we decided ...", "ADR", "decision record", or asks what/when/why/by whom something was decided. Distinct from `ckb-ingest` (which turns raw source material into entity pages) and from `ckb-retrieve` (general KB questions — this skill is the decisions-scoped path, and hands off to it for anything wider).
|
|
||||||
---
|
|
||||||
|
|
||||||
# Decision log skill
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
A decision is a different kind of knowledge from an entity page. An entity
|
|
||||||
page describes what something *is*, and gets rewritten as understanding
|
|
||||||
improves. A decision record describes what was chosen *at a point in time*,
|
|
||||||
by whom, and why — and is never rewritten to reflect a later change of mind.
|
|
||||||
When the choice changes, a new decision supersedes the old one and both stay
|
|
||||||
on the record. That's the whole point: the value is in being able to ask
|
|
||||||
"why is it like this?" and get the reasoning, the people, and the date, not
|
|
||||||
just the current state.
|
|
||||||
|
|
||||||
This skill owns both halves of that:
|
|
||||||
|
|
||||||
- **Recording** — turning "we decided X" into a numbered, linked, logged
|
|
||||||
decision record under `wiki/decisions/`, with the supersession links wired
|
|
||||||
in both directions.
|
|
||||||
- **Looking up** — answering questions about decisions already recorded,
|
|
||||||
scoped to `wiki/decisions/` rather than searching the whole wiki.
|
|
||||||
|
|
||||||
It writes only inside `wiki/` (`wiki/decisions/`, `wiki/decisions/index.md`,
|
|
||||||
`wiki/decisions/log.md`, `wiki/graph/edges.json`, and a pointer line in
|
|
||||||
`wiki/log.md`). It never edits an existing decision's substance — see
|
|
||||||
Rule: decisions are append-only, below.
|
|
||||||
|
|
||||||
## Trigger phrases
|
|
||||||
|
|
||||||
**Recording:**
|
|
||||||
- "record a decision" / "log a decision" / "add a decision record" / "write an ADR"
|
|
||||||
- "we decided ..." / "we've agreed to ..." / "the call was ..."
|
|
||||||
- "we're reversing ..." / "that supersedes decision N"
|
|
||||||
|
|
||||||
**Looking up:**
|
|
||||||
- "what did we decide about X" / "why do we ... ?" (when the answer is a decision)
|
|
||||||
- "show decision 7" / "show me D-0007"
|
|
||||||
- "which decisions are still open" / "what's proposed but not accepted"
|
|
||||||
- "who decided X" / "when did we decide X"
|
|
||||||
- "what decisions affect <project/system/person>"
|
|
||||||
- "what superseded decision N" / "what changed the X decision"
|
|
||||||
|
|
||||||
For a question that only *touches* decisions as part of a wider answer, use
|
|
||||||
`ckb-retrieve` instead and let it pull decision pages in as one source
|
|
||||||
among many. This skill is for when decisions themselves are the subject.
|
|
||||||
|
|
||||||
## The decision record format
|
|
||||||
|
|
||||||
One page per decision at `wiki/decisions/NNNN-short-slug.md`, numbered
|
|
||||||
sequentially from `0001`. Numbers are never reused and never renumbered — a
|
|
||||||
superseded or reversed decision keeps its number and its page.
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
type: decision
|
|
||||||
tldr: One sentence stating the decision itself, not the topic.
|
|
||||||
status: accepted
|
|
||||||
decided_on: 2026-09-01
|
|
||||||
decided_by: Alice Smith, Bob Jones
|
|
||||||
affects: /wiki/entities/billing-service.md, /wiki/projects/index.md
|
|
||||||
review_on: 2027-03-01
|
|
||||||
supersedes: /wiki/decisions/0003-use-mysql.md
|
|
||||||
confidence: 0.9
|
|
||||||
quality: 0.8
|
|
||||||
last_updated: 2026-09-01
|
|
||||||
freshness_window_days: 365
|
|
||||||
retention: high
|
|
||||||
---
|
|
||||||
|
|
||||||
# D-0007 — Use Postgres for the billing store
|
|
||||||
|
|
||||||
**Status:** Accepted · **Decided:** 2026-09-01 · **Deciders:** [[Alice Smith]] / [Alice Smith](/wiki/entities/alice-smith.md), [[Bob Jones]] / [Bob Jones](/wiki/entities/bob-jones.md)
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
What forced a choice. The problem, the constraints, what was true at the
|
|
||||||
time. Written so it still makes sense to someone reading it in two years
|
|
||||||
with none of the surrounding conversation.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
What was actually decided, stated plainly and in the active voice.
|
|
||||||
|
|
||||||
## Rationale
|
|
||||||
|
|
||||||
Why this option won. The reasoning that would have to change for the
|
|
||||||
decision to be worth revisiting.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
What follows — what this commits us to, what it rules out, what work it
|
|
||||||
creates. Both the good and the costly.
|
|
||||||
|
|
||||||
## Alternatives considered
|
|
||||||
|
|
||||||
What else was on the table and why each was not chosen. A decision record
|
|
||||||
without this is much less useful on re-reading: it's the part that stops
|
|
||||||
the same option being re-proposed every six months.
|
|
||||||
|
|
||||||
## Supersession
|
|
||||||
|
|
||||||
Supersedes [[D-0003]] / [D-0003](/wiki/decisions/0003-use-mysql.md) — MySQL
|
|
||||||
was chosen before the reporting requirements landed.
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- `raw/archive/2026-09-21/arch-review.md` — sha256:3f9a2c1e (checked 2026-09-21)
|
|
||||||
|
|
||||||
## Crux
|
|
||||||
|
|
||||||
> We're going with Postgres. The reporting requirements need window
|
|
||||||
> functions and nobody wants to maintain a second analytics store.
|
|
||||||
— `raw/archive/2026-09-21/arch-review.md`, Ana Reyes
|
|
||||||
```
|
|
||||||
|
|
||||||
A decision record is the page type where `## Crux` earns its place most
|
|
||||||
clearly. Everything above it is this skill's reconstruction of a choice;
|
|
||||||
the Crux is the moment the call was actually made, in the words it was made
|
|
||||||
in. When someone later asks "did we really decide that, or did we just
|
|
||||||
discuss it?", a verbatim quote settles it and a summary doesn't — which is
|
|
||||||
also the distinction between `status: accepted` and `status: proposed` that
|
|
||||||
`ckb-ingest` is warned about.
|
|
||||||
|
|
||||||
Quote the decision itself, not the surrounding debate; if the material
|
|
||||||
contains no sentence where anyone actually decides, that is strong evidence
|
|
||||||
the record should be `proposed`. Fingerprint every cited local file
|
|
||||||
(`sha256sum <file> | cut -c1-8`) so `ckb-lint` check 12 can tell you when
|
|
||||||
the source underneath a decision changes.
|
|
||||||
|
|
||||||
Decision pages carry no `## Notes` section. Nothing regenerates them, so
|
|
||||||
there is nothing to protect against — and an append-only record with a
|
|
||||||
freely-editable annotation block invites exactly the retroactive revision
|
|
||||||
the append-only rule exists to prevent.
|
|
||||||
|
|
||||||
### Field reference
|
|
||||||
|
|
||||||
| Field | Required | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| `type` | yes | always `decision` |
|
|
||||||
| `tldr` | yes | the decision itself in one sentence ("Billing uses Postgres"), not the topic ("database choice") — this is what shows in the index and in search results |
|
|
||||||
| `status` | yes | `proposed` / `accepted` / `rejected` / `superseded` / `reversed` — vocabulary defined in `wiki/decisions/index.md` |
|
|
||||||
| `decided_on` | yes for `accepted`/`rejected`/`reversed` | `YYYY-MM-DD`, the date the call was made. Distinct from `last_updated`, which is when the *page* last changed. A `proposed` decision may have no `decided_on` yet. |
|
|
||||||
| `decided_by` | yes when known | comma-separated names, kept flat and plain so tooling can read it. The body's **Deciders** line carries the dual-links to entity pages. If genuinely unknown, write `unknown` rather than omitting the field — "we don't know who decided this" is itself worth recording. |
|
|
||||||
| `affects` | no | comma-separated project-root-absolute wiki paths this decision constrains. Cheap way to answer "what decisions touch X" without walking the graph. |
|
|
||||||
| `review_on` | no | `YYYY-MM-DD` to revisit. `ckb-lint` reports these once the date passes. |
|
|
||||||
| `supersedes` / `superseded_by` | when relevant | project-root-absolute path to the other decision. **Always set both sides** (see below). |
|
|
||||||
| `confidence`, `quality`, `last_updated`, `freshness_window_days`, `retention` | as usual | standard page schema. Decisions default to `freshness_window_days: 365` and `retention: high` — a decision record doesn't rot the way a status page does, and it should survive a retention sweep. |
|
|
||||||
|
|
||||||
## How to run this skill — recording
|
|
||||||
|
|
||||||
### Step 1 — Collect the facts, ask only for what's missing
|
|
||||||
|
|
||||||
Take everything the user already said at face value; don't re-ask for it.
|
|
||||||
Then check what's missing against this list, in priority order:
|
|
||||||
|
|
||||||
1. **The decision itself** — what was chosen. Without this there's nothing to record.
|
|
||||||
2. **Who decided** — names. This is the field users most often leave out and most often want later.
|
|
||||||
3. **When** — a date. "Today" is fine; "last Tuesday's architecture review" is fine, resolve it to a date.
|
|
||||||
4. **Why** — the rationale, and the alternatives that lost.
|
|
||||||
5. **Whether it changes an existing decision** — see Step 2.
|
|
||||||
|
|
||||||
Ask for the missing ones in a **single** `AskUserQuestion` round rather than
|
|
||||||
an interview — this is a recording task, not a discovery interview. If the
|
|
||||||
user is clearly mid-flow and wants it written down now, record what you have,
|
|
||||||
mark the gaps explicitly in the page body (`## Rationale` → *"Not captured at
|
|
||||||
recording time."*), and say which fields you left open so they can fill them
|
|
||||||
in later. A decision recorded with gaps beats a decision not recorded.
|
|
||||||
|
|
||||||
Do not invent context, rationale, or consequences. If the user gave you one
|
|
||||||
sentence, the record is one sentence plus the metadata — a fabricated
|
|
||||||
`## Consequences` section is worse than an absent one, because a later reader
|
|
||||||
can't tell it wasn't real.
|
|
||||||
|
|
||||||
### Step 2 — Check whether it supersedes anything
|
|
||||||
|
|
||||||
Before writing, read `wiki/decisions/index.md` and scan for a decision on the
|
|
||||||
same subject. If one exists and is still `accepted`:
|
|
||||||
|
|
||||||
- Confirm with the user that the new decision replaces it (don't assume — two
|
|
||||||
decisions can coexist on the same subject at different scopes).
|
|
||||||
- If it replaces it outright, set `supersedes` on the new page and, on the
|
|
||||||
old page, set `superseded_by` **and** change its `status` to `superseded`.
|
|
||||||
- If it undoes it and returns to the prior state, use the same two links but
|
|
||||||
set the old page's status to `reversed` instead.
|
|
||||||
- Never edit anything else on the old page. Its context, rationale, and
|
|
||||||
consequences stay exactly as they were written — that's the historical
|
|
||||||
record.
|
|
||||||
|
|
||||||
Both directions must be set. A one-sided supersession link is a lint finding,
|
|
||||||
and it breaks the "what changed this?" lookup in the other direction.
|
|
||||||
|
|
||||||
### Step 3 — Allocate the number and write the page
|
|
||||||
|
|
||||||
The next number is the highest existing `NNNN` in `wiki/decisions/` plus one,
|
|
||||||
zero-padded to four digits — never reuse a number, even if the highest-numbered
|
|
||||||
decision was rejected or superseded. Slug from the decision itself, not the
|
|
||||||
topic: `0007-use-postgres-for-billing.md`, not `0007-database.md`.
|
|
||||||
|
|
||||||
Write the page using the format above. Link deciders to their entity pages
|
|
||||||
where those pages exist (dual-linked, per Rule C). Where a decider has no
|
|
||||||
entity page, write the name as plain text and mention the gap in your report
|
|
||||||
— creating person pages is `ckb-ingest`'s job, not this skill's.
|
|
||||||
|
|
||||||
### Step 4 — Wire it into the index and graph
|
|
||||||
|
|
||||||
- **`wiki/decisions/index.md`** — add a bullet in number order:
|
|
||||||
`* **[Accepted]** [D-0007 — Use Postgres for the billing store](0007-use-postgres-for-billing.md) — <tldr>`
|
|
||||||
and remove the "no decisions recorded yet" placeholder once there's a first
|
|
||||||
entry. When a decision's status changes (Step 2), update its index line too.
|
|
||||||
- **`wiki/graph/edges.json`** — add the edges this decision creates:
|
|
||||||
`decided_by` (decision → person page), `affects` (decision → each page named
|
|
||||||
in `affects`), and `supersedes` (new decision → old decision) where
|
|
||||||
applicable. Skip any edge whose target page doesn't exist rather than
|
|
||||||
pointing at a page you'd have to invent.
|
|
||||||
|
|
||||||
### Step 5 — Log it, then report
|
|
||||||
|
|
||||||
Log the change in **`wiki/decisions/log.md`** (not `wiki/log.md`) using
|
|
||||||
Rule B's format, and add a single pointer line to `wiki/log.md`:
|
|
||||||
`- See wiki/decisions/log.md for decision-record changes on this date.`
|
|
||||||
Per the Recursive Index & Log Convention, each change gets exactly one home
|
|
||||||
log — don't write the full entry in both.
|
|
||||||
|
|
||||||
Then tell the user: the number and title assigned, the fields you filled,
|
|
||||||
any fields left open, what it superseded (and that the old page's status was
|
|
||||||
updated), which deciders had no entity page, and the standard reminder that
|
|
||||||
this is on disk but not committed — "say 'sync changes' when you want it
|
|
||||||
pushed."
|
|
||||||
|
|
||||||
## How to run this skill — looking up
|
|
||||||
|
|
||||||
### Step 1 — Start at the decisions index
|
|
||||||
|
|
||||||
Read `wiki/decisions/index.md` first. It carries every decision's number,
|
|
||||||
title, status, and one-line summary — enough to answer "which decisions are
|
|
||||||
open", "what's been decided about X", and "which decision covers Y" without
|
|
||||||
opening a single page.
|
|
||||||
|
|
||||||
### Step 2 — Open the pages that actually matter, and read them fully
|
|
||||||
|
|
||||||
For a specific decision, read the whole page — a decision's `tldr` states the
|
|
||||||
choice but not the reasoning, and "why" is usually the real question. For a
|
|
||||||
subject-scoped question ("what have we decided about billing?"), open every
|
|
||||||
decision whose `affects` or body mentions the subject, including superseded
|
|
||||||
ones.
|
|
||||||
|
|
||||||
**Superseded decisions are part of the answer, not noise.** "We use Postgres,
|
|
||||||
and before that MySQL, changed in September because of reporting" is the
|
|
||||||
useful answer; "we use Postgres" is the impoverished one. Follow
|
|
||||||
`supersedes`/`superseded_by` chains in both directions and present the
|
|
||||||
history in order.
|
|
||||||
|
|
||||||
### Step 3 — Verify against the source before answering
|
|
||||||
|
|
||||||
Apply `ckb-retrieve`'s standing rule: a decision page's `## Sources` section
|
|
||||||
points at where the decision came from. When the answer hinges on detail
|
|
||||||
beyond what the page states — exact wording, a number, who was actually in
|
|
||||||
the room — follow the source rather than paraphrasing the paraphrase.
|
|
||||||
|
|
||||||
Where the page has a `## Crux`, take `ckb-retrieve` Step 6's shortcut: check
|
|
||||||
the recorded fingerprint against the cited file, and if it matches, quote the
|
|
||||||
Crux directly. If it doesn't match, the source underneath this decision has
|
|
||||||
been edited since the record was written — say so, answer from the source,
|
|
||||||
and flag it. That is worth stating plainly rather than folding into a
|
|
||||||
caveat: a decision record whose evidence has moved is the one case where
|
|
||||||
"what we decided" and "what the record says we decided" can come apart.
|
|
||||||
|
|
||||||
### Step 4 — Answer with the metadata attached
|
|
||||||
|
|
||||||
A decision answer is incomplete without **who** and **when** — lead with the
|
|
||||||
decision, then attribute it. State the status plainly, especially when it
|
|
||||||
isn't `accepted`: an answer built on a `proposed` or `superseded` decision
|
|
||||||
must say so in the same breath, or the reader will act on something that
|
|
||||||
isn't in force.
|
|
||||||
|
|
||||||
If nothing is recorded on the subject, say so plainly and offer to record one
|
|
||||||
now — don't reconstruct a decision from surrounding wiki content and present
|
|
||||||
it as if it were on the record. If the question revealed a genuine gap, that's
|
|
||||||
a `wiki/query-gaps.md` entry (`ckb-retrieve` owns that file).
|
|
||||||
|
|
||||||
## Rule: decisions are append-only
|
|
||||||
|
|
||||||
The only edits this skill makes to an *existing* decision page are:
|
|
||||||
`status`, `superseded_by`, `last_updated`, and — when the user is explicitly
|
|
||||||
correcting a recording error rather than changing their mind — the factual
|
|
||||||
metadata fields. Context, Decision, Rationale, Consequences, and Alternatives
|
|
||||||
are never rewritten to match a later view.
|
|
||||||
|
|
||||||
When the user says "actually we changed our mind about D-0003", that is a new
|
|
||||||
decision superseding D-0003, not an edit to D-0003. Say so and record it that
|
|
||||||
way. The exception is a genuine transcription error ("I said Alice, it was
|
|
||||||
actually Anna") — fix that in place and note the correction in the log entry.
|
|
||||||
|
|
||||||
## Edge cases
|
|
||||||
|
|
||||||
- **No `wiki/decisions/` directory yet** — create it along with `index.md`
|
|
||||||
and `log.md` (using the scaffold this template ships), then record the
|
|
||||||
decision as `0001-…`.
|
|
||||||
- **The user describes a decision that was already recorded** — don't create
|
|
||||||
a duplicate. Point at the existing record and ask whether they want to
|
|
||||||
supersede it, correct it, or leave it.
|
|
||||||
- **A decision with no clear decider** ("we just kind of settled on it") —
|
|
||||||
record `decided_by: unknown` rather than guessing or attributing it to
|
|
||||||
whoever is in the room. Unattributed is a fact; misattributed is a defect.
|
|
||||||
- **A decision that was never actually made** ("we should probably...") —
|
|
||||||
record it as `status: proposed` with no `decided_on`, or don't record it at
|
|
||||||
all. Ask which; a proposal filed as `accepted` is the most damaging failure
|
|
||||||
mode this skill has.
|
|
||||||
- **A decision that reverses a reversal** — normal chaining. Each link points
|
|
||||||
one step back; the chain is the history.
|
|
||||||
- **Bulk import of historical decisions** (a meeting-notes backlog) — that's
|
|
||||||
`ckb-ingest`'s job for the extraction, then this skill's format for the
|
|
||||||
pages. Record them in chronological order so the numbers run in the same
|
|
||||||
order as the decisions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
|
|
||||||
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*
|
|
||||||
|
|
@ -17,16 +17,9 @@ dual-linking, the cascade layers). This is a one-way, on-demand export —
|
||||||
`wiki/` stays the authoritative source; `outputs/okf/` is always a derived
|
`wiki/` stays the authoritative source; `outputs/okf/` is always a derived
|
||||||
artifact of it, never edited by hand and never fed back in.
|
artifact of it, never edited by hand and never fed back in.
|
||||||
|
|
||||||
The whole transform — frontmatter remapping, link rewriting, index and log
|
|
||||||
regeneration, conformance validation — is done by a deterministic Python
|
|
||||||
script, not by reading and rewriting every page by hand. The mapping is a
|
|
||||||
fixed ruleset over a wiki that will keep growing, and a mechanical transform
|
|
||||||
like this belongs in code, not in per-page model reasoning. Nothing in this
|
|
||||||
skill needs an LLM to run correctly; the model's job is to invoke the script
|
|
||||||
and relay its report.
|
|
||||||
|
|
||||||
This skill only runs when explicitly invoked — it is deliberately not part
|
This skill only runs when explicitly invoked — it is deliberately not part
|
||||||
of the always-loaded `CLAUDE.md`/`AGENTS.md` Ingest/Lint workflows.
|
of the always-loaded `CLAUDE.md`/`AGENTS.md` Ingest/Lint workflows, so its
|
||||||
|
mapping ruleset doesn't tax every session's context.
|
||||||
|
|
||||||
## Trigger phrases
|
## Trigger phrases
|
||||||
|
|
||||||
|
|
@ -38,120 +31,154 @@ Use this skill when the user says things like:
|
||||||
|
|
||||||
## How to run this skill
|
## How to run this skill
|
||||||
|
|
||||||
### Step 1 — Run the script
|
### Step 1 — Read the source tree
|
||||||
|
|
||||||
From the repository root:
|
Read every file under `wiki/` (the whole tree, including `entities/`,
|
||||||
|
`graph/`, and any other subdirectories present). Note which filenames are
|
||||||
|
`index.md` or `log.md` at any depth (these are OKF's two reserved names) —
|
||||||
|
everything else is a concept document.
|
||||||
|
|
||||||
```bash
|
### Step 2 — Clear and rebuild `outputs/okf/`
|
||||||
python3 "<skill-dir>/scripts/export_okf.py"
|
|
||||||
```
|
|
||||||
|
|
||||||
Resolve `<skill-dir>` to this skill's own directory. Flags:
|
`outputs/okf/` is a pure, fully-regenerated build artifact. Delete its
|
||||||
|
current contents (if any) and rebuild it from scratch by mirroring `wiki/`'s
|
||||||
|
directory structure 1:1 under `outputs/okf/`. Never hand-patch an existing
|
||||||
|
export — always regenerate the whole bundle, so it can't accumulate stale
|
||||||
|
files left over from wiki pages that were since renamed or removed.
|
||||||
|
|
||||||
- `--check` — build into a temporary directory, run the conformance checks,
|
Do not touch anything outside `outputs/okf/`. Do not run any `git`
|
||||||
print the report, and write nothing to `outputs/`. Use this when the user
|
commands — regenerating files is this skill's job; staging and committing
|
||||||
wants to know whether the wiki *would* export cleanly without touching the
|
the result is a separate, explicit action left to the user.
|
||||||
working tree (e.g. as part of a review, or before a lint pass).
|
|
||||||
- `--out DIR` — write somewhere other than `outputs/okf` (rarely needed).
|
|
||||||
|
|
||||||
The script deletes and rebuilds the output directory on every run, so the
|
### Step 3 — Transform concept documents (every file that isn't `index.md`/`log.md`)
|
||||||
bundle can never accumulate stale files from pages that were since renamed
|
|
||||||
or removed. It touches nothing outside the output directory and runs no
|
|
||||||
`git` commands.
|
|
||||||
|
|
||||||
The exit code is `0` when the generated bundle conforms and `1` when it does
|
For each such file, rewrite its frontmatter using this field mapping:
|
||||||
not — the report always prints either way, so read it rather than relying on
|
|
||||||
the exit code alone.
|
|
||||||
|
|
||||||
### Step 2 — Relay the script's report
|
|
||||||
|
|
||||||
The script prints, and you should summarize back to the user:
|
|
||||||
|
|
||||||
- Concept documents exported, and `index.md`/`log.md` files regenerated.
|
|
||||||
- Wikilinks stripped and `/wiki/` links rewritten to bundle-root paths.
|
|
||||||
- How many `linked/`/`libs/` cross-cascade references were left unconverted
|
|
||||||
— those won't resolve if the bundle is ever moved or shipped standalone,
|
|
||||||
which is spec-compliant (OKF tolerates broken links) but worth stating.
|
|
||||||
- Any `SOURCE ISSUE:` lines. These are problems in `wiki/` itself, not in
|
|
||||||
the export — most commonly a page with no `type`, which the bundle exports
|
|
||||||
as `type: unknown`. Surface them; the fix belongs in the source wiki (run
|
|
||||||
`ckb-lint`), not in the export.
|
|
||||||
- Any `NONCONFORMANT:` lines. These are bugs in the export — an
|
|
||||||
intra-bundle link that doesn't resolve, an index with the wrong
|
|
||||||
frontmatter shape, a malformed log header. Do not report the run as
|
|
||||||
successful when these appear; investigate the script rather than
|
|
||||||
hand-patching `outputs/okf/`.
|
|
||||||
- A reminder that `outputs/okf/` has been regenerated on disk but not staged
|
|
||||||
or committed — that's a separate step for the user when they're ready to
|
|
||||||
publish the update. (`outputs/okf` is gitignored by default in this
|
|
||||||
template, so "publish" usually means copying the bundle elsewhere rather
|
|
||||||
than committing it in place.)
|
|
||||||
|
|
||||||
## What the transform does (for context, not something you need to re-derive)
|
|
||||||
|
|
||||||
**Concept documents** (every `.md` that isn't `index.md`/`log.md`, including
|
|
||||||
`overview.md`, `error-book.md`, and `query-gaps.md` — only `index.md` and
|
|
||||||
`log.md` are OKF reserved names):
|
|
||||||
|
|
||||||
| wiki/ field | OKF output field | Rule |
|
| wiki/ field | OKF output field | Rule |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `type` | `type` | passthrough; a page with no `type` exports as `unknown` and is reported as a source issue |
|
| `type` | `type` | passthrough (required on both sides — every wiki page should already have one; if a page is somehow missing it, use `type: unknown` and flag it in the final report rather than skipping the file) |
|
||||||
| *(derive)* | `title` | the first `# H1` in the body, else the slugified filename (`foo-bar.md` → "Foo Bar") |
|
|
||||||
| `tldr` | `description` | rename |
|
|
||||||
| `resource` | `resource` | passthrough |
|
| `resource` | `resource` | passthrough |
|
||||||
| `last_updated` | `timestamp` | passthrough as-is (bare `YYYY-MM-DD` is valid ISO 8601 — no time-of-day is fabricated) |
|
| `tldr` | `description` | rename |
|
||||||
| `confidence`, `quality`, `retention`, `supersedes`, `superseded_by`, `freshness_window_days`, `source_fingerprint`, `source_checked` | same keys | passthrough as OKF extension fields, which consumers must tolerate |
|
| `last_updated` | `timestamp` | passthrough as-is (bare `YYYY-MM-DD` is a valid ISO 8601 date — do not fabricate a time-of-day that was never recorded) |
|
||||||
| `kb_schema_version` | *(dropped)* | the root `index.md`'s frontmatter is spec-limited to `okf_version`; this key has no valid home in the bundle |
|
| *(none — derive)* | `title` | the first `# H1` heading in the body; if there is none, slugify the filename (e.g. `foo-bar.md` → "Foo Bar") |
|
||||||
| *(none)* | `tags` | omitted — no source field to derive it from |
|
| `confidence`, `quality`, `retention`, `supersedes`, `superseded_by`, `freshness_window_days` | same key names | passthrough unchanged, as OKF extension fields — OKF requires consumers to tolerate unrecognized keys, so these ride along as-is |
|
||||||
|
| `kb_schema_version` (only ever present on `wiki/index.md`) | *(omit)* | OKF's root `index.md` frontmatter is spec-limited to `okf_version` only; this key has no valid home in the bundle and is dropped, not relocated |
|
||||||
|
| *(none)* | `tags` | omit — there is no source field to derive it from; do not fabricate |
|
||||||
|
|
||||||
**Links:** the `[[Wikilink]]` half of every dual-link is dropped, keeping the
|
Then rewrite the body's links:
|
||||||
markdown half (a bare wikilink with no markdown twin degrades to its plain
|
|
||||||
label). Root-absolute `/wiki/entities/foo.md` becomes `/entities/foo.md`,
|
|
||||||
since the bundle root is `outputs/okf/`, not the repo root. Relative links
|
|
||||||
need no change — the export mirrors `wiki/`'s tree exactly.
|
|
||||||
`linked/<name>/...` and `libs/<name>/...` references are left untouched and
|
|
||||||
counted.
|
|
||||||
|
|
||||||
**Indexes:** regenerated from the tree rather than transcribed from the
|
1. **Strip wikilinks.** Every dual-link in the source looks like
|
||||||
source, so they can't drift. The root `index.md` carries only
|
`[[Page Name]] / [Page Name](path.md)`. Delete the `[[...]]` half
|
||||||
`okf_version: "0.1"` (the one documented exception to "index.md has no
|
(and the ` / ` separator if present), keep only the
|
||||||
frontmatter"); nested indexes carry none. Each body is an H1 plus a flat
|
`[text](path.md)` half. OKF has no wikilink concept.
|
||||||
`* [Title](path) - description` bullet list of that directory's direct
|
2. **Rewrite repo-root-absolute intra-wiki links.** A link like
|
||||||
children, sorted by path, with each page's own `description` as the
|
`/wiki/entities/foo.md` becomes `/entities/foo.md` — the OKF bundle
|
||||||
description text. The source's "Use when" column and prose sections are
|
root is `outputs/okf/`, not the repo root, so the leading `/wiki`
|
||||||
dropped — they are Claude-agent lazy-loading optimizations with no meaning
|
segment must be stripped. Plain relative links (e.g.
|
||||||
to a generic OKF consumer. An empty list is spec-valid.
|
`../entities/foo.md`) need no change, since the export mirrors
|
||||||
|
`wiki/`'s tree exactly.
|
||||||
|
3. **Leave `linked/<name>/...` and `libs/<name>/...` references
|
||||||
|
untouched.** These point outside `wiki/` and outside any
|
||||||
|
self-contained bundle — converting or inlining them is out of scope.
|
||||||
|
OKF explicitly tolerates broken links, so leaving these as
|
||||||
|
unresolvable-outside-the-repo references is spec-compliant. Count
|
||||||
|
how many of these you leave untouched, for the final report.
|
||||||
|
|
||||||
**Logs:** entries are regrouped under `## YYYY-MM-DD` headers (date only,
|
### Step 4 — Regenerate `index.md` files
|
||||||
newest date first) as `* **Verb**: [affected files] — description (source: ...)`.
|
|
||||||
`CREATE` → Creation, `UPDATE` → Update, `DELETE` → Deprecation, anything else
|
|
||||||
→ Update. The `- **Source:**` line rides along as the trailing parenthetical
|
|
||||||
rather than being dropped — it's provenance worth keeping. The `---`
|
|
||||||
separators between entries are dropped; they aren't part of OKF's log format.
|
|
||||||
|
|
||||||
**Validation** runs against the generated bundle before the report prints:
|
**Root `outputs/okf/index.md`:** frontmatter contains *only*
|
||||||
every non-reserved page has a non-empty `type`; the root index has only
|
`okf_version: "0.1"` — this is the one documented exception to "index.md
|
||||||
`okf_version` and nested indexes have no frontmatter; every `log.md` header
|
has no frontmatter" in the OKF spec. Body is a flat bullet list, one line
|
||||||
matches `## YYYY-MM-DD`; and every intra-bundle link resolves to a file that
|
per linked page or subdirectory, in the form
|
||||||
exists (cross-cascade `linked/`/`libs/` references are exempt by design).
|
`* [Title](path) - one-line description` (reuse each page's `description`,
|
||||||
|
i.e. the renamed `tldr`, as the description text). Drop the source
|
||||||
|
`wiki/index.md`'s "Use when" column entirely — it's a Claude-agent-only
|
||||||
|
lazy-loading optimization with no meaning to a generic OKF consumer — and
|
||||||
|
drop any prose sections (like the source's "## Entity Pages" paragraph).
|
||||||
|
|
||||||
|
**Every nested `index.md`** (`entities/index.md`, `graph/index.md`, and
|
||||||
|
any future topic-folder index): no frontmatter, ever. Strip the source's
|
||||||
|
explanatory prose and italic placeholder notes (e.g.
|
||||||
|
"*(No entities yet — populated on the next ingest.)*"), keeping only the
|
||||||
|
bullet list — an empty list is spec-valid if the directory has no pages
|
||||||
|
yet.
|
||||||
|
|
||||||
|
### Step 5 — Regenerate `log.md` files
|
||||||
|
|
||||||
|
For each `log.md` in the source tree (root and any subdirectory), reformat
|
||||||
|
its entries into OKF's convention: group entries by date under
|
||||||
|
`## YYYY-MM-DD` headers (date only, no time, newest date first), each
|
||||||
|
entry as a bullet:
|
||||||
|
|
||||||
|
```
|
||||||
|
* **Verb**: description with [links](/path/to.md) (source: original source text).
|
||||||
|
```
|
||||||
|
|
||||||
|
Map the source's `ACTION TYPE` to an OKF verb: `CREATE` → Creation,
|
||||||
|
`UPDATE` → Update, `DELETE` → Deprecation, `RESTRUCTURE` → Update (fallback
|
||||||
|
for anything else). Fold the source entry's `- **Source:**` line in as the
|
||||||
|
trailing parenthetical shown above rather than dropping it — it's useful
|
||||||
|
provenance information and OKF's format has room for free text after the
|
||||||
|
verb. Drop the `---` horizontal-rule separators between entries; they are
|
||||||
|
not part of OKF's log format.
|
||||||
|
|
||||||
|
### Step 6 — Handle non-reserved special pages
|
||||||
|
|
||||||
|
`wiki/error-book.md` and `wiki/overview.md` are not OKF reserved
|
||||||
|
filenames (only `index.md` and `log.md` are) — export them as ordinary
|
||||||
|
concept documents using the Step 3 rules like any other page. They should
|
||||||
|
already carry `type`/`tldr`/`last_updated` frontmatter; if you ever find
|
||||||
|
one that doesn't, that's a lint problem in the source wiki — flag it in
|
||||||
|
the report rather than silently patching the export.
|
||||||
|
|
||||||
|
### Step 7 — Validate the output bundle
|
||||||
|
|
||||||
|
Before reporting done, re-check the *generated* `outputs/okf/` bundle
|
||||||
|
against OKF's own conformance criteria (the same shape of check `CLAUDE.md`
|
||||||
|
§6 step 1 runs against the source wiki):
|
||||||
|
|
||||||
|
- Every non-reserved `.md` file has frontmatter with a non-empty `type`.
|
||||||
|
- The root `index.md`'s frontmatter contains only `okf_version` (or is
|
||||||
|
empty); every nested `index.md` has no frontmatter at all.
|
||||||
|
- Every `log.md` entry matches the `## YYYY-MM-DD` header pattern.
|
||||||
|
- Every intra-bundle link (i.e. every link you did NOT leave untouched in
|
||||||
|
Step 3.3) resolves to a file that actually exists in `outputs/okf/`. If
|
||||||
|
the rewrite in Step 3.2 produced a link that doesn't resolve, that's a
|
||||||
|
bug in this export, not an acceptable "broken link" — fix it before
|
||||||
|
reporting done, don't just note it as a warning.
|
||||||
|
|
||||||
|
### Step 8 — Report
|
||||||
|
|
||||||
|
Tell the user:
|
||||||
|
- How many concept documents were exported.
|
||||||
|
- How many `index.md`/`log.md` files were regenerated.
|
||||||
|
- How many `linked/`/`libs/` cross-cascade references were left
|
||||||
|
unconverted (Step 3.3), since those won't resolve if the bundle is ever
|
||||||
|
moved or shipped standalone.
|
||||||
|
- Any conformance issues found in Step 7 and whether they were fixed.
|
||||||
|
- A reminder that `outputs/okf/` has been regenerated on disk but not
|
||||||
|
staged or committed — that's a separate step for the user to take when
|
||||||
|
ready to publish the update.
|
||||||
|
|
||||||
## Edge cases
|
## Edge cases
|
||||||
|
|
||||||
- **Empty `wiki/entities/` or `wiki/graph/`:** their `index.md` is still
|
- **Empty `wiki/entities/` or `wiki/graph/`** (as of writing, both are
|
||||||
regenerated, as an H1 with an empty bullet list. An empty index is
|
empty): still regenerate their `index.md` as an empty bullet list under
|
||||||
spec-valid; the directory is never skipped.
|
`outputs/okf/entities/` and `outputs/okf/graph/` — an empty index is
|
||||||
- **A future `wiki/<newtopic>/` subdirectory:** handled automatically — the
|
spec-valid, don't skip the directory entirely.
|
||||||
script discovers directories dynamically, mirrors them, and generates an
|
- **A wiki page missing `type`:** per Step 3, use `type: unknown` and flag
|
||||||
index for each. No script changes needed.
|
it in the report — this indicates the source wiki itself failed lint's
|
||||||
- **A future `wiki/archived/`:** exported like any other subdirectory. OKF
|
conformance check (see `CLAUDE.md` §6 step 1), which is worth surfacing
|
||||||
has no notion of archival status; `retention`/`freshness_window_days`
|
to the user rather than quietly masking it in the export.
|
||||||
|
- **A future `wiki/archived/` directory:** export it like any other
|
||||||
|
subdirectory (mirror the structure, apply the same per-file rules) —
|
||||||
|
OKF has no notion of archival status; `retention`/`freshness_window_days`
|
||||||
already ride along as extension fields for any consumer that cares.
|
already ride along as extension fields for any consumer that cares.
|
||||||
- **Non-markdown files in `wiki/`** (e.g. `graph/edges.json`): copied
|
- **Re-running the skill with no wiki changes since the last run** should
|
||||||
verbatim into the same relative position and listed in their directory's
|
produce byte-identical output — if you notice non-determinism (e.g. from
|
||||||
index. Dotfiles (`.gitadd`) are skipped.
|
arbitrary ordering when listing directory entries), sort filenames
|
||||||
- **Re-running with no wiki changes:** produces byte-identical output — every
|
alphabetically wherever you're generating a bullet list or walking a
|
||||||
directory walk and generated list is sorted. If a run is ever
|
directory, so re-runs are stable.
|
||||||
nondeterministic, that's a bug in the script, not expected behavior.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,531 +0,0 @@
|
||||||
#!/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/ as an Open Knowledge Format (OKF) v0.1 bundle at outputs/okf/.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python3 export_okf.py [--check] [--out DIR]
|
|
||||||
|
|
||||||
Run from the repository root (the directory containing wiki/ and outputs/).
|
|
||||||
|
|
||||||
--check validate only: build the bundle in a temporary directory, run the
|
|
||||||
conformance checks, print the report, and write nothing to outputs/.
|
|
||||||
--out override the output directory (default: outputs/okf).
|
|
||||||
|
|
||||||
The transform is fully deterministic: two runs over an unchanged wiki/ produce
|
|
||||||
byte-identical output. Every directory walk and generated list is sorted.
|
|
||||||
"""
|
|
||||||
import argparse
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
REPO_ROOT = Path.cwd()
|
|
||||||
WIKI = REPO_ROOT / "wiki"
|
|
||||||
|
|
||||||
OKF_VERSION = "0.1"
|
|
||||||
RESERVED = {"index.md", "log.md"}
|
|
||||||
|
|
||||||
# wiki/ frontmatter keys that ride along unchanged as OKF extension fields.
|
|
||||||
PASSTHROUGH_EXT = [
|
|
||||||
"confidence",
|
|
||||||
"quality",
|
|
||||||
"retention",
|
|
||||||
"supersedes",
|
|
||||||
"superseded_by",
|
|
||||||
"freshness_window_days",
|
|
||||||
"source_fingerprint",
|
|
||||||
"source_checked",
|
|
||||||
]
|
|
||||||
# Keys with no valid home in an OKF bundle.
|
|
||||||
DROPPED = {"kb_schema_version"}
|
|
||||||
|
|
||||||
VERB_MAP = {
|
|
||||||
"CREATE": "Creation",
|
|
||||||
"UPDATE": "Update",
|
|
||||||
"DELETE": "Deprecation",
|
|
||||||
"RESTRUCTURE": "Update",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# frontmatter
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def parse_frontmatter(text):
|
|
||||||
"""Return (dict, body). Flat `key: value` YAML only — that is all the
|
|
||||||
schema uses. Unparseable or absent frontmatter yields ({}, text)."""
|
|
||||||
if not text.startswith("---\n"):
|
|
||||||
return {}, text
|
|
||||||
end = text.find("\n---\n", 4)
|
|
||||||
if end == -1:
|
|
||||||
return {}, text
|
|
||||||
raw = text[4:end]
|
|
||||||
body = text[end + 5 :]
|
|
||||||
fm = {}
|
|
||||||
for line in raw.split("\n"):
|
|
||||||
line = line.rstrip()
|
|
||||||
if not line or line.lstrip().startswith("#"):
|
|
||||||
continue
|
|
||||||
if ":" not in line:
|
|
||||||
continue
|
|
||||||
key, _, value = line.partition(":")
|
|
||||||
fm[key.strip()] = unquote(value.strip())
|
|
||||||
return fm, body
|
|
||||||
|
|
||||||
|
|
||||||
def unquote(value):
|
|
||||||
if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'):
|
|
||||||
return value[1:-1]
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def yaml_scalar(value):
|
|
||||||
"""Emit a value that round-trips through the parser above."""
|
|
||||||
s = str(value)
|
|
||||||
if s == "":
|
|
||||||
return '""'
|
|
||||||
if s[0] in "\"'&*!|>%@`[]{},#" or s[-1] == ":" or ": " in s or s.strip() != s:
|
|
||||||
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def render_frontmatter(pairs):
|
|
||||||
lines = ["---"]
|
|
||||||
for key, value in pairs:
|
|
||||||
lines.append(f"{key}: {yaml_scalar(value)}")
|
|
||||||
lines.append("---")
|
|
||||||
return "\n".join(lines) + "\n"
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# body text
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def first_h1(body):
|
|
||||||
for line in body.split("\n"):
|
|
||||||
if line.startswith("# "):
|
|
||||||
return line[2:].strip()
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def slug_title(filename):
|
|
||||||
stem = Path(filename).stem
|
|
||||||
return " ".join(w.capitalize() for w in re.split(r"[-_]+", stem) if w)
|
|
||||||
|
|
||||||
|
|
||||||
def strip_wikilinks(body, counters):
|
|
||||||
"""Drop the [[...]] half of every dual-link, keeping the markdown half."""
|
|
||||||
|
|
||||||
def drop_pair(m):
|
|
||||||
counters["wikilinks"] += 1
|
|
||||||
return m.group("keep")
|
|
||||||
|
|
||||||
# [[X]] / [text](path) and [text](path) / [[X]]
|
|
||||||
body = re.sub(
|
|
||||||
r"\[\[[^\]]*\]\]\s*/\s*(?P<keep>\[[^\]]*\]\([^)]*\))", drop_pair, body
|
|
||||||
)
|
|
||||||
body = re.sub(
|
|
||||||
r"(?P<keep>\[[^\]]*\]\([^)]*\))\s*/\s*\[\[[^\]]*\]\]", drop_pair, body
|
|
||||||
)
|
|
||||||
|
|
||||||
# Any remaining bare wikilink degrades to its plain label.
|
|
||||||
def bare(m):
|
|
||||||
counters["wikilinks"] += 1
|
|
||||||
label = m.group(1)
|
|
||||||
return label.split("|", 1)[-1].strip()
|
|
||||||
|
|
||||||
return re.sub(r"\[\[([^\]]*)\]\]", bare, body)
|
|
||||||
|
|
||||||
|
|
||||||
def rewrite_links(body, counters):
|
|
||||||
"""Strip the /wiki prefix from repo-root-absolute intra-wiki links and
|
|
||||||
count (but never touch) cross-cascade linked//libs/ references."""
|
|
||||||
|
|
||||||
def repl(m):
|
|
||||||
target = m.group(2)
|
|
||||||
if re.match(r"^\.{0,2}/?(linked|libs)/", target):
|
|
||||||
counters["cascade_refs"] += 1
|
|
||||||
return m.group(0)
|
|
||||||
if target.startswith("/wiki/"):
|
|
||||||
counters["rewritten"] += 1
|
|
||||||
target = target[5:]
|
|
||||||
elif target == "/wiki" or target == "/wiki/":
|
|
||||||
counters["rewritten"] += 1
|
|
||||||
target = "/"
|
|
||||||
return f"[{m.group(1)}]({target})"
|
|
||||||
|
|
||||||
return re.sub(r"\[([^\]]*)\]\(([^)]*)\)", repl, body)
|
|
||||||
|
|
||||||
|
|
||||||
def transform_body(body, counters):
|
|
||||||
return rewrite_links(strip_wikilinks(body, counters), counters)
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# source tree
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class Page:
|
|
||||||
def __init__(self, relpath, text):
|
|
||||||
self.relpath = relpath # PosixPath relative to wiki/
|
|
||||||
self.fm, self.body = parse_frontmatter(text)
|
|
||||||
self.title = first_h1(self.body) or slug_title(relpath.name)
|
|
||||||
self.description = self.fm.get("tldr", "")
|
|
||||||
|
|
||||||
|
|
||||||
def read_tree(wiki):
|
|
||||||
pages, logs, indexes, assets = {}, {}, {}, []
|
|
||||||
for path in sorted(wiki.rglob("*")):
|
|
||||||
if not path.is_file() or path.name.startswith("."):
|
|
||||||
continue
|
|
||||||
rel = path.relative_to(wiki)
|
|
||||||
if path.suffix != ".md":
|
|
||||||
assets.append(rel)
|
|
||||||
continue
|
|
||||||
text = path.read_text(encoding="utf-8")
|
|
||||||
if path.name == "index.md":
|
|
||||||
indexes[rel] = text
|
|
||||||
elif path.name == "log.md":
|
|
||||||
logs[rel] = text
|
|
||||||
else:
|
|
||||||
pages[rel] = Page(rel, text)
|
|
||||||
return pages, logs, indexes, assets
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# emitters
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def emit_concept(page, counters, issues):
|
|
||||||
ptype = page.fm.get("type", "").strip()
|
|
||||||
if not ptype:
|
|
||||||
ptype = "unknown"
|
|
||||||
issues.append(f"{page.relpath}: no `type` in source frontmatter (exported as `unknown`)")
|
|
||||||
|
|
||||||
pairs = [("type", ptype), ("title", page.title)]
|
|
||||||
if page.description:
|
|
||||||
pairs.append(("description", page.description))
|
|
||||||
if page.fm.get("resource"):
|
|
||||||
pairs.append(("resource", page.fm["resource"]))
|
|
||||||
if page.fm.get("last_updated"):
|
|
||||||
pairs.append(("timestamp", page.fm["last_updated"]))
|
|
||||||
for key in PASSTHROUGH_EXT:
|
|
||||||
if key in page.fm:
|
|
||||||
pairs.append((key, page.fm[key]))
|
|
||||||
for key in sorted(page.fm):
|
|
||||||
if key in DROPPED or key in PASSTHROUGH_EXT:
|
|
||||||
continue
|
|
||||||
if key in ("type", "resource", "tldr", "last_updated"):
|
|
||||||
continue
|
|
||||||
pairs.append((key, page.fm[key]))
|
|
||||||
|
|
||||||
body = transform_body(page.body, counters).lstrip("\n")
|
|
||||||
return render_frontmatter(pairs) + "\n" + body.rstrip("\n") + "\n"
|
|
||||||
|
|
||||||
|
|
||||||
def index_entries(directory, pages, logs, indexes, assets):
|
|
||||||
"""Every direct child of `directory` (a PosixPath relative to wiki/, or
|
|
||||||
Path('.') for the root), as (title, href, description) — sorted, so the
|
|
||||||
generated list is stable across runs."""
|
|
||||||
entries = []
|
|
||||||
for rel, page in pages.items():
|
|
||||||
if rel.parent == directory:
|
|
||||||
entries.append((page.title, rel.name, page.description))
|
|
||||||
for rel in logs:
|
|
||||||
if rel.parent == directory:
|
|
||||||
entries.append(("Change Log", rel.name, "Chronological record of changes in this directory."))
|
|
||||||
for rel in assets:
|
|
||||||
if rel.parent == directory:
|
|
||||||
entries.append((rel.name, rel.name, ""))
|
|
||||||
subdirs = set()
|
|
||||||
for rel in list(pages) + list(logs) + list(indexes) + list(assets):
|
|
||||||
parent = rel.parent
|
|
||||||
while parent != Path("."):
|
|
||||||
if parent.parent == directory:
|
|
||||||
subdirs.add(parent)
|
|
||||||
parent = parent.parent
|
|
||||||
for sub in subdirs:
|
|
||||||
sub_index = sub / "index.md"
|
|
||||||
title = slug_title(sub.name)
|
|
||||||
if sub_index in indexes:
|
|
||||||
title = first_h1(parse_frontmatter(indexes[sub_index])[1]) or title
|
|
||||||
entries.append((title, f"{sub.name}/index.md", ""))
|
|
||||||
return sorted(entries, key=lambda e: e[1])
|
|
||||||
|
|
||||||
|
|
||||||
def emit_index(directory, pages, logs, indexes, assets, is_root):
|
|
||||||
lines = []
|
|
||||||
if is_root:
|
|
||||||
lines.append(render_frontmatter([("okf_version", OKF_VERSION)]).rstrip("\n"))
|
|
||||||
lines.append("")
|
|
||||||
src = indexes.get(directory / "index.md" if directory != Path(".") else Path("index.md"), "")
|
|
||||||
heading = first_h1(parse_frontmatter(src)[1]) if src else None
|
|
||||||
lines.append(f"# {heading or slug_title(directory.name) or 'Index'}")
|
|
||||||
lines.append("")
|
|
||||||
for title, href, description in index_entries(directory, pages, logs, indexes, assets):
|
|
||||||
if description:
|
|
||||||
lines.append(f"* [{title}]({href}) - {description}")
|
|
||||||
else:
|
|
||||||
lines.append(f"* [{title}]({href})")
|
|
||||||
return "\n".join(lines).rstrip("\n") + "\n"
|
|
||||||
|
|
||||||
|
|
||||||
LOG_HEADER_RE = re.compile(
|
|
||||||
r"^##\s*\[?(?P<date>\d{4}-\d{2}-\d{2})(?:[ T](?P<time>\d{2}:\d{2}))?\]?\s*-\s*\[?(?P<action>[^\]\n]*?)\]?\s*$"
|
|
||||||
)
|
|
||||||
FIELD_RE = re.compile(r"^-\s*\*\*(?P<key>[^:*]+):?\*\*:?\s*(?P<value>.*)$")
|
|
||||||
|
|
||||||
|
|
||||||
def parse_log(text):
|
|
||||||
"""Return (heading, [entry dicts]) from a Rule B formatted log."""
|
|
||||||
body = parse_frontmatter(text)[1]
|
|
||||||
heading = first_h1(body)
|
|
||||||
entries, current = [], None
|
|
||||||
for line in body.split("\n"):
|
|
||||||
m = LOG_HEADER_RE.match(line.strip())
|
|
||||||
if m:
|
|
||||||
if current:
|
|
||||||
entries.append(current)
|
|
||||||
current = {
|
|
||||||
"date": m.group("date"),
|
|
||||||
"time": m.group("time") or "",
|
|
||||||
"action": (m.group("action") or "").strip(),
|
|
||||||
"fields": {},
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
if current is None:
|
|
||||||
continue
|
|
||||||
f = FIELD_RE.match(line.strip())
|
|
||||||
if f:
|
|
||||||
current["fields"][f.group("key").strip().lower()] = f.group("value").strip()
|
|
||||||
if current:
|
|
||||||
entries.append(current)
|
|
||||||
return heading, entries
|
|
||||||
|
|
||||||
|
|
||||||
def verb_for(action):
|
|
||||||
for token in re.split(r"[/,\s]+", action.upper()):
|
|
||||||
if token in VERB_MAP:
|
|
||||||
return VERB_MAP[token]
|
|
||||||
return "Update"
|
|
||||||
|
|
||||||
|
|
||||||
def files_to_links(value, counters):
|
|
||||||
"""`wiki/a.md`, `wiki/b.md` -> [/a.md](/a.md), [/b.md](/b.md)
|
|
||||||
|
|
||||||
Only entries that name an actual file inside wiki/ become links. A
|
|
||||||
Rule B entry may also name a directory or carry a parenthetical
|
|
||||||
("`wiki/decisions/` (directory)") — those stay plain text, since a link
|
|
||||||
to them would not resolve inside the bundle."""
|
|
||||||
out = []
|
|
||||||
for chunk in re.split(r",\s*", value):
|
|
||||||
chunk = chunk.strip()
|
|
||||||
if not chunk:
|
|
||||||
continue
|
|
||||||
m = re.match(r"^`([^`]+)`(.*)$", chunk)
|
|
||||||
name = (m.group(1) if m else chunk).strip()
|
|
||||||
trailer = (m.group(2).strip() if m else "")
|
|
||||||
if name.startswith("wiki/") and name.endswith(".md") and not re.search(r"\s", name):
|
|
||||||
href = "/" + name[len("wiki/") :]
|
|
||||||
rendered = f"[{href}]({href})"
|
|
||||||
else:
|
|
||||||
rendered = name
|
|
||||||
out.append(f"{rendered} {trailer}".strip() if trailer else rendered)
|
|
||||||
return ", ".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def emit_log(text, counters):
|
|
||||||
heading, entries = parse_log(text)
|
|
||||||
lines = [f"# {heading or 'Change Log'}", ""]
|
|
||||||
by_date = {}
|
|
||||||
for entry in entries:
|
|
||||||
by_date.setdefault(entry["date"], []).append(entry)
|
|
||||||
for date in sorted(by_date, reverse=True):
|
|
||||||
lines.append(f"## {date}")
|
|
||||||
lines.append("")
|
|
||||||
for entry in by_date[date]:
|
|
||||||
verb = verb_for(entry["action"])
|
|
||||||
fields = entry["fields"]
|
|
||||||
desc = transform_body(fields.get("description", ""), counters).strip()
|
|
||||||
files = files_to_links(fields.get("file affected", ""), counters)
|
|
||||||
source = fields.get("source", "").strip().rstrip(".")
|
|
||||||
parts = []
|
|
||||||
if files:
|
|
||||||
parts.append(files)
|
|
||||||
if desc:
|
|
||||||
parts.append(desc)
|
|
||||||
text_part = " — ".join(parts) if parts else "(no description recorded)"
|
|
||||||
bullet = f"* **{verb}**: {text_part}"
|
|
||||||
if source:
|
|
||||||
bullet += f" (source: {source})"
|
|
||||||
lines.append(bullet.rstrip(".") + ".")
|
|
||||||
lines.append("")
|
|
||||||
return "\n".join(lines).rstrip("\n") + "\n"
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# build + validate
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def build(out_dir):
|
|
||||||
pages, logs, indexes, assets = read_tree(WIKI)
|
|
||||||
counters = {"wikilinks": 0, "cascade_refs": 0, "rewritten": 0}
|
|
||||||
issues = []
|
|
||||||
|
|
||||||
if out_dir.exists():
|
|
||||||
shutil.rmtree(out_dir)
|
|
||||||
out_dir.mkdir(parents=True)
|
|
||||||
|
|
||||||
for rel, page in sorted(pages.items()):
|
|
||||||
dest = out_dir / rel
|
|
||||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
dest.write_text(emit_concept(page, counters, issues), encoding="utf-8")
|
|
||||||
|
|
||||||
directories = {Path(".")}
|
|
||||||
for rel in list(pages) + list(logs) + list(indexes) + list(assets):
|
|
||||||
parent = rel.parent
|
|
||||||
while parent != Path("."):
|
|
||||||
directories.add(parent)
|
|
||||||
parent = parent.parent
|
|
||||||
for directory in sorted(directories):
|
|
||||||
dest = out_dir / directory / "index.md"
|
|
||||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
dest.write_text(
|
|
||||||
emit_index(directory, pages, logs, indexes, assets, directory == Path(".")),
|
|
||||||
encoding="utf-8",
|
|
||||||
)
|
|
||||||
|
|
||||||
for rel, text in sorted(logs.items()):
|
|
||||||
dest = out_dir / rel
|
|
||||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
dest.write_text(emit_log(text, counters), encoding="utf-8")
|
|
||||||
|
|
||||||
for rel in assets:
|
|
||||||
dest = out_dir / rel
|
|
||||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
shutil.copy2(WIKI / rel, dest)
|
|
||||||
|
|
||||||
report = {
|
|
||||||
"concepts": len(pages),
|
|
||||||
"indexes": len(directories),
|
|
||||||
"logs": len(logs),
|
|
||||||
"assets": len(assets),
|
|
||||||
"counters": counters,
|
|
||||||
"issues": issues,
|
|
||||||
}
|
|
||||||
return report
|
|
||||||
|
|
||||||
|
|
||||||
def validate(out_dir):
|
|
||||||
"""Conformance checks against the generated bundle. Returns a list of
|
|
||||||
strings; empty means the bundle conforms."""
|
|
||||||
problems = []
|
|
||||||
md_files = sorted(p for p in out_dir.rglob("*.md"))
|
|
||||||
for path in md_files:
|
|
||||||
rel = path.relative_to(out_dir)
|
|
||||||
text = path.read_text(encoding="utf-8")
|
|
||||||
fm, body = parse_frontmatter(text)
|
|
||||||
|
|
||||||
if path.name == "index.md":
|
|
||||||
if rel == Path("index.md"):
|
|
||||||
if set(fm) - {"okf_version"}:
|
|
||||||
problems.append(f"{rel}: root index frontmatter must contain only okf_version")
|
|
||||||
elif fm:
|
|
||||||
problems.append(f"{rel}: nested index.md must have no frontmatter")
|
|
||||||
elif path.name == "log.md":
|
|
||||||
for line in body.split("\n"):
|
|
||||||
if line.startswith("## ") and not re.match(r"^## \d{4}-\d{2}-\d{2}$", line.strip()):
|
|
||||||
problems.append(f"{rel}: log header is not `## YYYY-MM-DD`: {line.strip()}")
|
|
||||||
else:
|
|
||||||
if not fm.get("type", "").strip():
|
|
||||||
problems.append(f"{rel}: missing or empty `type`")
|
|
||||||
|
|
||||||
for m in re.finditer(r"\[[^\]]*\]\(([^)]+)\)", body):
|
|
||||||
target = m.group(1).split("#", 1)[0].strip()
|
|
||||||
if not target or re.match(r"^[a-z][a-z0-9+.-]*:", target):
|
|
||||||
continue
|
|
||||||
if re.match(r"^\.{0,2}/?(linked|libs)/", target):
|
|
||||||
continue # intentionally left unconverted
|
|
||||||
if target.startswith("/"):
|
|
||||||
candidate = out_dir / target.lstrip("/")
|
|
||||||
else:
|
|
||||||
candidate = (path.parent / target).resolve()
|
|
||||||
if not candidate.exists():
|
|
||||||
problems.append(f"{rel}: intra-bundle link does not resolve: {target}")
|
|
||||||
return problems
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description="Export wiki/ as an OKF v0.1 bundle.")
|
|
||||||
parser.add_argument("--check", action="store_true", help="validate only; write nothing")
|
|
||||||
parser.add_argument("--out", default="outputs/okf", help="output directory (default: outputs/okf)")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not WIKI.is_dir():
|
|
||||||
print(f"error: no wiki/ directory under {REPO_ROOT} — run from the repository root", file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
if args.check:
|
|
||||||
tmp = Path(tempfile.mkdtemp(prefix="okf-check-"))
|
|
||||||
try:
|
|
||||||
out_dir = tmp / "okf"
|
|
||||||
report = build(out_dir)
|
|
||||||
problems = validate(out_dir)
|
|
||||||
print_report(report, problems, out_dir, checked_only=True)
|
|
||||||
finally:
|
|
||||||
shutil.rmtree(tmp, ignore_errors=True)
|
|
||||||
else:
|
|
||||||
out_dir = REPO_ROOT / args.out
|
|
||||||
report = build(out_dir)
|
|
||||||
problems = validate(out_dir)
|
|
||||||
print_report(report, problems, out_dir, checked_only=False)
|
|
||||||
|
|
||||||
return 1 if problems else 0
|
|
||||||
|
|
||||||
|
|
||||||
def print_report(report, problems, out_dir, checked_only):
|
|
||||||
counters = report["counters"]
|
|
||||||
print(f"okf_version: {OKF_VERSION}")
|
|
||||||
print(f"concept documents exported: {report['concepts']}")
|
|
||||||
print(f"index.md files regenerated: {report['indexes']}")
|
|
||||||
print(f"log.md files regenerated: {report['logs']}")
|
|
||||||
if report["assets"]:
|
|
||||||
print(f"non-markdown files copied: {report['assets']}")
|
|
||||||
print(f"wikilinks stripped: {counters['wikilinks']}")
|
|
||||||
print(f"/wiki/ links rewritten to bundle-root: {counters['rewritten']}")
|
|
||||||
print(f"linked//libs/ cross-cascade refs left unconverted: {counters['cascade_refs']}")
|
|
||||||
for issue in report["issues"]:
|
|
||||||
print(f"SOURCE ISSUE: {issue}")
|
|
||||||
if problems:
|
|
||||||
print(f"NONCONFORMANT: {len(problems)} problem(s) in the generated bundle:")
|
|
||||||
for p in problems:
|
|
||||||
print(f" - {p}")
|
|
||||||
else:
|
|
||||||
print("conformance: OK")
|
|
||||||
if checked_only:
|
|
||||||
print("output: none (--check)")
|
|
||||||
else:
|
|
||||||
print(f"output: {out_dir}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
name: ckb-index-external
|
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).
|
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. 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 workflow from CLAUDE.md/AGENTS.md §4, 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
|
# Index external sources skill
|
||||||
|
|
@ -12,33 +12,16 @@ git-copy one: a `libs/<name>/source.yaml` declares a *live* external
|
||||||
source — a SharePoint folder, a Google Drive folder, a plain URL, or
|
source — a SharePoint folder, a Google Drive folder, a plain URL, or
|
||||||
another connector — that the user doesn't want to fully mirror locally.
|
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
|
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/`
|
what's there: one entity page per document found, plus the same
|
||||||
already uses, rooted at `libs/<name>/` instead.
|
`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
|
This is deliberately **not** blended into the main `wiki/entities/` or
|
||||||
`wiki/graph/edges.json` — the generated index lives entirely inside its own
|
`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
|
`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
|
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/`
|
the connector says, that's what writing the corrected version into `wiki/`
|
||||||
is for (cascade priority already covers that — `wiki/` always wins). Where
|
is for (cascade priority already covers that — `wiki/` always wins).
|
||||||
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:
|
Two refinements on top of that base design:
|
||||||
|
|
||||||
|
|
@ -60,7 +43,7 @@ Two refinements on top of that base design:
|
||||||
This skill only ever writes within a connector-backed `libs/<name>/` (never
|
This skill only ever writes within a connector-backed `libs/<name>/` (never
|
||||||
`source.yaml` itself, never a git-copy lib, never anything under `wiki/`)
|
`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
|
— and it never touches this repo's own git remote; pushing the resulting
|
||||||
changes is a separate, explicit step (see Step 8).
|
changes is a separate, explicit step (see Step 7).
|
||||||
|
|
||||||
## Trigger phrases
|
## Trigger phrases
|
||||||
|
|
||||||
|
|
@ -77,13 +60,9 @@ Use this skill when the user says:
|
||||||
the same turn unless the user also asks for one.
|
the same turn unless the user also asks for one.
|
||||||
|
|
||||||
Do **not** use this skill for "Ingest" / "Sync the wiki" (that's
|
Do **not** use this skill for "Ingest" / "Sync the wiki" (that's
|
||||||
`ckb-ingest` — raw material into `wiki/`, unrelated to `libs/`), for
|
`ckb-ingest` — raw material into `wiki/`, unrelated to `libs/`) or for
|
||||||
"Lint" (that's `ckb-lint`, which now also health-checks a connector's own
|
"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
|
generated index, but doesn't build it in the first place).
|
||||||
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
|
## Configuration
|
||||||
|
|
||||||
|
|
@ -94,12 +73,6 @@ connector: sharepoint # sharepoint | google_drive | web | custom — open
|
||||||
location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
||||||
description: "Finance team's shared reports folder" # optional
|
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
|
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)
|
# 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
|
store: git # git | shared — open string, where the built index is published to / fetched from
|
||||||
|
|
@ -127,36 +100,6 @@ 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
|
finance reports source" / "I don't want write access to X anymore"), never
|
||||||
as a side effect of just running "index external sources."
|
as a side effect of just running "index external sources."
|
||||||
|
|
||||||
## Regeneration rule: `## Notes` is never overwritten
|
|
||||||
|
|
||||||
Every page this skill writes is *generated* — a later run rebuilds it from
|
|
||||||
the connector and the previous contents are gone. That makes this index the
|
|
||||||
one place in the KB where a human annotation is most useful and most
|
|
||||||
fragile: the person who knows that a document is superseded, or that a name
|
|
||||||
in it refers to someone who has since left, has nowhere safe to write it
|
|
||||||
down.
|
|
||||||
|
|
||||||
`## Notes` is that place. Before rewriting any page under `libs/<name>/`,
|
|
||||||
read the existing file and lift its `## Notes` section out verbatim; after
|
|
||||||
regenerating everything above it, append it back **byte-for-byte**. Not
|
|
||||||
reflowed, not summarized, not merged into the `tldr`, not "improved" —
|
|
||||||
unchanged, including whitespace and any half-finished sentence.
|
|
||||||
|
|
||||||
Practically: `## Notes` is the last section of every generated page, so a
|
|
||||||
rebuild is "replace everything above the `## Notes` heading". Create it empty
|
|
||||||
on a page's first write so the affordance exists before anyone needs it:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
<!-- Yours. Never rewritten by any skill. -->
|
|
||||||
```
|
|
||||||
|
|
||||||
If a page somehow has content *after* `## Notes` that this skill would
|
|
||||||
otherwise generate, preserve the whole tail rather than guessing where the
|
|
||||||
human part ends. Losing generated content costs one re-run; losing a person's
|
|
||||||
annotation costs the thing itself.
|
|
||||||
|
|
||||||
## How to run this skill
|
## How to run this skill
|
||||||
|
|
||||||
### Step 1 — Find connector-backed libs
|
### Step 1 — Find connector-backed libs
|
||||||
|
|
@ -216,18 +159,9 @@ This is the one place access level actually changes behavior:
|
||||||
- **Write access**: always continue to Step 5, whether Step 3 fetched an
|
- **Write access**: always continue to Step 5, whether Step 3 fetched an
|
||||||
existing index (refresh it) or found the store empty (build the very
|
existing index (refresh it) or found the store empty (build the very
|
||||||
first version from scratch) — the two cases are handled identically
|
first version from scratch) — the two cases are handled identically
|
||||||
from here on; Step 8 is what actually creates the remote copy either
|
from here on; Step 7 is what actually creates the remote copy either
|
||||||
way.
|
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
|
### Step 5 — Resolve the connector and enumerate/summarize documents
|
||||||
|
|
||||||
Resolve `connector` to whatever live tool is actually available this
|
Resolve `connector` to whatever live tool is actually available this
|
||||||
|
|
@ -247,8 +181,8 @@ For each document found at the location:
|
||||||
- Read enough content to write a real 1-2 sentence `tldr` when the
|
- Read enough content to write a real 1-2 sentence `tldr` when the
|
||||||
connector allows fetching content.
|
connector allows fetching content.
|
||||||
- If only metadata is available (listing only, no read access), say so
|
- If only metadata is available (listing only, no read access), say so
|
||||||
honestly in the `tldr` (e.g. "Metadata only — content not readable") and
|
honestly in the `tldr` (e.g. "Metadata only — read access not granted")
|
||||||
set `confidence` low (e.g. 0.3) rather than fabricating a summary.
|
and set `confidence` low (e.g. 0.3) rather than fabricating a summary.
|
||||||
|
|
||||||
Create/update one entity page per document at
|
Create/update one entity page per document at
|
||||||
`libs/<name>/entities/<doc-slug>.md`:
|
`libs/<name>/entities/<doc-slug>.md`:
|
||||||
|
|
@ -260,104 +194,12 @@ tldr: ...
|
||||||
confidence: 0.3–0.9 # per the rule above
|
confidence: 0.3–0.9 # per the rule above
|
||||||
quality: ...
|
quality: ...
|
||||||
last_updated: YYYY-MM-DD
|
last_updated: YYYY-MM-DD
|
||||||
freshness_window_days: 30 # this source's refresh_interval_days, or 30 if unset — shorter than a
|
freshness_window_days: 30 # shorter than a typical wiki page — external sources change without notice
|
||||||
# typical wiki page, because external sources change without notice
|
|
||||||
retention: medium
|
|
||||||
source_fingerprint: sha256:3f9a2c1e # see below — what actually detects drift
|
|
||||||
source_checked: YYYY-MM-DD
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
**Fingerprint every document page.** A short `freshness_window_days` is a
|
|
||||||
guess that a source *might* have moved; a fingerprint is a fact about whether
|
|
||||||
it *did*. This matters far more here than in `wiki/`: an archived file under
|
|
||||||
`raw/archive/` is effectively immutable, while a SharePoint or Drive document
|
|
||||||
is edited by other people with no notification to anyone.
|
|
||||||
|
|
||||||
Take the fingerprint from whatever the connector gives you, in this order of
|
|
||||||
preference:
|
|
||||||
|
|
||||||
1. A version identifier the connector already maintains — an ETag, a
|
|
||||||
`version`, a content hash, a `lastModifiedDateTime`. Record it as
|
|
||||||
`etag:<value>` / `mtime:<iso8601>`. This is free and needs no content
|
|
||||||
fetch.
|
|
||||||
2. Failing that, `sha256` of the fetched content, recorded as
|
|
||||||
`sha256:<first 8 hex chars>`.
|
|
||||||
3. Metadata-only documents with neither: omit the field rather than
|
|
||||||
inventing one, and leave `confidence` low as Step 5 already requires.
|
|
||||||
|
|
||||||
Set `source_checked` to the date the fingerprint was last confirmed — which
|
|
||||||
is **not** always `last_updated`. A re-index that confirms a document
|
|
||||||
unchanged advances `source_checked` and leaves `last_updated` alone; that
|
|
||||||
distinction is exactly what tells a reader "this was verified yesterday" from
|
|
||||||
"this was written six months ago and nobody has looked since".
|
|
||||||
|
|
||||||
Give every document page a trailing `## Notes` section, created empty on
|
|
||||||
first write (see Step 7's protection rule).
|
|
||||||
|
|
||||||
### 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.3–0.9
|
|
||||||
quality: ...
|
|
||||||
last_updated: YYYY-MM-DD
|
|
||||||
freshness_window_days: 30 # or this source's refresh_interval_days, if set
|
|
||||||
retention: medium
|
retention: medium
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
Body: a short "Mentioned in" list, one bullet per document that discusses
|
### Step 6 — Update the hub page, graph, index, and log
|
||||||
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 as
|
|
||||||
edges in `libs/<name>/graph/edges.json`, using the same closed vocabulary
|
|
||||||
`wiki/graph/index.md` defines (`part_of`, `uses`, `depends_on`, `produces`,
|
|
||||||
`configures`, `validates`, `implements`, `caused`, `contradicts`,
|
|
||||||
`supersedes`) — each verb answering the question listed there. 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
|
Create/update `libs/<name>/index.md` — the root routing page for this
|
||||||
connector, mirroring `wiki/index.md`'s own role:
|
connector, mirroring `wiki/index.md`'s own role:
|
||||||
|
|
@ -365,35 +207,26 @@ connector, mirroring `wiki/index.md`'s own role:
|
||||||
---
|
---
|
||||||
type: external-source
|
type: external-source
|
||||||
resource: <location, from source.yaml>
|
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
|
tldr: One sentence — what this source is and how many documents were found
|
||||||
last_updated: YYYY-MM-DD
|
last_updated: YYYY-MM-DD
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
followed by a short routing table pointing to `entities/index.md` and
|
followed by a short routing table pointing to `entities/index.md` and
|
||||||
`graph/index.md`.
|
`graph/index.md`.
|
||||||
|
|
||||||
Update `libs/<name>/entities/index.md` as two sections, so both kinds of
|
Record any real relationships between documents (e.g. folder hierarchy) as
|
||||||
page stay easy to find without conflating them:
|
edges in `libs/<name>/graph/edges.json`, using a `contains`/`part_of`-style
|
||||||
```markdown
|
edge type — this is fine to be thin or empty for a flat source with no
|
||||||
## Documents
|
useful structure beyond a document list.
|
||||||
- [<doc-slug>](<doc-slug>.md) — <tldr>
|
|
||||||
...
|
|
||||||
|
|
||||||
## Entities & Processes
|
Update `libs/<name>/entities/index.md` and `libs/<name>/graph/index.md`
|
||||||
- [<entity-slug>](<entity-slug>.md) — <tldr>
|
(flat bullet lists, no frontmatter, same convention as `wiki/entities/
|
||||||
...
|
index.md`/`wiki/graph/index.md`). Log every created/updated page in
|
||||||
```
|
`libs/<name>/log.md`, same reverse-chronological format as Rule B
|
||||||
Same flat-bullet, no-frontmatter convention as `wiki/entities/index.md`
|
(`CLAUDE.md`/`AGENTS.md` §7) — this log is independent of `wiki/log.md`;
|
||||||
otherwise — this is just a two-heading variant of it, not a new format.
|
**nothing under `wiki/` is touched by this skill at all.**
|
||||||
|
|
||||||
Update `libs/<name>/graph/index.md` (same convention as
|
### Step 7 — Publish, if this source has both write access and a configured index store
|
||||||
`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:`
|
If this user has `access: write` **and** `source.yaml` has an `index:`
|
||||||
block, push the refreshed `libs/<name>/{index.md,entities/,graph/,log.md}`
|
block, push the refreshed `libs/<name>/{index.md,entities/,graph/,log.md}`
|
||||||
|
|
@ -416,7 +249,7 @@ 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
|
has, by construction, nothing of their own to save back — Step 4 already
|
||||||
stopped them before anything was built.
|
stopped them before anything was built.
|
||||||
|
|
||||||
### Step 9 — Remind to review and sync
|
### Step 8 — Remind to review and sync
|
||||||
|
|
||||||
This is always the last step, every time this skill makes any change at
|
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
|
all. Close with a short reminder — do not sync or push anything yourself
|
||||||
|
|
@ -452,46 +285,18 @@ was read-only with nothing new to fetch), skip this reminder.
|
||||||
the whole run; don't let a transient network issue block a read-only
|
the whole run; don't let a transient network issue block a read-only
|
||||||
user from seeing the last-known index.
|
user from seeing the last-known index.
|
||||||
- **Re-running against an already-indexed source** — refresh existing
|
- **Re-running against an already-indexed source** — refresh existing
|
||||||
document *and* entity/process pages in place (update
|
entity pages in place (update `tldr`/`last_updated`/`confidence`); never
|
||||||
`tldr`/`last_updated`/`confidence`/`source_fingerprint`/`source_checked`,
|
duplicate a document's page.
|
||||||
add newly-seen `mentioned_in` edges); never duplicate a document's or
|
|
||||||
entity's page, and carry every `## Notes` section across untouched.
|
|
||||||
- **A document's fingerprint is unchanged since the last run** — don't
|
|
||||||
re-summarize it. Advance `source_checked` to today, leave `last_updated`,
|
|
||||||
`tldr`, and `confidence` exactly as they were, and spend the run's budget
|
|
||||||
on the documents that did change. On a large source this is most of them,
|
|
||||||
and it is the difference between a re-index that costs a full pass and one
|
|
||||||
that costs a listing.
|
|
||||||
- **A document's fingerprint changed** — re-fetch and re-summarize it, and
|
|
||||||
say so in the report. A changed fingerprint on a document some wiki page
|
|
||||||
cites is worth calling out by name: that wiki page was built from a version
|
|
||||||
of this document that no longer exists.
|
|
||||||
- **A previously-indexed document is no longer found at the source** —
|
- **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
|
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
|
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
|
`ckb-lint` retention sweep archives it naturally on a later pass — no new
|
||||||
archival mechanism needed. Leave any entity pages that cite it alone;
|
archival mechanism needed.
|
||||||
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
|
- **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
|
one run and explicitly report what was skipped (e.g. "42 of ~300
|
||||||
documents summarized this pass — re-run to continue"). Never silently
|
documents summarized this pass — re-run to continue"). Never silently
|
||||||
truncate while implying full coverage.
|
truncate while implying full coverage.
|
||||||
- **Write access but publishing (Step 8) conflicts with a newer version
|
- **Write access but publishing (Step 7) conflicts with a newer version
|
||||||
someone else already pushed** — this is why Step 3 (fetch) always runs
|
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
|
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
|
state rather than blindly overwriting it. If a real conflict still shows
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
name: ckb-ingest
|
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).
|
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 from CLAUDE.md/AGENTS.md §3, 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
|
# Ingest skill
|
||||||
|
|
@ -18,7 +18,7 @@ This skill only ever writes to `wiki/` (and moves processed files within
|
||||||
`raw/`). It never touches `linked/` or `libs/` — those are immutable
|
`raw/`). It never touches `linked/` or `libs/` — those are immutable
|
||||||
upstream sources of truth — and it never touches this repo's own git
|
upstream sources of truth — and it never touches this repo's own git
|
||||||
remote; pushing the resulting changes is a separate, explicit step (see
|
remote; pushing the resulting changes is a separate, explicit step (see
|
||||||
Step 8).
|
Step 7).
|
||||||
|
|
||||||
## Trigger phrases
|
## Trigger phrases
|
||||||
|
|
||||||
|
|
@ -60,74 +60,10 @@ than treated as final.
|
||||||
Identify typed entities in the source material — people, projects,
|
Identify typed entities in the source material — people, projects,
|
||||||
libraries, concepts, systems. Create entity pages at
|
libraries, concepts, systems. Create entity pages at
|
||||||
`wiki/entities/<entity-name>.md` if they don't already exist. Record typed
|
`wiki/entities/<entity-name>.md` if they don't already exist. Record typed
|
||||||
relationships between entities as edge data in `wiki/graph/edges.json`,
|
relationships between entities — `uses`, `depends_on`, `caused`,
|
||||||
using the closed vocabulary in `wiki/graph/index.md` — `part_of`, `uses`,
|
`contradicts`, `supersedes` — as edge data in `wiki/graph/edges.json`.
|
||||||
`depends_on`, `produces`, `configures`, `validates`, `implements`, `caused`,
|
|
||||||
`contradicts`, `supersedes`. Each verb is defined there by the question it
|
|
||||||
answers; if the relationship you have in mind doesn't answer one of those
|
|
||||||
questions, it belongs in the page's prose, not in the graph.
|
|
||||||
|
|
||||||
Also record `has_expertise_in` and `owns` edges when the material actually
|
### Step 4 — Synthesize pages
|
||||||
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.
|
|
||||||
|
|
||||||
**Decisions are not entity pages.** When the material records a choice that
|
|
||||||
was actually made — a call with a rationale, a date, and someone who made it
|
|
||||||
— that belongs in `wiki/decisions/` as a numbered decision record, not in
|
|
||||||
`wiki/entities/`. Use `ckb-decide`'s format (it owns the field set, the
|
|
||||||
numbering, and the two-sided supersession links) and record them in
|
|
||||||
chronological order so decision numbers run in the same order as the
|
|
||||||
decisions. Two things worth being strict about, because ingest is where they
|
|
||||||
go wrong: a proposal that was discussed but not settled is
|
|
||||||
`status: proposed`, never `accepted`; and where the source doesn't say who
|
|
||||||
decided, `decided_by: unknown` beats attributing it to whoever was loudest in
|
|
||||||
the notes. If the material only *references* a decision without recording
|
|
||||||
it ("as agreed last quarter, we use Postgres"), that's a query-gap candidate,
|
|
||||||
not a decision record — you don't have the rationale or the date.
|
|
||||||
|
|
||||||
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 — Blast radius: what does this material invalidate?
|
|
||||||
|
|
||||||
Ingest is additive by habit, which is how a wiki accumulates two pages that
|
|
||||||
quietly disagree. Before writing anything, find out what the incoming
|
|
||||||
material lands on.
|
|
||||||
|
|
||||||
For every entity from Step 3 that already has a page, walk
|
|
||||||
`wiki/graph/edges.json` **backwards** — every edge whose `to` is that page —
|
|
||||||
one hop out. That set, plus the entity pages themselves, is the blast radius:
|
|
||||||
the pages whose claims could be affected by what just arrived. Read their
|
|
||||||
`tldr`s (and the bodies of any that look genuinely exposed) and sort each one
|
|
||||||
into:
|
|
||||||
|
|
||||||
- **Confirms** — the new material agrees. Note it; corroboration is a reason
|
|
||||||
to raise `confidence` in Step 5, not to rewrite anything.
|
|
||||||
- **Extends** — the new material adds detail the page doesn't have. Update
|
|
||||||
the page in Step 5.
|
|
||||||
- **Contradicts** — the new material and the page can't both be right. This
|
|
||||||
is the case worth catching: handle it as supersession (see Edge cases), and
|
|
||||||
say so explicitly in the report rather than letting the newer page silently
|
|
||||||
win.
|
|
||||||
- **Untouched** — most of the radius. Drop it and move on.
|
|
||||||
|
|
||||||
Name the owners too. Where an `owns` or `has_expertise_in` edge points at an
|
|
||||||
affected page, that person is who should review the change — surface them in
|
|
||||||
the Step 8 report. Skip this step entirely when Step 3 produced no entity
|
|
||||||
that already exists; a first ingest into an empty wiki has no radius.
|
|
||||||
|
|
||||||
### Step 5 — Synthesize pages
|
|
||||||
|
|
||||||
Convert the core knowledge into clean, modular Markdown files. Every page
|
Convert the core knowledge into clean, modular Markdown files. Every page
|
||||||
gets frontmatter with:
|
gets frontmatter with:
|
||||||
|
|
@ -139,125 +75,33 @@ gets frontmatter with:
|
||||||
- A `retention:` level.
|
- A `retention:` level.
|
||||||
|
|
||||||
`type` is also required (per the page frontmatter schema in
|
`type` is also required (per the page frontmatter schema in
|
||||||
`CLAUDE.md`/`AGENTS.md` page schema) — set it once, based on the entity/content
|
`CLAUDE.md`/`AGENTS.md` §2) — set it once, based on the entity/content
|
||||||
kind (person, project, concept, library, decision, playbook, ...).
|
kind (person, project, concept, library, decision, playbook, ...).
|
||||||
|
|
||||||
Every synthesized page ends with the reserved body sections from the page
|
### Step 5 — Link and cross-reference
|
||||||
schema. `## Sources` and `## Crux` are written here; `## Notes` is created
|
|
||||||
empty and never touched again:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- `raw/archive/2026-09-21/kickoff-notes.md` — sha256:3f9a2c1e (checked 2026-09-21)
|
|
||||||
- `raw/archive/2026-09-21/access-thread.eml` — sha256:be40d7aa (checked 2026-09-21)
|
|
||||||
|
|
||||||
## Crux
|
|
||||||
|
|
||||||
> FDEs need the VDI *and* a Jira account before day one; the VDI request
|
|
||||||
> alone takes ten working days.
|
|
||||||
— `raw/archive/2026-09-21/kickoff-notes.md`, under "Access"
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
<!-- Yours. Never rewritten by any skill. -->
|
|
||||||
```
|
|
||||||
|
|
||||||
Compute each fingerprint from the archived file, not from memory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sha256sum raw/archive/<date>/<file> | cut -c1-8
|
|
||||||
```
|
|
||||||
|
|
||||||
Record the digest the page was actually built from. That is what lets
|
|
||||||
`ckb-lint` tell "old but still accurate" apart from "the source changed
|
|
||||||
underneath this page" without asking a model — a calendar window can't
|
|
||||||
distinguish those. When a page is built from exactly one source, also set
|
|
||||||
`source_fingerprint` and `source_checked` in frontmatter.
|
|
||||||
|
|
||||||
**`## Crux` is quoted, never paraphrased.** Lift the handful of lines that
|
|
||||||
actually carry the claim — the number, the constraint, the condition, the
|
|
||||||
commitment — verbatim, and attribute each quote to the source bullet it came
|
|
||||||
from. Three to ten lines is the working range; a Crux approaching the length
|
|
||||||
of the summary has stopped being evidence and become a second copy of the
|
|
||||||
source. The synthesis above it says what the material means; the Crux is what
|
|
||||||
it said, so a reader can check the first against the second. This is also
|
|
||||||
what lets `ckb-retrieve` ground an answer without opening the archive every
|
|
||||||
time.
|
|
||||||
|
|
||||||
A page with no quotable source — synthesized from conversation, or from
|
|
||||||
material too diffuse to quote — simply has no `## Crux`. An empty or
|
|
||||||
paraphrased one is worse than none, because it looks like evidence.
|
|
||||||
|
|
||||||
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 6 — Link and cross-reference
|
|
||||||
|
|
||||||
Use **both** `[[Wikilinks]]` (Obsidian-compatible) and standard
|
Use **both** `[[Wikilinks]]` (Obsidian-compatible) and standard
|
||||||
`[markdown](path.md)` links on every cross-reference, so the wiki works in
|
`[markdown](path.md)` links on every cross-reference, so the wiki works in
|
||||||
Obsidian, GitHub, and CLI tools alike. Where useful, reference upstream
|
Obsidian, GitHub, and CLI tools alike. Where useful, reference upstream
|
||||||
files directly at `linked/<name>/...` or `libs/<name>/...`.
|
files directly at `linked/<name>/...` or `libs/<name>/...`.
|
||||||
|
|
||||||
### Step 7 — Update index and log
|
### Step 6 — Update index and log
|
||||||
|
|
||||||
Add new pages to the routing table in `wiki/index.md` with a **Use when**
|
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
|
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
|
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
|
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`/
|
root `wiki/log.md` — following the format in Rule B (`CLAUDE.md`/
|
||||||
`AGENTS.md` Rule B).
|
`AGENTS.md` §7).
|
||||||
|
|
||||||
If this step creates a brand-new `wiki/<topic>/` subdirectory, immediately
|
If this step creates a brand-new `wiki/<topic>/` subdirectory, immediately
|
||||||
create that subdirectory's `index.md` per the Recursive Index & Log
|
create that subdirectory's `index.md` per the Recursive Index & Log
|
||||||
Convention.
|
Convention.
|
||||||
|
|
||||||
If this step creates or updates a project scope under `wiki/projects/`,
|
### Step 7 — Remind to review and sync
|
||||||
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 8 — Report, then 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
|
||||||
Report what changed before the reminder: pages created vs. updated, and —
|
|
||||||
from Step 4 — which existing pages the new material confirmed, extended, or
|
|
||||||
contradicted, naming the owner of each contradicted page. A contradiction
|
|
||||||
resolved silently is the one outcome of an ingest a reviewer must not have to
|
|
||||||
discover for themselves.
|
|
||||||
|
|
||||||
Then close with a short reminder — do not sync or push
|
|
||||||
anything yourself here, and do not skip this even if the changes look
|
anything yourself here, and do not skip this even if the changes look
|
||||||
small or obviously correct:
|
small or obviously correct:
|
||||||
|
|
||||||
|
|
@ -271,14 +115,14 @@ process), skip this reminder — there's nothing to review or sync.
|
||||||
## Edge cases
|
## Edge cases
|
||||||
|
|
||||||
- **Inbox and `raw/` both empty** — report that there's nothing to
|
- **Inbox and `raw/` both empty** — report that there's nothing to
|
||||||
ingest. Don't touch `wiki/`, and skip the Step 8 reminder.
|
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
|
- **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.
|
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
|
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
|
considered, but note in the ingest report that it produced no wiki
|
||||||
change.
|
change.
|
||||||
- **Conflicting information vs. an existing local wiki page** — this is a
|
- **Conflicting information vs. an existing local wiki page** — this is a
|
||||||
supersession case handled consistently with `ckb-lint`, not a
|
supersession case (Rule via `CLAUDE.md`/`AGENTS.md` §6 lint), not a
|
||||||
silent overwrite: update the existing page if the new source is clearly
|
silent overwrite: update the existing page if the new source is clearly
|
||||||
more current/corroborated, and link `supersedes`/`superseded_by` if an
|
more current/corroborated, and link `supersedes`/`superseded_by` if an
|
||||||
older version is worth preserving rather than edited in place.
|
older version is worth preserving rather than edited in place.
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,23 @@
|
||||||
---
|
---
|
||||||
name: ckb-init
|
name: ckb-init
|
||||||
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, sourcing the template either from this repo's own files or by pulling a fresh clone of the canonical ckb repo (or a user-supplied fork/mirror URL) into a scratch folder, so it works even when run outside an existing KB. The target folder is typically empty, or a new project that doesn't have a KB 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", "pull the ckb repo into this folder", "clone the wiki template into <dir>", or calls it a "wiki initializer". Also use when they name a release channel or branch to bootstrap from - "initialize from the test branch", "set up a dev KB from experimental", "bootstrap from <url> branch <name>" - since the template repo keeps main (stable), test (release candidate) and experimental (development) branches, and main is the default. 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.
|
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
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
Copy the Cascade Knowledge Base *schema* - not its content - into a new
|
Copy this project's Cascade Knowledge Base *schema* - not its content - into
|
||||||
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, the full default skill set,
|
system prompt that defines how the KB behaves, the full default skill set,
|
||||||
`LICENSE` and `VERSION`, the generic `README`/`MANUAL` docs, and the empty
|
`LICENSE` and `VERSION`, the generic `README`/`MANUAL` docs, and the empty
|
||||||
`wiki/` scaffold (routing table, overview, log, error book, entity/graph
|
`wiki/` scaffold (routing table, overview, log, error book, entity/graph
|
||||||
indexes). The result is a new, empty KB that behaves exactly like this one,
|
indexes). The result is a new, empty KB that behaves exactly like this one,
|
||||||
ready for its first `raw/inbox/` drop and "Ingest."
|
ready for its first `raw/inbox/` drop and "Ingest."
|
||||||
|
|
||||||
The template it copies from is either this repo's own files or a fresh
|
This is a one-way copy from this repo's own template files into a
|
||||||
shallow clone of the canonical template repo pulled into a scratch folder
|
different folder. It never reads or writes anything in this repo's `raw/`,
|
||||||
(Step 3) - the latter is what makes this skill usable from anywhere, not
|
`wiki/entities/`, `wiki/graph/edges.json`, or `outputs/` - those hold this
|
||||||
only from inside an existing KB, and what "pull the repo into a folder to
|
|
||||||
set up a wiki" means in practice. Either way it is a one-way copy into a
|
|
||||||
different folder, and the clone is scratch: it never becomes the new KB's
|
|
||||||
`.git/`.
|
|
||||||
|
|
||||||
It never reads or writes anything in the template source's `raw/`,
|
|
||||||
`wiki/entities/`, `wiki/graph/edges.json`, or `outputs/` - those hold that
|
|
||||||
project's actual accumulated knowledge, which is exactly what should *not*
|
project's actual accumulated knowledge, which is exactly what should *not*
|
||||||
travel into a fresh KB. (For catching an *already-populated* KB up with
|
travel into a fresh KB. (For catching an *already-populated* KB up with
|
||||||
newer template files without losing its accumulated content, see
|
newer template files without losing its accumulated content, see
|
||||||
|
|
@ -37,8 +30,6 @@ folder.)
|
||||||
- "set up a new wiki like this one" / "initialize a new cascade KB"
|
- "set up a new wiki like this one" / "initialize a new cascade KB"
|
||||||
- "bootstrap a wiki here" / "create a knowledge base with this schema"
|
- "bootstrap a wiki here" / "create a knowledge base with this schema"
|
||||||
- "wiki initializer" / "clone this KB structure into a new project"
|
- "wiki initializer" / "clone this KB structure into a new project"
|
||||||
- "pull the ckb repo into `<dir>` and set up the wiki structure"
|
|
||||||
- "initialize a KB from `<url>`" / "get the latest template and bootstrap a KB"
|
|
||||||
|
|
||||||
## How to run this skill
|
## How to run this skill
|
||||||
|
|
||||||
|
|
@ -68,106 +59,19 @@ alongside them. Note any top-level name collisions (e.g. an existing
|
||||||
`wiki/` folder used for something else) and ask before touching those
|
`wiki/` folder used for something else) and ask before touching those
|
||||||
specifically.
|
specifically.
|
||||||
|
|
||||||
### Step 3 - Resolve the template source
|
### Step 3 - Default skill set (no need to ask)
|
||||||
|
|
||||||
Everything this skill copies (`AGENTS.md`, `LICENSE`, `VERSION`, the
|
|
||||||
README/MANUAL docs, `.gitignore`, the `wiki/` scaffold, the skill set) comes
|
|
||||||
from one *template source*. There are two, and they produce the same result:
|
|
||||||
|
|
||||||
**a) A fresh clone of the canonical template repo (default when this skill
|
|
||||||
runs outside a Cascade KB, and always available).** Pull the repo into a
|
|
||||||
scratch folder and copy out of it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rm -rf <scratch>
|
|
||||||
git clone --depth 1 --branch <branch> https://git.wierzbowa.cloud/michal/ckb.git <scratch>
|
|
||||||
```
|
|
||||||
|
|
||||||
`<scratch>` is `tmp/ckb-init-src` under the *current* KB when this skill
|
|
||||||
runs inside one (matching `ckb-upgrade`'s convention - that directory is
|
|
||||||
already gitignored), or a system temp path such as
|
|
||||||
`/tmp/ckb-init-src` when it doesn't.
|
|
||||||
|
|
||||||
Use a user-supplied URL instead if they named one ("initialize from
|
|
||||||
`<url>`") - a fork or an internal mirror is a legitimate template source.
|
|
||||||
|
|
||||||
#### Which branch (the release channel)
|
|
||||||
|
|
||||||
The canonical repo keeps three branches, and they are not interchangeable:
|
|
||||||
|
|
||||||
| Branch | What it is | Use it when |
|
|
||||||
|---|---|---|
|
|
||||||
| `main` | **Stable.** The released template. | Always, unless the user says otherwise. **This is the default.** |
|
|
||||||
| `test` | **Release candidate.** Changes validated before they reach `main`. | The user is helping validate a release, or needs a fix that has landed but not shipped. |
|
|
||||||
| `experimental` | **Development.** Active work; may be broken, may be reverted. | The user is developing the template itself, or explicitly wants the newest ideas and accepts the risk. |
|
|
||||||
|
|
||||||
Default to `main`. Take a different branch only when the user asks for one,
|
|
||||||
in whatever words: "initialize from the test branch", "use experimental",
|
|
||||||
"set up a dev KB", "bootstrap from `<url>` branch `<name>`", "I want the
|
|
||||||
bleeding edge". A fork's branch works the same way - `--branch` takes any
|
|
||||||
ref the source repo has.
|
|
||||||
|
|
||||||
If the user names a branch that does not exist on the source repo, the clone
|
|
||||||
fails with git's own error. Report it and list what does exist
|
|
||||||
(`git ls-remote --heads <url>`) rather than silently falling back to `main`:
|
|
||||||
a KB quietly initialized from the wrong channel is exactly the kind of thing
|
|
||||||
nobody notices until an upgrade behaves strangely.
|
|
||||||
|
|
||||||
**When initializing from `test` or `experimental`, say so plainly in the Step
|
|
||||||
12 report and explain what it means** - that this KB will keep pulling from
|
|
||||||
that channel until someone changes it, and that `experimental` in particular
|
|
||||||
carries no stability promise. Initializing onto a dev channel is a reasonable
|
|
||||||
thing to want; doing it without realizing is not.
|
|
||||||
If `git` isn't available or the clone fails (network, auth, unreachable
|
|
||||||
host), report the raw error; fall back to (b) if this skill is running
|
|
||||||
inside a Cascade KB, otherwise stop - there is nothing to copy from.
|
|
||||||
|
|
||||||
Clone into a scratch path *outside* the target folder, never into the
|
|
||||||
target itself. Cloning straight into the target would drag the template's
|
|
||||||
own git history, `.git/`, `workload/`, and any committed sample content
|
|
||||||
into the new KB; the new KB gets its own history in Step 11 instead. The
|
|
||||||
one exception is if the user explicitly says they want the template repo
|
|
||||||
itself checked out in place (i.e. "just clone ckb into `<dir>`") - that's a
|
|
||||||
plain `git clone`, not an init; do that, say so plainly, and skip the rest
|
|
||||||
of these steps.
|
|
||||||
|
|
||||||
**b) This repo's own working tree (default when this skill runs from inside
|
|
||||||
a Cascade KB and the user hasn't asked for a fresh pull).** Copy directly
|
|
||||||
from the current repo's files, exactly as described in the steps below.
|
|
||||||
This is the faster path and is what you want when the current KB is itself
|
|
||||||
the template being propagated - but it copies whatever local, possibly
|
|
||||||
uncommitted, template edits exist here. If the user says "pull the latest",
|
|
||||||
"from the remote", or "from upstream", use (a) instead.
|
|
||||||
|
|
||||||
If both are viable and the user hasn't indicated a preference, ask which
|
|
||||||
one - a stale local template and an upstream one that moved are a real
|
|
||||||
difference, not a formality.
|
|
||||||
|
|
||||||
For the rest of this skill, "this repo's `<path>`" means "`<path>` in the
|
|
||||||
resolved template source" - the steps are identical either way. Note the
|
|
||||||
source (URL + short commit hash, or "local working tree") for the Step 12
|
|
||||||
report, and delete the scratch clone once Step 10 has finished copying:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rm -rf <scratch>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4 - 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 full reusable KB
|
`wiki/` templates) is always included, and so is the full reusable KB
|
||||||
skill set - these operate purely on the `wiki/` structure (or, for
|
skill set - these operate purely on the `wiki/` structure (or, for
|
||||||
`ckb-init` and `ckb-upgrade` themselves, on the schema layer), so they
|
`ckb-init` and `ckb-upgrade` themselves, on the schema layer), so they
|
||||||
carry over cleanly and are part of "the schema" as far as this skill is
|
carry over cleanly and are part of "the schema" as far as this skill is
|
||||||
concerned. As of this writing, that's every skill under the template
|
concerned. As of this writing, that's every skill under this repo's
|
||||||
source's `.agents/skills/`:
|
`.agents/skills/`:
|
||||||
|
|
||||||
- `ckb-init` (this skill - a new KB can bootstrap further KBs of its own)
|
- `ckb-init` (this skill - a new KB can bootstrap further KBs of its own)
|
||||||
- `ckb-upgrade` (lets the new KB catch up with template changes later)
|
- `ckb-upgrade` (lets the new KB catch up with template changes later)
|
||||||
- `ckb-ingest`
|
- `ckb-ingest`
|
||||||
- `ckb-decide`
|
|
||||||
- `ckb-retrieve`
|
|
||||||
- `ckb-index-external`
|
|
||||||
- `ckb-lint`
|
- `ckb-lint`
|
||||||
- `ckb-sync-changes`
|
- `ckb-sync-changes`
|
||||||
- `ckb-project-summary`
|
- `ckb-project-summary`
|
||||||
|
|
@ -175,7 +79,7 @@ source's `.agents/skills/`:
|
||||||
- `ckb-export-starlight`
|
- `ckb-export-starlight`
|
||||||
- `ckb-onboard-me`
|
- `ckb-onboard-me`
|
||||||
- `ckb-teach-me`
|
- `ckb-teach-me`
|
||||||
- `ckb-quiz`
|
- `cbk-quiz`
|
||||||
|
|
||||||
Don't ask about any of these - just include them.
|
Don't ask about any of these - just include them.
|
||||||
|
|
||||||
|
|
@ -190,9 +94,9 @@ that case, only include it if the user explicitly asks for it by name,
|
||||||
e.g. "also bring over `<skill-name>`."
|
e.g. "also bring over `<skill-name>`."
|
||||||
|
|
||||||
Record the final skill list (the defaults above, plus anything explicitly
|
Record the final skill list (the defaults above, plus anything explicitly
|
||||||
added) - this affects Steps 9 and 10.
|
added) - this affects Steps 8 and 9.
|
||||||
|
|
||||||
### Step 5 - Create the directory structure
|
### Step 4 - Create the directory structure
|
||||||
|
|
||||||
Under the target folder, create:
|
Under the target folder, create:
|
||||||
|
|
||||||
|
|
@ -205,19 +109,17 @@ raw/archive/
|
||||||
tmp/
|
tmp/
|
||||||
wiki/entities/
|
wiki/entities/
|
||||||
wiki/graph/
|
wiki/graph/
|
||||||
wiki/projects/
|
|
||||||
wiki/decisions/
|
|
||||||
workload/
|
workload/
|
||||||
```
|
```
|
||||||
|
|
||||||
Git doesn't track empty directories, so every one of these - plus
|
Git doesn't track empty directories, so every one of these - plus
|
||||||
`.agents/`, `.agents/skills/`, and `.claude/` created in Step 10 - needs a
|
`.agents/`, `.agents/skills/`, and `.claude/` created in Step 9 - needs a
|
||||||
placeholder to survive a fresh `git init` and first commit. This repo's
|
placeholder to survive a fresh `git init` and first commit. This repo's
|
||||||
own convention is an empty file named `.gitadd` in each directory (not
|
own convention is an empty file named `.gitadd` in each directory (not
|
||||||
`.gitkeep`) - match that convention exactly, so a new KB's directory
|
`.gitkeep`) - match that convention exactly, so a new KB's directory
|
||||||
listing looks identical to this one's.
|
listing looks identical to this one's.
|
||||||
|
|
||||||
`libs/` and `tmp/` are gitignored per the schema (Step 9) except for their
|
`libs/` and `tmp/` are gitignored per the schema (Step 8) except for their
|
||||||
own `.gitadd` placeholder, so they stay effectively empty. `linked/` gets
|
own `.gitadd` placeholder, so they stay effectively empty. `linked/` gets
|
||||||
a `.gitadd` too but is *not* gitignored - it's meant to hold real
|
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
|
symlinks, which git tracks natively (as a small blob storing the link
|
||||||
|
|
@ -225,7 +127,7 @@ target), so nothing extra is needed there. `raw/inbox/`, `raw/archive/`,
|
||||||
and `workload/` are meant to be tracked and start genuinely empty aside
|
and `workload/` are meant to be tracked and start genuinely empty aside
|
||||||
from their placeholder.
|
from their placeholder.
|
||||||
|
|
||||||
### Step 6 - Write `AGENTS.md` and the `CLAUDE.md` symlink
|
### Step 5 - Write `AGENTS.md` and the `CLAUDE.md` symlink
|
||||||
|
|
||||||
Copy this repo's `AGENTS.md` verbatim into the target as `AGENTS.md` - it
|
Copy this repo's `AGENTS.md` verbatim into the target as `AGENTS.md` - it
|
||||||
is already fully generic (no project-specific content; it *is* the
|
is already fully generic (no project-specific content; it *is* the
|
||||||
|
|
@ -233,7 +135,7 @@ 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 7 - Write `LICENSE`, `VERSION`, and the generic docs
|
### Step 6 - Write `LICENSE`, `VERSION`, and the generic docs
|
||||||
|
|
||||||
Copy these verbatim from this repo - they're already fully generic (no
|
Copy these verbatim from this repo - they're already fully generic (no
|
||||||
project-specific content, confirmed by having zero references to any
|
project-specific content, confirmed by having zero references to any
|
||||||
|
|
@ -245,39 +147,9 @@ actual project entity):
|
||||||
should point somewhere else - it's what lets the new KB's own
|
should point somewhere else - it's what lets the new KB's own
|
||||||
`ckb-upgrade` find template updates later.
|
`ckb-upgrade` find template updates later.
|
||||||
- `MANUAL.md` and `MANUAL.pl.md` - the task-oriented user guide.
|
- `MANUAL.md` and `MANUAL.pl.md` - the task-oriented user guide.
|
||||||
- `CHANGELOG.md` and `CHANGELOG.pl.md` - the full page schema reference plus
|
|
||||||
the schema and template version histories. Copy verbatim: the history
|
|
||||||
belongs to the template, not to the new KB, and a fresh KB starts on the
|
|
||||||
version that history ends at.
|
|
||||||
- `VERSION` - copy the exact current value; the new KB starts life on the
|
- `VERSION` - copy the exact current value; the new KB starts life on the
|
||||||
same template version it was just cloned from.
|
same template version it was just cloned from.
|
||||||
|
|
||||||
Then write `ckb.yaml` at the new KB's root, recording where its tooling came
|
|
||||||
from so its own `ckb-upgrade` knows what to check later:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
template:
|
|
||||||
repo: https://git.wierzbowa.cloud/michal/ckb.git
|
|
||||||
branch: main
|
|
||||||
```
|
|
||||||
|
|
||||||
Use the URL and branch actually resolved in Step 3 - the user's fork URL if
|
|
||||||
they named one, `test`/`experimental` if they asked for that channel. Write
|
|
||||||
this block **even for the default** `main`/canonical case: an explicit record
|
|
||||||
is what makes a later channel switch visible, and it costs one file.
|
|
||||||
|
|
||||||
If the template source was this repo's own working tree (Step 3b) rather than
|
|
||||||
a clone, record the canonical repo URL and the branch this repo is currently
|
|
||||||
on (`git rev-parse --abbrev-ref HEAD`), since that is where the new KB's
|
|
||||||
upgrades will actually come from. Where that branch is `test` or
|
|
||||||
`experimental`, say so in the Step 12 report - propagating a dev channel into
|
|
||||||
a fresh KB by accident is easy, and the local working tree is exactly how it
|
|
||||||
happens.
|
|
||||||
|
|
||||||
`ckb.yaml` is also the modules manifest (`ckb-module` owns the `kb_modules:`
|
|
||||||
list). A fresh KB has no modules, so at init time the file holds the
|
|
||||||
`template:` block alone.
|
|
||||||
|
|
||||||
For `LICENSE`, copy the Apache License 2.0 text, but **ask the user first**
|
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
|
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
|
still effectively part of the same umbrella/organization) or update it to
|
||||||
|
|
@ -286,16 +158,14 @@ project that will accumulate its own content under its own ownership) -
|
||||||
don't silently carry over a copyright attribution that may not apply to
|
don't silently carry over a copyright attribution that may not apply to
|
||||||
what the new KB is about to collect.
|
what the new KB is about to collect.
|
||||||
|
|
||||||
### Step 8 - Write the empty `wiki/` scaffold
|
### 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
|
||||||
content (Grant Thornton, Cloud Drift, specific entities, etc.) down to the
|
content (Grant Thornton, Cloud Drift, specific entities, etc.) down to the
|
||||||
generic structure:
|
generic structure:
|
||||||
|
|
||||||
- **`wiki/index.md`** - frontmatter with `kb_schema_version` only, set to
|
- **`wiki/index.md`** - frontmatter with `kb_schema_version: "1.1"` only.
|
||||||
whatever value the template source's own `wiki/index.md` carries (do not
|
|
||||||
hard-code it here - it moves with the schema).
|
|
||||||
Body: the routing table with just its four fixed infrastructure rows
|
Body: the routing table with just its four fixed infrastructure rows
|
||||||
(Overview, Log, Error Book, Entities, Graph) and no entity rows, plus the
|
(Overview, Log, Error Book, Entities, Graph) and no entity rows, plus the
|
||||||
"## Entity Pages" section with its placeholder note. Use today's date
|
"## Entity Pages" section with its placeholder note. Use today's date
|
||||||
|
|
@ -307,29 +177,16 @@ generic structure:
|
||||||
- **`wiki/error-book.md`** - copy verbatim (already generic: empty table
|
- **`wiki/error-book.md`** - copy verbatim (already generic: empty table
|
||||||
+ placeholder note). Set `last_updated` to today.
|
+ placeholder note). Set `last_updated` to today.
|
||||||
- **`wiki/entities/index.md`** - header + placeholder note, no entries.
|
- **`wiki/entities/index.md`** - header + placeholder note, no entries.
|
||||||
- **`wiki/graph/index.md`** - header + pointer to `edges.json`, the edge
|
- **`wiki/graph/index.md`** - header + pointer to `edges.json`, with a
|
||||||
vocabulary table and its conventions **verbatim** (`ckb-ingest`,
|
generic "Current graph coverage: (none yet)" line instead of this
|
||||||
`ckb-decide`, `ckb-index-external`, and `ckb-lint` all write against that
|
repo's specific bullet list.
|
||||||
closed set, so it is contract, not example content), and a generic
|
|
||||||
"(No edges recorded yet)" placeholder instead of any real coverage line.
|
|
||||||
- **`wiki/graph/edges.json`** - `{"version": 1, "last_updated": "<today>", "edges": []}`.
|
- **`wiki/graph/edges.json`** - `{"version": 1, "last_updated": "<today>", "edges": []}`.
|
||||||
- **`wiki/projects/index.md`** - header + placeholder note, no project
|
|
||||||
scopes yet.
|
|
||||||
- **`wiki/decisions/index.md`** - header, the status-vocabulary table, and a
|
|
||||||
placeholder note; no decisions yet. Copy the status vocabulary verbatim —
|
|
||||||
`ckb-decide` and `ckb-lint` both validate against it.
|
|
||||||
- **`wiki/decisions/log.md`** - header and explanation only, no entries.
|
|
||||||
- **`wiki/query-gaps.md`** - header + explanation only, no recorded gaps.
|
|
||||||
|
|
||||||
If the template source's `wiki/` holds scaffold files beyond this list,
|
|
||||||
carry them over the same way - empty, structure only. The list above is
|
|
||||||
what the schema requires, not a cap.
|
|
||||||
|
|
||||||
Do not carry over any entity pages, graph edges, log entries, or overview
|
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 9 - Write `.gitignore`
|
### Step 8 - Write `.gitignore`
|
||||||
|
|
||||||
Copy this repo's actual current `.gitignore` verbatim rather than
|
Copy this repo's actual current `.gitignore` verbatim rather than
|
||||||
reconstructing it from memory - it uses a `<dir>/*` + `!<dir>/.gitadd`
|
reconstructing it from memory - it uses a `<dir>/*` + `!<dir>/.gitadd`
|
||||||
|
|
@ -339,16 +196,6 @@ tracked shell but have their real contents ignored:
|
||||||
```
|
```
|
||||||
libs/*
|
libs/*
|
||||||
!libs/.gitadd
|
!libs/.gitadd
|
||||||
!libs/*/
|
|
||||||
libs/*/*
|
|
||||||
!libs/*/source.yaml
|
|
||||||
!libs/*/index.md
|
|
||||||
!libs/*/log.md
|
|
||||||
!libs/*/entities/
|
|
||||||
!libs/*/graph/
|
|
||||||
# Per-user local override (e.g. source.local.yaml's access: write) — never shared, stays ignored
|
|
||||||
# by the libs/*/* catch-all above; listed explicitly for clarity, not because it changes behavior.
|
|
||||||
libs/*/*.local.yaml
|
|
||||||
tmp/*
|
tmp/*
|
||||||
!tmp/.gitadd
|
!tmp/.gitadd
|
||||||
outputs/starlight
|
outputs/starlight
|
||||||
|
|
@ -357,13 +204,8 @@ outputs/teaching
|
||||||
.env
|
.env
|
||||||
```
|
```
|
||||||
|
|
||||||
(That `libs/` block is what keeps a connector-backed lib's `source.yaml`
|
|
||||||
and its agent-generated index tracked while ignoring everything else under
|
|
||||||
it - copy it as a unit. If the template source's `.gitignore` has since
|
|
||||||
changed, its version wins over the snapshot above.)
|
|
||||||
|
|
||||||
Since `ckb-export-starlight`, `ckb-export-okf`, and `ckb-teach-me` are all
|
Since `ckb-export-starlight`, `ckb-export-okf`, and `ckb-teach-me` are all
|
||||||
in the default skill set (Step 4), their `outputs/` subfolders
|
in the default skill set (Step 3), their `outputs/` subfolders
|
||||||
(`outputs/starlight`, `outputs/okf`, `outputs/teaching`) are gitignored by
|
(`outputs/starlight`, `outputs/okf`, `outputs/teaching`) are gitignored by
|
||||||
default too - each exists to be ignored precisely because its skill is
|
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
|
present by default. If a future skill set change ever drops one of those
|
||||||
|
|
@ -371,61 +213,26 @@ 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,
|
if the user explicitly adds a skill with its own `outputs/<x>` convention,
|
||||||
ask whether it needs a similar ignore line.
|
ask whether it needs a similar ignore line.
|
||||||
|
|
||||||
### Step 10 - Copy the skill set from Step 4
|
### 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 (including each skill's own
|
target's `.agents/skills/<name>/` unchanged (including each skill's own
|
||||||
license footer, and any support files like
|
license footer, and any support files like
|
||||||
`ckb-export-okf/scripts/export_okf.py`,
|
`ckb-export-starlight/scripts/export_starlight.py`) - the full default set
|
||||||
`ckb-export-starlight/scripts/export_starlight.py`,
|
from Step 3, plus anything explicitly added. Then create `.claude/skills`
|
||||||
`ckb-lint/scripts/lint_report.py`) - the full default set
|
|
||||||
from Step 4, plus anything explicitly added. Then create `.claude/skills`
|
|
||||||
in the target as a symlink to `../.agents/skills`, matching this repo's
|
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.
|
convention - do this once, after copying the whole set, not per-skill.
|
||||||
|
|
||||||
Also copy `.agents/modules/` wholesale. Modules are inert until installed,
|
### Step 10 - Report
|
||||||
so a new KB carries the full catalogue at zero context cost, and the user
|
|
||||||
can enable one later with `ckb-module` instead of re-deriving it. Do **not**
|
|
||||||
install any module into the new KB, and do not create a `ckb.yaml`: an
|
|
||||||
absent manifest correctly means "nothing installed". If the user has said
|
|
||||||
the new KB is for software they are building, mention the `software` module
|
|
||||||
and offer to install it after Step 11 — offer, don't assume.
|
|
||||||
|
|
||||||
### Step 11 - Initialize the new KB's own git history (ask first)
|
|
||||||
|
|
||||||
The target is now a complete KB but has no history of its own - and if the
|
|
||||||
template came from a clone (Step 3a), it deliberately carries none of the
|
|
||||||
template's. Ask whether to initialize one:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git init
|
|
||||||
git add .
|
|
||||||
git commit -m "Initialize Cascade Knowledge Base from ckb template v<VERSION>"
|
|
||||||
```
|
|
||||||
|
|
||||||
Only do this if the target isn't already inside a git repo (`git rev-parse
|
|
||||||
--is-inside-work-tree` from the target) - if it is, say so and leave the
|
|
||||||
staging to the user rather than committing into someone else's repo. Don't
|
|
||||||
add a remote and don't push; that's `ckb-sync-changes`' job once the user
|
|
||||||
has a remote to point at.
|
|
||||||
|
|
||||||
### Step 12 - Report
|
|
||||||
|
|
||||||
Tell the user:
|
Tell the user:
|
||||||
- The resolved target path.
|
- The resolved target path.
|
||||||
- The template source used: the clone URL, **branch**, and short commit hash,
|
|
||||||
or "this repo's local working tree" and the branch it was on. Name the
|
|
||||||
channel in words when it isn't `main` - "this KB tracks `experimental`,
|
|
||||||
which carries no stability promise; say 'switch to the main channel' to
|
|
||||||
change that."
|
|
||||||
- That `ckb.yaml` records the repo and branch for future upgrades.
|
|
||||||
- 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 full default set, plus anything explicitly
|
- Which skills were copied (the full default set, plus anything explicitly
|
||||||
added).
|
added).
|
||||||
- The `VERSION` the new KB starts on, and what was decided for `LICENSE`'s
|
- The `VERSION` the new KB starts on, and what was decided for `LICENSE`'s
|
||||||
copyright line.
|
copyright line.
|
||||||
- Whether a git repo was initialized in the target, or why not.
|
|
||||||
- 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
|
||||||
|
|
@ -439,19 +246,6 @@ Tell the user:
|
||||||
- **User wants only *some* of the wiki template files** (e.g. just the
|
- **User wants only *some* of the wiki template files** (e.g. just the
|
||||||
directory structure, no `AGENTS.md`) - honor that; the steps above are
|
directory structure, no `AGENTS.md`) - honor that; the steps above are
|
||||||
the default full scaffold, not an all-or-nothing bundle.
|
the default full scaffold, not an all-or-nothing bundle.
|
||||||
- **Clone fails and there's no local KB to fall back on** - stop and report
|
|
||||||
the raw git error. Don't hand-write an approximation of the scaffold from
|
|
||||||
memory; a KB whose `AGENTS.md` is a paraphrase is worse than no KB.
|
|
||||||
- **User asks to clone the template repo straight into the target** - point
|
|
||||||
out that this gives them the template's git history and files rather than
|
|
||||||
a fresh KB, and offer both: a plain `git clone` (if that's really what
|
|
||||||
they want) or the scratch-clone-then-init flow, which is the default.
|
|
||||||
- **Target already contains a `.git/`** - proceed with the scaffold, but
|
|
||||||
skip Step 11's `git init`/commit and say so; committing into someone
|
|
||||||
else's repo is theirs to decide.
|
|
||||||
- **Scratch clone path already exists** (a previous run died mid-way) -
|
|
||||||
remove it before cloning (`rm -rf`), and remove it again when done. Never
|
|
||||||
reuse a half-cloned scratch dir.
|
|
||||||
- **This repo's own `AGENTS.md` or template `wiki/` files have since
|
- **This repo's own `AGENTS.md` or template `wiki/` files have since
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
name: ckb-lint
|
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).
|
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 from CLAUDE.md/AGENTS.md §6, 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
|
# Lint skill
|
||||||
|
|
@ -17,18 +17,9 @@ Implemented as a skill (rather than living inline in
|
||||||
`CLAUDE.md`/`AGENTS.md`) so the full checklist only loads into context when
|
`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.
|
actually invoked — on demand, or on a schedule if the user has set one up.
|
||||||
|
|
||||||
The **detection** half of this checklist is mechanical — a fixed set of
|
|
||||||
frontmatter, date, link, graph, and index/log rules over a tree that keeps
|
|
||||||
growing — so it runs as a read-only Python script rather than as per-page
|
|
||||||
model reasoning (Step 0 below). What the script cannot decide is what this
|
|
||||||
skill is actually for: which findings to auto-fix, which to leave for the
|
|
||||||
user, whether two pages genuinely supersede one another, and whether a
|
|
||||||
cluster of findings is a systemic pattern worth an error-book entry. Read
|
|
||||||
the script's report, then do that work.
|
|
||||||
|
|
||||||
This skill only ever writes within `wiki/` (including moves into
|
This skill only ever writes within `wiki/` (including moves into
|
||||||
`wiki/archived/`) and, for any connector-backed `libs/<name>/` (one with a
|
`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
|
`source.yaml` — see `CLAUDE.md`/`AGENTS.md` §1/§4) where this user has
|
||||||
local `access: write` (see `ckb-index-external`), within that connector's
|
local `access: write` (see `ckb-index-external`), within that connector's
|
||||||
own agent-owned generated index (`index.md`/`entities/`/`graph/`/`log.md`).
|
own agent-owned generated index (`index.md`/`entities/`/`graph/`/`log.md`).
|
||||||
For a connector-backed `libs/<name>/` where this user is read-only (the
|
For a connector-backed `libs/<name>/` where this user is read-only (the
|
||||||
|
|
@ -66,107 +57,59 @@ 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`/
|
git-copy `libs/<name>/` (no `source.yaml`) and any `source.yaml`/
|
||||||
`source.local.yaml` are never touched by any of these checks.
|
`source.local.yaml` are never touched by any of these checks.
|
||||||
|
|
||||||
### 0 — Run the checker script
|
|
||||||
|
|
||||||
From the repository root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 "<skill-dir>/scripts/lint_report.py"
|
|
||||||
```
|
|
||||||
|
|
||||||
Resolve `<skill-dir>` to this skill's own directory. Flags: `--scope
|
|
||||||
wiki|libs|all` (default `all`) to limit which trees are checked; `--today
|
|
||||||
YYYY-MM-DD` to pin the date for reproducible runs; `--json` for a
|
|
||||||
machine-readable report; `--quick` for the one-line session-start summary
|
|
||||||
Rule E calls (conformance, freshness, and source drift only — it prints a
|
|
||||||
single line and does none of the work below). Exit code is `0` when nothing was found and `1`
|
|
||||||
when there are findings — read the report either way.
|
|
||||||
|
|
||||||
The script is strictly read-only: it writes nothing, moves nothing, and
|
|
||||||
runs no `git` commands. It covers the mechanical detection in checks 1, 2,
|
|
||||||
3, 4, 6, 7, 8, 10, 12, and 13 below — plus the structural half of check 5
|
|
||||||
(decision records) — across `wiki/` and every connector-backed
|
|
||||||
`libs/<name>/` (reporting each one's `access:` level so you know where a
|
|
||||||
fix is even permitted). Check 9, the semantic half of check 5, and every
|
|
||||||
auto-fix-vs-report decision, are yours.
|
|
||||||
|
|
||||||
The numbered checks below stay as the reference for *what each finding
|
|
||||||
means and what to do about it* — you don't need to re-derive the detection
|
|
||||||
by hand. Do read the pages the script flags: a finding is a pointer to a
|
|
||||||
page that needs a decision, not the decision itself.
|
|
||||||
|
|
||||||
### 1 — Conformance check
|
### 1 — Conformance check
|
||||||
|
|
||||||
The script flags every non-reserved `.md` file under `wiki/` (i.e.
|
Verify every non-reserved `.md` file under `wiki/` (i.e. excluding
|
||||||
excluding `index.md` and `log.md`) whose frontmatter is unparseable,
|
`index.md` and `log.md`) has parseable YAML frontmatter with a non-empty
|
||||||
absent, or missing a non-empty `type`. Treat flagged pages as unreliable
|
`type` field. Flag violations first, and treat flagged pages as
|
||||||
input for the checks below rather than guessing at their intended
|
unreliable input for the checks below rather than guessing at their
|
||||||
type/content, and fix the frontmatter before acting on any later finding
|
intended type/content.
|
||||||
about the same page.
|
|
||||||
|
|
||||||
### 2 — Freshness check
|
### 2 — Freshness check
|
||||||
|
|
||||||
The script reports every page whose `last_updated` exceeds its
|
Scan every page whose `last_updated` exceeds its `freshness_window_days`.
|
||||||
`freshness_window_days`, and by how much. Suggest the user confirm or
|
Flag as stale; suggest the user confirm or update the content — don't
|
||||||
update the content — don't silently rewrite stale content yourself.
|
silently rewrite stale content yourself.
|
||||||
|
|
||||||
### 3 — Confidence decay
|
### 3 — Confidence decay
|
||||||
|
|
||||||
The script reports pages already below 0.3 `confidence` (and any
|
Reduce `confidence` on pages not reinforced by a new source since the last
|
||||||
unparseable value). Decaying `confidence` on pages not reinforced by a new
|
check. Pages that fall below 0.3 confidence get flagged for re-review.
|
||||||
source since the last check is a write and a judgment call, so it stays
|
|
||||||
yours — the script only surfaces where the floor has been crossed.
|
|
||||||
|
|
||||||
### 4 — Retention sweep
|
### 4 — Retention sweep
|
||||||
|
|
||||||
The script reports `retention: low` pages older than 2x their
|
Move `retention: low` pages older than 2× their `freshness_window_days`
|
||||||
`freshness_window_days` as archive candidates. Move them into
|
into `wiki/archived/`. Never delete — always move, and log the move (see
|
||||||
`wiki/archived/` — never delete, always move — and log each move (see
|
Rule B in `CLAUDE.md`/`AGENTS.md` §7) with a note explaining why.
|
||||||
Rule B in `CLAUDE.md`/`AGENTS.md`) with a note explaining why. Check each
|
|
||||||
candidate against recent use before moving it (see Edge cases).
|
|
||||||
|
|
||||||
### 5 — Supersession and decision records
|
### 5 — Supersession detection
|
||||||
|
|
||||||
The script checks the *structure* of every `type: decision` page: a status
|
When two pages appear to cover the same entity, check for contradictions.
|
||||||
from the vocabulary, a `decided_on` date where the status implies one, a
|
If one is clearly newer/better corroborated, add `supersedes` /
|
||||||
`decided_by` (`unknown` counts — an omitted field doesn't), `affects` targets
|
`superseded_by` links between them. Preserve the older page rather than
|
||||||
and supersession links that resolve, supersession set on **both** sides,
|
deleting it, but mark it stale.
|
||||||
`status: superseded`/`reversed` matched by a `superseded_by`, unique
|
|
||||||
four-digit decision numbers, and any `review_on` date that has passed. Fix
|
|
||||||
those directly — they're mechanical. A `review_on` that has come due is not a
|
|
||||||
defect, though: report it so the user can decide whether to revisit, and never
|
|
||||||
change a decision's substance yourself (`ckb-decide` → decisions are
|
|
||||||
append-only).
|
|
||||||
|
|
||||||
The *semantic* half stays yours: when two pages appear to cover the same
|
|
||||||
entity or the same choice, 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. For
|
|
||||||
two decisions, that judgment is exactly what the script can't make — it can
|
|
||||||
tell you a link is one-sided, not that a link should exist.
|
|
||||||
|
|
||||||
### 6 — Orphan detection
|
### 6 — Orphan detection
|
||||||
|
|
||||||
The script reports pages with no inbound links (`[[wikilinks]]` or
|
Find pages with no inbound links (`[[wikilinks]]` or
|
||||||
`[markdown](path.md)` references from anywhere else in the tree). For each
|
`[markdown](path.md)` references from elsewhere in the wiki). Either add
|
||||||
one, either add backlinks from relevant pages where an obvious connection
|
backlinks from relevant pages where an obvious connection exists, or move
|
||||||
exists, or move the orphan to `wiki/archived/` with a log note if no
|
the orphan to `wiki/archived/` with a log note if no natural backlink
|
||||||
natural backlink does — that choice is the judgment the script leaves you.
|
exists.
|
||||||
|
|
||||||
### 7 — Graph consistency
|
### 7 — Graph consistency
|
||||||
|
|
||||||
The script reports every edge in `wiki/graph/edges.json` whose `from`/`to`
|
Verify every edge in `wiki/graph/edges.json` points to an existing entity
|
||||||
does not resolve to an existing page (plus malformed edges and invalid
|
page. Remove or fix broken edges; note what was removed rather than
|
||||||
JSON). Remove or fix them; note what was removed rather than silently
|
silently dropping entries.
|
||||||
dropping entries.
|
|
||||||
|
|
||||||
### 8 — Index/log consistency
|
### 8 — Index/log consistency
|
||||||
|
|
||||||
The script reports subdirectories holding pages but no `index.md`, index
|
Verify every subdirectory under `wiki/` that contains pages has an
|
||||||
files that don't list a page sitting next to them, and any change recorded
|
`index.md` listing all of them, and that no single change is recorded in
|
||||||
in both a subdirectory `log.md` and the root `wiki/log.md` (per the
|
both a subdirectory `log.md` and the root `wiki/log.md` (per the
|
||||||
Recursive Index & Log Convention). Fix missing index entries and duplicate
|
Recursive Index & Log Convention). Fix missing index entries and
|
||||||
log entries directly — these are the safest auto-fixes on the list.
|
duplicate log entries directly.
|
||||||
|
|
||||||
### 9 — Error Book entry
|
### 9 — Error Book entry
|
||||||
|
|
||||||
|
|
@ -178,123 +121,22 @@ doesn't need an Error Book entry — this is for patterns, not incidents.
|
||||||
|
|
||||||
### 10 — External source config check
|
### 10 — External source config check
|
||||||
|
|
||||||
The script validates each `libs/<name>/source.yaml` (non-empty `connector`
|
For each `libs/<name>/source.yaml`, verify it has a non-empty `connector`
|
||||||
and `location`; non-empty `store` and `location` inside any `index:` block;
|
and `location` — report only, this file is never edited by any skill. If
|
||||||
a positive integer `refresh_interval_days`) and flags a `libs/<name>/` that
|
an `index:` block is present, verify it has a non-empty `store` and
|
||||||
ambiguously holds both real content files and a `source.yaml`. All of this
|
`location` too. Also flag (report only) a `libs/<name>/` that ambiguously
|
||||||
is **report only** — `source.yaml` is never edited by any skill, and the
|
has both real content files and a `source.yaml` — that's a configuration
|
||||||
ambiguous-content case is a configuration conflict for the user to resolve,
|
conflict for the user to resolve, not something to guess at.
|
||||||
not something to guess at.
|
|
||||||
|
|
||||||
It also reports any connector-backed source whose generated index is
|
|
||||||
overdue — newest `last_updated` in `libs/<name>/` older than its
|
|
||||||
`refresh_interval_days` (default 30) — with how overdue it is, since a
|
|
||||||
source two days past a 7-day interval is a different situation from one six
|
|
||||||
months past a 30-day one. Relay that 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. 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
|
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
|
absence is the correct, read-only default (see `ckb-index-external`), not
|
||||||
a gap to report or fix. The script follows the same rule: it reads that
|
a gap to report or fix.
|
||||||
file only to label each connector `access: read-only` / `access: write` in
|
|
||||||
its report.
|
|
||||||
|
|
||||||
### 11 — Installed module checks
|
|
||||||
|
|
||||||
If `ckb.yaml` lists installed modules, run whatever extra checks those
|
|
||||||
modules define, and skip this step entirely when the file is absent or
|
|
||||||
empty. Modules are optional by design: their absence is never a finding.
|
|
||||||
|
|
||||||
For the `software` module specifically:
|
|
||||||
|
|
||||||
- **Stale code maps** — a `type: repository` page whose `commit` is behind
|
|
||||||
the current `HEAD` of its `src/` clone. Report how far behind; suggest
|
|
||||||
`ckb-code-map`. A missing clone is **not** a finding — `src/` is
|
|
||||||
gitignored and expected to be empty on a fresh checkout.
|
|
||||||
- **Spec drift** — the four checks `ckb-spec` defines: root specs with
|
|
||||||
empty `implemented_by`, repo-level specs implementing a missing or
|
|
||||||
archived parent, one-sided `implements`/`implemented_by` links, and root
|
|
||||||
specs older than the mapped commit of every repo implementing them.
|
|
||||||
- **Remote-less repos** — a mapped repository page with no `resource:`,
|
|
||||||
meaning a gitignored clone that exists nowhere else.
|
|
||||||
|
|
||||||
All report-only. Never auto-fix any of these: each is a statement about
|
|
||||||
intent, and only the user knows which side is correct.
|
|
||||||
|
|
||||||
### 12 — Source fingerprint drift
|
|
||||||
|
|
||||||
The script recomputes the `sha256` of every local file cited in a page's
|
|
||||||
`## Sources` and compares it against the digest recorded there. Three
|
|
||||||
findings come out of it, and they are not the same problem:
|
|
||||||
|
|
||||||
- **`CHANGED since this page was built`** — the file the page was
|
|
||||||
synthesized from has been edited. This outranks every freshness finding
|
|
||||||
on the list: check 2 says a page has *aged*, this says its evidence has
|
|
||||||
*moved*. Read both, and either re-verify the page's claims against the
|
|
||||||
new content and re-stamp the digest, or flag it for `ckb-ingest`. Never
|
|
||||||
re-stamp a digest without reading what changed — that converts a real
|
|
||||||
finding into a silent lie.
|
|
||||||
- **`does not exist`** — a cited source was moved, renamed, or deleted. The
|
|
||||||
page now rests on nothing. Find where the source went if you can; if you
|
|
||||||
can't, say so on the page rather than leaving a citation that looks
|
|
||||||
valid.
|
|
||||||
- **`has no fingerprint recorded`** — a `## Sources` bullet predating this
|
|
||||||
convention, or written by hand. Safe to auto-fix *only* when the page has
|
|
||||||
not otherwise changed: compute the digest and record it with today's
|
|
||||||
`source_checked`. If the page is also flagged by check 2 or 13, fix those
|
|
||||||
first — stamping a digest onto a page you haven't verified just freezes
|
|
||||||
the drift in place.
|
|
||||||
|
|
||||||
An `etag:`/`mtime:` fingerprint (connector-sourced, per
|
|
||||||
`ckb-index-external`) is skipped here: there is nothing local to recompute.
|
|
||||||
Those are verified by re-indexing that source, not by this script.
|
|
||||||
|
|
||||||
### 13 — Crux verbatimness
|
|
||||||
|
|
||||||
`## Crux` is quoted evidence, so it admits a check no synthesized prose
|
|
||||||
does: the quote either still appears in the source or it doesn't. The
|
|
||||||
script flags four cases:
|
|
||||||
|
|
||||||
- **Quote not found verbatim in the cited source** — the strongest finding
|
|
||||||
the linter produces. The page asserts, in quotation marks, something its
|
|
||||||
source does not say. Either the source was edited (check 12 usually fires
|
|
||||||
alongside; fix them together) or the quote was paraphrased into existence
|
|
||||||
at ingest time, which is a correctness problem, not a formatting one.
|
|
||||||
Never "fix" this by editing the quote to match the source — re-read the
|
|
||||||
source, decide what it actually supports, and rewrite the page's claim.
|
|
||||||
- **`## Crux` has no quoted lines** — prose sitting under an evidence
|
|
||||||
heading. Either quote the source properly or delete the section; a
|
|
||||||
paraphrase labelled as evidence is worse than no evidence.
|
|
||||||
- **Quote is not attributed** — no `— \`path\`` line saying which source it
|
|
||||||
came from, so it can't be verified by anyone. Attribute it from the
|
|
||||||
page's `## Sources` if the origin is unambiguous, otherwise flag it.
|
|
||||||
- **`## Crux` with no `## Sources`** — an evidence section with nothing to
|
|
||||||
verify against.
|
|
||||||
|
|
||||||
Quotes under 24 characters and sources in non-text formats (PDF, DOCX,
|
|
||||||
audio) are fingerprinted but not quote-matched; the script skips them
|
|
||||||
rather than reporting false positives.
|
|
||||||
|
|
||||||
### 14 — Protected `## Notes`
|
|
||||||
|
|
||||||
This one has no script check, because it is a rule about *writing*, not a
|
|
||||||
property of a tree: no skill may rewrite, reflow, summarize, or drop a
|
|
||||||
`## Notes` section (page schema, `CLAUDE.md`/`AGENTS.md`). It is listed
|
|
||||||
here so the rule has somewhere to be enforced from. When a lint fix touches
|
|
||||||
a page — a backlink, an index entry, an archival move — carry its
|
|
||||||
`## Notes` across byte-for-byte, and check after any bulk edit that none
|
|
||||||
were lost. On a generated page under `libs/<name>/`, a missing `## Notes`
|
|
||||||
is worth adding empty so the affordance exists; anywhere else its absence
|
|
||||||
is normal and not a finding.
|
|
||||||
|
|
||||||
### Auto-fix vs. report
|
### Auto-fix vs. report
|
||||||
|
|
||||||
Auto-fix what can be done safely and mechanically: broken links, missing
|
Auto-fix what can be done safely and mechanically: broken links, missing
|
||||||
backlinks, stale flags, missing index entries, duplicate log entries,
|
backlinks, stale flags, missing index entries, duplicate log entries,
|
||||||
dangling graph edges, and a missing fingerprint on an otherwise-unflagged
|
dangling graph edges. Report anything that needs a judgment call
|
||||||
page. Report anything that needs a judgment call
|
|
||||||
(supersession decisions, low-confidence content, ambiguous orphans) rather
|
(supersession decisions, low-confidence content, ambiguous orphans) rather
|
||||||
than guessing on the user's behalf.
|
than guessing on the user's behalf.
|
||||||
|
|
||||||
|
|
@ -319,14 +161,11 @@ fix or flag), skip this reminder — there's nothing to review or sync.
|
||||||
## Edge cases
|
## Edge cases
|
||||||
|
|
||||||
- **Wiki is empty or near-empty** — report that there's little to lint
|
- **Wiki is empty or near-empty** — report that there's little to lint
|
||||||
yet; don't fabricate findings to look thorough. A `total findings: 0`
|
yet; don't fabricate findings to look thorough.
|
||||||
report is a valid outcome; say so plainly rather than hunting for
|
|
||||||
something to say.
|
|
||||||
- **A page's frontmatter is unparseable** (not just missing `type`, but
|
- **A page's frontmatter is unparseable** (not just missing `type`, but
|
||||||
invalid YAML) — the script reports it under conformance and skips it in
|
invalid YAML) — flag it prominently in the conformance check and skip
|
||||||
the later checks rather than reasoning from a half-parsed page. Surface
|
it in every later numbered check rather than letting a parse error
|
||||||
it prominently and fix the frontmatter first; a page can't be judged
|
crash or silently mis-handle downstream logic.
|
||||||
stale, orphaned, or archivable until it parses.
|
|
||||||
- **Retention sweep would archive a page that's clearly still in active
|
- **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
|
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
|
the user to confirm rather than auto-archiving; recency of use can
|
||||||
|
|
@ -334,19 +173,9 @@ fix or flag), skip this reminder — there's nothing to review or sync.
|
||||||
- **Supersession is ambiguous** (two pages disagree and neither is
|
- **Supersession is ambiguous** (two pages disagree and neither is
|
||||||
clearly newer/better corroborated) — report the conflict rather than
|
clearly newer/better corroborated) — report the conflict rather than
|
||||||
guessing which one wins.
|
guessing which one wins.
|
||||||
- **Repeated run with nothing changed since the last lint** — produces the
|
- **Repeated run with nothing changed since the last lint** — should
|
||||||
same report each time (pass `--today` to pin the date if you need a
|
produce essentially the same clean report each time; don't invent
|
||||||
byte-identical one); don't invent variation just to seem active.
|
variation just to seem active.
|
||||||
- **A changed fingerprint and a diverged crux quote on the same page** —
|
|
||||||
one incident, not two. The source was edited; fix the page once against
|
|
||||||
the new content and re-stamp both the digest and the quote together.
|
|
||||||
- **Every page in the wiki lacks fingerprints** — a wiki built before this
|
|
||||||
convention. Don't stamp them all in one pass; that produces a tree of
|
|
||||||
digests attesting to nothing anyone verified. Report the count, and
|
|
||||||
backfill as pages are touched for other reasons.
|
|
||||||
- **The script errors out or isn't runnable** (no Python 3, unreadable
|
|
||||||
tree) — say so and fall back to working the numbered checks by hand
|
|
||||||
rather than reporting a clean bill of health you didn't actually verify.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,793 +0,0 @@
|
||||||
#!/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.
|
|
||||||
"""Deterministic half of the ckb-lint health check: detect, never fix.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python3 lint_report.py [--scope wiki|libs|all] [--today YYYY-MM-DD] [--json] [--quick]
|
|
||||||
|
|
||||||
Run from the repository root (the directory containing wiki/ and libs/).
|
|
||||||
|
|
||||||
This script is strictly READ-ONLY. It writes no files, moves nothing, and runs
|
|
||||||
no git commands — it prints findings for the agent to act on. The mechanical
|
|
||||||
checks (conformance, freshness, retention/decay candidates, orphans, graph
|
|
||||||
consistency, index/log consistency, source.yaml validity, source fingerprints,
|
|
||||||
crux verbatimness) live here; the
|
|
||||||
judgment calls the ckb-lint skill owns (whether two pages genuinely supersede
|
|
||||||
one another, ambiguous orphans, error-book entries, auto-fix vs. report) stay
|
|
||||||
with the model. Decision records get their own structural checks here — status
|
|
||||||
vocabulary, required dates, two-sided supersession links — since those are
|
|
||||||
mechanical; deciding that a new decision *replaces* an old one is not.
|
|
||||||
|
|
||||||
Exit code is 0 when nothing was found and 1 when there are findings.
|
|
||||||
"""
|
|
||||||
import argparse
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from datetime import date, datetime
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
REPO_ROOT = Path.cwd()
|
|
||||||
WIKI = REPO_ROOT / "wiki"
|
|
||||||
LIBS = REPO_ROOT / "libs"
|
|
||||||
|
|
||||||
RESERVED = {"index.md", "log.md"}
|
|
||||||
DEFAULT_REFRESH_DAYS = 30
|
|
||||||
DECISION_STATUSES = {"proposed", "accepted", "rejected", "superseded", "reversed"}
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# tiny YAML readers (stdlib only — this template ships without dependencies)
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def parse_frontmatter(text):
|
|
||||||
"""Return (dict, body, error). `error` is a string when the frontmatter is
|
|
||||||
present but unparseable, else None. Flat `key: value` pairs only."""
|
|
||||||
if not text.startswith("---\n"):
|
|
||||||
return {}, text, None
|
|
||||||
end = text.find("\n---\n", 4)
|
|
||||||
if end == -1:
|
|
||||||
return {}, text, "frontmatter opened with `---` but never closed"
|
|
||||||
raw, body = text[4:end], text[end + 5 :]
|
|
||||||
fm = {}
|
|
||||||
for lineno, line in enumerate(raw.split("\n"), start=2):
|
|
||||||
stripped = line.strip()
|
|
||||||
if not stripped or stripped.startswith("#"):
|
|
||||||
continue
|
|
||||||
if line[:1].isspace():
|
|
||||||
return {}, body, f"line {lineno}: nested/indented YAML is not supported in page frontmatter"
|
|
||||||
if ":" not in stripped:
|
|
||||||
return {}, body, f"line {lineno}: not a `key: value` pair: {stripped!r}"
|
|
||||||
key, _, value = stripped.partition(":")
|
|
||||||
fm[key.strip()] = unquote(value.strip())
|
|
||||||
return fm, body, None
|
|
||||||
|
|
||||||
|
|
||||||
def unquote(value):
|
|
||||||
if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'):
|
|
||||||
return value[1:-1]
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def parse_simple_yaml(text):
|
|
||||||
"""Two-level `key: value` / `key:` + indented block YAML, enough for
|
|
||||||
source.yaml and source.local.yaml. Returns a dict; nested blocks become
|
|
||||||
nested dicts."""
|
|
||||||
out, current = {}, None
|
|
||||||
for line in text.split("\n"):
|
|
||||||
if not line.strip() or line.strip().startswith("#"):
|
|
||||||
continue
|
|
||||||
indented = line[:1].isspace()
|
|
||||||
stripped = line.strip()
|
|
||||||
if ":" not in stripped:
|
|
||||||
continue
|
|
||||||
key, _, value = stripped.partition(":")
|
|
||||||
key, value = key.strip(), unquote(value.split(" #", 1)[0].strip())
|
|
||||||
if indented:
|
|
||||||
if isinstance(current, dict):
|
|
||||||
current[key] = value
|
|
||||||
continue
|
|
||||||
if value == "":
|
|
||||||
current = {}
|
|
||||||
out[key] = current
|
|
||||||
else:
|
|
||||||
out[key] = value
|
|
||||||
current = None
|
|
||||||
# `key:` with nothing indented under it is an empty scalar, not a block.
|
|
||||||
return {k: ("" if v == {} else v) for k, v in out.items()}
|
|
||||||
|
|
||||||
|
|
||||||
def as_float(value):
|
|
||||||
try:
|
|
||||||
return float(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def as_int(value):
|
|
||||||
try:
|
|
||||||
return int(str(value).strip())
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def as_date(value):
|
|
||||||
try:
|
|
||||||
return datetime.strptime(str(value).strip(), "%Y-%m-%d").date()
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# tree model
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
LINK_RE = re.compile(r"\[[^\]]*\]\(([^)]+)\)")
|
|
||||||
WIKILINK_RE = re.compile(r"\[\[([^\]]+)\]\]")
|
|
||||||
SOURCE_BULLET_RE = re.compile(r"^\s*[-*]\s+`([^`]+)`(.*)$", re.M)
|
|
||||||
FINGERPRINT_RE = re.compile(r"\b(sha256|etag|mtime):([^\s,)]+)")
|
|
||||||
ATTRIBUTION_RE = re.compile(r"^\s*[\u2014-]\s*`([^`]+)`", re.M)
|
|
||||||
# Text-ish sources a quote can actually be checked against byte-for-byte.
|
|
||||||
# Anything else (pdf, docx, audio) is fingerprinted but never quote-verified.
|
|
||||||
QUOTABLE_SUFFIXES = {".md", ".txt", ".eml", ".csv", ".json", ".yaml", ".yml", ".html", ".rst", ".log"}
|
|
||||||
|
|
||||||
|
|
||||||
class Doc:
|
|
||||||
def __init__(self, root, path):
|
|
||||||
self.path = path
|
|
||||||
self.rel = path.relative_to(root)
|
|
||||||
text = path.read_text(encoding="utf-8", errors="replace")
|
|
||||||
self.fm, self.body, self.fm_error = parse_frontmatter(text)
|
|
||||||
self.reserved = path.name in RESERVED
|
|
||||||
|
|
||||||
def links(self):
|
|
||||||
"""Every intra-tree link target as a path relative to the tree root."""
|
|
||||||
out = set()
|
|
||||||
for target in LINK_RE.findall(self.body):
|
|
||||||
target = target.split("#", 1)[0].strip()
|
|
||||||
if not target or re.match(r"^[a-z][a-z0-9+.-]*:", target):
|
|
||||||
continue
|
|
||||||
if target.startswith("/wiki/"):
|
|
||||||
out.add(target[len("/wiki/") :])
|
|
||||||
elif target.startswith("/"):
|
|
||||||
out.add(target.lstrip("/"))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
resolved = (self.rel.parent / target).as_posix()
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
parts = []
|
|
||||||
for part in resolved.split("/"):
|
|
||||||
if part == "..":
|
|
||||||
if parts:
|
|
||||||
parts.pop()
|
|
||||||
elif part not in (".", ""):
|
|
||||||
parts.append(part)
|
|
||||||
out.add("/".join(parts))
|
|
||||||
return out
|
|
||||||
|
|
||||||
def wikilinks(self):
|
|
||||||
return {w.split("|", 1)[-1].strip() for w in WIKILINK_RE.findall(self.body)}
|
|
||||||
|
|
||||||
|
|
||||||
def read_tree(root):
|
|
||||||
docs = []
|
|
||||||
for path in sorted(root.rglob("*.md")):
|
|
||||||
if any(part.startswith(".") for part in path.relative_to(root).parts):
|
|
||||||
continue
|
|
||||||
docs.append(Doc(root, path))
|
|
||||||
return docs
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# checks (each returns a list of finding strings)
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def check_conformance(docs):
|
|
||||||
findings = []
|
|
||||||
for doc in docs:
|
|
||||||
if doc.reserved:
|
|
||||||
continue
|
|
||||||
if doc.fm_error:
|
|
||||||
findings.append(f"{doc.rel}: unparseable frontmatter — {doc.fm_error}")
|
|
||||||
elif not doc.fm:
|
|
||||||
findings.append(f"{doc.rel}: no frontmatter")
|
|
||||||
elif not doc.fm.get("type", "").strip():
|
|
||||||
findings.append(f"{doc.rel}: missing or empty `type`")
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
def check_freshness(docs, today):
|
|
||||||
findings = []
|
|
||||||
for doc in docs:
|
|
||||||
if doc.reserved or doc.fm_error:
|
|
||||||
continue
|
|
||||||
window = as_int(doc.fm.get("freshness_window_days"))
|
|
||||||
updated = as_date(doc.fm.get("last_updated"))
|
|
||||||
if window is None or updated is None:
|
|
||||||
if doc.fm and doc.fm.get("last_updated") and updated is None:
|
|
||||||
findings.append(f"{doc.rel}: `last_updated` is not a YYYY-MM-DD date: {doc.fm['last_updated']!r}")
|
|
||||||
continue
|
|
||||||
age = (today - updated).days
|
|
||||||
if age > window:
|
|
||||||
findings.append(f"{doc.rel}: stale — {age}d since last_updated, window is {window}d ({age - window}d over)")
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
def check_confidence(docs):
|
|
||||||
findings = []
|
|
||||||
for doc in docs:
|
|
||||||
if doc.reserved or doc.fm_error or "confidence" not in doc.fm:
|
|
||||||
continue
|
|
||||||
value = as_float(doc.fm["confidence"])
|
|
||||||
if value is None:
|
|
||||||
findings.append(f"{doc.rel}: `confidence` is not a number: {doc.fm['confidence']!r}")
|
|
||||||
elif value < 0.3:
|
|
||||||
findings.append(f"{doc.rel}: confidence {value} is below 0.3 — flag for re-review")
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
def check_retention(docs, today):
|
|
||||||
findings = []
|
|
||||||
for doc in docs:
|
|
||||||
if doc.reserved or doc.fm_error:
|
|
||||||
continue
|
|
||||||
if doc.fm.get("retention", "").strip().lower() != "low":
|
|
||||||
continue
|
|
||||||
window = as_int(doc.fm.get("freshness_window_days"))
|
|
||||||
updated = as_date(doc.fm.get("last_updated"))
|
|
||||||
if window is None or updated is None:
|
|
||||||
continue
|
|
||||||
age = (today - updated).days
|
|
||||||
if age > 2 * window:
|
|
||||||
findings.append(
|
|
||||||
f"{doc.rel}: archive candidate — retention: low, {age}d old, 2x window is {2 * window}d"
|
|
||||||
)
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
def check_decisions(docs, root, today):
|
|
||||||
"""Decision-record specific rules (see the ckb-decide skill)."""
|
|
||||||
findings = []
|
|
||||||
decisions = [d for d in docs if not d.reserved and not d.fm_error
|
|
||||||
and d.fm.get("type", "").strip().lower() == "decision"]
|
|
||||||
by_path = {d.rel.as_posix(): d for d in decisions}
|
|
||||||
seen_numbers = {}
|
|
||||||
|
|
||||||
for doc in sorted(decisions, key=lambda d: d.rel.as_posix()):
|
|
||||||
rel = doc.rel.as_posix()
|
|
||||||
status = doc.fm.get("status", "").strip().lower()
|
|
||||||
if not status:
|
|
||||||
findings.append(f"{rel}: decision has no `status`")
|
|
||||||
elif status not in DECISION_STATUSES:
|
|
||||||
findings.append(
|
|
||||||
f"{rel}: `status: {status}` is not one of {', '.join(sorted(DECISION_STATUSES))}"
|
|
||||||
)
|
|
||||||
|
|
||||||
decided_on = as_date(doc.fm.get("decided_on"))
|
|
||||||
if status in ("accepted", "rejected", "reversed"):
|
|
||||||
if not doc.fm.get("decided_on"):
|
|
||||||
findings.append(f"{rel}: `status: {status}` but no `decided_on` date")
|
|
||||||
elif decided_on is None:
|
|
||||||
findings.append(f"{rel}: `decided_on` is not a YYYY-MM-DD date: {doc.fm['decided_on']!r}")
|
|
||||||
if decided_on and decided_on > today:
|
|
||||||
findings.append(f"{rel}: `decided_on` is in the future: {decided_on.isoformat()}")
|
|
||||||
if not doc.fm.get("decided_by", "").strip():
|
|
||||||
findings.append(f"{rel}: no `decided_by` — record `unknown` rather than omitting it")
|
|
||||||
|
|
||||||
review = doc.fm.get("review_on")
|
|
||||||
if review:
|
|
||||||
review_date = as_date(review)
|
|
||||||
if review_date is None:
|
|
||||||
findings.append(f"{rel}: `review_on` is not a YYYY-MM-DD date: {review!r}")
|
|
||||||
elif review_date <= today:
|
|
||||||
findings.append(
|
|
||||||
f"{rel}: due for review — `review_on: {review_date.isoformat()}` "
|
|
||||||
f"passed {(today - review_date).days}d ago"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Supersession links must resolve and must be reciprocal.
|
|
||||||
for field, mirror in (("supersedes", "superseded_by"), ("superseded_by", "supersedes")):
|
|
||||||
target = doc.fm.get(field, "").strip()
|
|
||||||
if not target:
|
|
||||||
continue
|
|
||||||
key = target[len("/wiki/") :] if target.startswith("/wiki/") else target.lstrip("/")
|
|
||||||
other = by_path.get(key)
|
|
||||||
if other is None:
|
|
||||||
if not (root / key).is_file():
|
|
||||||
findings.append(f"{rel}: `{field}: {target}` does not resolve to a page")
|
|
||||||
continue
|
|
||||||
back = other.fm.get(mirror, "").strip()
|
|
||||||
back_key = back[len("/wiki/") :] if back.startswith("/wiki/") else back.lstrip("/")
|
|
||||||
if back_key != rel:
|
|
||||||
findings.append(
|
|
||||||
f"{rel}: `{field}` points at {key}, but that page's `{mirror}` "
|
|
||||||
f"does not point back ({back or 'unset'}) — supersession must be two-sided"
|
|
||||||
)
|
|
||||||
|
|
||||||
if status in ("superseded", "reversed") and not doc.fm.get("superseded_by", "").strip():
|
|
||||||
findings.append(f"{rel}: `status: {status}` but no `superseded_by` naming what replaced it")
|
|
||||||
if doc.fm.get("superseded_by", "").strip() and status not in ("superseded", "reversed"):
|
|
||||||
findings.append(
|
|
||||||
f"{rel}: has `superseded_by` but `status: {status or 'unset'}` — "
|
|
||||||
"expected `superseded` or `reversed`"
|
|
||||||
)
|
|
||||||
|
|
||||||
for target in [t.strip() for t in doc.fm.get("affects", "").split(",") if t.strip()]:
|
|
||||||
key = target[len("/wiki/") :] if target.startswith("/wiki/") else target.lstrip("/")
|
|
||||||
if not (root / key).is_file():
|
|
||||||
findings.append(f"{rel}: `affects` entry does not resolve to a page: {target}")
|
|
||||||
|
|
||||||
m = re.match(r"^(\d{4})-", doc.rel.name)
|
|
||||||
if m:
|
|
||||||
seen_numbers.setdefault(m.group(1), []).append(rel)
|
|
||||||
else:
|
|
||||||
findings.append(f"{rel}: decision filename does not start with a four-digit number")
|
|
||||||
|
|
||||||
for number, paths in sorted(seen_numbers.items()):
|
|
||||||
if len(paths) > 1:
|
|
||||||
findings.append(f"decision number {number} used by more than one page: {', '.join(sorted(paths))}")
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
def check_orphans(docs):
|
|
||||||
inbound = set()
|
|
||||||
titles = {}
|
|
||||||
for doc in docs:
|
|
||||||
rel = doc.rel.as_posix()
|
|
||||||
titles[rel] = rel
|
|
||||||
stem = doc.rel.with_suffix("").as_posix()
|
|
||||||
titles[stem] = rel
|
|
||||||
for doc in docs:
|
|
||||||
for target in doc.links():
|
|
||||||
if target in titles:
|
|
||||||
inbound.add(titles[target])
|
|
||||||
for wl in doc.wikilinks():
|
|
||||||
key = wl[:-3] if wl.endswith(".md") else wl
|
|
||||||
if key in titles:
|
|
||||||
inbound.add(titles[key])
|
|
||||||
findings = []
|
|
||||||
for doc in docs:
|
|
||||||
rel = doc.rel.as_posix()
|
|
||||||
if doc.reserved or rel in inbound:
|
|
||||||
continue
|
|
||||||
findings.append(f"{rel}: no inbound links from anywhere in the tree")
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
def check_graph(root, docs):
|
|
||||||
findings = []
|
|
||||||
edges_path = root / "graph" / "edges.json"
|
|
||||||
if not edges_path.is_file():
|
|
||||||
return findings
|
|
||||||
try:
|
|
||||||
data = json.loads(edges_path.read_text(encoding="utf-8"))
|
|
||||||
except json.JSONDecodeError as exc:
|
|
||||||
return [f"graph/edges.json: invalid JSON — {exc}"]
|
|
||||||
edges = data.get("edges", data) if isinstance(data, dict) else data
|
|
||||||
if not isinstance(edges, list):
|
|
||||||
return ["graph/edges.json: `edges` is not a list"]
|
|
||||||
known = {doc.rel.as_posix() for doc in docs}
|
|
||||||
known |= {doc.rel.with_suffix("").as_posix() for doc in docs}
|
|
||||||
for i, edge in enumerate(edges):
|
|
||||||
if not isinstance(edge, dict):
|
|
||||||
findings.append(f"graph/edges.json: edge {i} is not an object")
|
|
||||||
continue
|
|
||||||
for side in ("from", "to"):
|
|
||||||
value = str(edge.get(side, "")).strip()
|
|
||||||
if not value:
|
|
||||||
findings.append(f"graph/edges.json: edge {i} has no `{side}`")
|
|
||||||
continue
|
|
||||||
# Edge paths are project-root-absolute per Rule C
|
|
||||||
# (`/wiki/entities/foo.md`), while `known` holds paths relative to
|
|
||||||
# wiki/. Strip the prefix the same way the decision-record check
|
|
||||||
# above already does, or every conformant edge reads as broken.
|
|
||||||
stripped = value[len("/wiki/"):] if value.startswith("/wiki/") else value.lstrip("/")
|
|
||||||
candidates = {
|
|
||||||
value,
|
|
||||||
value.removesuffix(".md"),
|
|
||||||
stripped,
|
|
||||||
stripped.removesuffix(".md"),
|
|
||||||
f"entities/{value}",
|
|
||||||
f"entities/{value}".removesuffix(".md"),
|
|
||||||
}
|
|
||||||
if not candidates & known:
|
|
||||||
findings.append(f"graph/edges.json: edge {i} `{side}: {value}` does not resolve to a page")
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
def check_index_and_logs(root, docs):
|
|
||||||
findings = []
|
|
||||||
by_dir = {}
|
|
||||||
for doc in docs:
|
|
||||||
by_dir.setdefault(doc.rel.parent, []).append(doc)
|
|
||||||
|
|
||||||
for directory, entries in sorted(by_dir.items()):
|
|
||||||
pages = [d for d in entries if not d.reserved]
|
|
||||||
index = next((d for d in entries if d.path.name == "index.md"), None)
|
|
||||||
if not pages:
|
|
||||||
continue
|
|
||||||
if index is None:
|
|
||||||
findings.append(f"{(directory / 'index.md').as_posix()}: missing — directory holds {len(pages)} page(s)")
|
|
||||||
continue
|
|
||||||
listed = {Path(t).name for t in index.links()} | {
|
|
||||||
(w if w.endswith(".md") else w + ".md").split("/")[-1] for w in index.wikilinks()
|
|
||||||
}
|
|
||||||
for page in sorted(pages, key=lambda d: d.rel.as_posix()):
|
|
||||||
if page.path.name not in listed:
|
|
||||||
findings.append(f"{index.rel.as_posix()}: does not list {page.rel.as_posix()}")
|
|
||||||
|
|
||||||
# A change must have exactly one home log (Recursive Index & Log Convention).
|
|
||||||
logs = [d for d in docs if d.path.name == "log.md"]
|
|
||||||
root_log = next((d for d in logs if d.rel == Path("log.md")), None)
|
|
||||||
if root_log is not None and len(logs) > 1:
|
|
||||||
root_entries = log_entry_keys(root_log.body)
|
|
||||||
for sub in logs:
|
|
||||||
if sub is root_log:
|
|
||||||
continue
|
|
||||||
for key in sorted(log_entry_keys(sub.body) & root_entries):
|
|
||||||
findings.append(
|
|
||||||
f"log.md and {sub.rel.as_posix()}: same change recorded in both — {key}"
|
|
||||||
)
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
LOG_HEADER_RE = re.compile(r"^##\s*\[?(\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2})?)\]?")
|
|
||||||
|
|
||||||
|
|
||||||
def log_entry_keys(body):
|
|
||||||
"""Timestamp + affected-files pairs, used to spot a change logged twice."""
|
|
||||||
keys, stamp = set(), None
|
|
||||||
for line in body.split("\n"):
|
|
||||||
m = LOG_HEADER_RE.match(line.strip())
|
|
||||||
if m:
|
|
||||||
stamp = m.group(1)
|
|
||||||
continue
|
|
||||||
f = re.match(r"^-\s*\*\*File Affected:?\*\*:?\s*(.+)$", line.strip())
|
|
||||||
if f and stamp:
|
|
||||||
keys.add(f"{stamp} {f.group(1).strip()}")
|
|
||||||
return keys
|
|
||||||
|
|
||||||
|
|
||||||
def check_sources(today):
|
|
||||||
"""source.yaml validity plus overdue-index reporting for connector libs."""
|
|
||||||
findings, connectors = [], []
|
|
||||||
if not LIBS.is_dir():
|
|
||||||
return findings, connectors
|
|
||||||
for lib in sorted(p for p in LIBS.iterdir() if p.is_dir()):
|
|
||||||
source = lib / "source.yaml"
|
|
||||||
if not source.is_file():
|
|
||||||
continue # git-copy lib — never touched by lint
|
|
||||||
connectors.append(lib)
|
|
||||||
cfg = parse_simple_yaml(source.read_text(encoding="utf-8"))
|
|
||||||
name = lib.name
|
|
||||||
for key in ("connector", "location"):
|
|
||||||
if not str(cfg.get(key, "")).strip():
|
|
||||||
findings.append(f"libs/{name}/source.yaml: `{key}` is missing or empty")
|
|
||||||
index_block = cfg.get("index")
|
|
||||||
if isinstance(index_block, dict):
|
|
||||||
for key in ("store", "location"):
|
|
||||||
if not str(index_block.get(key, "")).strip():
|
|
||||||
findings.append(f"libs/{name}/source.yaml: `index.{key}` is missing or empty")
|
|
||||||
refresh = cfg.get("refresh_interval_days")
|
|
||||||
interval = DEFAULT_REFRESH_DAYS
|
|
||||||
if refresh is not None and not isinstance(refresh, dict):
|
|
||||||
parsed = as_int(refresh)
|
|
||||||
if parsed is None or parsed <= 0:
|
|
||||||
findings.append(f"libs/{name}/source.yaml: `refresh_interval_days` is not a positive integer: {refresh!r}")
|
|
||||||
else:
|
|
||||||
interval = parsed
|
|
||||||
|
|
||||||
generated = {"index.md", "log.md", "entities", "graph", "source.yaml", "source.local.yaml"}
|
|
||||||
stray = [
|
|
||||||
p.name
|
|
||||||
for p in sorted(lib.iterdir())
|
|
||||||
if p.name not in generated and not p.name.startswith(".")
|
|
||||||
]
|
|
||||||
if stray:
|
|
||||||
findings.append(
|
|
||||||
f"libs/{name}/: has both a source.yaml and non-index content ({', '.join(stray)}) — "
|
|
||||||
"ambiguous configuration for the user to resolve"
|
|
||||||
)
|
|
||||||
|
|
||||||
newest = None
|
|
||||||
for md in lib.rglob("*.md"):
|
|
||||||
fm, _, err = parse_frontmatter(md.read_text(encoding="utf-8", errors="replace"))
|
|
||||||
if err:
|
|
||||||
continue
|
|
||||||
updated = as_date(fm.get("last_updated"))
|
|
||||||
if updated and (newest is None or updated > newest):
|
|
||||||
newest = updated
|
|
||||||
if newest is None:
|
|
||||||
findings.append(f"libs/{name}/: no generated index yet — run 'index external sources'")
|
|
||||||
else:
|
|
||||||
age = (today - newest).days
|
|
||||||
if age > interval:
|
|
||||||
findings.append(
|
|
||||||
f"libs/{name}/: index is {age}d old against a {interval}d refresh interval "
|
|
||||||
f"({age - interval}d overdue) — suggest 'index external sources'"
|
|
||||||
)
|
|
||||||
return findings, connectors
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# driver
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def sections(body):
|
|
||||||
"""Split a page body into {heading: text} for `##`-level headings."""
|
|
||||||
out, current, buf = {}, None, []
|
|
||||||
for line in body.split("\n"):
|
|
||||||
match = re.match(r"^##\s+(.+?)\s*$", line)
|
|
||||||
if match:
|
|
||||||
if current is not None:
|
|
||||||
out[current] = "\n".join(buf)
|
|
||||||
current, buf = match.group(1).strip(), []
|
|
||||||
elif current is not None:
|
|
||||||
buf.append(line)
|
|
||||||
if current is not None:
|
|
||||||
out[current] = "\n".join(buf)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def digest(path):
|
|
||||||
"""First 8 hex chars of the file's sha256, or None if unreadable."""
|
|
||||||
try:
|
|
||||||
return hashlib.sha256(path.read_bytes()).hexdigest()[:8]
|
|
||||||
except OSError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def source_bullets(body):
|
|
||||||
"""[(cited path, kind, value)] from a page's `## Sources` section."""
|
|
||||||
section = sections(body).get("Sources")
|
|
||||||
if section is None:
|
|
||||||
return None
|
|
||||||
out = []
|
|
||||||
for cited, rest in SOURCE_BULLET_RE.findall(section):
|
|
||||||
fp = FINGERPRINT_RE.search(rest)
|
|
||||||
out.append((cited.strip(), fp.group(1) if fp else None, fp.group(2) if fp else None))
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def check_evidence(docs, root):
|
|
||||||
"""Check 12 — `## Sources` fingerprints against the files they cite."""
|
|
||||||
findings = []
|
|
||||||
for doc in docs:
|
|
||||||
if doc.reserved or doc.fm_error:
|
|
||||||
continue
|
|
||||||
bullets = source_bullets(doc.body)
|
|
||||||
if bullets is None:
|
|
||||||
continue
|
|
||||||
if not bullets:
|
|
||||||
findings.append(f"{doc.rel}: `## Sources` section is empty")
|
|
||||||
continue
|
|
||||||
for cited, kind, value in bullets:
|
|
||||||
if re.match(r"^[a-z][a-z0-9+.-]*://", cited):
|
|
||||||
continue # a URL: nothing local to hash
|
|
||||||
target = (REPO_ROOT / cited.lstrip("/")).resolve()
|
|
||||||
if not target.is_file():
|
|
||||||
target = (root / cited.lstrip("/")).resolve()
|
|
||||||
if not target.is_file():
|
|
||||||
findings.append(f"{doc.rel}: cited source `{cited}` does not exist")
|
|
||||||
continue
|
|
||||||
if kind is None:
|
|
||||||
findings.append(f"{doc.rel}: cited source `{cited}` has no fingerprint recorded")
|
|
||||||
continue
|
|
||||||
if kind != "sha256":
|
|
||||||
continue # etag/mtime come from a connector; nothing local to recompute
|
|
||||||
actual = digest(target)
|
|
||||||
if actual is None:
|
|
||||||
findings.append(f"{doc.rel}: cited source `{cited}` could not be read")
|
|
||||||
elif not value.lower().startswith(actual):
|
|
||||||
findings.append(
|
|
||||||
f"{doc.rel}: source `{cited}` CHANGED since this page was built "
|
|
||||||
f"(recorded sha256:{value}, now sha256:{actual})"
|
|
||||||
)
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
def check_crux(docs, root):
|
|
||||||
"""Check 13 — `## Crux` quotes are verbatim, attributed, and non-empty."""
|
|
||||||
findings = []
|
|
||||||
for doc in docs:
|
|
||||||
if doc.reserved or doc.fm_error:
|
|
||||||
continue
|
|
||||||
secs = sections(doc.body)
|
|
||||||
crux = secs.get("Crux")
|
|
||||||
if crux is None:
|
|
||||||
continue
|
|
||||||
quotes = [line[1:].strip() for line in crux.split("\n") if line.startswith(">")]
|
|
||||||
quoted = " ".join(q for q in quotes if q)
|
|
||||||
if not quoted:
|
|
||||||
findings.append(f"{doc.rel}: `## Crux` has no quoted lines (evidence sections must quote, not paraphrase)")
|
|
||||||
continue
|
|
||||||
if "Sources" not in secs:
|
|
||||||
findings.append(f"{doc.rel}: `## Crux` present but the page has no `## Sources` to attribute it to")
|
|
||||||
cited = ATTRIBUTION_RE.findall(crux)
|
|
||||||
if not cited:
|
|
||||||
findings.append(f"{doc.rel}: `## Crux` quote is not attributed to a source")
|
|
||||||
continue
|
|
||||||
for ref in cited:
|
|
||||||
ref = ref.strip().lstrip("/")
|
|
||||||
target = (REPO_ROOT / ref).resolve()
|
|
||||||
if not target.is_file():
|
|
||||||
target = (root / ref).resolve()
|
|
||||||
if not target.is_file() or target.suffix.lower() not in QUOTABLE_SUFFIXES:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
haystack = " ".join(target.read_text(encoding="utf-8", errors="replace").split())
|
|
||||||
except OSError:
|
|
||||||
continue
|
|
||||||
for quote in quotes:
|
|
||||||
if not quote or len(quote) < 24:
|
|
||||||
continue # too short to match meaningfully
|
|
||||||
needle = " ".join(quote.split())
|
|
||||||
if needle not in haystack:
|
|
||||||
findings.append(
|
|
||||||
f"{doc.rel}: `## Crux` quote not found verbatim in `{ref}` — "
|
|
||||||
f"{needle[:60]!r}..."
|
|
||||||
)
|
|
||||||
return findings
|
|
||||||
|
|
||||||
|
|
||||||
CHECKS = [
|
|
||||||
("1 conformance", "conformance"),
|
|
||||||
("2 freshness", "freshness"),
|
|
||||||
("3 confidence", "confidence"),
|
|
||||||
("4 retention", "retention"),
|
|
||||||
("5 decisions", "decisions"),
|
|
||||||
("6 orphans", "orphans"),
|
|
||||||
("7 graph", "graph"),
|
|
||||||
("8 index/log", "index_log"),
|
|
||||||
("12 source fingerprints", "evidence"),
|
|
||||||
("13 crux", "crux"),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The subset worth running at session start (Rule E): cheap, and each finding
|
|
||||||
# means something has actually changed rather than merely aged on a calendar.
|
|
||||||
QUICK_CHECKS = [("conformance", "malformed"), ("freshness", "past freshness window"),
|
|
||||||
("evidence", "source drift"), ("crux", "crux quotes diverged")]
|
|
||||||
|
|
||||||
|
|
||||||
def run_tree(root, today, label):
|
|
||||||
docs = read_tree(root)
|
|
||||||
return {
|
|
||||||
"label": label,
|
|
||||||
"documents": len(docs),
|
|
||||||
"conformance": check_conformance(docs),
|
|
||||||
"freshness": check_freshness(docs, today),
|
|
||||||
"confidence": check_confidence(docs),
|
|
||||||
"retention": check_retention(docs, today),
|
|
||||||
"decisions": check_decisions(docs, root, today),
|
|
||||||
"orphans": check_orphans(docs),
|
|
||||||
"graph": check_graph(root, docs),
|
|
||||||
"index_log": check_index_and_logs(root, docs),
|
|
||||||
"evidence": check_evidence(docs, root),
|
|
||||||
"crux": check_crux(docs, root),
|
|
||||||
"hubs": in_degree(root, docs),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def in_degree(root, docs):
|
|
||||||
"""Top pages by inbound edge count — not a finding, a retrieval signal.
|
|
||||||
|
|
||||||
`ckb-retrieve` fuses in-degree as one ranked list among several; surfacing
|
|
||||||
it here means the same number comes from one place rather than being
|
|
||||||
recomputed by eye at query time.
|
|
||||||
"""
|
|
||||||
edges_path = root / "graph" / "edges.json"
|
|
||||||
if not edges_path.is_file():
|
|
||||||
return []
|
|
||||||
try:
|
|
||||||
data = json.loads(edges_path.read_text(encoding="utf-8"))
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
return []
|
|
||||||
edges = data.get("edges", data) if isinstance(data, dict) else data
|
|
||||||
if not isinstance(edges, list):
|
|
||||||
return []
|
|
||||||
counts = {}
|
|
||||||
for edge in edges:
|
|
||||||
if not isinstance(edge, dict):
|
|
||||||
continue
|
|
||||||
target = str(edge.get("to", "")).strip()
|
|
||||||
if target:
|
|
||||||
counts[target] = counts.get(target, 0) + 1
|
|
||||||
return sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:10]
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description="Read-only mechanical checks for ckb-lint.")
|
|
||||||
parser.add_argument("--scope", choices=("wiki", "libs", "all"), default="all")
|
|
||||||
parser.add_argument("--today", help="override today's date (YYYY-MM-DD) for reproducible runs")
|
|
||||||
parser.add_argument("--json", action="store_true", help="emit machine-readable JSON instead of text")
|
|
||||||
parser.add_argument("--quick", action="store_true",
|
|
||||||
help="one-line session-start summary (Rule E): conformance, freshness, and source drift only")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
today = as_date(args.today) if args.today else date.today()
|
|
||||||
if today is None:
|
|
||||||
print(f"error: --today is not a YYYY-MM-DD date: {args.today!r}", file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
trees, source_findings = [], []
|
|
||||||
if args.scope in ("wiki", "all"):
|
|
||||||
if not WIKI.is_dir():
|
|
||||||
print(f"error: no wiki/ directory under {REPO_ROOT} — run from the repository root", file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
trees.append(run_tree(WIKI, today, "wiki/"))
|
|
||||||
if args.scope in ("libs", "all"):
|
|
||||||
source_findings, connectors = check_sources(today)
|
|
||||||
for lib in connectors:
|
|
||||||
access = "read-only"
|
|
||||||
local = lib / "source.local.yaml"
|
|
||||||
if local.is_file():
|
|
||||||
cfg = parse_simple_yaml(local.read_text(encoding="utf-8"))
|
|
||||||
if str(cfg.get("access", "")).strip().lower() == "write":
|
|
||||||
access = "write"
|
|
||||||
tree = run_tree(lib, today, f"libs/{lib.name}/ (access: {access})")
|
|
||||||
tree["access"] = access
|
|
||||||
trees.append(tree)
|
|
||||||
|
|
||||||
total = sum(len(t[key]) for t in trees for _, key in CHECKS) + len(source_findings)
|
|
||||||
|
|
||||||
if args.quick:
|
|
||||||
counts = {key: sum(len(t[key]) for t in trees) for key, _ in QUICK_CHECKS}
|
|
||||||
flagged = [f"{counts[key]} {label}" for key, label in QUICK_CHECKS if counts[key]]
|
|
||||||
if not flagged:
|
|
||||||
print("ckb check: clean")
|
|
||||||
return 0
|
|
||||||
print("ckb check: " + ", ".join(flagged) + " — run \"lint\" for detail")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if args.json:
|
|
||||||
print(json.dumps({"today": today.isoformat(), "trees": trees, "sources": source_findings, "findings": total}, indent=2))
|
|
||||||
return 1 if total else 0
|
|
||||||
|
|
||||||
for tree in trees:
|
|
||||||
print(f"=== {tree['label']} — {tree['documents']} markdown file(s)")
|
|
||||||
for title, key in CHECKS:
|
|
||||||
items = tree[key]
|
|
||||||
if not items:
|
|
||||||
continue
|
|
||||||
print(f" [{title}] {len(items)} finding(s)")
|
|
||||||
for item in items:
|
|
||||||
print(f" - {item}")
|
|
||||||
if not any(tree[key] for _, key in CHECKS):
|
|
||||||
print(" clean")
|
|
||||||
if tree["hubs"]:
|
|
||||||
top = ", ".join(f"{name} ({n})" for name, n in tree["hubs"][:5])
|
|
||||||
print(f" [in-degree] most-referenced pages: {top}")
|
|
||||||
if args.scope in ("libs", "all"):
|
|
||||||
print("=== external sources (libs/*/source.yaml)")
|
|
||||||
if source_findings:
|
|
||||||
for item in source_findings:
|
|
||||||
print(f" - {item}")
|
|
||||||
else:
|
|
||||||
print(" clean")
|
|
||||||
print(f"total findings: {total}")
|
|
||||||
print("checks NOT covered here (model's job): whether two pages genuinely supersede "
|
|
||||||
"each other, 9 error-book, auto-fix vs. report")
|
|
||||||
print("note: a changed source fingerprint (check 12) or a diverged crux quote (check 13) "
|
|
||||||
"means the evidence moved, not that a page merely aged — read those first")
|
|
||||||
return 1 if total else 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
---
|
|
||||||
name: ckb-module
|
|
||||||
description: Install, list, or uninstall an optional Cascade KB module from .agents/modules/<name>/ — copying its skills into .agents/skills/, creating its scaffold directories, appending its routing block to AGENTS.md and its rules to .gitignore, and recording it in the root ckb.yaml manifest. Use when the user says "install the software module", "add the <name> module", "what modules are available", "which modules are installed", "uninstall the <name> module", or asks to make this KB handle source code / a capability the base KB doesn't have. Distinct from `ckb-init` (bootstraps a whole new KB) and `ckb-upgrade` (catches the template layer up with upstream); this skill toggles optional capabilities within an existing KB.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Module skill
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
The base KB ships every skill always-loaded. Some capabilities are only useful to
|
|
||||||
*some* knowledge bases, and their skill descriptions would otherwise sit in
|
|
||||||
context for every KB that will never need them.
|
|
||||||
|
|
||||||
Optional capabilities therefore live in `.agents/modules/<name>/` — present in the
|
|
||||||
repo, inert until installed. This skill is the install/uninstall path. See
|
|
||||||
[D-0001](/wiki/decisions/0001-opt-in-file-based-kb-modules.md) for why this is
|
|
||||||
files-in-the-repo rather than a native agent plugin: a module is a property of the
|
|
||||||
knowledge base, so it must travel with a clone and work for any agent that reads
|
|
||||||
`AGENTS.md`.
|
|
||||||
|
|
||||||
## Trigger phrases
|
|
||||||
|
|
||||||
- "install the `<name>` module" / "add the `<name>` module" / "enable `<name>`"
|
|
||||||
- "what modules are available" / "which modules are installed"
|
|
||||||
- "uninstall the `<name>` module" / "remove `<name>`" / "disable `<name>`"
|
|
||||||
- "make this KB handle source code" (→ offer the `software` module)
|
|
||||||
|
|
||||||
## The manifest
|
|
||||||
|
|
||||||
Installed modules are recorded in `ckb.yaml` at the repo root:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
template: # written by ckb-init, read and updated by ckb-upgrade
|
|
||||||
repo: https://git.wierzbowa.cloud/michal/ckb.git
|
|
||||||
branch: main # main | test | experimental, or any branch of a fork
|
|
||||||
|
|
||||||
kb_modules:
|
|
||||||
- name: software
|
|
||||||
version: 1.0.0
|
|
||||||
installed_on: 2026-09-20
|
|
||||||
config:
|
|
||||||
src_repos: []
|
|
||||||
```
|
|
||||||
|
|
||||||
`ckb.yaml` holds repo-level configuration — what's installed, what it's
|
|
||||||
configured with. It is **not** the wiki's content contract: `kb_schema_version`
|
|
||||||
stays in `wiki/index.md` where it has always been. Two different things, two
|
|
||||||
different homes.
|
|
||||||
|
|
||||||
The `template:` block records which upstream repo and **branch** this KB takes
|
|
||||||
its tooling from. It belongs to `ckb-init` (writes it) and `ckb-upgrade` (reads
|
|
||||||
it as the default source, updates it when the user switches channel). This
|
|
||||||
skill must **never** drop or rewrite it: a module install that silently reset a
|
|
||||||
KB from the `experimental` channel back to `main` would change what its next
|
|
||||||
upgrade pulls, which is not a module's business. Read `ckb.yaml`, edit the
|
|
||||||
`kb_modules:` list, write it back with everything else intact.
|
|
||||||
|
|
||||||
If `ckb.yaml` doesn't exist, no modules are installed and no template source is
|
|
||||||
recorded; create it on first install, carrying forward any `template:` block
|
|
||||||
that a later `ckb-upgrade` may add.
|
|
||||||
|
|
||||||
## Installing
|
|
||||||
|
|
||||||
### Step 1 — Resolve and read the module
|
|
||||||
|
|
||||||
Find `.agents/modules/<name>/module.yaml`. If the name doesn't match, list what's
|
|
||||||
actually in `.agents/modules/` rather than guessing at a near-miss.
|
|
||||||
|
|
||||||
Read the manifest: `skills`, `scaffold`, `agents_fragment`, `gitignore_fragment`,
|
|
||||||
`schema_additions`, `depends_on`, `requires_kb_schema`.
|
|
||||||
|
|
||||||
### Step 2 — Check preconditions, and report them before writing anything
|
|
||||||
|
|
||||||
- **Already installed?** If `ckb.yaml` lists it, say so and offer to reinstall
|
|
||||||
(refresh the files) or stop. Don't silently re-copy.
|
|
||||||
- **Schema.** If `requires_kb_schema` is above `wiki/index.md`'s
|
|
||||||
`kb_schema_version`, the bump is part of this install (Step 4) — additive only.
|
|
||||||
If the module needs a *major* version above the KB's, stop and hand it to
|
|
||||||
`ckb-upgrade`.
|
|
||||||
- **Dependencies.** Run each `check`. A failing **optional** dependency is a
|
|
||||||
warning, not a blocker — report it plainly and continue. A failing required one
|
|
||||||
stops the install.
|
|
||||||
- **Collisions.** If a skill name in `skills:` already exists in
|
|
||||||
`.agents/skills/`, or a scaffold path exists with unrelated content, stop and
|
|
||||||
ask. Never overwrite a skill the user may have edited.
|
|
||||||
|
|
||||||
Report all findings **before** the first write, then proceed (or ask, if anything
|
|
||||||
needs a decision). A half-installed module is worse than an uninstalled one.
|
|
||||||
|
|
||||||
### Step 3 — Copy the payload
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp -r .agents/modules/<name>/skills/<skill> .agents/skills/<skill>
|
|
||||||
```
|
|
||||||
|
|
||||||
`.claude/skills` is a symlink to `.agents/skills` (git mode `120000`), so the
|
|
||||||
skills appear under `.claude/` automatically. **Do not create a second copy
|
|
||||||
there** — that symlink is a deliberate choice, and duplicating it creates two
|
|
||||||
things to keep in sync.
|
|
||||||
|
|
||||||
Create each `scaffold` path, copying any `from` template to its `as` destination.
|
|
||||||
Never overwrite an existing scaffold file — skip it and note the skip.
|
|
||||||
|
|
||||||
### Step 4 — Patch the shared files
|
|
||||||
|
|
||||||
Both patches are wrapped in markers so uninstall is exact:
|
|
||||||
|
|
||||||
```
|
|
||||||
<!-- ckb-module:<name> START -->
|
|
||||||
...fragment...
|
|
||||||
<!-- ckb-module:<name> END -->
|
|
||||||
```
|
|
||||||
|
|
||||||
- **`AGENTS.md`** — append `agents_fragment` at the end of the file, inside
|
|
||||||
markers. `CLAUDE.md` is a symlink to `AGENTS.md`, so it updates for free;
|
|
||||||
never write `CLAUDE.md` directly.
|
|
||||||
- **`.gitignore`** — append `gitignore_fragment`, inside markers (`#`-commented).
|
|
||||||
- **`wiki/index.md`** — bump `kb_schema_version` to `requires_kb_schema` if the
|
|
||||||
module's additions raise it. Additive only, per `ckb-upgrade`'s versioning
|
|
||||||
policy.
|
|
||||||
|
|
||||||
### Step 5 — Record, log, report
|
|
||||||
|
|
||||||
- Add the module to `ckb.yaml` with its version and today's date.
|
|
||||||
- Log to `wiki/log.md` per Rule B **only** for the `wiki/index.md` change — the
|
|
||||||
rest is template layer, not wiki content.
|
|
||||||
- Append to `workload/YYYY-MM-DD_summary.md` per Rule D.
|
|
||||||
- Report: skills now available and what they do, directories created, files
|
|
||||||
patched, dependency warnings, schema bump if any. Then the standard reminder —
|
|
||||||
on disk, not committed.
|
|
||||||
|
|
||||||
Offer to record a decision (`ckb-decide`) if installing the module was a real
|
|
||||||
choice for this KB rather than following a prior one.
|
|
||||||
|
|
||||||
## Uninstalling
|
|
||||||
|
|
||||||
Reverse Step 3 and 4, in this order, and be conservative about data:
|
|
||||||
|
|
||||||
1. Remove `.agents/skills/<skill>` for each skill the manifest lists — **but
|
|
||||||
first diff it against `.agents/modules/<name>/skills/<skill>`.** If the
|
|
||||||
installed copy was edited, show the diff and ask before deleting. Offer to
|
|
||||||
copy the edits back into the module folder so they survive.
|
|
||||||
2. Strip the marked blocks from `AGENTS.md` and `.gitignore`.
|
|
||||||
3. Remove the module's entry from `ckb.yaml`.
|
|
||||||
|
|
||||||
**Never remove scaffold directories or any content under them.** `src/` may hold
|
|
||||||
the user's clones, `openspec/` their specs. Say explicitly what was left behind
|
|
||||||
and why, so nothing looks like an oversight.
|
|
||||||
|
|
||||||
**Never remove wiki pages the module's skills created**, and never lower
|
|
||||||
`kb_schema_version`. Those pages are knowledge; the module produced them but does
|
|
||||||
not own them, and they must stay readable without it. Say which page types
|
|
||||||
(`repository`, `component`, `spec`) will now have no skill maintaining them.
|
|
||||||
|
|
||||||
## Listing
|
|
||||||
|
|
||||||
On "what modules are available": list `.agents/modules/*/module.yaml` with each
|
|
||||||
`title` and `summary`, marking which are installed per `ckb.yaml`. Keep it short
|
|
||||||
— name, one line, installed or not.
|
|
||||||
|
|
||||||
## Edge cases
|
|
||||||
|
|
||||||
- **A module directory with no `module.yaml`** — not a module. Report it as
|
|
||||||
malformed; don't try to infer its contents from its file layout.
|
|
||||||
- **The user asks for a capability no module provides** — say so, and don't
|
|
||||||
install a near-match hoping it fits. Offer to note it in `wiki/query-gaps.md`.
|
|
||||||
- **`ckb-upgrade` brought a newer version of an installed module** — the installed
|
|
||||||
skill copies are stale. Offer to reinstall; show what changed before doing it.
|
|
||||||
- **Markers missing at uninstall** (someone hand-edited `AGENTS.md`) — don't
|
|
||||||
guess at the block's boundaries. Show the fragment and ask the user to remove
|
|
||||||
it, or point at exactly what to delete.
|
|
||||||
- **A module's skill was renamed upstream** — treat as collision (Step 2), ask.
|
|
||||||
Don't delete the old name automatically.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
|
|
||||||
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
---
|
|
||||||
name: ckb-reset
|
|
||||||
description: DESTRUCTIVE — empty this knowledge base back to a clean template, deleting every accumulated wiki page, decision record, graph edge, log entry, workload summary and (on request) raw/outputs material, then restoring the empty scaffold that `ckb-init` would create. Use when the user says "reset the wiki", "empty the wiki", "clean this KB out", "make this a clean template", "wipe the knowledge base", or wants to turn a working KB back into a distributable template. Always inventories what would be destroyed and takes explicit confirmation before deleting anything. Never touches the template layer (AGENTS.md, skills, modules, docs) or `src/`.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Reset skill
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
A KB that also serves as the template for other KBs accumulates content that
|
|
||||||
shouldn't ship with it — decisions made about this project, workload summaries,
|
|
||||||
entity pages, graph edges. This skill returns it to the shape `ckb-init` creates:
|
|
||||||
same structure, same schema, no knowledge.
|
|
||||||
|
|
||||||
It is the only skill in this KB that deletes knowledge on purpose. Everything
|
|
||||||
below is written on the assumption that **getting this wrong destroys work that
|
|
||||||
took a long time to accumulate**, and that "the user asked for it" is not enough
|
|
||||||
on its own — people ask for this in the wrong repo.
|
|
||||||
|
|
||||||
## Trigger phrases
|
|
||||||
|
|
||||||
- "reset the wiki" / "empty the wiki" / "wipe the knowledge base"
|
|
||||||
- "clean this KB out" / "make this a clean template"
|
|
||||||
- "turn this back into a template" / "strip the content but keep the structure"
|
|
||||||
|
|
||||||
## Non-negotiable safety rules
|
|
||||||
|
|
||||||
1. **Never delete without an inventory shown first and an explicit confirmation
|
|
||||||
after it.** Not "proceed?" before you know the scale — the count is the thing
|
|
||||||
that makes the answer meaningful.
|
|
||||||
2. **Never touch the template layer.** `AGENTS.md`, `CLAUDE.md`, `.agents/`,
|
|
||||||
`.claude/`, `LICENSE`, `VERSION`, `README*`, `MANUAL*`, `OPENSPEC*`,
|
|
||||||
`.gitignore`, `ckb.yaml`, `.git/`. Those *are* the template.
|
|
||||||
3. **Never touch `src/`.** Those are independent repositories holding code this
|
|
||||||
skill has no business deleting. Not even with confirmation — tell the user to
|
|
||||||
delete them with `rm -rf` themselves if that's really what they want.
|
|
||||||
4. **Never run when the working tree is dirty**, unless the user overrides after
|
|
||||||
being told why. Uncommitted work is unrecoverable once deleted; committed work
|
|
||||||
is always recoverable from git.
|
|
||||||
5. **Never guess the scope.** If the user says "reset" without saying how far,
|
|
||||||
ask — don't assume the widest or the narrowest reading.
|
|
||||||
|
|
||||||
## How to run this skill
|
|
||||||
|
|
||||||
### Step 0 — Establish that git can undo this
|
|
||||||
|
|
||||||
Run `git status --short` and `git log --oneline -1`.
|
|
||||||
|
|
||||||
- **Dirty tree** — stop and report exactly what's uncommitted. Offer to commit
|
|
||||||
first ("Let me commit this as a restore point, then reset"), and only proceed
|
|
||||||
without committing if the user explicitly says the uncommitted work is
|
|
||||||
disposable. This is the single highest-value check in the skill: after a reset,
|
|
||||||
committed content is a `git checkout` away and uncommitted content is gone.
|
|
||||||
- **Not a git repository at all** — say so plainly and require a much more
|
|
||||||
explicit confirmation, because nothing here is recoverable. Recommend the user
|
|
||||||
copy the folder somewhere safe first.
|
|
||||||
- **Clean tree** — note the commit hash and tell the user it's the restore point.
|
|
||||||
Offer to tag it (`git tag pre-reset-<date>`), since a hash in a chat log is
|
|
||||||
easy to lose.
|
|
||||||
|
|
||||||
### Step 1 — Agree the scope
|
|
||||||
|
|
||||||
Present the tiers and let the user choose. Default to **Tier 1 only** if they
|
|
||||||
just said "reset the wiki" — the narrowest reading that satisfies the request.
|
|
||||||
|
|
||||||
| Tier | Contents | Default |
|
|
||||||
|---|---|---|
|
|
||||||
| **1 — Wiki knowledge** | Every page under `wiki/entities/`, `wiki/projects/`, `wiki/decisions/` (the `NNNN-*.md` records), `wiki/graph/edges.json` entries, and all log/overview/query-gap/error-book *entries* | **Yes** |
|
|
||||||
| **2 — Workload history** | Every `workload/*_summary.md` | Ask |
|
|
||||||
| **3 — Source material** | `raw/inbox/*`, `raw/archive/*` | Ask — this is the user's original material and may exist nowhere else |
|
|
||||||
| **4 — Generated artifacts** | `outputs/*` | Ask — cheap to regenerate, usually safe |
|
|
||||||
| **5 — External sources** | `libs/*`, `linked/*` | Ask — **default no.** `linked/` holds symlinks to *other people's* KBs, and deleting a symlink target by accident is a real risk |
|
|
||||||
| **6 — Installed modules** | `ckb.yaml` + module-installed skills + the marked blocks in `AGENTS.md`/`.gitignore` | Ask — **default no.** That's `ckb-module uninstall`'s job, not this skill's |
|
|
||||||
|
|
||||||
For a template being prepared for distribution, tiers 1–4 are the usual answer.
|
|
||||||
|
|
||||||
**Say what each tier means in files, not just in names.** "Tier 3 deletes 48
|
|
||||||
files in `raw/archive/` including the original meeting recordings" is a sentence
|
|
||||||
that changes answers.
|
|
||||||
|
|
||||||
### Step 2 — Build and show the inventory
|
|
||||||
|
|
||||||
Count, don't estimate. For the chosen tiers:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
find wiki/entities wiki/projects -name '*.md' ! -name 'index.md' | wc -l
|
|
||||||
ls wiki/decisions/[0-9]*.md 2>/dev/null | wc -l
|
|
||||||
python3 -c "import json;print(len(json.load(open('wiki/graph/edges.json'))['edges']))"
|
|
||||||
ls workload/*_summary.md 2>/dev/null | wc -l
|
|
||||||
find raw/inbox raw/archive -type f 2>/dev/null | wc -l
|
|
||||||
```
|
|
||||||
|
|
||||||
Present it as a table: what, how many, and — for anything irreplaceable — where
|
|
||||||
it came from. Call out explicitly:
|
|
||||||
|
|
||||||
- **Decision records**, by number and title. These are append-only by design and
|
|
||||||
represent reasoning that cannot be reconstructed; losing them is the most
|
|
||||||
expensive part of any reset.
|
|
||||||
- **`raw/archive/` material with no upstream**, if tier 3 is selected.
|
|
||||||
- **Anything with `retention: high`** in its frontmatter — the wiki's own signal
|
|
||||||
that it was meant to be kept.
|
|
||||||
|
|
||||||
Then state what will *survive*, in one line. That's as reassuring as the deletion
|
|
||||||
list is alarming, and it's what tells the user whether you understood the task.
|
|
||||||
|
|
||||||
### Step 3 — Take the confirmation
|
|
||||||
|
|
||||||
Ask for a typed phrase, not a yes:
|
|
||||||
|
|
||||||
> This will permanently delete **47 wiki pages, 12 decision records, 9 workload
|
|
||||||
> summaries and 128 graph edges**. Restore point: `491ca8a` (clean tree).
|
|
||||||
> Type **`reset the wiki`** to proceed, or anything else to cancel.
|
|
||||||
|
|
||||||
Accept only that phrase. A "yes", a "go ahead", or silence is **not** consent
|
|
||||||
here — the typed phrase exists precisely so the action can't be taken by reflex
|
|
||||||
or by an agent misreading agreement to something else in the conversation.
|
|
||||||
|
|
||||||
If the user modifies the scope in their reply, go back to Step 2 and re-inventory.
|
|
||||||
Never carry a confirmation across a scope change: they confirmed a number, and
|
|
||||||
the number moved.
|
|
||||||
|
|
||||||
### Step 4 — Delete, then restore the scaffold
|
|
||||||
|
|
||||||
Delete the agreed tiers. Then restore the empty scaffold **exactly as `ckb-init`
|
|
||||||
Step 8 defines it** — that skill is the single source of truth for what a clean
|
|
||||||
`wiki/` looks like, so read it rather than reconstructing the files from memory:
|
|
||||||
|
|
||||||
- `wiki/index.md` — routing table, infrastructure rows only, no entity rows.
|
|
||||||
**Keep `kb_schema_version` at its current value.** A reset empties content; it
|
|
||||||
does not roll back the schema contract.
|
|
||||||
- `wiki/overview.md` — the generic version, `last_updated` today.
|
|
||||||
- `wiki/log.md`, `wiki/decisions/log.md` — headers and explanation, no entries.
|
|
||||||
- `wiki/error-book.md`, `wiki/query-gaps.md` — headers, no entries.
|
|
||||||
- `wiki/entities/index.md`, `wiki/projects/index.md` — headers plus
|
|
||||||
placeholder notes.
|
|
||||||
- `wiki/graph/index.md` — header, placeholder, and the edge-vocabulary table
|
|
||||||
and conventions **verbatim**; that table is the contract every writing
|
|
||||||
skill validates against, not accumulated content, so a reset keeps it.
|
|
||||||
- `wiki/decisions/index.md` — header, placeholder, and the status-vocabulary
|
|
||||||
table **verbatim** (`ckb-decide` and `ckb-lint` both validate against it).
|
|
||||||
- `wiki/graph/edges.json` — `{"version": 1, "last_updated": "<today>", "edges": []}`.
|
|
||||||
- `.gitadd` placeholder files in any directory that would otherwise be empty, so
|
|
||||||
the structure survives in git.
|
|
||||||
|
|
||||||
Preserve directories even when empty. A reset KB must have the same shape as a
|
|
||||||
fresh one, or the next ingest starts by rebuilding scaffolding.
|
|
||||||
|
|
||||||
### Step 5 — Verify before reporting success
|
|
||||||
|
|
||||||
Do not report completion until you've checked it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ls wiki/entities/ wiki/decisions/ wiki/projects/
|
|
||||||
python3 .agents/skills/ckb-lint/scripts/lint_report.py
|
|
||||||
git status --short
|
|
||||||
```
|
|
||||||
|
|
||||||
Lint should come back clean on a fresh scaffold. If it doesn't, the scaffold is
|
|
||||||
wrong — say so and fix it rather than declaring done. A reset that leaves a
|
|
||||||
malformed wiki is worse than no reset, because the damage is already unrecoverable
|
|
||||||
and now the structure is broken too.
|
|
||||||
|
|
||||||
### Step 6 — Report
|
|
||||||
|
|
||||||
State: tiers reset, counts deleted per tier, scaffold files restored, the restore
|
|
||||||
point hash (and tag, if one was made), lint result, and what was deliberately left
|
|
||||||
alone (`src/`, modules, template layer).
|
|
||||||
|
|
||||||
**Do not append a workload summary.** Rule D's session note would be the first
|
|
||||||
entry in a workload directory this skill just emptied, which defeats the purpose.
|
|
||||||
Say so in the report instead — this is a deliberate, one-off exception to Rule D,
|
|
||||||
and worth naming so it doesn't read as an oversight.
|
|
||||||
|
|
||||||
Finish with: the reset is on disk but not committed. `git checkout .` still undoes
|
|
||||||
everything until it is. That sentence is the last safety net, so don't omit it.
|
|
||||||
|
|
||||||
## Edge cases
|
|
||||||
|
|
||||||
- **Already clean** — say so and change nothing. Don't rewrite identical scaffold
|
|
||||||
files to look busy.
|
|
||||||
- **Run in a KB that is not a template** — this skill can't tell the difference,
|
|
||||||
so if the wiki holds substantial content (say, 50+ pages or any decision
|
|
||||||
records), name that in the confirmation: "This KB has 12 decision records —
|
|
||||||
templates don't usually have those. Are you in the right repository?" Ask once;
|
|
||||||
don't refuse if they confirm.
|
|
||||||
- **`linked/` symlinks** — never follow them when deleting. `rm -rf linked/foo`
|
|
||||||
where `foo` is a symlink to another KB is catastrophic and silent. Remove the
|
|
||||||
*link*, never its target, and prefer leaving tier 5 alone.
|
|
||||||
- **Partial failure mid-delete** — stop, report exactly what was and wasn't
|
|
||||||
deleted, and point at the restore point. Don't continue on the theory that
|
|
||||||
finishing is tidier.
|
|
||||||
- **User asks to reset "everything including the skills"** — that's not a reset,
|
|
||||||
it's deleting the KB. Say so and point at `ckb-init` for a fresh one elsewhere.
|
|
||||||
- **Module installed** — module-created page types (`repository`, `component`,
|
|
||||||
`spec`) are wiki content and reset with tier 1. The module itself stays
|
|
||||||
installed unless tier 6 was chosen.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Licensed under the Apache License, Version 2.0 — see [LICENSE](../../../LICENSE)
|
|
||||||
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*
|
|
||||||
|
|
@ -1,336 +0,0 @@
|
||||||
---
|
|
||||||
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. `wiki/decisions/index.md` — for any "why is it like this", "who decided",
|
|
||||||
"when did we choose", or "is that still current" question. If decisions
|
|
||||||
*are* the subject of the question, hand off to `ckb-decide`, which owns
|
|
||||||
the decisions-scoped path including supersession chains; pull decision
|
|
||||||
pages in here as one source among many when they're only part of a wider
|
|
||||||
answer. Either way, a superseded decision is history, not noise — say so
|
|
||||||
rather than silently answering from the current one.
|
|
||||||
5. 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.
|
|
||||||
|
|
||||||
While the edge file is open, build one more ranked list for Step 4:
|
|
||||||
**in-degree** — for each candidate already on the shortlist, how many edges
|
|
||||||
point *at* it.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 -c "import json,collections;e=json.load(open('wiki/graph/edges.json'))['edges'];\
|
|
||||||
c=collections.Counter(x['to'] for x in e);print(c.most_common(20))"
|
|
||||||
```
|
|
||||||
|
|
||||||
Centrality is a genuinely different signal from every other one in Step 4:
|
|
||||||
text match, freshness, confidence and quality are all properties of a page in
|
|
||||||
isolation, while in-degree is what the rest of the KB has to say about it. A
|
|
||||||
page a dozen others depend on is usually the one that settles a question;
|
|
||||||
a leaf page with an identical `tldr` usually isn't. Rank only the candidates
|
|
||||||
already shortlisted — this ranks the shortlist, it doesn't widen it. Skip the
|
|
||||||
list entirely when `edges.json` is empty or every candidate has in-degree 0,
|
|
||||||
since a list with no ordering contributes nothing to the fusion.
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
Give the in-degree list a weight *below* 1.0 (around 0.5 is a reasonable
|
|
||||||
start). Centrality is a prior, not evidence — it says a page matters
|
|
||||||
generally, not that it answers this question — so it should break ties
|
|
||||||
between comparable candidates without ever promoting a page the text signals
|
|
||||||
didn't surface.
|
|
||||||
|
|
||||||
`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 0–10 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 with a `## Crux` covering the exact claim** — the Crux
|
|
||||||
is verbatim source text, not synthesis, so it is already the evidence
|
|
||||||
this step exists to fetch. Confirm the page's recorded fingerprint still
|
|
||||||
matches the cited source (cheap: `sha256sum <source> | cut -c1-8` against
|
|
||||||
the digest in the `## Sources` bullet). If it matches, quote the Crux and
|
|
||||||
answer — one hash beats one full file read, and citing a quote is strictly
|
|
||||||
stronger than citing a summary. If it **differs**, the source moved under
|
|
||||||
the page: open the source, answer from it, and say the page's Crux is now
|
|
||||||
stale — that's a real `ckb-lint` finding, not a formality. If the page has
|
|
||||||
no fingerprint recorded at all, treat the Crux as unverified and fall
|
|
||||||
through to the full read below.
|
|
||||||
|
|
||||||
This shortcut is narrow on purpose. It applies only when the quote covers
|
|
||||||
the specific claim being used — not when it is merely on-topic, and never
|
|
||||||
as a reason to skip reading a source that would qualify or contradict it.
|
|
||||||
- **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.
|
|
||||||
- **A page's `## Crux` quote is no longer found in the source it cites** —
|
|
||||||
the fingerprint check will usually catch this first, but where it doesn't
|
|
||||||
(no fingerprint recorded, or a source edited without the page being
|
|
||||||
rebuilt), answer from the source and report the divergence. A quote that
|
|
||||||
has drifted from its source is a stronger lint finding than a stale date,
|
|
||||||
because it is a page asserting something the evidence no longer says.
|
|
||||||
- **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>.*
|
|
||||||
|
|
@ -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 ingest workflow routed by `CLAUDE.md`/`AGENTS.md` instead
|
the content-level Ingestion Workflow in `CLAUDE.md`/`AGENTS.md` §3 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,23 +63,6 @@ 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 1b — Nested source repos (`software` module only)
|
|
||||||
|
|
||||||
If `ckb.yaml` lists the `software` module and `src/` holds clones, those are
|
|
||||||
**independent repositories with their own remotes**. This skill syncs *this*
|
|
||||||
repo only.
|
|
||||||
|
|
||||||
- Never `git add` anything under `src/`, and never `git commit`, `git push`,
|
|
||||||
or `git checkout` inside one. They are gitignored; if `git status` shows
|
|
||||||
something there, treat it as a `.gitignore` defect to report, not content
|
|
||||||
to stage.
|
|
||||||
- Do report each clone's state — current branch, dirty or clean, ahead or
|
|
||||||
behind its own remote — so uncommitted work isn't silently forgotten while
|
|
||||||
the KB itself gets pushed. Reporting is the whole contribution here;
|
|
||||||
acting on it is the user's call, in that repo.
|
|
||||||
|
|
||||||
Skip this step entirely when the module isn't installed or `src/` is empty.
|
|
||||||
|
|
||||||
### Step 2 — Ensure `origin` is configured
|
### Step 2 — Ensure `origin` is configured
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
name: ckb-upgrade
|
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". Also use when they name a release channel - "upgrade from the test branch", "check experimental for updates", "switch this KB to the stable channel" - since the template repo keeps main (stable), test (release candidate) and experimental (development) branches; the branch this KB tracks is recorded in ckb.yaml's template block and defaults to main. 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.
|
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
|
# Upgrade skill
|
||||||
|
|
@ -16,8 +16,8 @@ neither:
|
||||||
|
|
||||||
- **Template layer** — the schema/tooling that defines *how* the KB
|
- **Template layer** — the schema/tooling that defines *how* the KB
|
||||||
behaves: `AGENTS.md`/`CLAUDE.md`, `.agents/skills/*`, `LICENSE`,
|
behaves: `AGENTS.md`/`CLAUDE.md`, `.agents/skills/*`, `LICENSE`,
|
||||||
`VERSION`, `README.md`/`README.pl.md`/`MANUAL.md`/`MANUAL.pl.md`/
|
`VERSION`, `README.md`/`README.pl.md`/`MANUAL.md`/`MANUAL.pl.md`, and the
|
||||||
`CHANGELOG.md`/`CHANGELOG.pl.md`, and the base `.gitignore` rules. Compared via the root `VERSION` file. Freely
|
base `.gitignore` rules. Compared via the root `VERSION` file. Freely
|
||||||
upgradable — these files hold no project-specific content.
|
upgradable — these files hold no project-specific content.
|
||||||
- **Content layer** — everything the KB has actually learned:
|
- **Content layer** — everything the KB has actually learned:
|
||||||
`wiki/entities/`, `wiki/graph/edges.json`, `wiki/index.md`'s entity rows,
|
`wiki/entities/`, `wiki/graph/edges.json`, `wiki/index.md`'s entity rows,
|
||||||
|
|
@ -36,29 +36,6 @@ the template layer, or vice versa: a KB might already have the latest
|
||||||
an older (or no) schema version, or the reverse. This skill checks and
|
an older (or no) schema version, or the reverse. This skill checks and
|
||||||
offers to fix both, separately.
|
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
|
This is different from `ckb-sync-changes` (reconciles *this* repo's own
|
||||||
commit history with *its own* `origin` remote — same content, no template
|
commit history with *its own* `origin` remote — same content, no template
|
||||||
comparison) and from `ckb-init` (bootstraps a brand-new, empty KB from the
|
comparison) and from `ckb-init` (bootstraps a brand-new, empty KB from the
|
||||||
|
|
@ -79,54 +56,19 @@ repo's own `origin`) or for setting up a brand-new KB (that's `ckb-init`).
|
||||||
|
|
||||||
## How to run this skill
|
## How to run this skill
|
||||||
|
|
||||||
### Step 0 — Resolve which repo and branch to compare against
|
### Step 1 — Fetch the canonical template for comparison
|
||||||
|
|
||||||
Read `ckb.yaml` at the repo root:
|
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:
|
||||||
```yaml
|
|
||||||
template:
|
|
||||||
repo: https://git.wierzbowa.cloud/michal/ckb.git
|
|
||||||
branch: main
|
|
||||||
```
|
|
||||||
|
|
||||||
That block records where this KB's tooling comes from (written by
|
|
||||||
`ckb-init`). Missing file, missing block, or missing field → fall back to the
|
|
||||||
canonical repo on **`main`**, which is what every KB predating this
|
|
||||||
convention was effectively tracking.
|
|
||||||
|
|
||||||
#### The three channels
|
|
||||||
|
|
||||||
| Branch | What it is | Who should be on it |
|
|
||||||
|---|---|---|
|
|
||||||
| `main` | **Stable.** The released template. | Everyone, by default. |
|
|
||||||
| `test` | **Release candidate.** Validated before merging to `main`. | Anyone helping validate a release, or needing a landed-but-unshipped fix. |
|
|
||||||
| `experimental` | **Development.** Active work; may be broken or reverted. | People developing the template itself. |
|
|
||||||
|
|
||||||
A user switches channel by saying so: "upgrade from the test branch", "check
|
|
||||||
experimental for updates", "switch this KB to the stable channel", "I want
|
|
||||||
the dev version". A branch named in the request **overrides** `ckb.yaml` for
|
|
||||||
this run, and — once the upgrade is actually applied — is written back to
|
|
||||||
`ckb.yaml` so the next run stays on the channel the user chose. Do not write
|
|
||||||
it back on a run the user cancelled: they asked to *look*, not to move.
|
|
||||||
|
|
||||||
Clone shallowly into a scratch location under `tmp/` — never touch this
|
|
||||||
repo's own git remotes or history to do this comparison:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rm -rf tmp/ckb-upgrade-src
|
rm -rf tmp/ckb-upgrade-src
|
||||||
git clone --depth 1 --branch <branch> <repo> 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
|
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
|
error and stop — this skill does not fall back to a cached or partial
|
||||||
check, and does not retry silently. If it fails specifically because the
|
check, and does not retry silently.
|
||||||
**branch doesn't exist**, list what does (`git ls-remote --heads <repo>`)
|
|
||||||
and stop; never quietly substitute `main` for a branch the user named.
|
|
||||||
|
|
||||||
State the resolved repo and branch before doing anything else. On `test` or
|
|
||||||
`experimental`, say what that means in one line — an upgrade that pulls
|
|
||||||
unreleased or actively-broken tooling into a working KB should never be a
|
|
||||||
surprise.
|
|
||||||
|
|
||||||
### Step 2 — Compare template (tooling) versions
|
### Step 2 — Compare template (tooling) versions
|
||||||
|
|
||||||
|
|
@ -148,24 +90,6 @@ 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
|
wiki content schema (Step 3) is checked independently and may still be
|
||||||
behind.
|
behind.
|
||||||
|
|
||||||
**When the local version is *higher* than the remote's, say so explicitly
|
|
||||||
rather than reporting "up to date".** The two are different facts and only
|
|
||||||
one of them is reassuring. This happens routinely once channels exist: a KB
|
|
||||||
that took tooling from `experimental` sits on a version `main` has not
|
|
||||||
released yet, so comparing it against `main` finds nothing newer — which is
|
|
||||||
true, and also not what "up to date" usually means to a reader.
|
|
||||||
|
|
||||||
Report it as what it is: "local v1.9.0 is ahead of `main`'s v1.8.0 — this KB
|
|
||||||
is carrying tooling from a pre-release channel." Then ask what they want,
|
|
||||||
because both answers are legitimate: stay ahead, or **downgrade** to the
|
|
||||||
stable branch. A downgrade is a real operation with real consequences — it
|
|
||||||
can remove skills, remove schema fields that local pages already use, and
|
|
||||||
lower `kb_schema_version` below what the content is written against — so
|
|
||||||
never perform one as a side effect of a channel switch. Name the specific
|
|
||||||
losses, take an explicit confirmation, and treat a `kb_schema_version` that
|
|
||||||
would drop below the local wiki's as a **blocker**, not a warning: the
|
|
||||||
content would stop conforming to its own declared contract.
|
|
||||||
|
|
||||||
### Step 3 — Compare the wiki content's own schema version
|
### Step 3 — Compare the wiki content's own schema version
|
||||||
|
|
||||||
This is a **separate** check from Step 2, and can find something to do
|
This is a **separate** check from Step 2, and can find something to do
|
||||||
|
|
@ -202,20 +126,10 @@ The template layer is exactly these paths — never anything under `wiki/`,
|
||||||
symlink)
|
symlink)
|
||||||
- `LICENSE`
|
- `LICENSE`
|
||||||
- `VERSION`
|
- `VERSION`
|
||||||
- `README.md`, `README.pl.md`, `MANUAL.md`, `MANUAL.pl.md`,
|
- `README.md`, `README.pl.md`, `MANUAL.md`, `MANUAL.pl.md` — whichever
|
||||||
`CHANGELOG.md`, `CHANGELOG.pl.md` — whichever exist upstream (an older
|
exist upstream (an older local KB may not have some of these yet)
|
||||||
local KB may not have some of these yet). Take the changelogs from
|
|
||||||
upstream wholesale rather than merging: they record the template's
|
|
||||||
history, which the upstream copy is authoritative about.
|
|
||||||
- `.agents/skills/<name>/**` for every skill folder present in
|
- `.agents/skills/<name>/**` for every skill folder present in
|
||||||
`tmp/ckb-upgrade-src/.agents/skills/`
|
`tmp/ckb-upgrade-src/.agents/skills/`
|
||||||
- `.agents/modules/<name>/**` for every optional module present upstream
|
|
||||||
(see `ckb-module`). Upgrade the module *payload* freely — it is inert
|
|
||||||
template content. But **never delete a skill under `.agents/skills/`
|
|
||||||
just because it is absent upstream**: it may be a module-installed copy
|
|
||||||
that this KB deliberately enabled. Check `ckb.yaml` before treating any
|
|
||||||
skill as removed upstream. When an installed module's payload changes,
|
|
||||||
say so and suggest reinstalling it — the installed copies are now stale.
|
|
||||||
- The base ignore rules in `.gitignore` (`libs/`, `linked/`, `tmp/`,
|
- The base ignore rules in `.gitignore` (`libs/`, `linked/`, `tmp/`,
|
||||||
regenerated `outputs/` subfolders, `.env`, etc.) — merged, never
|
regenerated `outputs/` subfolders, `.env`, etc.) — merged, never
|
||||||
replaced wholesale (see Step 6)
|
replaced wholesale (see Step 6)
|
||||||
|
|
@ -307,13 +221,6 @@ Only if `template_upgrade_available` and at least one item was accepted:
|
||||||
this step — `wiki/` changes, if any, happen only in Step 7.
|
this step — `wiki/` changes, if any, happen only in Step 7.
|
||||||
- Update local `VERSION` to the template's new version last, only once
|
- Update local `VERSION` to the template's new version last, only once
|
||||||
every other accepted change has been applied successfully.
|
every other accepted change has been applied successfully.
|
||||||
- If the user switched channel for this run (Step 0), write the new
|
|
||||||
`branch:` into `ckb.yaml`'s `template:` block now that the switch has
|
|
||||||
actually happened. Create the file with just that block if it doesn't
|
|
||||||
exist; if it does, edit only `template:` and leave `kb_modules:` exactly
|
|
||||||
as found — that list belongs to `ckb-module`. Persisting the branch is
|
|
||||||
what stops the *next* upgrade from silently pulling the KB back to
|
|
||||||
whichever channel it was on before.
|
|
||||||
|
|
||||||
### Step 7 — Rebuild wiki content to the new schema (only if confirmed in Step 5)
|
### Step 7 — Rebuild wiki content to the new schema (only if confirmed in Step 5)
|
||||||
|
|
||||||
|
|
@ -333,7 +240,7 @@ exists, even if it looks sparse):
|
||||||
its own `index.md` per the Recursive Index & Log Convention.
|
its own `index.md` per the Recursive Index & Log Convention.
|
||||||
|
|
||||||
**b. Backfill frontmatter on every existing page**, per the schema in
|
**b. Backfill frontmatter on every existing page**, per the schema in
|
||||||
`AGENTS.md` page schema. For each `.md` file under `wiki/` (excluding the reserved
|
`AGENTS.md` §2. For each `.md` file under `wiki/` (excluding the reserved
|
||||||
`index.md`/`log.md`/`error-book.md`), check its frontmatter against the
|
`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
|
schema and fix only what's missing — never touch a field that's already
|
||||||
present, and never alter the page's body text:
|
present, and never alter the page's body text:
|
||||||
|
|
@ -359,35 +266,6 @@ present, and never alter the page's body text:
|
||||||
vice versa) — add the missing form. Don't invent new cross-references
|
vice versa) — add the missing form. Don't invent new cross-references
|
||||||
that weren't already there in some form.
|
that weren't already there in some form.
|
||||||
|
|
||||||
**b2. Schema 1.4 → 1.5: evidence sections.** Schema 1.5 added the reserved
|
|
||||||
body sections (`## Sources`, `## Crux`, `## Notes`), the
|
|
||||||
`source_fingerprint`/`source_checked` frontmatter fields, and the completed
|
|
||||||
edge vocabulary in `wiki/graph/index.md`. Migrating a 1.4 wiki:
|
|
||||||
|
|
||||||
- **`wiki/graph/index.md`** — replace its vocabulary section with the
|
|
||||||
template's table verbatim. Existing edges stay valid; 1.5 only added
|
|
||||||
verbs (`part_of`, `produces`, `configures`, `validates`, `implements`),
|
|
||||||
it removed none.
|
|
||||||
- **`## Sources` without fingerprints** — backfill a digest for every cited
|
|
||||||
file that still exists (`sha256sum <file> | cut -c1-8`), with
|
|
||||||
`source_checked` set to today. Where the cited file is **gone**, do not
|
|
||||||
quietly drop the bullet: leave it and flag the page, because a page whose
|
|
||||||
evidence has vanished is a finding the migration surfaced, not one it
|
|
||||||
should bury.
|
|
||||||
- **`## Crux`** — do **not** manufacture one. A Crux is verbatim source
|
|
||||||
text, and inventing quotes during a migration is precisely the failure
|
|
||||||
the section exists to make impossible. Pages gain a Crux when they are
|
|
||||||
next ingested or deliberately re-grounded, and a 1.5 page with no Crux is
|
|
||||||
fully conformant.
|
|
||||||
- **`## Notes`** — add an empty one to generated pages under
|
|
||||||
`libs/<name>/` so the protected affordance exists. Elsewhere, leave it
|
|
||||||
absent.
|
|
||||||
|
|
||||||
These are additive: a 1.4 page with none of them is valid 1.5, so this step
|
|
||||||
never blocks the version bump. Backfilling fingerprints is worth doing in
|
|
||||||
the same pass anyway, since it is mechanical and it is what makes `ckb-lint`
|
|
||||||
check 12 meaningful from that point on.
|
|
||||||
|
|
||||||
**c. Bump `wiki/index.md`'s `kb_schema_version`** to the template's
|
**c. Bump `wiki/index.md`'s `kb_schema_version`** to the template's
|
||||||
current value, once every page has been checked.
|
current value, once every page has been checked.
|
||||||
|
|
||||||
|
|
@ -419,9 +297,7 @@ Report a summary covering whichever tracks actually ran:
|
||||||
```markdown
|
```markdown
|
||||||
## Upgrade report
|
## Upgrade report
|
||||||
|
|
||||||
**Source:** <repo> on branch **<branch>** (channel: stable / release candidate / development)
|
**Template version:** v<old> → v<new> (or "already current")
|
||||||
**Channel changed:** <old> → <new> (or "no")
|
|
||||||
**Template version:** v<old> → v<new> (or "already current", or "local vX is ahead of this branch's vY")
|
|
||||||
**Added:** <N> file(s) — <paths, or "none">
|
**Added:** <N> file(s) — <paths, or "none">
|
||||||
**Updated:** <N> file(s) — <paths, or "none">
|
**Updated:** <N> file(s) — <paths, or "none">
|
||||||
**Kept local (declined template version):** <N> file(s) — <paths, or "none">
|
**Kept local (declined template version):** <N> file(s) — <paths, or "none">
|
||||||
|
|
@ -440,27 +316,6 @@ other than the template project itself.
|
||||||
|
|
||||||
## Edge cases
|
## Edge cases
|
||||||
|
|
||||||
- **`ckb.yaml` exists but has no `template:` block** — a KB created before
|
|
||||||
this convention. Treat it as canonical repo on `main`, and add the block
|
|
||||||
once an upgrade is actually applied. Don't write it on a check-only run.
|
|
||||||
- **`ckb.yaml` names a branch that no longer exists upstream** (a dev branch
|
|
||||||
that was merged and deleted) — report it with the list of branches that do
|
|
||||||
exist, and ask which to move to. Don't guess: a deleted `experimental` may
|
|
||||||
mean "its work is in `main` now" or "it was abandoned", and those lead to
|
|
||||||
different answers.
|
|
||||||
- **User asks to switch channel but declines every proposed change** — the
|
|
||||||
switch didn't happen, so don't persist the new branch. They looked at
|
|
||||||
another channel and chose not to take it; recording it would make the next
|
|
||||||
run pull from a channel they rejected.
|
|
||||||
- **Local `VERSION` is ahead of the target branch** — see Step 2. Report it
|
|
||||||
as "ahead", never as "up to date", and treat any downgrade as an explicit,
|
|
||||||
separately-confirmed operation.
|
|
||||||
- **Template repo is this very repo** (the template project upgrading
|
|
||||||
itself) — the comparison is between branches of one repo rather than
|
|
||||||
between two repos. That's valid, and the usual flow is `experimental` →
|
|
||||||
`test` → `main`; but say plainly that this is a self-upgrade, since the
|
|
||||||
"local vs upstream" framing in the report reads oddly otherwise.
|
|
||||||
|
|
||||||
- **No `VERSION` file locally** — treat local version as `0.0.0`; any real
|
- **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
|
template version counts as newer. Mention in the report that this KB
|
||||||
predates version tracking.
|
predates version tracking.
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -16,5 +16,3 @@ outputs/starlight
|
||||||
outputs/okf
|
outputs/okf
|
||||||
outputs/teaching
|
outputs/teaching
|
||||||
.env
|
.env
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
|
||||||
369
AGENTS.md
369
AGENTS.md
|
|
@ -1,256 +1,209 @@
|
||||||
# SYSTEM PROMPT: CASCADE KNOWLEDGE BASE ARCHITECT
|
# SYSTEM PROMPT: CASCADE KNOWLEDGE BASE ARCHITECT
|
||||||
|
|
||||||
## ROLE
|
## ROLE & PHILOSOPHY
|
||||||
You are an autonomous Knowledge Architect Agent for a Cascade Knowledge Base.
|
You are an autonomous Knowledge Architect Agent for a **Cascade Knowledge Base**. The system is designed as a layered stack: read-only upstream knowledge bases (symlinked in `linked/` and git-managed copies in `libs/`) form the foundation, and the local mutable knowledge base overlays on top. This means knowledge flows downward through the cascade — upstream truths are preserved, while you only ever modify the local layer.
|
||||||
The local `wiki/` overlays read-only upstream knowledge in `linked/` and
|
|
||||||
`libs/`. Local knowledge wins when the same entity exists in multiple layers.
|
|
||||||
|
|
||||||
Keep the always-loaded rules small. Detailed workflows live in skills and
|
If an entity exists in both the local wiki and any upstream KB, the local version takes precedence and overrides the upstream one.
|
||||||
should be loaded only when their trigger applies.
|
|
||||||
|
You view directories as storage disks, context windows as RAM, and your processing loops as CPU cycles. Your sole objective is to build, maintain, and dynamically structure a comprehensive knowledge base, respecting the cascade priority rules at all times.
|
||||||
|
|
||||||
|
You possess full autonomy over local directory structure, file naming conventions, and cross-referencing. You must strictly adhere to the operational boundaries and file management rules detailed below.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. DIRECTORY CONTRACT
|
## 1. DIRECTORY STRUCTURE
|
||||||
Maintain this root layout:
|
The root directory contains exactly seven top-level entries. You must maintain this structure flawlessly:
|
||||||
|
|
||||||
```
|
```
|
||||||
├── .agents/modules/ # Optional modules, inert until installed
|
├── libs/ # Read-only external sources, one of two kinds per <name>/ subfolder:
|
||||||
├── libs/ # Read-only external sources:
|
│ └── <name>/ # - GIT-COPY: a git-managed clone/ZIP unpack, gitignored, fully immutable — never write here.
|
||||||
│ └── <name>/ # - git-copy clone/ZIP: immutable, never write here
|
│ # - CONNECTOR: identified by a user-authored source.yaml (connector + location,
|
||||||
│ # - connector source: has user-authored source.yaml
|
│ # optionally an index: block pointing at a shared/pre-built index to fetch from).
|
||||||
│ # and an agent-owned generated index
|
│ # The agent owns and maintains a self-contained generated index alongside it —
|
||||||
├── linked/ # Symlinks to other KB roots, read-only
|
│ # index.md/entities/graph/log.md, mirroring wiki/'s own shape but scoped entirely
|
||||||
├── outputs/ # Agent-generated artifacts and exports
|
│ # to this one connector. See §4 EXTERNAL SOURCE INDEXING. source.yaml itself stays
|
||||||
├── raw/ # User-provided source material
|
│ # user-only, same as everything in a git-copy lib. Whether *this* user may rebuild
|
||||||
│ ├── inbox/ # Drop zone for unprocessed material
|
│ # it (vs. only read a fetched/published copy) is a local, per-user, gitignored
|
||||||
│ └── archive/ # Agent-filed processed inputs by date
|
│ # source.local.yaml — read-only by default.
|
||||||
├── tmp/ # Agent temporary files and caches
|
├── linked/ # SYMLINKS ONLY. Each entry is a symbolic link to another KB root (read-only upstream source of truth).
|
||||||
├── wiki/ # Local mutable structured wiki
|
│ └── <name>/ # Individual upstream knowledge base (immutable — never write here).
|
||||||
│ ├── index.md # Routing table with kb_schema_version
|
├── outputs/ # MANAGED BY AGENT. Generated artifacts, exports, compiled files produced from the wiki.
|
||||||
│ ├── overview.md
|
│ # On-demand workflows beyond Ingest/Lint may be defined as Claude Code Skills under
|
||||||
│ ├── log.md
|
│ # `.claude/skills/` — check there before assuming a capability doesn't exist.
|
||||||
│ ├── error-book.md
|
├── raw/ # WRITTEN BY USER ONLY. Raw files, scratchpad notes, URLs, links.txt.
|
||||||
│ ├── query-gaps.md
|
│ ├── inbox/ # Drop zone: unprocessed material the agent cleans on ingest.
|
||||||
│ ├── projects/
|
│ └── archive/ # AGENT MAINTAINED. Ingested raw material, filed by ingestion date.
|
||||||
│ ├── decisions/ # Numbered, append-only decision records
|
│ └── <YYYY-MM-DD>/ # One folder per ingestion date; holds every raw/inbox file processed that day.
|
||||||
│ ├── entities/
|
├── tmp/ # MANAGED BY AGENT. Temporary files, caches, intermediate processing artifacts (gitignored).
|
||||||
│ └── graph/
|
├── wiki/ # MANAGED BY AGENT. The local, mutable, structured markdown wiki. Overlays linked/ and libs/.
|
||||||
└── workload/ # Session summaries and decisions
|
│ ├── index.md # Entry point / routing table with "Use when" triggers. Carries kb_schema_version.
|
||||||
|
│ ├── overview.md # High-level map of the knowledge base.
|
||||||
|
│ ├── log.md # AGENT LOG. Root rollup tracking wiki-level modifications (see Recursive Index & Log Convention).
|
||||||
|
│ ├── error-book.md # AGENT MAINTAINED. Records compilation errors and derived constraints.
|
||||||
|
│ ├── entities/ # AGENT POPULATED. Typed entity pages (people, projects, concepts, libraries). Has its own index.md.
|
||||||
|
│ └── graph/ # AGENT MAINTAINED. Edge lists and relationship data for the knowledge graph. Has its own index.md.
|
||||||
|
└── workload/ # MANAGED BY AGENT. Summaries of discussions and decisions.
|
||||||
└── YYYY-MM-DD_summary.md
|
└── YYYY-MM-DD_summary.md
|
||||||
```
|
```
|
||||||
|
|
||||||
### Optional Modules
|
### Cascade Lookup Priority
|
||||||
Capabilities not every KB needs ship in `.agents/modules/<name>/` and are
|
When searching for any entity, concept, or file, use the following cascade (first match wins):
|
||||||
inert until installed. Installing copies the module's skills into
|
|
||||||
`.agents/skills/`, creates its scaffold, appends a marked block to this
|
|
||||||
file and `.gitignore`, and records it in the root `ckb.yaml`. `ckb-module`
|
|
||||||
owns that; read `ckb.yaml` to see what is installed. A module may add
|
|
||||||
directories (such as `src/`) and optional page types — always additively.
|
|
||||||
|
|
||||||
`CLAUDE.md` is a symlink to this file and `.claude/skills` a symlink to
|
1. **Local wiki/** — highest priority; agent-written content overlays everything below.
|
||||||
`.agents/skills`. Write the `.agents`-side path only; never duplicate.
|
2. **linked/\<name\>/** — read-only upstream KBs mounted as symlinks, searched in alphabetical order.
|
||||||
|
3. **libs/\<name\>/** — read-only external sources, searched in alphabetical order. For a git-copy lib this is its cloned files; for a connector-backed lib (one with a `source.yaml`) this layer's content *is* the agent-generated index (`index.md`/`entities/`/`graph/`) built by the `ckb-index-external` skill, not raw copied files — see §4.
|
||||||
|
4. If no match is found anywhere, treat the entity as unknown.
|
||||||
|
|
||||||
### Cascade Priority
|
You must **never** create, modify, move, or delete any file or directory inside `linked/` or a git-copy `libs/<name>/`. The one exception is a connector-backed `libs/<name>/`'s own generated index, which the agent owns and maintains exactly like `wiki/` — see Rule A in §7.
|
||||||
When searching for any entity, concept, or file, use first match wins:
|
|
||||||
|
|
||||||
1. `wiki/` - local mutable layer.
|
|
||||||
2. `linked/<name>/` - read-only upstream KBs, alphabetical.
|
|
||||||
3. `libs/<name>/` - read-only external sources, alphabetical. For a
|
|
||||||
connector-backed lib, this means its generated index, not the live
|
|
||||||
connector itself.
|
|
||||||
4. If no match is found, treat the entity as unknown.
|
|
||||||
|
|
||||||
Never write inside `linked/` or a git-copy `libs/<name>/`.
|
|
||||||
|
|
||||||
Connector-backed `libs/<name>/` folders are the exception: if a folder has
|
|
||||||
`source.yaml`, the agent may maintain that folder's generated
|
|
||||||
`index.md`/`entities/`/`graph/`/`log.md` through `ckb-index-external`.
|
|
||||||
The agent must never edit `source.yaml`. Rebuilding from the live connector
|
|
||||||
requires local `libs/<name>/source.local.yaml` with `access: write`;
|
|
||||||
absence means read-only.
|
|
||||||
|
|
||||||
### Index-First Navigation
|
### Index-First Navigation
|
||||||
For KB questions, start at `wiki/index.md`, then matching subdirectory
|
When searching for information, always start by looking for `index.md` files.
|
||||||
indexes. Only drill into pages that match the task. If local indexes do not
|
Read the index to discover what pages and subdirectories are available before
|
||||||
answer, continue through `linked/` and `libs/` indexes in cascade order.
|
drilling into individual files. Scan `index.md` across all layers:
|
||||||
|
|
||||||
Every `wiki/` subdirectory that groups pages, including `projects/`,
|
1. **wiki/** — scan `wiki/index.md`, then recursively check any subdirectory `wiki/<topic>/index.md`.
|
||||||
`entities/`, and `graph/`, must have its own `index.md`. Use the same
|
2. **linked/\<name\>/** — for each linked upstream KB, scan its root `index.md` and subdirectory indexes.
|
||||||
convention inside connector-backed libs for their generated index.
|
3. **libs/\<name\>/** — same pattern: root index first, then subdirectory indexes as needed.
|
||||||
|
|
||||||
|
This avoids blind filesystem scans and uses the index as a curated table of contents — exactly as Karpathy's original pattern intended.
|
||||||
|
|
||||||
|
### Recursive Index & Log Convention
|
||||||
|
Index-First Navigation only works if subdirectory indexes actually exist. Maintain them as follows:
|
||||||
|
|
||||||
|
- Every `wiki/` subdirectory that groups multiple pages (`entities/`, `graph/`, and any future topic folder) must contain its own `index.md`. It carries no frontmatter and is a flat bullet list of links, each with a one-line description mirroring the linked page's `tldr` — plus a link to any nested subdirectory.
|
||||||
|
- A subdirectory may also keep its own `log.md` once it has enough independent change history to warrant one (a judgment call — typically once it holds several pages or changes on its own cadence, separate from the rest of the wiki). Entries follow the same reverse-chronological format as Rule B.
|
||||||
|
- The root `wiki/log.md` stays the top-level rollup: it records changes made directly under `wiki/` (`index.md`, `overview.md`, `error-book.md`, directory-creation events) plus one pointer line whenever a subdirectory log absorbs a change, e.g. `- See wiki/entities/log.md for entity-page changes on this date.` Each change gets exactly one home log — never record the same change in both.
|
||||||
|
- The same convention applies verbatim inside a connector-backed `libs/<name>/` (§4) — its generated `index.md`/`entities/index.md`/`graph/index.md`/`log.md` mirror this pattern exactly, scoped entirely to that one connector. Its `log.md` is independent of `wiki/log.md` — never record a connector-indexing change in both.
|
||||||
|
|
||||||
|
### Lazy-Loading with "Use When" Triggers
|
||||||
|
The `wiki/index.md` is a routing table. Each entry has a **Use when** column
|
||||||
|
that lists trigger keywords. Before loading any page:
|
||||||
|
|
||||||
|
1. Read `wiki/index.md` (stays in context — it is small).
|
||||||
|
2. Match the current task's keywords against the **Use when** entries.
|
||||||
|
3. Only load the matching page(s). Do not load every page.
|
||||||
|
4. If a page has a `tldr:` frontmatter field, read that first. If it answers the query, skip the body.
|
||||||
|
|
||||||
|
This keeps context lean: ~3–4 pages loaded instead of all pages.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. PAGE SCHEMA
|
## 2. PAGE FRONTMATTER SCHEMA
|
||||||
Every non-reserved wiki page uses YAML frontmatter. `type` is required; the
|
|
||||||
other fields are optional but preferred when meaningful:
|
Every wiki page must use YAML frontmatter. `type` is required; the rest are optional:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
type: concept
|
type: concept # REQUIRED. Open string for the entity/content kind (e.g. person, project, concept, library, decision, playbook). Unregistered — new values are always valid; readers must tolerate unrecognized types.
|
||||||
resource: https://...
|
resource: https://... # Optional. Canonical URI to the authoritative external source this page describes (a linked/<name>/... or libs/<name>/... path, ticket, repo, doc, dataset). Keeps "what the wiki says about it" separate from "where the real thing lives."
|
||||||
tldr: One-sentence summary optimised for LLM reading
|
tldr: One-sentence summary optimised for LLM reading
|
||||||
confidence: 0.0-1.0
|
confidence: 0.0–1.0 # How many/corroborated sources support this
|
||||||
quality: 0.0-1.0
|
quality: 0.0–1.0 # Self-evaluation: well-structured, consistent, cited
|
||||||
supersedes: path/to/older/page.md
|
supersedes: path/to/older/page.md
|
||||||
superseded_by: path/to/newer/page.md
|
superseded_by: path/to/newer/page.md
|
||||||
last_updated: YYYY-MM-DD
|
last_updated: YYYY-MM-DD
|
||||||
freshness_window_days: 90
|
freshness_window_days: 90 # Days before considered potentially stale
|
||||||
retention: high|medium|low
|
retention: high|medium|low # How aggressively to deprioritize when old
|
||||||
source_fingerprint: sha256:3f9a2c1e # digest of the source this page was built from
|
|
||||||
source_checked: YYYY-MM-DD # when that digest was last verified
|
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
### Page Body Sections
|
- **`type`** — required on every page. Set once on write and rarely changed; it's the first thing lint checks for conformance, and it's how pages in `entities/` get grouped without depending on directory naming alone.
|
||||||
Four body sections are reserved across the whole KB. All are optional, but
|
- **`resource`** — set when the page describes something with a stable external address. Omit for pages that are pure synthesis (e.g. an overview or a decision writeup with no single external source).
|
||||||
where present they mean exactly this and nothing else:
|
- **`tldr`** — generated on write. If the TLDR alone answers a query, the body is never loaded.
|
||||||
|
- **`confidence`** — set on write based on source corroboration. Decays with time unless reinforced by new sources.
|
||||||
|
- **`quality`** — self-score on write. Below 0.7 → flag for review.
|
||||||
|
- **`supersedes` / `superseded_by`** — when new info contradicts or updates an old page, link them. Old pages are preserved but marked stale.
|
||||||
|
- **`last_updated`** — set automatically on every write or edit.
|
||||||
|
- **`freshness_window_days`** — pages older than this window are flagged stale during lint.
|
||||||
|
- **`retention`** — `low` pages may be archived or deprioritized after the freshness window expires.
|
||||||
|
|
||||||
- **`## Sources`** — where this page came from. One bullet per source, each
|
### Schema Versioning
|
||||||
carrying a fingerprint so drift is detectable by machine rather than by
|
`wiki/index.md` (only) carries an additional frontmatter field, `kb_schema_version` (e.g. `"1.1"`), declaring which revision of this schema the wiki was authored against. Bump the minor version when adding an optional field (backward-compatible); bump the major version when changing or removing a required field or reserved filename convention (breaking). Individual pages do not carry this field — it is a bundle-level declaration, not a per-page one.
|
||||||
calendar:
|
|
||||||
`` - `raw/archive/2026-09-21/kickoff.md` — sha256:3f9a2c1e (checked 2026-09-21) ``
|
|
||||||
For a page built from exactly one source, the same digest also goes in
|
|
||||||
`source_fingerprint`/`source_checked` frontmatter.
|
|
||||||
- **`## Crux`** — verbatim excerpts from those sources, never a paraphrase.
|
|
||||||
Quote the few lines that actually carry the claim, attributed to the
|
|
||||||
specific source bullet they came from. A quote is evidence: it either
|
|
||||||
still matches the source or it doesn't, which is what makes drift
|
|
||||||
visible. Never edit a quote to read better — if it no longer matches,
|
|
||||||
that is a finding, not an edit.
|
|
||||||
- **`## Notes`** — human-authored, and **protected**. No skill may
|
|
||||||
rewrite, reflow, summarize, or drop this section; regeneration
|
|
||||||
preserves it byte-for-byte. It is the only place a person can annotate
|
|
||||||
an agent-regenerated page and expect it to survive.
|
|
||||||
- **`## Evidence`** / other sections are ordinary content with no special
|
|
||||||
handling.
|
|
||||||
|
|
||||||
Pages with `type: decision` live in `wiki/decisions/` as `NNNN-slug.md` and
|
|
||||||
add `status` (`proposed`/`accepted`/`rejected`/`superseded`/`reversed`),
|
|
||||||
`decided_on`, `decided_by`, and optionally `affects` and `review_on`. They are
|
|
||||||
append-only: never rewrite a decision's substance to match a later change of
|
|
||||||
mind — record a new decision that supersedes it, and set both
|
|
||||||
`supersedes` and `superseded_by`. `ckb-decide` owns the format.
|
|
||||||
|
|
||||||
`wiki/index.md` alone also carries `kb_schema_version`, currently `"1.5"`
|
|
||||||
for the base contract. An installed module may raise it additively by
|
|
||||||
declaring `requires_kb_schema` and contributing optional types and fields;
|
|
||||||
`ckb-module` applies that bump at install time. Detailed schema migration
|
|
||||||
and version-bump policy belongs in `ckb-upgrade`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. SKILL ROUTING
|
## 3. INGESTION WORKFLOW (TRIGGERED ON DEMAND)
|
||||||
Use skills for procedural work instead of keeping full workflows in this
|
|
||||||
file.
|
|
||||||
|
|
||||||
| User intent | Skill |
|
When the user says "Ingest", "Sync the wiki", or "Update the Wiki" (for syncing this repo's own git history with its remote, see the ckb-sync-changes skill under `.claude/skills/` instead), run the **ckb-ingest** Claude Code Skill — see `.agents/skills/ckb-ingest/SKILL.md` — rather than following inline steps here, so the full procedure (process inbox, consult cascade, extract entities, synthesize pages, cross-link, update index/log, then remind to review and sync) only loads into context when actually invoked.
|
||||||
|---|---|
|
|
||||||
| Answer or research a KB question | `ckb-retrieve` |
|
|
||||||
| Ingest raw material into `wiki/` | `ckb-ingest` |
|
|
||||||
| Record a decision, or answer what/why/who/when was decided | `ckb-decide` |
|
|
||||||
| Index connector-backed `libs/` sources | `ckb-index-external` |
|
|
||||||
| Health-check or repair wiki/index structure | `ckb-lint` |
|
|
||||||
| Sync this repo with `origin` | `ckb-sync-changes` |
|
|
||||||
| Upgrade template or wiki schema | `ckb-upgrade` |
|
|
||||||
| Bootstrap a new empty KB (from local files or a fresh clone of the template repo) | `ckb-init` |
|
|
||||||
| Install, list, or uninstall an optional module | `ckb-module` |
|
|
||||||
| Empty the wiki back to a clean template (destructive) | `ckb-reset` |
|
|
||||||
| Export OKF or Starlight artifacts | `ckb-export-okf`, `ckb-export-starlight` |
|
|
||||||
| Generate a project overview | `ckb-project-summary` |
|
|
||||||
| Teach, quiz, or onboard from the wiki | `ckb-teach-me`, `ckb-quiz`, `ckb-onboard-me` |
|
|
||||||
|
|
||||||
Short routing rules:
|
|
||||||
|
|
||||||
- For questions, use `ckb-retrieve`; it owns project scopes, hybrid local
|
|
||||||
search, rank fusion across signals, dedupe/rerank, expertise and
|
|
||||||
ownership lookups, evidence packets, source verification, answer
|
|
||||||
caveats, and query-gap capture.
|
|
||||||
- For "Ingest", "Sync the wiki", or "Update the wiki", use `ckb-ingest`.
|
|
||||||
- For "record a decision", "we decided ...", or a question whose subject is a
|
|
||||||
decision (what/why/who/when, what superseded it, what is still open), use
|
|
||||||
`ckb-decide`. For a wider question that merely touches decisions, stay in
|
|
||||||
`ckb-retrieve`.
|
|
||||||
- For "Index external sources", "index libs", or "refresh the external
|
|
||||||
index", use `ckb-index-external`.
|
|
||||||
- For "Lint" or "health-check the wiki", use `ckb-lint`.
|
|
||||||
- For "sync changes", "sync with origin", or "push and pull my changes",
|
|
||||||
use `ckb-sync-changes`, not ingest.
|
|
||||||
- For "upgrade the wiki" or "check for a newer template version", use
|
|
||||||
`ckb-upgrade`. The template repo keeps three branches — `main` (stable),
|
|
||||||
`test` (release candidate), `experimental` (development) — and a request
|
|
||||||
naming one ("upgrade from `test`", "switch to the stable channel") routes
|
|
||||||
here too. Which branch this KB tracks lives in `ckb.yaml`'s `template:`
|
|
||||||
block and defaults to `main`; `ckb-init` names it the same way when
|
|
||||||
bootstrapping.
|
|
||||||
- For "install/uninstall the `<name>` module", "what modules are
|
|
||||||
available", or a request for a capability the base KB lacks (such as
|
|
||||||
handling source code), use `ckb-module`.
|
|
||||||
- For "reset the wiki", "empty the wiki", or "make this a clean template",
|
|
||||||
use `ckb-reset`. It deletes accumulated knowledge, so it always shows an
|
|
||||||
inventory and takes a typed confirmation first, and never touches the
|
|
||||||
template layer or `src/`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. NON-NEGOTIABLE RULES
|
## 4. EXTERNAL SOURCE INDEXING (TRIGGERED ON DEMAND)
|
||||||
|
|
||||||
### Rule A: Immutability
|
When the user says "Index external sources" (or "index libs", "refresh the external index"), run the **ckb-index-external** Claude Code Skill — see `.agents/skills/ckb-index-external/SKILL.md` — rather than following inline steps here, so the full procedure only loads into context when actually invoked. It walks every connector-backed `libs/<name>/` (one with a `source.yaml` — see §1), fetches a shared/pre-built index if `source.yaml` declares one (`index.store`/`index.location` — git or a shared resource), and — only if this user has local `access: write` in `libs/<name>/source.local.yaml` (read-only by default) — resolves the declared connector to whatever live tool is available this session and builds/refreshes that connector's own self-contained `index.md`/`entities/`/`graph/`/`log.md`, publishing it back to the shared store if one is configured. This never touches `wiki/`, never touches `source.yaml`, and never touches a git-copy lib.
|
||||||
Do not modify `linked/` or git-copy `libs/<name>/`. To correct upstream
|
|
||||||
knowledge, write a local override in `wiki/`.
|
|
||||||
|
|
||||||
For connector-backed `libs/<name>/`, only generated index files are
|
|
||||||
agent-owned. `source.yaml` is user-owned. `source.local.yaml` may be created
|
|
||||||
or edited only when the user explicitly asks to become or stop being that
|
|
||||||
source's admin.
|
|
||||||
|
|
||||||
### Rule B: Wiki Change Log
|
|
||||||
Every create, update, move, or delete inside `wiki/` must be logged
|
|
||||||
immediately in `wiki/log.md` before proceeding.
|
|
||||||
|
|
||||||
Use reverse chronological order and this format:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## [YYYY-MM-DD HH:MM] - [ACTION TYPE]
|
|
||||||
- **File Affected:** `wiki/path/to/file.md`
|
|
||||||
- **Description:** Brief summary of what knowledge or structure changed.
|
|
||||||
- **Source:** Chat conversation, raw file, URL, or skill name.
|
|
||||||
---
|
---
|
||||||
```
|
|
||||||
|
|
||||||
### Rule C: Links
|
## 5. QUERY WORKFLOW
|
||||||
For upstream references, link with project-root-relative paths such as
|
|
||||||
`linked/<name>/...` or `libs/<name>/...`.
|
|
||||||
|
|
||||||
For local wiki references, prefer project-root-absolute paths such as
|
When answering a question or researching a topic:
|
||||||
`/wiki/entities/foo.md`. Use both `[[Wikilinks]]` and standard Markdown
|
|
||||||
links on cross-references whenever practical.
|
|
||||||
|
|
||||||
### Rule D: Session Summary
|
1. **Read the index** — `wiki/index.md` first. Match query keywords against **Use when** triggers.
|
||||||
After every conversational turn where you read, write, search, ingest, lint,
|
2. **Read TLDRs** — for any matched page, read its `tldr:` frontmatter first. If it answers the query, stop.
|
||||||
or answer, append a short note to `workload/YYYY-MM-DD_summary.md`.
|
3. **Load full pages** — only if the TLDR was insufficient.
|
||||||
|
4. **Walk the graph** — if the entity has relationships in `wiki/graph/edges.json`, follow them to discover connected pages (e.g. "what depends on X?").
|
||||||
|
5. **Fall back upstream** — if the local wiki has no match, check `linked/<name>/` indexes, then `libs/<name>/` indexes (for a connector-backed lib, that means its generated `entities/`/`index.md`, not the live source directly — if it's not there yet, suggest running "index external sources" rather than fetching the live source ad hoc). Apply cascade priority throughout.
|
||||||
|
|
||||||
### Rule E: Session Start and End
|
---
|
||||||
At session start, read `wiki/index.md`, the latest `workload/` summary, and
|
|
||||||
run two cheap checks:
|
|
||||||
|
|
||||||
```bash
|
## 6. MAINTENANCE WORKFLOW (LINT)
|
||||||
git status --short --branch
|
|
||||||
python3 .agents/skills/ckb-lint/scripts/lint_report.py --quick
|
|
||||||
```
|
|
||||||
|
|
||||||
`git status` covers unsynchronized history — if there are uncommitted
|
Periodically (or when asked to "Lint"), run the **ckb-lint** Claude Code Skill — see `.agents/skills/ckb-lint/SKILL.md` — rather than following inline steps here, so the full checklist (conformance, freshness, confidence decay, retention sweep, supersession detection, orphan detection, graph consistency, index/log consistency, error-book entries, auto-fix vs. report, then a reminder to review and sync) only loads into context when actually invoked.
|
||||||
changes or the branch is ahead/behind its remote-tracking branch, say so and
|
|
||||||
suggest `ckb-sync-changes`. `--quick` covers knowledge rot: one line stating
|
|
||||||
how many pages are past their freshness window, how many cited sources no
|
|
||||||
longer match their recorded fingerprint, and how many pages are malformed.
|
|
||||||
It is deterministic and costs no model tokens. Report the line if anything
|
|
||||||
is non-zero and suggest `ckb-lint`; stay quiet when it is clean.
|
|
||||||
|
|
||||||
At session end, update `workload/` and repeat the unsynchronized-change
|
---
|
||||||
check. Do not fetch or push unless the user asks.
|
|
||||||
|
|
||||||
### Rule F: Demand-Driven Context
|
## 7. COMPLIANCE & LOGGING RULES (NON-NEGOTIABLE)
|
||||||
When the KB cannot answer something, identify the missing knowledge and
|
|
||||||
propose the smallest source or page that would close the gap. `ckb-retrieve`
|
### Rule A: Immutability of linked/ and libs/
|
||||||
owns durable query-gap entries; `ckb-ingest` owns turning approved/source
|
You must **never** write, modify, move, or delete any file or directory inside `linked/` or a git-copy `libs/<name>/`. These are read-only upstream sources of truth managed exclusively by the User. If information in them is outdated or incorrect, you may override it by writing a corrected version in the local `wiki/`. The local version will take priority in the cascade lookup.
|
||||||
material into wiki pages.
|
|
||||||
|
**Exception — connector-backed `libs/<name>/`:** identified by the presence of a `source.yaml` (see §1). Its `source.yaml` is user-authored and stays just as untouchable as anything else here. But everything else in that folder — `index.md`, `entities/`, `graph/`, `log.md` — is a generated index the agent owns and maintains exactly as it would `wiki/`, built and refreshed by the `ckb-index-external` skill (§4). This exception applies only to a `libs/<name>/` that has a `source.yaml`; a plain git-copy lib has no such carve-out.
|
||||||
|
|
||||||
|
Within that exception, two things the agent may always do regardless of this user's access level: fetch a shared/pre-built index down into `libs/<name>/` if `source.yaml` declares one, and read whatever's cached there. Actually rebuilding it from the live connector — and publishing that rebuild back to a shared store — is gated by a separate, local, per-user `libs/<name>/source.local.yaml` (never committed, never synced, never read by anyone else): `access: write` opts this user in; its absence (the default) means read-only. Unlike `source.yaml`, the agent *may* create or edit `source.local.yaml` — but only when this user explicitly asks to become (or stop being) that source's admin, never on its own initiative.
|
||||||
|
|
||||||
|
### Rule B: The Wiki Change Log (`wiki/log.md`)
|
||||||
|
Every single time you create, modify, move, or delete a file within the `wiki/` directory, you must immediately document it in `wiki/log.md` before proceeding.
|
||||||
|
- **Ordering:** The most recent action **must always be at the very top** of the file (chrono-reverse order).
|
||||||
|
- **Format Per Entry:**
|
||||||
|
```markdown
|
||||||
|
## [YYYY-MM-DD HH:MM] - [ACTION TYPE: e.g., CREATE/UPDATE/DELETE]
|
||||||
|
- **File Affected:** `wiki/path/to/file.md`
|
||||||
|
- **Description:** Brief summary of what knowledge was added or altered.
|
||||||
|
- **Source:** [e.g., Chat conversation, raw/notes.txt, URL]
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rule C: Cascade-Anchored References with Dual-Linking
|
||||||
|
When cross-referencing an entity that exists in an upstream KB, write the link using the relative path from the project root (e.g., `linked/<name>/wiki/concepts/foo.md` or `libs/<name>/docs/bar.md`). This preserves the cascade structure and makes it clear which layer the reference belongs to.
|
||||||
|
|
||||||
|
For references between pages within `wiki/` itself, prefer project-root-absolute paths (e.g. `/wiki/entities/foo.md`) over relative paths (`../entities/foo.md`). Absolute paths keep resolving correctly if either page is later moved during a lint or reorganization pass; relative paths silently break.
|
||||||
|
|
||||||
|
Use **both** `[[Wikilinks]]` (Obsidian-compatible) and standard `[markdown](path.md)` links on every cross-reference. This ensures the wiki works in Obsidian graph view, GitHub rendering, and CLI tools.
|
||||||
|
|
||||||
|
### Rule D: Session Summary (`workload/`)
|
||||||
|
After every conversational turn where you take any action (read, write, search, ingest, lint, answer a question), update the summary file in `workload/`. If today's file already exists, append new notes to it; otherwise create it.
|
||||||
|
- **Naming:** `workload/YYYY-MM-DD_summary.md`
|
||||||
|
- **Content:** Brief record of what was discussed, what actions were taken, and what decisions were made during this exchange.
|
||||||
|
- **Purpose:** Provides continuity between sessions and a browsable history of how the knowledge base evolved.
|
||||||
|
|
||||||
|
### Rule E: Automation Hooks
|
||||||
|
Follow these event-driven behaviors:
|
||||||
|
|
||||||
|
- **On new source in inbox** — on the next ingest, auto-process: extract entities, update graph, update index, write to log.
|
||||||
|
- **On new or changed `libs/<name>/source.yaml`** — on the next "index external sources" run, process it: resolve the connector, enumerate documents, build/refresh that connector's own `index.md`/`entities/`/`graph/`/`log.md`.
|
||||||
|
- **On session start** — read `wiki/index.md` and the latest `workload/` summary to load relevant context. Also check for unsynchronized changes (`git status` — uncommitted local changes, or the local branch ahead/behind its remote-tracking ref) and, if any are found, tell the user and suggest running the `ckb-sync-changes` skill before proceeding. This is a cheap, read-only check (no `git fetch`) — a heads-up, not a substitute for actually running that skill.
|
||||||
|
- **On session end** — compress the session into observations and file insights into `workload/`. Also re-run the same unsynchronized-changes check as at session start — the session's own work may have just created new local changes — and suggest `ckb-sync-changes` if anything is now pending.
|
||||||
|
- **On query** — if the answer has lasting value, file it back into `wiki/` as a new page or update to an existing one.
|
||||||
|
- **On memory write** — check for contradictions with existing wiki content. If found, apply supersession (link old → new) and log it.
|
||||||
|
- **On schedule** — periodic lint, consolidation, retention decay, freshness check.
|
||||||
|
|
||||||
|
### Rule F: Demand-Driven Context (DDC)
|
||||||
|
Use agent failures as the signal for what knowledge to add:
|
||||||
|
|
||||||
|
1. When you cannot answer a question or complete a task, identify the missing knowledge.
|
||||||
|
2. Propose a minimal entity or page to fill the gap.
|
||||||
|
3. The user approves or provides the source material.
|
||||||
|
4. Add it to `raw/inbox/` or describe it in chat.
|
||||||
|
5. Next ingest cycle incorporates it.
|
||||||
|
|
||||||
|
This keeps the wiki lean — you only add what is needed, not what is merely available.
|
||||||
|
|
|
||||||
370
CHANGELOG.md
370
CHANGELOG.md
|
|
@ -1,370 +0,0 @@
|
||||||
# Changelog & Schema Reference
|
|
||||||
|
|
||||||
*Read this in: **English** | [Polski](CHANGELOG.pl.md)*
|
|
||||||
|
|
||||||
This file tracks two things: the page schema exactly as it stands today, and
|
|
||||||
how this project arrived at its current version numbers.
|
|
||||||
|
|
||||||
There are **two independent version numbers**, and they are not the same
|
|
||||||
thing:
|
|
||||||
|
|
||||||
| Number | Lives in | Describes | Who bumps it |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `kb_schema_version` | frontmatter of `wiki/index.md` | the **content contract** — what a page may contain and what those fields mean | `ckb-upgrade` (on a confirmed migration), `ckb-module` (additively, on install) |
|
|
||||||
| Template version | [`VERSION`](VERSION) | the **tooling layer** — `AGENTS.md`, the skills, the scripts, the docs | `ckb-upgrade`, when it pulls a newer template |
|
|
||||||
|
|
||||||
They move independently on purpose. You can take a newer set of skills
|
|
||||||
without touching a single wiki page, and a wiki authored against an older
|
|
||||||
schema keeps working — that is what the schema version is for.
|
|
||||||
|
|
||||||
**Versioning policy.** Bump the **minor** version for an additive change: a
|
|
||||||
new optional field, a new optional body section, a new optional scaffold
|
|
||||||
file. Bump the **major** version for a breaking one: changing or removing a
|
|
||||||
required field, or changing an existing reserved filename convention. There
|
|
||||||
has never been a major bump; every schema version to date has been additive,
|
|
||||||
so any page written since 2026-07-13 is still valid today.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contents
|
|
||||||
|
|
||||||
1. [Current page schema (1.5)](#current-page-schema-kb_schema_version-15)
|
|
||||||
2. [KB schema version history](#kb-schema-version-history)
|
|
||||||
3. [Template version history](#template-version-history)
|
|
||||||
4. [Migrating between versions](#migrating-between-versions)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current page schema (`kb_schema_version: "1.5"`)
|
|
||||||
|
|
||||||
### Frontmatter — every non-reserved page
|
|
||||||
|
|
||||||
Every `.md` file under `wiki/` except the reserved ones (`index.md`,
|
|
||||||
`log.md`) carries YAML frontmatter. Only `type` is required; the rest are
|
|
||||||
optional, and preferred where meaningful. The parser is deliberately simple —
|
|
||||||
flat `key: value` pairs only, no nesting.
|
|
||||||
|
|
||||||
| Field | Required | Meaning |
|
|
||||||
|---|---|---|
|
|
||||||
| `type` | **yes** | Open string: `person`, `project`, `concept`, `library`, `decision`, `playbook`, `repository`, `component`, … New values are always valid; readers tolerate unrecognised ones. |
|
|
||||||
| `resource` | no | Canonical URI of the authoritative external source this page describes, kept separate from the wiki's own commentary. |
|
|
||||||
| `tldr` | no | One-sentence summary optimised for LLM reading. This is what the index shows and what decides whether a page gets opened at all. |
|
|
||||||
| `confidence` | no | `0.0`–`1.0`. Source corroboration. Set on write, decays if nothing reinforces it, raised by a new source that agrees. |
|
|
||||||
| `quality` | no | `0.0`–`1.0`. Self-evaluation of the page's own structure and citations. Below `0.7` is flagged for review. |
|
|
||||||
| `supersedes` | no | Path to the older page this one replaces. |
|
|
||||||
| `superseded_by` | no | Path to the newer page that replaced this one. **Always set both sides.** |
|
|
||||||
| `last_updated` | no | `YYYY-MM-DD`. When the *page* last changed. |
|
|
||||||
| `freshness_window_days` | no | Days before lint flags the page as stale. Typical: 90 for a wiki page, 365 for a decision, 30 for a connector-indexed document. |
|
|
||||||
| `retention` | no | `high` / `medium` / `low`. A `low` page is archived (never deleted) after 2× its freshness window. |
|
|
||||||
| `source_fingerprint` | no | *(1.5)* Digest of the source this page was built from — `sha256:<8 hex>` for a local file, or `etag:<value>` / `mtime:<iso8601>` for a connector item. |
|
|
||||||
| `source_checked` | no | *(1.5)* `YYYY-MM-DD` — when that digest was last verified. Distinct from `last_updated`: a re-check that finds nothing changed advances this and leaves `last_updated` alone. |
|
|
||||||
|
|
||||||
`wiki/index.md` alone carries `kb_schema_version`. It is a bundle-level
|
|
||||||
declaration, not a per-page one — individual pages never carry it.
|
|
||||||
|
|
||||||
### Frontmatter — decision pages
|
|
||||||
|
|
||||||
Pages with `type: decision` live in `wiki/decisions/` as `NNNN-slug.md` and
|
|
||||||
add:
|
|
||||||
|
|
||||||
| Field | Required | Meaning |
|
|
||||||
|---|---|---|
|
|
||||||
| `status` | **yes** | `proposed` / `accepted` / `rejected` / `superseded` / `reversed`. The vocabulary is defined in `wiki/decisions/index.md` and validated by lint. |
|
|
||||||
| `decided_on` | for `accepted`/`rejected`/`reversed` | `YYYY-MM-DD`, the date the call was made. |
|
|
||||||
| `decided_by` | when known | Comma-separated names. Where genuinely unknown, write `unknown` rather than omitting the field — "we don't know who decided this" is itself worth recording. |
|
|
||||||
| `affects` | no | Comma-separated wiki paths this decision constrains. |
|
|
||||||
| `review_on` | no | `YYYY-MM-DD` to revisit. Lint reports these once the date passes. |
|
|
||||||
|
|
||||||
Decision records are **append-only**. A decision is never rewritten to match
|
|
||||||
a later change of mind: record a new one that supersedes it, and both stay on
|
|
||||||
the record.
|
|
||||||
|
|
||||||
### Reserved body sections
|
|
||||||
|
|
||||||
*(New in 1.5.)* Four `##` headings mean the same thing on every page in every
|
|
||||||
layer of the cascade. All are optional; where present, they mean exactly this
|
|
||||||
and nothing else.
|
|
||||||
|
|
||||||
#### `## Sources`
|
|
||||||
|
|
||||||
Where the page came from. One bullet per source, each carrying a fingerprint:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- `raw/archive/2026-09-21/kickoff-notes.md` — sha256:3f9a2c1e (checked 2026-09-21)
|
|
||||||
```
|
|
||||||
|
|
||||||
A freshness window is a guess that a source *might* have moved. A fingerprint
|
|
||||||
is a fact about whether it *did*. Lint recomputes local digests and flags what
|
|
||||||
actually changed, which is a different and more urgent finding than a page
|
|
||||||
that has merely aged.
|
|
||||||
|
|
||||||
#### `## Crux`
|
|
||||||
|
|
||||||
Verbatim excerpts from those sources — evidence, never paraphrase — attributed
|
|
||||||
to the source bullet they came from:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Crux
|
|
||||||
|
|
||||||
> FDEs need the VDI *and* a Jira account before day one; the VDI request
|
|
||||||
> alone takes ten working days.
|
|
||||||
— `raw/archive/2026-09-21/kickoff-notes.md`, under "Access"
|
|
||||||
```
|
|
||||||
|
|
||||||
Three to ten lines is the working range. A Crux approaching the length of the
|
|
||||||
summary above it has stopped being evidence and become a second copy of the
|
|
||||||
source. Two things follow from quoting rather than paraphrasing: a question
|
|
||||||
can often be answered from the page instead of the archive, and drift becomes
|
|
||||||
visible — a summary can wander from its source silently, a quote either still
|
|
||||||
matches or it does not.
|
|
||||||
|
|
||||||
A page with no quotable source simply has no `## Crux`. An empty or
|
|
||||||
paraphrased one is worse than none, because it looks like evidence.
|
|
||||||
|
|
||||||
#### `## Notes`
|
|
||||||
|
|
||||||
Human-authored, and **protected**. No skill may rewrite, reflow, summarise or
|
|
||||||
drop this section; regeneration preserves it byte-for-byte.
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
<!-- Yours. Never rewritten by any skill. -->
|
|
||||||
```
|
|
||||||
|
|
||||||
This matters most on pages the agent *regenerates* — connector indexes, code
|
|
||||||
maps — where everything else is discarded and rebuilt on the next run. It is
|
|
||||||
the only place an annotation survives. Decision pages deliberately have no
|
|
||||||
`## Notes`: nothing regenerates them, and an append-only record with a freely
|
|
||||||
editable annotation block invites exactly the retroactive revision the
|
|
||||||
append-only rule exists to prevent.
|
|
||||||
|
|
||||||
### Edge vocabulary
|
|
||||||
|
|
||||||
Relationships live in `wiki/graph/edges.json` (and each connector index's own
|
|
||||||
`graph/edges.json`). The vocabulary is closed, and each verb is defined by the
|
|
||||||
question it answers — if a proposed edge answers none of them, it belongs in
|
|
||||||
the page's prose instead.
|
|
||||||
|
|
||||||
| Verb | Question it answers | Since |
|
|
||||||
|---|---|---|
|
|
||||||
| `part_of` | Where does this live? What is it a piece of? | 1.5 |
|
|
||||||
| `uses` | What does this reach for at runtime? | 1.1 |
|
|
||||||
| `depends_on` | What breaks if I change this? | 1.1 |
|
|
||||||
| `produces` | Where does this output come from? | 1.5 |
|
|
||||||
| `configures` | What changes this thing's behaviour? | 1.5 |
|
|
||||||
| `validates` | What checks, tests, or judges this? | 1.5 |
|
|
||||||
| `implements` | What contract must this honour? | 1.5 |
|
|
||||||
| `caused` | Why did this happen? | 1.1 |
|
|
||||||
| `contradicts` | What disagrees with this, unresolved? | 1.1 |
|
|
||||||
| `supersedes` | What replaced this, and what did it replace? | 1.1 |
|
|
||||||
| `decided_by` | Who made this call? | 1.4 |
|
|
||||||
| `affects` | What does this decision constrain? | 1.4 |
|
|
||||||
| `has_expertise_in` | Who can answer questions on this? | 1.3 |
|
|
||||||
| `owns` | Who is responsible for this? | 1.3 |
|
|
||||||
| `mentioned_in` | Which source document discusses this? | 1.2 (lib indexes only) |
|
|
||||||
|
|
||||||
Record one direction per relationship — `part_of`, `supersedes`,
|
|
||||||
`depends_on` and `uses` are canonical, and the inverse is not stored as a
|
|
||||||
second edge. Record edges only from demonstrated evidence; an absent edge
|
|
||||||
beats a fabricated one, and a padded graph degrades retrieval rather than
|
|
||||||
improving it (in-degree is used as a ranking signal).
|
|
||||||
|
|
||||||
### Reserved scaffold
|
|
||||||
|
|
||||||
| Path | Since | Purpose |
|
|
||||||
|---|---|---|
|
|
||||||
| `wiki/index.md` | 1.1 | Routing table; the only page carrying `kb_schema_version` |
|
|
||||||
| `wiki/overview.md` | 1.1 | High-level map of the KB |
|
|
||||||
| `wiki/log.md` | 1.1 | Reverse-chronological change log for `wiki/` |
|
|
||||||
| `wiki/error-book.md` | 1.1 | Systemic issues with root cause and fix |
|
|
||||||
| `wiki/entities/` | 1.1 | Typed entity pages |
|
|
||||||
| `wiki/graph/` | 1.1 | `edges.json` plus the vocabulary in `index.md` |
|
|
||||||
| `wiki/query-gaps.md` | 1.2 | Questions the wiki could not answer, driving demand-driven ingest |
|
|
||||||
| `wiki/projects/` | 1.2 | Optional local query scopes |
|
|
||||||
| `wiki/decisions/` | 1.4 | Numbered, append-only decision records, with their own `log.md` |
|
|
||||||
|
|
||||||
Every subdirectory that groups pages carries its own `index.md`, so
|
|
||||||
navigation stays lazy.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## KB schema version history
|
|
||||||
|
|
||||||
### 1.5 — 2026-09-21 · evidence, fingerprints, and a completed edge vocabulary
|
|
||||||
|
|
||||||
Adopted from an analysis of [trailhq/Graft](https://github.com/trailhq/Graft),
|
|
||||||
a context layer for coding agents that keeps a derived code graph in sync via
|
|
||||||
content hashes rather than dates, and protects a user-authored block on every
|
|
||||||
regenerated node. Graft's store is disposable and regenerable; this one is not,
|
|
||||||
so most of its design does not transfer — but several mechanisms do, and two
|
|
||||||
of them closed real gaps here.
|
|
||||||
|
|
||||||
**Added:**
|
|
||||||
|
|
||||||
- **`## Crux`** — verbatim source excerpts alongside the synthesis. Lets
|
|
||||||
`ckb-retrieve` ground an answer without a round-trip to the archive (when
|
|
||||||
the fingerprint still matches), and makes drift detectable.
|
|
||||||
- **`## Notes`** — human-authored and protected everywhere. This closed a real
|
|
||||||
gap: `ckb-index-external` regenerates connector pages wholesale, so an
|
|
||||||
annotation written there was previously destroyed on the next refresh.
|
|
||||||
- **`## Sources`** — formalised as a reserved section with one fingerprinted
|
|
||||||
bullet per source. Previously an informal convention that `ckb-retrieve`
|
|
||||||
relied on but no skill actually specified.
|
|
||||||
- **`source_fingerprint` / `source_checked`** frontmatter.
|
|
||||||
- **Edge verbs** `part_of`, `produces`, `configures`, `validates`,
|
|
||||||
`implements`. `part_of` fixed a live inconsistency: `ckb-code-map` had been
|
|
||||||
writing it since template 1.7.0 while the schema never declared it.
|
|
||||||
|
|
||||||
**Changed:**
|
|
||||||
|
|
||||||
- `wiki/graph/index.md` rewritten as a question-per-verb table with
|
|
||||||
conventions on edge direction and evidence.
|
|
||||||
- `ckb-ingest` gained a blast-radius step: before writing, walk the graph
|
|
||||||
backwards from touched entities to find what the incoming material
|
|
||||||
confirms, extends or contradicts, and name the owners of affected pages.
|
|
||||||
Ingest had been additive-first, which is how a wiki accumulates two pages
|
|
||||||
that quietly disagree.
|
|
||||||
- `ckb-retrieve` fuses graph in-degree as one ranked list among several,
|
|
||||||
weighted below 1.0 — centrality is a prior, not evidence.
|
|
||||||
- Rule E (session start) now runs `lint_report.py --quick` alongside
|
|
||||||
`git status`: a deterministic one-line knowledge-rot signal costing no
|
|
||||||
model tokens.
|
|
||||||
- Lint gained checks 12 (fingerprint drift), 13 (crux verbatimness) and 14
|
|
||||||
(the protected-`## Notes` rule).
|
|
||||||
|
|
||||||
**Compatibility:** fully additive. A 1.4 page with none of the new sections or
|
|
||||||
fields is valid 1.5. A migration will never manufacture a `## Crux` —
|
|
||||||
inventing quotes is precisely the failure that section exists to prevent.
|
|
||||||
|
|
||||||
### 1.4 — 2026-09-01 · decision records
|
|
||||||
|
|
||||||
**Added:** `type: decision` pages under `wiki/decisions/` as `NNNN-slug.md`,
|
|
||||||
with `status`, `decided_on`, `decided_by`, `affects`, `review_on`; the
|
|
||||||
`decided_by` and `affects` edge verbs; `wiki/decisions/index.md` and its own
|
|
||||||
`log.md`; the append-only rule. Answers "why is it like this", "who decided",
|
|
||||||
and "what changed that decision" as direct lookups instead of a full-text
|
|
||||||
guess. Owned by the `ckb-decide` skill.
|
|
||||||
|
|
||||||
### 1.3 — 2026-08-06 · people-to-topic edges
|
|
||||||
|
|
||||||
**Added:** the `has_expertise_in` and `owns` edge verbs, making "who knows
|
|
||||||
about X" and "who owns X" a graph lookup rather than a full-text search.
|
|
||||||
Recorded only from demonstrated evidence — attending a meeting is not
|
|
||||||
expertise, and a job title is not ownership.
|
|
||||||
|
|
||||||
### 1.2 — 2026-07-29 · query scopes, gaps, and connector-backed libs
|
|
||||||
|
|
||||||
**Added:** `wiki/projects/` (optional local query scopes grouping related
|
|
||||||
pages, sources and graph areas); `wiki/query-gaps.md` (failed searches
|
|
||||||
recorded as future ingest targets); `raw/archive/<YYYY-MM-DD>/` as the
|
|
||||||
agent-maintained filing destination; connector-backed `libs/<name>/` with a
|
|
||||||
user-authored `source.yaml`, an agent-owned generated index, and the
|
|
||||||
`mentioned_in` edge verb used inside those indexes.
|
|
||||||
|
|
||||||
### 1.1 — 2026-07-13 · initial schema
|
|
||||||
|
|
||||||
The first versioned contract, shipped with the initial commit. Established
|
|
||||||
the frontmatter field set (`type`, `resource`, `tldr`, `confidence`,
|
|
||||||
`quality`, `supersedes`/`superseded_by`, `last_updated`,
|
|
||||||
`freshness_window_days`, `retention`), the core edge verbs (`uses`,
|
|
||||||
`depends_on`, `caused`, `contradicts`, `supersedes`), the `wiki/` scaffold,
|
|
||||||
the cascade priority rule, and the recursive index-and-log convention.
|
|
||||||
|
|
||||||
There was never a 1.0: versioning began with the first published schema.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Template version history
|
|
||||||
|
|
||||||
The tooling layer — `AGENTS.md`/`CLAUDE.md`, the skills, the scripts, the
|
|
||||||
docs. Independent of the content schema above.
|
|
||||||
|
|
||||||
| Version | Date | What landed | Schema |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **1.9.0** | 2026-09-22 | Release channels: `main`/`test`/`experimental` branches, branch-aware `ckb-init` and `ckb-upgrade`, `template:` block in `ckb.yaml` | 1.5 |
|
|
||||||
| 1.8.0 | 2026-09-21 | Seven ideas adopted from Graft: crux/notes/fingerprints, `--quick` lint mode, in-degree ranking, ingest blast radius, completed edge vocabulary | → 1.5 |
|
|
||||||
| 1.7.0 | 2026-09-20 | Opt-in modules (`.agents/modules/`, `ckb-module`, `ckb.yaml`); the `software` module with `ckb-code-map` and `ckb-spec`; `ckb-reset`; OpenSpec docs | 1.4 |
|
|
||||||
| 1.6.1 | 2026-09-01 | Fixed false-positive broken-edge findings in the lint graph check | 1.4 |
|
|
||||||
| 1.6.0 | 2026-09-01 | `ckb-decide`; detection half of lint moved into `lint_report.py`; OKF export moved into `export_okf.py` | → 1.4 |
|
|
||||||
| 1.3.0 | 2026-08-06 | Rank fusion, dedupe and rerank in `ckb-retrieve`; expertise and ownership lookups | → 1.3 |
|
|
||||||
| 1.2.1 | 2026-07-29 | `AGENTS.md` compressed — workflows moved out into skills, leaving a small always-loaded rule set | 1.2 |
|
|
||||||
| 1.2.0 | 2026-07-29 | Project scopes, query gaps, source-verified retrieval | → 1.2 |
|
|
||||||
| 1.1.0 | 2026-07-20 | Connector-backed `libs/` with self-contained external source indexing (`ckb-index-external`) | 1.1 \* |
|
|
||||||
| 1.0.0 | 2026-07-17 | First tagged template: full skill set, `LICENSE`, `MANUAL`, bilingual docs | 1.1 |
|
|
||||||
|
|
||||||
\* Connector-backed libs shipped as tooling in 1.1.0, but the schema only
|
|
||||||
recorded them — the `source.yaml` contract, the generated index shape, the
|
|
||||||
`mentioned_in` verb — at 1.2, nine days later. The two numbers catching up
|
|
||||||
with each other like this is normal, and is why the schema column shows what
|
|
||||||
was in effect *after* each template release rather than what the release was
|
|
||||||
about.
|
|
||||||
|
|
||||||
Versions 1.4.0 and 1.5.0 were never published — the template jumped from
|
|
||||||
1.3.0 to 1.6.0 on 2026-09-01.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Release channels
|
|
||||||
|
|
||||||
The template repo keeps three branches, and the version histories above track
|
|
||||||
`main` only:
|
|
||||||
|
|
||||||
| Branch | What it is | Who should track it |
|
|
||||||
|---|---|---|
|
|
||||||
| `main` | **Stable** — the released template | Everyone, by default |
|
|
||||||
| `test` | **Release candidate** — validated before merging to `main` | Anyone helping validate a release |
|
|
||||||
| `experimental` | **Development** — active work, may be broken or reverted | People developing the template itself |
|
|
||||||
|
|
||||||
The branch a KB tracks lives in `ckb.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
template:
|
|
||||||
repo: https://git.wierzbowa.cloud/michal/ckb.git
|
|
||||||
branch: main
|
|
||||||
```
|
|
||||||
|
|
||||||
`ckb-init` writes it, `ckb-upgrade` reads it as the default and updates it when
|
|
||||||
you switch. No `ckb.yaml` and no `template:` block both mean `main`.
|
|
||||||
|
|
||||||
One consequence worth knowing: a KB that took tooling from `test` or
|
|
||||||
`experimental` can sit on a `VERSION` that `main` has not released yet.
|
|
||||||
Comparing it against `main` then finds nothing newer — which is true, but is
|
|
||||||
**not** the same as "up to date", and `ckb-upgrade` reports it as "ahead"
|
|
||||||
rather than as current. Moving such a KB back to `main` is a *downgrade*: it
|
|
||||||
can remove skills and lower `kb_schema_version` below what local pages are
|
|
||||||
written against. It requires an explicit confirmation, and is blocked outright
|
|
||||||
where the schema would drop below the content's own declared contract.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migrating between versions
|
|
||||||
|
|
||||||
Say **"upgrade the wiki"** or **"check for a newer template version"**. To use
|
|
||||||
a different channel, name it: *"upgrade from the test branch"*, *"check
|
|
||||||
experimental"*, *"switch back to stable"*. The
|
|
||||||
`ckb-upgrade` skill checks the canonical template repo, updates the tooling
|
|
||||||
layer in place, and — separately, and only after you explicitly confirm —
|
|
||||||
migrates existing `wiki/` content up to the current schema while preserving
|
|
||||||
every fact already collected.
|
|
||||||
|
|
||||||
The two halves are deliberately separate. Taking newer skills never rewrites
|
|
||||||
your pages, and a content migration is never silent: it reports what it
|
|
||||||
intends to change, batches anything it had to infer (such as a missing `type`)
|
|
||||||
for your confirmation, and logs every touched page in `wiki/log.md` marked as
|
|
||||||
a schema-migration backfill rather than new knowledge.
|
|
||||||
|
|
||||||
Because every schema version so far has been additive, an older wiki keeps
|
|
||||||
working unmigrated. Migration is worth doing to make the newer checks
|
|
||||||
meaningful — backfilled fingerprints are what give lint anything to verify —
|
|
||||||
not because anything is broken without it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Version & License
|
|
||||||
|
|
||||||
Current template version: [VERSION](VERSION). Current schema version: the
|
|
||||||
`kb_schema_version` field in `wiki/index.md`. Licensed under the
|
|
||||||
[Apache License 2.0](LICENSE).
|
|
||||||
377
CHANGELOG.pl.md
377
CHANGELOG.pl.md
|
|
@ -1,377 +0,0 @@
|
||||||
# Historia zmian i referencja schematu
|
|
||||||
|
|
||||||
*Przeczytaj to w: [English](CHANGELOG.md) | **Polski***
|
|
||||||
|
|
||||||
Ten plik śledzi dwie rzeczy: schemat strony dokładnie w takiej postaci, w
|
|
||||||
jakiej obowiązuje dziś, oraz to, jak projekt doszedł do swoich obecnych
|
|
||||||
numerów wersji.
|
|
||||||
|
|
||||||
Istnieją **dwa niezależne numery wersji** i nie są tym samym:
|
|
||||||
|
|
||||||
| Numer | Mieszka w | Opisuje | Kto go podnosi |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `kb_schema_version` | frontmatter `wiki/index.md` | **kontrakt treści** — co strona może zawierać i co te pola znaczą | `ckb-upgrade` (przy potwierdzonej migracji), `ckb-module` (addytywnie, przy instalacji) |
|
|
||||||
| Wersja szablonu | [`VERSION`](VERSION) | **warstwa narzędziowa** — `AGENTS.md`, skille, skrypty, dokumentacja | `ckb-upgrade`, gdy pobiera nowszy szablon |
|
|
||||||
|
|
||||||
Poruszają się niezależnie i jest to celowe. Możesz wziąć nowszy zestaw skilli
|
|
||||||
bez dotykania choćby jednej strony wiki, a wiki napisane pod starszy schemat
|
|
||||||
nadal działa — po to właśnie jest wersja schematu.
|
|
||||||
|
|
||||||
**Polityka wersjonowania.** Podnoś wersję **pomniejszą** przy zmianie
|
|
||||||
addytywnej: nowe pole opcjonalne, nowa opcjonalna sekcja treści, nowy
|
|
||||||
opcjonalny plik szkieletu. Podnoś wersję **główną** przy zmianie łamiącej
|
|
||||||
kompatybilność: zmiana lub usunięcie pola wymaganego albo zmiana istniejącej
|
|
||||||
zastrzeżonej konwencji nazw plików. Nigdy nie było podniesienia wersji
|
|
||||||
głównej — każda dotychczasowa wersja schematu była addytywna, więc dowolna
|
|
||||||
strona napisana od 2026-07-13 jest dziś nadal poprawna.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Spis treści
|
|
||||||
|
|
||||||
1. [Aktualny schemat strony (1.5)](#aktualny-schemat-strony-kb_schema_version-15)
|
|
||||||
2. [Historia wersji schematu KB](#historia-wersji-schematu-kb)
|
|
||||||
3. [Historia wersji szablonu](#historia-wersji-szablonu)
|
|
||||||
4. [Migracja między wersjami](#migracja-między-wersjami)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Aktualny schemat strony (`kb_schema_version: "1.5"`)
|
|
||||||
|
|
||||||
### Frontmatter — każda strona niezastrzeżona
|
|
||||||
|
|
||||||
Każdy plik `.md` w `wiki/` poza zastrzeżonymi (`index.md`, `log.md`) niesie
|
|
||||||
frontmatter YAML. Wymagane jest wyłącznie `type`; reszta jest opcjonalna i
|
|
||||||
preferowana tam, gdzie ma sens. Parser jest celowo prosty — wyłącznie płaskie
|
|
||||||
pary `klucz: wartość`, bez zagnieżdżeń.
|
|
||||||
|
|
||||||
| Pole | Wymagane | Znaczenie |
|
|
||||||
|---|---|---|
|
|
||||||
| `type` | **tak** | Otwarty ciąg: `person`, `project`, `concept`, `library`, `decision`, `playbook`, `repository`, `component`, … Nowe wartości są zawsze poprawne; czytelnicy tolerują nierozpoznane. |
|
|
||||||
| `resource` | nie | Kanoniczny URI autorytatywnego źródła zewnętrznego, które opisuje ta strona, trzymany oddzielnie od własnego komentarza wiki. |
|
|
||||||
| `tldr` | nie | Jednozdaniowe streszczenie zoptymalizowane pod odczyt przez LLM. To ono trafia do indeksu i decyduje, czy strona w ogóle zostanie otwarta. |
|
|
||||||
| `confidence` | nie | `0.0`–`1.0`. Potwierdzenie przez źródła. Ustawiane przy zapisie, zanika, jeśli nic go nie wzmacnia, rośnie przy nowym zgodnym źródle. |
|
|
||||||
| `quality` | nie | `0.0`–`1.0`. Samoocena struktury i cytowań samej strony. Poniżej `0.7` oflagowane do przeglądu. |
|
|
||||||
| `supersedes` | nie | Ścieżka do starszej strony, którą ta zastępuje. |
|
|
||||||
| `superseded_by` | nie | Ścieżka do nowszej strony, która zastąpiła tę. **Zawsze ustawiaj obie strony.** |
|
|
||||||
| `last_updated` | nie | `YYYY-MM-DD`. Kiedy zmieniła się *strona*. |
|
|
||||||
| `freshness_window_days` | nie | Liczba dni, po której lint oznacza stronę jako nieaktualną. Typowo: 90 dla strony wiki, 365 dla decyzji, 30 dla dokumentu z indeksu konektora. |
|
|
||||||
| `retention` | nie | `high` / `medium` / `low`. Strona `low` jest archiwizowana (nigdy usuwana) po 2× swoim oknie świeżości. |
|
|
||||||
| `source_fingerprint` | nie | *(1.5)* Skrót źródła, z którego zbudowano stronę — `sha256:<8 hex>` dla pliku lokalnego albo `etag:<wartość>` / `mtime:<iso8601>` dla elementu z konektora. |
|
|
||||||
| `source_checked` | nie | *(1.5)* `YYYY-MM-DD` — kiedy ten skrót był ostatnio zweryfikowany. To co innego niż `last_updated`: ponowne sprawdzenie, które nie wykryło zmiany, przesuwa to pole i zostawia `last_updated` w spokoju. |
|
|
||||||
|
|
||||||
Wyłącznie `wiki/index.md` niesie `kb_schema_version`. To deklaracja na
|
|
||||||
poziomie całego zbioru, nie pojedynczej strony — same strony nigdy jej nie
|
|
||||||
niosą.
|
|
||||||
|
|
||||||
### Frontmatter — strony decyzji
|
|
||||||
|
|
||||||
Strony z `type: decision` mieszkają w `wiki/decisions/` jako `NNNN-slug.md` i
|
|
||||||
dodają:
|
|
||||||
|
|
||||||
| Pole | Wymagane | Znaczenie |
|
|
||||||
|---|---|---|
|
|
||||||
| `status` | **tak** | `proposed` / `accepted` / `rejected` / `superseded` / `reversed`. Słownik zdefiniowany w `wiki/decisions/index.md` i walidowany przez lint. |
|
|
||||||
| `decided_on` | dla `accepted`/`rejected`/`reversed` | `YYYY-MM-DD`, data podjęcia decyzji. |
|
|
||||||
| `decided_by` | gdy wiadomo | Nazwiska po przecinku. Gdy naprawdę nie wiadomo, wpisz `unknown` zamiast pomijać pole — „nie wiemy, kto to zdecydował" samo w sobie warto zapisać. |
|
|
||||||
| `affects` | nie | Ścieżki wiki po przecinku, które ta decyzja ogranicza. |
|
|
||||||
| `review_on` | nie | `YYYY-MM-DD` do ponownego rozważenia. Lint zgłasza je po przekroczeniu daty. |
|
|
||||||
|
|
||||||
Rekordy decyzji są **tylko do dopisywania**. Decyzji nigdy nie przepisuje się
|
|
||||||
pod późniejszą zmianę zdania: zapisz nową, która ją zastępuje, a obie zostają
|
|
||||||
na wokandzie.
|
|
||||||
|
|
||||||
### Zastrzeżone sekcje treści
|
|
||||||
|
|
||||||
*(Nowość w 1.5.)* Cztery nagłówki `##` znaczą to samo na każdej stronie w
|
|
||||||
każdej warstwie kaskady. Wszystkie są opcjonalne; tam, gdzie występują,
|
|
||||||
znaczą dokładnie to i nic innego.
|
|
||||||
|
|
||||||
#### `## Sources`
|
|
||||||
|
|
||||||
Skąd wzięła się strona. Po jednym punkcie na źródło, każdy ze skrótem:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- `raw/archive/2026-09-21/kickoff-notes.md` — sha256:3f9a2c1e (checked 2026-09-21)
|
|
||||||
```
|
|
||||||
|
|
||||||
Okno świeżości to przypuszczenie, że źródło *mogło* się zmienić. Skrót to
|
|
||||||
fakt, czy *się zmieniło*. Lint przelicza skróty lokalne i oznacza to, co
|
|
||||||
faktycznie się zmieniło — a to inne i pilniejsze znalezisko niż strona, która
|
|
||||||
jedynie się zestarzała.
|
|
||||||
|
|
||||||
#### `## Crux`
|
|
||||||
|
|
||||||
Dosłowne fragmenty tych źródeł — dowód, nigdy parafraza — przypisane do
|
|
||||||
punktu źródła, z którego pochodzą:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Crux
|
|
||||||
|
|
||||||
> FDEs need the VDI *and* a Jira account before day one; the VDI request
|
|
||||||
> alone takes ten working days.
|
|
||||||
— `raw/archive/2026-09-21/kickoff-notes.md`, sekcja „Access"
|
|
||||||
```
|
|
||||||
|
|
||||||
Roboczy zakres to od trzech do dziesięciu linijek. Crux zbliżający się
|
|
||||||
długością do streszczenia nad nim przestał być dowodem i stał się drugą kopią
|
|
||||||
źródła. Z cytowania zamiast parafrazowania wynikają dwie rzeczy: na pytanie
|
|
||||||
często da się odpowiedzieć ze strony zamiast z archiwum, a odpływ od źródła
|
|
||||||
staje się widoczny — streszczenie może po cichu oddalić się od źródła, cytat
|
|
||||||
albo wciąż się zgadza, albo nie.
|
|
||||||
|
|
||||||
Strona bez cytowalnego źródła po prostu nie ma `## Crux`. Pusty albo
|
|
||||||
sparafrazowany jest gorszy niż żaden, bo wygląda jak dowód.
|
|
||||||
|
|
||||||
#### `## Notes`
|
|
||||||
|
|
||||||
Pisane przez człowieka i **chronione**. Żaden skill nie może tej sekcji
|
|
||||||
nadpisać, przeformatować, streścić ani usunąć; regeneracja zachowuje ją co do
|
|
||||||
bajtu.
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
<!-- Twoje. Żaden skill tego nie nadpisuje. -->
|
|
||||||
```
|
|
||||||
|
|
||||||
Ma to największe znaczenie na stronach, które agent *regeneruje* — indeksy
|
|
||||||
konektorów, mapy kodu — gdzie cała reszta jest odrzucana i budowana od nowa
|
|
||||||
przy kolejnym przebiegu. To jedyne miejsce, w którym adnotacja przetrwa.
|
|
||||||
Strony decyzji celowo nie mają `## Notes`: nic ich nie regeneruje, a rekord
|
|
||||||
tylko-do-dopisywania ze swobodnie edytowalnym blokiem adnotacji zaprasza
|
|
||||||
dokładnie do tej wstecznej korekty, której zasada append-only ma zapobiegać.
|
|
||||||
|
|
||||||
### Słownik krawędzi
|
|
||||||
|
|
||||||
Relacje mieszkają w `wiki/graph/edges.json` (oraz we własnym
|
|
||||||
`graph/edges.json` każdego indeksu konektora). Słownik jest zamknięty, a każdy
|
|
||||||
czasownik zdefiniowany przez pytanie, na jakie odpowiada — jeśli proponowana
|
|
||||||
krawędź nie odpowiada na żadne z nich, jej miejsce jest w tekście strony.
|
|
||||||
|
|
||||||
| Czasownik | Pytanie, na jakie odpowiada | Od |
|
|
||||||
|---|---|---|
|
|
||||||
| `part_of` | Gdzie to mieszka? Czego jest częścią? | 1.5 |
|
|
||||||
| `uses` | Po co to sięga w czasie działania? | 1.1 |
|
|
||||||
| `depends_on` | Co się zepsuje, jeśli to zmienię? | 1.1 |
|
|
||||||
| `produces` | Skąd bierze się ten wynik? | 1.5 |
|
|
||||||
| `configures` | Co zmienia zachowanie tej rzeczy? | 1.5 |
|
|
||||||
| `validates` | Co to sprawdza, testuje albo ocenia? | 1.5 |
|
|
||||||
| `implements` | Jakiego kontraktu to musi dotrzymać? | 1.5 |
|
|
||||||
| `caused` | Dlaczego to się stało? | 1.1 |
|
|
||||||
| `contradicts` | Co jest z tym sprzeczne i nierozstrzygnięte? | 1.1 |
|
|
||||||
| `supersedes` | Co to zastąpiło i co ono zastąpiło? | 1.1 |
|
|
||||||
| `decided_by` | Kto podjął tę decyzję? | 1.4 |
|
|
||||||
| `affects` | Co ta decyzja ogranicza? | 1.4 |
|
|
||||||
| `has_expertise_in` | Kto potrafi odpowiedzieć na pytania o to? | 1.3 |
|
|
||||||
| `owns` | Kto za to odpowiada? | 1.3 |
|
|
||||||
| `mentioned_in` | Który dokument źródłowy o tym mówi? | 1.2 (tylko indeksy libs) |
|
|
||||||
|
|
||||||
Zapisuj po jednym kierunku na relację — `part_of`, `supersedes`,
|
|
||||||
`depends_on` i `uses` są kanoniczne, a odwrotność nie jest przechowywana jako
|
|
||||||
druga krawędź. Zapisuj krawędzie wyłącznie na podstawie wykazanych dowodów;
|
|
||||||
brak krawędzi jest lepszy niż krawędź zmyślona, a napompowany graf pogarsza
|
|
||||||
wyszukiwanie zamiast je poprawiać (stopień wejściowy jest sygnałem
|
|
||||||
rankingowym).
|
|
||||||
|
|
||||||
### Zastrzeżony szkielet
|
|
||||||
|
|
||||||
| Ścieżka | Od | Do czego służy |
|
|
||||||
|---|---|---|
|
|
||||||
| `wiki/index.md` | 1.1 | Tabela routingu; jedyna strona niosąca `kb_schema_version` |
|
|
||||||
| `wiki/overview.md` | 1.1 | Mapa KB z lotu ptaka |
|
|
||||||
| `wiki/log.md` | 1.1 | Dziennik zmian `wiki/` w odwrotnej chronologii |
|
|
||||||
| `wiki/error-book.md` | 1.1 | Problemy systemowe wraz z przyczyną i naprawą |
|
|
||||||
| `wiki/entities/` | 1.1 | Typowane strony encji |
|
|
||||||
| `wiki/graph/` | 1.1 | `edges.json` plus słownik w `index.md` |
|
|
||||||
| `wiki/query-gaps.md` | 1.2 | Pytania, na które wiki nie umiało odpowiedzieć, napędzające ingest sterowany popytem |
|
|
||||||
| `wiki/projects/` | 1.2 | Opcjonalne lokalne zakresy zapytań |
|
|
||||||
| `wiki/decisions/` | 1.4 | Numerowane rekordy decyzji tylko-do-dopisywania, z własnym `log.md` |
|
|
||||||
|
|
||||||
Każdy podkatalog grupujący strony niesie własny `index.md`, dzięki czemu
|
|
||||||
nawigacja pozostaje leniwa.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Historia wersji schematu KB
|
|
||||||
|
|
||||||
### 1.5 — 2026-09-21 · dowody, skróty źródeł i dokończony słownik krawędzi
|
|
||||||
|
|
||||||
Zaadaptowane z analizy [trailhq/Graft](https://github.com/trailhq/Graft),
|
|
||||||
warstwy kontekstu dla agentów kodujących, która utrzymuje wyprowadzony graf
|
|
||||||
kodu w zgodzie ze źródłem przez skróty treści, a nie daty, i chroni blok
|
|
||||||
pisany przez użytkownika na każdym regenerowanym węźle. Magazyn Graftu jest
|
|
||||||
jednorazowy i odtwarzalny; ten nie jest, więc większość jego projektu się nie
|
|
||||||
przenosi — ale kilka mechanizmów tak, a dwa z nich zamknęły tu realne luki.
|
|
||||||
|
|
||||||
**Dodano:**
|
|
||||||
|
|
||||||
- **`## Crux`** — dosłowne fragmenty źródeł obok syntezy. Pozwala
|
|
||||||
`ckb-retrieve` ugruntować odpowiedź bez powrotu do archiwum (gdy skrót się
|
|
||||||
wciąż zgadza) i czyni odpływ od źródła wykrywalnym.
|
|
||||||
- **`## Notes`** — pisane przez człowieka i chronione wszędzie. To zamknęło
|
|
||||||
realną lukę: `ckb-index-external` regeneruje strony konektora w całości,
|
|
||||||
więc napisana tam adnotacja ginęła dotąd przy kolejnym odświeżeniu.
|
|
||||||
- **`## Sources`** — sformalizowane jako sekcja zastrzeżona z jednym
|
|
||||||
punktem ze skrótem na źródło. Wcześniej nieformalna konwencja, na której
|
|
||||||
`ckb-retrieve` polegało, ale której żaden skill faktycznie nie określał.
|
|
||||||
- **`source_fingerprint` / `source_checked`** we frontmatterze.
|
|
||||||
- **Czasowniki krawędzi** `part_of`, `produces`, `configures`, `validates`,
|
|
||||||
`implements`. `part_of` naprawiło żywą niespójność: `ckb-code-map`
|
|
||||||
zapisywał go od szablonu 1.7.0, a schemat nigdy go nie deklarował.
|
|
||||||
|
|
||||||
**Zmieniono:**
|
|
||||||
|
|
||||||
- `wiki/graph/index.md` przepisany jako tabela pytanie-na-czasownik wraz z
|
|
||||||
konwencjami dotyczącymi kierunku krawędzi i dowodów.
|
|
||||||
- `ckb-ingest` dostał krok promienia rażenia: przed zapisem przejdź graf
|
|
||||||
wstecz od dotkniętych encji, żeby ustalić, co nadchodzący materiał
|
|
||||||
potwierdza, rozszerza albo z czym jest sprzeczny, i wskaż właścicieli
|
|
||||||
dotkniętych stron. Ingest był dotąd przede wszystkim addytywny, a tak
|
|
||||||
właśnie wiki gromadzi dwie strony, które po cichu się ze sobą nie zgadzają.
|
|
||||||
- `ckb-retrieve` wtapia stopień wejściowy grafu jako jedną z rankowanych list,
|
|
||||||
z wagą poniżej 1.0 — centralność jest przesłanką, nie dowodem.
|
|
||||||
- Reguła E (start sesji) uruchamia teraz `lint_report.py --quick` obok
|
|
||||||
`git status`: deterministyczny jednoliniowy sygnał gnicia wiedzy, niekosztujący
|
|
||||||
żadnych tokenów modelu.
|
|
||||||
- Lint zyskał kontrole 12 (odpływ skrótu źródła), 13 (dosłowność sekcji Crux)
|
|
||||||
i 14 (zasada chronionego `## Notes`).
|
|
||||||
|
|
||||||
**Kompatybilność:** w pełni addytywna. Strona 1.4 bez żadnej z nowych sekcji
|
|
||||||
i pól jest poprawną stroną 1.5. Migracja nigdy nie wytworzy `## Crux` —
|
|
||||||
zmyślanie cytatów to dokładnie ta porażka, której ta sekcja ma zapobiegać.
|
|
||||||
|
|
||||||
### 1.4 — 2026-09-01 · rekordy decyzji
|
|
||||||
|
|
||||||
**Dodano:** strony `type: decision` w `wiki/decisions/` jako `NNNN-slug.md`,
|
|
||||||
z `status`, `decided_on`, `decided_by`, `affects`, `review_on`; czasowniki
|
|
||||||
krawędzi `decided_by` i `affects`; `wiki/decisions/index.md` wraz z własnym
|
|
||||||
`log.md`; zasadę append-only. Odpowiada na „dlaczego jest tak, jak jest",
|
|
||||||
„kto zdecydował" i „co zmieniło tę decyzję" bezpośrednim wyszukaniem zamiast
|
|
||||||
zgadywania pełnotekstowego. Właścicielem jest skill `ckb-decide`.
|
|
||||||
|
|
||||||
### 1.3 — 2026-08-06 · krawędzie osoba–temat
|
|
||||||
|
|
||||||
**Dodano:** czasowniki krawędzi `has_expertise_in` i `owns`, dzięki którym
|
|
||||||
„kto wie o X" i „kto jest właścicielem X" to wyszukanie w grafie, a nie
|
|
||||||
przeszukiwanie pełnotekstowe. Zapisywane wyłącznie na podstawie wykazanych
|
|
||||||
dowodów — obecność na spotkaniu to nie ekspertyza, a stanowisko to nie
|
|
||||||
własność.
|
|
||||||
|
|
||||||
### 1.2 — 2026-07-29 · zakresy zapytań, luki i libs oparte na konektorach
|
|
||||||
|
|
||||||
**Dodano:** `wiki/projects/` (opcjonalne lokalne zakresy zapytań grupujące
|
|
||||||
powiązane strony, źródła i obszary grafu); `wiki/query-gaps.md` (nieudane
|
|
||||||
wyszukiwania zapisane jako przyszłe cele ingestu);
|
|
||||||
`raw/archive/<YYYY-MM-DD>/` jako utrzymywane przez agenta miejsce
|
|
||||||
archiwizacji; `libs/<name>/` oparte na konektorach, z pisanym przez
|
|
||||||
użytkownika `source.yaml`, należącym do agenta generowanym indeksem oraz
|
|
||||||
czasownikiem krawędzi `mentioned_in` używanym wewnątrz tych indeksów.
|
|
||||||
|
|
||||||
### 1.1 — 2026-07-13 · pierwszy schemat
|
|
||||||
|
|
||||||
Pierwszy wersjonowany kontrakt, wydany wraz z pierwszym commitem. Ustanowił
|
|
||||||
zestaw pól frontmatteru (`type`, `resource`, `tldr`, `confidence`, `quality`,
|
|
||||||
`supersedes`/`superseded_by`, `last_updated`, `freshness_window_days`,
|
|
||||||
`retention`), podstawowe czasowniki krawędzi (`uses`, `depends_on`, `caused`,
|
|
||||||
`contradicts`, `supersedes`), szkielet `wiki/`, zasadę priorytetu kaskady i
|
|
||||||
rekurencyjną konwencję indeksu i dziennika.
|
|
||||||
|
|
||||||
Wersji 1.0 nigdy nie było: wersjonowanie zaczęło się od pierwszego
|
|
||||||
opublikowanego schematu.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Historia wersji szablonu
|
|
||||||
|
|
||||||
Warstwa narzędziowa — `AGENTS.md`/`CLAUDE.md`, skille, skrypty, dokumentacja.
|
|
||||||
Niezależna od schematu treści powyżej.
|
|
||||||
|
|
||||||
| Wersja | Data | Co weszło | Schemat |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **1.9.0** | 2026-09-22 | Kanały wydawnicze: gałęzie `main`/`test`/`experimental`, świadome gałęzi `ckb-init` i `ckb-upgrade`, blok `template:` w `ckb.yaml` | 1.5 |
|
|
||||||
| 1.8.0 | 2026-09-21 | Siedem pomysłów zaadaptowanych z Graftu: crux/notes/skróty źródeł, tryb `--quick` lintu, ranking po stopniu wejściowym, promień rażenia w ingeście, dokończony słownik krawędzi | → 1.5 |
|
|
||||||
| 1.7.0 | 2026-09-20 | Opcjonalne moduły (`.agents/modules/`, `ckb-module`, `ckb.yaml`); moduł `software` z `ckb-code-map` i `ckb-spec`; `ckb-reset`; dokumentacja OpenSpec | 1.4 |
|
|
||||||
| 1.6.1 | 2026-09-01 | Naprawa fałszywie dodatnich znalezisk uszkodzonych krawędzi w kontroli grafu | 1.4 |
|
|
||||||
| 1.6.0 | 2026-09-01 | `ckb-decide`; wykrywająca połowa lintu przeniesiona do `lint_report.py`; eksport OKF przeniesiony do `export_okf.py` | → 1.4 |
|
|
||||||
| 1.3.0 | 2026-08-06 | Fuzja rankingów, deduplikacja i ponowny ranking w `ckb-retrieve`; wyszukiwanie ekspertyzy i własności | → 1.3 |
|
|
||||||
| 1.2.1 | 2026-07-29 | `AGENTS.md` skompresowany — przepływy przeniesione do skilli, zostawiając mały, zawsze ładowany zestaw reguł | 1.2 |
|
|
||||||
| 1.2.0 | 2026-07-29 | Zakresy projektów, luki zapytań, wyszukiwanie weryfikowane względem źródła | → 1.2 |
|
|
||||||
| 1.1.0 | 2026-07-20 | `libs/` oparte na konektorach z samodzielnym indeksowaniem źródeł zewnętrznych (`ckb-index-external`) | 1.1 \* |
|
|
||||||
| 1.0.0 | 2026-07-17 | Pierwszy otagowany szablon: pełny zestaw skilli, `LICENSE`, `MANUAL`, dokumentacja dwujęzyczna | 1.1 |
|
|
||||||
|
|
||||||
\* `libs/` oparte na konektorach weszły jako narzędzia w 1.1.0, ale schemat
|
|
||||||
zapisał je — kontrakt `source.yaml`, kształt generowanego indeksu, czasownik
|
|
||||||
`mentioned_in` — dopiero w 1.2, dziewięć dni później. Takie doganianie się
|
|
||||||
tych dwóch numerów jest normalne i dlatego kolumna schematu pokazuje, co
|
|
||||||
obowiązywało *po* danym wydaniu szablonu, a nie czego to wydanie dotyczyło.
|
|
||||||
|
|
||||||
Wersje 1.4.0 i 1.5.0 nigdy nie zostały opublikowane — szablon przeskoczył z
|
|
||||||
1.3.0 na 1.6.0 dnia 2026-09-01.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Kanały wydawnicze
|
|
||||||
|
|
||||||
Repozytorium szablonu utrzymuje trzy gałęzie, a powyższe historie wersji
|
|
||||||
śledzą wyłącznie `main`:
|
|
||||||
|
|
||||||
| Gałąź | Czym jest | Kto powinien ją śledzić |
|
|
||||||
|---|---|---|
|
|
||||||
| `main` | **Stabilna** — wydany szablon | Wszyscy, domyślnie |
|
|
||||||
| `test` | **Kandydat do wydania** — walidowany przed scaleniem do `main` | Każdy, kto pomaga walidować wydanie |
|
|
||||||
| `experimental` | **Rozwojowa** — bieżąca praca, może być zepsuta albo wycofana | Osoby rozwijające sam szablon |
|
|
||||||
|
|
||||||
Śledzona przez KB gałąź mieszka w `ckb.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
template:
|
|
||||||
repo: https://git.wierzbowa.cloud/michal/ckb.git
|
|
||||||
branch: main
|
|
||||||
```
|
|
||||||
|
|
||||||
`ckb-init` ją zapisuje, `ckb-upgrade` czyta ją jako domyślną i aktualizuje przy
|
|
||||||
przełączeniu. Brak `ckb.yaml` i brak bloku `template:` oznaczają `main`.
|
|
||||||
|
|
||||||
Jedna konsekwencja warta poznania: KB, która wzięła narzędzia z `test` albo
|
|
||||||
`experimental`, może mieć `VERSION` wyższą niż to, co `main` w ogóle wydało.
|
|
||||||
Porównanie z `main` nie znajdzie wtedy nic nowszego — co jest prawdą, ale
|
|
||||||
**nie** znaczy „aktualne", i `ckb-upgrade` raportuje to jako „wyprzedza", a nie
|
|
||||||
jako bieżące. Cofnięcie takiej KB na `main` to *downgrade*: może usunąć skille
|
|
||||||
i obniżyć `kb_schema_version` poniżej tego, pod co napisano lokalne strony.
|
|
||||||
Wymaga wyraźnego potwierdzenia, a gdy schemat spadłby poniżej kontraktu
|
|
||||||
zadeklarowanego przez treść — jest blokowane.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migracja między wersjami
|
|
||||||
|
|
||||||
Powiedz **„upgrade the wiki"** albo **„check for a newer template version"**.
|
|
||||||
Aby użyć innego kanału, nazwij go: *„upgrade from the test branch"*, *„check
|
|
||||||
experimental"*, *„switch back to stable"*.
|
|
||||||
Skill `ckb-upgrade` sprawdza kanoniczne repozytorium szablonu, aktualizuje
|
|
||||||
warstwę narzędziową w miejscu i — osobno, i wyłącznie po twoim wyraźnym
|
|
||||||
potwierdzeniu — migruje istniejącą treść `wiki/` do bieżącego schematu,
|
|
||||||
zachowując każdy już zebrany fakt.
|
|
||||||
|
|
||||||
Te dwie połowy są celowo rozdzielone. Wzięcie nowszych skilli nigdy nie
|
|
||||||
przepisuje twoich stron, a migracja treści nigdy nie jest cicha: zgłasza, co
|
|
||||||
zamierza zmienić, zbiera wszystko, co musiała wywnioskować (na przykład
|
|
||||||
brakujące `type`), do twojego potwierdzenia i loguje każdą dotkniętą stronę w
|
|
||||||
`wiki/log.md` z adnotacją, że to uzupełnienie migracyjne schematu, a nie nowa
|
|
||||||
wiedza.
|
|
||||||
|
|
||||||
Ponieważ każda dotychczasowa wersja schematu była addytywna, starsze wiki
|
|
||||||
działa bez migracji. Migrację warto zrobić po to, żeby nowsze kontrole miały
|
|
||||||
sens — uzupełnione skróty źródeł dają lintowi cokolwiek do weryfikacji — a
|
|
||||||
nie dlatego, że bez niej coś jest zepsute.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Wersja i licencja
|
|
||||||
|
|
||||||
Aktualna wersja szablonu: [VERSION](VERSION). Aktualna wersja schematu: pole
|
|
||||||
`kb_schema_version` w `wiki/index.md`. Licencja:
|
|
||||||
[Apache License 2.0](LICENSE).
|
|
||||||
294
MANUAL.md
294
MANUAL.md
|
|
@ -6,9 +6,8 @@ This is the human-facing manual for working with a Cascade Knowledge Base
|
||||||
(this repo). It's written for the *person* using the wiki, not the agent —
|
(this repo). It's written for the *person* using the wiki, not the agent —
|
||||||
for the agent's own operating rules, see [AGENTS.md](AGENTS.md) /
|
for the agent's own operating rules, see [AGENTS.md](AGENTS.md) /
|
||||||
[CLAUDE.md](CLAUDE.md). For a feature-by-feature technical overview, see
|
[CLAUDE.md](CLAUDE.md). For a feature-by-feature technical overview, see
|
||||||
[README.md](README.md). For the page schema in full and the version
|
[README.md](README.md). This document is task-oriented: "I want to do X —
|
||||||
histories behind it, see [CHANGELOG.md](CHANGELOG.md). This document is
|
what do I say, and what happens?"
|
||||||
task-oriented: "I want to do X — what do I say, and what happens?"
|
|
||||||
|
|
||||||
Everywhere below, "say" means typing it to whatever AI coding agent you're
|
Everywhere below, "say" means typing it to whatever AI coding agent you're
|
||||||
using against this repo (Claude Code, or another agent that reads
|
using against this repo (Claude Code, or another agent that reads
|
||||||
|
|
@ -49,14 +48,6 @@ the target folder. It never copies this project's actual content (no
|
||||||
entities, no graph data, no notes). You get a fresh, empty KB, ready for its
|
entities, no graph data, no notes). You get a fresh, empty KB, ready for its
|
||||||
first `raw/inbox/` drop. See `.agents/skills/ckb-init/SKILL.md`.
|
first `raw/inbox/` drop. See `.agents/skills/ckb-init/SKILL.md`.
|
||||||
|
|
||||||
The template can come from two places: this repo's own files, or a fresh
|
|
||||||
shallow clone of the canonical template repo (or any fork/mirror URL you
|
|
||||||
name) pulled into a scratch folder. Say "pull the latest template and set
|
|
||||||
up a KB in \<folder\>" — or run it from outside any KB at all — and the
|
|
||||||
agent clones first, then builds the scaffold from that. The clone is
|
|
||||||
scratch only: the new KB gets its own git history (the agent asks before
|
|
||||||
running `git init`), not the template's.
|
|
||||||
|
|
||||||
If the target folder already looks like a KB (it has a `wiki/` or
|
If the target folder already looks like a KB (it has a `wiki/` or
|
||||||
`AGENTS.md` already), the agent will stop and ask before touching anything
|
`AGENTS.md` already), the agent will stop and ask before touching anything
|
||||||
— it won't silently overwrite an existing knowledge base.
|
— it won't silently overwrite an existing knowledge base.
|
||||||
|
|
@ -101,11 +92,7 @@ which stay entirely read-only. There are three ways to mount one:
|
||||||
- **You don't have to build the index yourself.** `source.yaml` can add
|
- **You don't have to build the index yourself.** `source.yaml` can add
|
||||||
an `index:` block pointing at an already-built index — a git repo, or
|
an `index:` block pointing at an already-built index — a git repo, or
|
||||||
a shared resource — so you just fetch what someone else already
|
a shared resource — so you just fetch what someone else already
|
||||||
indexed instead of scanning the live source yourself. Every run checks
|
indexed instead of scanning the live source yourself.
|
||||||
that location first: if there's already an index there, you get it; if
|
|
||||||
there isn't yet (the normal state before anyone's run this with write
|
|
||||||
access), that's not an error — whoever has write access is the one
|
|
||||||
whose next run creates and publishes it there for the first time.
|
|
||||||
- **Building/refreshing is opt-in per person, per source.** By default,
|
- **Building/refreshing is opt-in per person, per source.** By default,
|
||||||
everyone is read-only for a connector source — nobody's agent will
|
everyone is read-only for a connector source — nobody's agent will
|
||||||
scan the live connector on their behalf unless they've explicitly said
|
scan the live connector on their behalf unless they've explicitly said
|
||||||
|
|
@ -114,13 +101,6 @@ which stay entirely read-only. There are three ways to mount one:
|
||||||
committed, never seen by anyone else). This is deliberate: it lets one
|
committed, never seen by anyone else). This is deliberate: it lets one
|
||||||
or two people maintain a source for the whole team instead of everyone
|
or two people maintain a source for the whole team instead of everyone
|
||||||
redundantly re-scanning it.
|
redundantly re-scanning it.
|
||||||
- **You can set how often it should be refreshed.** Add an optional
|
|
||||||
`refresh_interval_days: 7` to `source.yaml` (it defaults to 30). A
|
|
||||||
folder that churns daily wants a shorter window than a quarterly
|
|
||||||
archive nobody touches. Both "index external sources" and "Lint" then
|
|
||||||
tell you when a source is overdue and by how much — which matters most
|
|
||||||
if you're read-only for it, since knowing *which* source has gone
|
|
||||||
stale is what lets you go ask the person who maintains it.
|
|
||||||
|
|
||||||
Either way, once it's mounted, just ask questions normally — the agent
|
Either way, once it's mounted, just ask questions normally — the agent
|
||||||
checks your local `wiki/` first, then walks `linked/`, then `libs/`, and
|
checks your local `wiki/` first, then walks `linked/`, then `libs/`, and
|
||||||
|
|
@ -159,17 +139,6 @@ Example:
|
||||||
> reminding you to review the result and say "sync changes" once you're
|
> reminding you to review the result and say "sync changes" once you're
|
||||||
> happy with it.
|
> happy with it.
|
||||||
|
|
||||||
For a long transcript, the agent doesn't just write one summary page. It
|
|
||||||
pulls out the searchable question, the summary, the resolution, the systems
|
|
||||||
and people involved — and promotes individual passages to their own
|
|
||||||
findable sections when they'd otherwise be lost inside a summary. That last
|
|
||||||
part has a deliberate bar: a passage has to contain a genuinely specific
|
|
||||||
term (a flag, an error string, a clause, a version), run to a couple of
|
|
||||||
sentences at least, and be corroborated by something later in the material.
|
|
||||||
Otherwise it stays folded into the summary. Without that bar every
|
|
||||||
paragraph looks quotable and the wiki page ends up being the transcript
|
|
||||||
again, which defeats the point of ingesting it.
|
|
||||||
|
|
||||||
If `raw/inbox/` is empty, the agent scans `raw/` directly instead (still
|
If `raw/inbox/` is empty, the agent scans `raw/` directly instead (still
|
||||||
skipping `raw/archive/`, which is already-processed history).
|
skipping `raw/archive/`, which is already-processed history).
|
||||||
|
|
||||||
|
|
@ -199,59 +168,6 @@ ingest incorporates it. This keeps the wiki demand-driven: it grows around
|
||||||
what you actually ask, not everything that could theoretically be written
|
what you actually ask, not everything that could theoretically be written
|
||||||
down.
|
down.
|
||||||
|
|
||||||
Durable misses can also be tracked in `wiki/query-gaps.md`. A good gap
|
|
||||||
entry is tiny: the question, where the agent looked, and the smallest
|
|
||||||
source or page that would make the answer available next time.
|
|
||||||
|
|
||||||
### D. Record a decision
|
|
||||||
|
|
||||||
When a call gets made — a technology choice, a process change, a policy —
|
|
||||||
say:
|
|
||||||
|
|
||||||
> "Record a decision: we're moving billing to Postgres. Alice and Bob
|
|
||||||
> decided it today, because the reporting queries were killing MySQL."
|
|
||||||
|
|
||||||
The agent writes a numbered record under `wiki/decisions/` with the decision,
|
|
||||||
who decided, when, the rationale, the alternatives, and what it affects. If
|
|
||||||
it replaces an earlier decision, it links the two in both directions and
|
|
||||||
marks the old one superseded — without touching the old page's reasoning.
|
|
||||||
Anything you leave out, it asks for in one round; if you're mid-flow, say so
|
|
||||||
and it records what you gave it and tells you which fields it left open.
|
|
||||||
|
|
||||||
Then ask about them however you like:
|
|
||||||
|
|
||||||
> "What did we decide about the billing database?"
|
|
||||||
> "Why do we use Postgres?"
|
|
||||||
> "Who decided that, and when?"
|
|
||||||
> "Which decisions are still just proposed?"
|
|
||||||
> "What superseded decision 3?"
|
|
||||||
|
|
||||||
The answer always comes with who and when attached, and says plainly when a
|
|
||||||
decision is proposed rather than accepted, or has since been superseded —
|
|
||||||
so you don't act on something that isn't in force. Implemented by the
|
|
||||||
`ckb-decide` skill.
|
|
||||||
|
|
||||||
Two things worth knowing:
|
|
||||||
|
|
||||||
- **Decisions are append-only.** "Actually, we changed our mind" creates a
|
|
||||||
*new* decision that supersedes the old one; it never edits the old one's
|
|
||||||
reasoning. That's deliberate — the history is the point. Genuine
|
|
||||||
transcription errors ("I said Alice, it was Anna") do get fixed in place.
|
|
||||||
- **A proposal is not a decision.** If it wasn't actually settled, it's
|
|
||||||
recorded as `proposed` with no decision date, and shows up when you ask
|
|
||||||
what's still open.
|
|
||||||
|
|
||||||
### E. Create a local project scope
|
|
||||||
|
|
||||||
When a topic, client, system, or initiative comes up often, ask:
|
|
||||||
|
|
||||||
> "Create a project scope for payments integration."
|
|
||||||
|
|
||||||
The agent creates or updates a plain Markdown page under `wiki/projects/`
|
|
||||||
listing the pages, entities, raw/archive files, connector indexes, and graph
|
|
||||||
areas that should be searched first for that scope. You still keep one
|
|
||||||
local wiki; this just gives repeated questions a better starting area.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Keeping it tidy
|
## 3. Keeping it tidy
|
||||||
|
|
@ -271,87 +187,13 @@ This runs a health check across the whole wiki:
|
||||||
- orphaned pages (nothing links to them) get backlinked or archived
|
- orphaned pages (nothing links to them) get backlinked or archived
|
||||||
- broken graph edges get fixed or removed
|
- broken graph edges get fixed or removed
|
||||||
- missing/duplicate index and log entries get corrected
|
- missing/duplicate index and log entries get corrected
|
||||||
- connector-backed sources whose index is overdue for a refresh get
|
|
||||||
flagged, with how overdue they are — useful even if you're read-only for
|
|
||||||
that source, since it tells you who to chase
|
|
||||||
- **pages whose source has actually changed** get flagged — see below
|
|
||||||
- **quotes that no longer appear in the source they cite** get flagged
|
|
||||||
- recurring systemic issues get written into `wiki/error-book.md`
|
- recurring systemic issues get written into `wiki/error-book.md`
|
||||||
|
|
||||||
The last two are worth understanding, because they're the difference
|
It fixes what it safely can on its own, and reports the rest for you to
|
||||||
between "this page is old" and "this page is wrong".
|
decide. Like Ingest, it finishes by reminding you to review and sync.
|
||||||
|
|
||||||
Every page records a fingerprint of the material it was built from. Staleness
|
|
||||||
by date is a guess: a page written a year ago may still be perfectly accurate.
|
|
||||||
A fingerprint is not a guess — the agent recomputes it and either the source
|
|
||||||
is byte-for-byte what the page was written against, or somebody edited it.
|
|
||||||
When a source changes, the page built on it goes to the top of the list,
|
|
||||||
ahead of anything that has merely aged.
|
|
||||||
|
|
||||||
Pages also quote their sources directly, in a `## Crux` section — a few
|
|
||||||
verbatim lines carrying the actual claim, underneath the agent's summary of
|
|
||||||
it. Two things follow from that. When you ask a question, the agent can often
|
|
||||||
answer from the quote instead of re-reading the whole source, and show you the
|
|
||||||
words rather than its paraphrase of them. And when a quote stops matching its
|
|
||||||
source, that's a page asserting, in quotation marks, something its evidence no
|
|
||||||
longer says — the strongest finding lint produces, and the agent will never
|
|
||||||
"fix" it by quietly editing the quote to match.
|
|
||||||
|
|
||||||
The detection half runs as a read-only Python script
|
|
||||||
(`scripts/lint_report.py`), so the same wiki always produces the same
|
|
||||||
findings list — the agent reads that report and then does the parts that
|
|
||||||
need judgment (supersession, ambiguous orphans, error-book entries, and
|
|
||||||
deciding what to fix versus what to hand back to you). It fixes what it
|
|
||||||
safely can on its own, and reports the rest for you to decide. Like Ingest, it finishes by reminding you to review and sync.
|
|
||||||
Implemented by the `ckb-lint` skill —
|
Implemented by the `ckb-lint` skill —
|
||||||
`.agents/skills/ckb-lint/SKILL.md`.
|
`.agents/skills/ckb-lint/SKILL.md`.
|
||||||
|
|
||||||
### Starting over: resetting to a clean template
|
|
||||||
|
|
||||||
Sometimes you want the *shape* of a knowledge base without its contents —
|
|
||||||
typically because this repo doubles as the template you hand to other
|
|
||||||
people, and it has accumulated decisions, workload summaries and entity
|
|
||||||
pages that shouldn't ship with it.
|
|
||||||
|
|
||||||
> "Reset the wiki." / "Make this a clean template."
|
|
||||||
|
|
||||||
This is the one command in this repo that **deletes knowledge on purpose**,
|
|
||||||
so it is built to be hard to trigger by accident:
|
|
||||||
|
|
||||||
1. **It looks for a restore point first.** If your working tree is dirty it
|
|
||||||
stops and offers to commit, because after a reset anything committed is a
|
|
||||||
`git checkout` away and anything uncommitted is simply gone. It can also
|
|
||||||
tag the commit (`pre-reset-<date>`) so you don't have to keep a hash in
|
|
||||||
your head.
|
|
||||||
2. **It asks how far to go.** Six tiers, chosen individually: wiki knowledge,
|
|
||||||
workload history, `raw/` source material, `outputs/`, external sources,
|
|
||||||
and installed modules. Only the first is on by default. `libs/`,
|
|
||||||
`linked/` and modules default to *no* — `linked/` holds symlinks into
|
|
||||||
other people's knowledge bases, and it will remove a link but never
|
|
||||||
follow one.
|
|
||||||
3. **It counts before it asks.** You get an inventory — how many pages, how
|
|
||||||
many decision records (listed by number and title), how many graph edges,
|
|
||||||
plus anything marked `retention: high` — and one line on what survives.
|
|
||||||
4. **It wants a typed phrase**, not a "yes". And if you change the scope in
|
|
||||||
your reply, it re-counts and asks again, because you agreed to a number
|
|
||||||
and the number moved.
|
|
||||||
5. **It verifies afterwards**, running lint before telling you it worked.
|
|
||||||
|
|
||||||
What it restores is exactly what `ckb-init` would create: same directories,
|
|
||||||
same scaffold files, same `kb_schema_version`. Emptying the content doesn't
|
|
||||||
roll back the schema.
|
|
||||||
|
|
||||||
What it never touches, with or without confirmation: the template layer
|
|
||||||
(`AGENTS.md`, `.agents/`, `LICENSE`, `VERSION`, the docs) and `src/`, which
|
|
||||||
holds independent code repositories this command has no business deleting.
|
|
||||||
|
|
||||||
One deliberate quirk: unlike every other skill, this one does **not** write a
|
|
||||||
`workload/` session note — that entry would be the first thing in a directory
|
|
||||||
it has just emptied. It tells you so in its report.
|
|
||||||
|
|
||||||
Implemented by the `ckb-reset` skill —
|
|
||||||
`.agents/skills/ckb-reset/SKILL.md`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Syncing — with yourself, and with other people
|
## 4. Syncing — with yourself, and with other people
|
||||||
|
|
@ -435,37 +277,6 @@ Say:
|
||||||
|
|
||||||
> "Upgrade the wiki." / "Check for a newer template version."
|
> "Upgrade the wiki." / "Check for a newer template version."
|
||||||
|
|
||||||
### Which channel you're pulling from
|
|
||||||
|
|
||||||
The template repo keeps three branches, and by default you get the stable
|
|
||||||
one:
|
|
||||||
|
|
||||||
| Branch | What it is | Who should be on it |
|
|
||||||
|---|---|---|
|
|
||||||
| `main` | **Stable** — the released template | You, unless you have a reason not to be |
|
|
||||||
| `test` | **Release candidate** — validated before it reaches `main` | You're helping validate a release, or you need a fix that's landed but not shipped |
|
|
||||||
| `experimental` | **Development** — active work, may be broken or reverted | You're developing the template itself |
|
|
||||||
|
|
||||||
To use a different one, just say which:
|
|
||||||
|
|
||||||
> "Upgrade from the test branch." / "Check experimental for updates." /
|
|
||||||
> "Switch this KB back to the stable channel."
|
|
||||||
|
|
||||||
Whichever you pick sticks — it's recorded in `ckb.yaml`, so the next upgrade
|
|
||||||
stays on the same channel rather than quietly pulling you back to `main`. The
|
|
||||||
same goes at creation time: *"initialize from the experimental branch"*.
|
|
||||||
|
|
||||||
One thing to watch. If you're tracking `test` or `experimental`, your KB can
|
|
||||||
sit on a version `main` hasn't released yet. Checking against `main` then
|
|
||||||
finds nothing newer — the agent will tell you you're **ahead**, not that
|
|
||||||
you're up to date, because those are different situations. Going back to
|
|
||||||
`main` from there is a *downgrade*: it can remove skills and roll the schema
|
|
||||||
back below what your pages are written against. You'll be asked to confirm
|
|
||||||
explicitly, and it's refused outright if your content would stop conforming
|
|
||||||
to its own declared schema.
|
|
||||||
|
|
||||||
### What gets checked
|
|
||||||
|
|
||||||
Two entirely separate things get checked, and either, both, or neither
|
Two entirely separate things get checked, and either, both, or neither
|
||||||
might turn something up:
|
might turn something up:
|
||||||
|
|
||||||
|
|
@ -531,43 +342,11 @@ Just ask, in plain language:
|
||||||
|
|
||||||
> "What do we know about the Q3 migration risk?"
|
> "What do we know about the Q3 migration risk?"
|
||||||
|
|
||||||
The agent reads `wiki/index.md` first to find relevant pages. If a matching
|
The agent reads `wiki/index.md` first to find relevant pages, checks their
|
||||||
project scope exists under `wiki/projects/`, it searches that scope first.
|
one-line `tldr` before loading the full page, walks the knowledge graph for
|
||||||
Then it checks one-line `tldr` fields, runs exact local search for literal
|
connected facts, and falls back to `linked/`/`libs/` if the local wiki has
|
||||||
tokens when needed, expands context around matching sections, walks the
|
nothing. You get an answer grounded in what's actually written down, not a
|
||||||
knowledge graph for connected facts, and falls back to `linked/`/`libs/` if
|
guess.
|
||||||
the local wiki has nothing. You get an answer grounded in what's actually
|
|
||||||
written down, not a guess.
|
|
||||||
|
|
||||||
Two things about that worth knowing as a user:
|
|
||||||
|
|
||||||
- **It searches `raw/inbox/` too.** Something you dropped in this morning
|
|
||||||
and haven't ingested yet can still answer your question. The agent will
|
|
||||||
tell you when an answer came from un-ingested material, which doubles as
|
|
||||||
a nudge that "Ingest" is overdue.
|
|
||||||
- **Answers carry their own caveats.** If the page behind an answer is past
|
|
||||||
its freshness window, scored low on confidence, or was read from a cached
|
|
||||||
connector index instead of a live check, the answer says so next to the
|
|
||||||
claim. If two pages disagree and neither has been marked superseded yet,
|
|
||||||
you'll hear about that too. The point is that you never have to go read
|
|
||||||
the frontmatter yourself to know how much to trust what you just got.
|
|
||||||
|
|
||||||
### Ask who knows something
|
|
||||||
|
|
||||||
> "Who knows about the checkpoint restore path?" / "Who owns the billing
|
|
||||||
> service?"
|
|
||||||
|
|
||||||
These are answered from the knowledge graph directly rather than by
|
|
||||||
keyword-searching for names. Ingest records an expertise or ownership edge
|
|
||||||
when the source material actually shows someone answering questions on a
|
|
||||||
topic or holding declared responsibility for it — not from having attended
|
|
||||||
a meeting or from a job title. If nobody has a recorded edge yet, the agent
|
|
||||||
falls back to who the archived sources show answering that kind of question
|
|
||||||
and tells you it's inferring rather than reporting.
|
|
||||||
|
|
||||||
When there is still no answer, the agent should tell you what is missing
|
|
||||||
and either add/propose a short entry in `wiki/query-gaps.md` or suggest the
|
|
||||||
smallest source to drop into `raw/inbox/`.
|
|
||||||
|
|
||||||
### Learn from the wiki
|
### Learn from the wiki
|
||||||
|
|
||||||
|
|
@ -578,7 +357,7 @@ smallest source to drop into `raw/inbox/`.
|
||||||
You'll be asked how many questions and what format (open / multiple
|
You'll be asked how many questions and what format (open / multiple
|
||||||
choice), then run through them one at a time with immediate feedback and a
|
choice), then run through them one at a time with immediate feedback and a
|
||||||
running score. Nothing is saved afterward — it's a one-off check.
|
running score. Nothing is saved afterward — it's a one-off check.
|
||||||
`.agents/skills/ckb-quiz/SKILL.md`.
|
`.agents/skills/cbk-quiz/SKILL.md`.
|
||||||
|
|
||||||
**A proper course, spread over time** — say:
|
**A proper course, spread over time** — say:
|
||||||
|
|
||||||
|
|
@ -667,7 +446,6 @@ Example:
|
||||||
> connector: sharepoint
|
> connector: sharepoint
|
||||||
> location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
> location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
||||||
> description: "Finance team's shared reports folder"
|
> description: "Finance team's shared reports folder"
|
||||||
> refresh_interval_days: 7
|
|
||||||
> ```
|
> ```
|
||||||
> *then say "Index external sources."*
|
> *then say "Index external sources."*
|
||||||
>
|
>
|
||||||
|
|
@ -714,22 +492,14 @@ adds an `index:` block to the shared `source.yaml`:
|
||||||
index:
|
index:
|
||||||
store: git
|
store: git
|
||||||
location: "https://github.com/finance-team/index-cache.git"
|
location: "https://github.com/finance-team/index-cache.git"
|
||||||
# ref: main — optional: pin a branch, tag, or subpath within that store
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The very first time anyone runs "index external sources" after that block
|
Now, whenever *anyone* says "index external sources," the agent first
|
||||||
is added, `https://github.com/finance-team/index-cache.git` is empty —
|
fetches whatever's already published there — read-only users stop right
|
||||||
that's expected, not an error. Every run checks it first: read-only users
|
there; the admin also rebuilds from the live connector and pushes the
|
||||||
just see "nothing published yet, ask the admin"; the admin's run is what
|
refreshed index back to that same location, so the next person's fetch
|
||||||
actually creates it there, since a write-access run always rebuilds from
|
picks it up. Leave the `index:` block out entirely (the simplest setup,
|
||||||
the live connector and pushes the result to that location, whether or not
|
and the right default for a single small team) and the index just lives
|
||||||
anything was there before. From then on, whenever *anyone* says "index
|
|
||||||
external sources," the agent first fetches whatever's already published
|
|
||||||
there — read-only users stop right there; the admin also rebuilds from the
|
|
||||||
live connector and pushes the refreshed index back to that same location,
|
|
||||||
so the next person's fetch picks it up. Leave the `index:` block out
|
|
||||||
entirely (the simplest setup, and the right default for a single small
|
|
||||||
team) and the index just lives
|
|
||||||
directly inside `libs/finance-reports/` in this KB's own repo, shared the
|
directly inside `libs/finance-reports/` in this KB's own repo, shared the
|
||||||
normal way via "sync changes" — exactly like the plain example above.
|
normal way via "sync changes" — exactly like the plain example above.
|
||||||
|
|
||||||
|
|
@ -745,32 +515,15 @@ graph) — not that you're forbidden from touching it. If you hand-edit a
|
||||||
wiki page, it's good practice to run "Lint" afterward so the index/log/
|
wiki page, it's good practice to run "Lint" afterward so the index/log/
|
||||||
graph stay in sync with what you changed.
|
graph stay in sync with what you changed.
|
||||||
|
|
||||||
There is one exception that runs the other way. On any page the agent
|
|
||||||
*regenerates* — a connector index, a code map — everything you write is
|
|
||||||
normally lost on the next rebuild. So every such page ends with a `## Notes`
|
|
||||||
section that no skill will ever touch:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
<!-- Yours. Never rewritten by any skill. -->
|
|
||||||
```
|
|
||||||
|
|
||||||
Write whatever you want there — that this document is obsolete, that the
|
|
||||||
person named in it has left, who to actually ask. It is carried across
|
|
||||||
rebuilds byte-for-byte. Anything you write *above* that heading on a
|
|
||||||
generated page will be overwritten.
|
|
||||||
|
|
||||||
| Location | Who normally writes it | Notes |
|
| Location | Who normally writes it | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `raw/inbox/`, loose files in `raw/` | **You, only** | The agent only reads, archives, and moves things here — it never originates content in `raw/` itself. |
|
| `raw/inbox/`, loose files in `raw/` | **You, only** | The agent only reads, archives, and moves things here — it never originates content in `raw/` itself. |
|
||||||
| `raw/archive/<date>/` | Agent | Auto-filed copy of what you dropped in `raw/inbox/`, organised by ingestion date. Don't hand-file here — let Ingest do it, so the date and pairing with the log entry stay accurate. |
|
| `raw/archive/<date>/` | Agent | Auto-filed copy of what you dropped in `raw/inbox/`, organised by ingestion date. Don't hand-file here — let Ingest do it, so the date and pairing with the log entry stay accurate. |
|
||||||
| `linked/<name>/` | **You** (you create the symlink) | Points at another KB's real files, which live and get edited *in that other repo* — never here. The agent must never write inside `linked/`. |
|
| `linked/<name>/` | **You** (you create the symlink) | Points at another KB's real files, which live and get edited *in that other repo* — never here. The agent must never write inside `linked/`. |
|
||||||
| `libs/<name>/` (git-copy, no `source.yaml`) | **You** (you `git clone`) | A frozen copy of an external KB. Update it by re-pulling that repo yourself, not by hand-editing files here. The agent must never write inside it. |
|
| `libs/<name>/` (git-copy, no `source.yaml`) | **You** (you `git clone`) | A frozen copy of an external KB. Update it by re-pulling that repo yourself, not by hand-editing files here. The agent must never write inside it. |
|
||||||
| `libs/<name>/source.yaml` (connector) | **You, only** | Declares the connector, location, optionally how often it should be refreshed (`refresh_interval_days:`), and optionally where a shared/pre-built index lives (`index:`). The agent reads it but never writes it — same as anything else upstream. |
|
| `libs/<name>/source.yaml` (connector) | **You, only** | Declares the connector, location, and optionally where a shared/pre-built index lives (`index:`). The agent reads it but never writes it — same as anything else upstream. |
|
||||||
| `libs/<name>/source.local.yaml` (connector) | **You** (or the agent, only when you explicitly ask to become/stop being that source's admin) | Per-person, per-machine `access: write`/`read` setting — never committed, never seen by anyone else. Absent = read-only, the default. |
|
| `libs/<name>/source.local.yaml` (connector) | **You** (or the agent, only when you explicitly ask to become/stop being that source's admin) | Per-person, per-machine `access: write`/`read` setting — never committed, never seen by anyone else. Absent = read-only, the default. |
|
||||||
| `libs/<name>/{index.md,entities/,graph/,log.md}` (connector) | Agent-generated, **you can freely edit** | The agent's own index of that one connector's source, built/refreshed by "Index external sources" — but only if you have `access: write` locally; read-only users just get a fetched copy. Structurally the same deal as the main `wiki/` row below — feel free to correct an entry by hand, then run "Lint" (it now also checks connector-backed indexes, respecting the same read/write split). Scoped entirely to that connector; never blended into `wiki/`. **Only `## Notes` survives a rebuild** — put anything you want to keep there. |
|
| `libs/<name>/{index.md,entities/,graph/,log.md}` (connector) | Agent-generated, **you can freely edit** | The agent's own index of that one connector's source, built/refreshed by "Index external sources" — but only if you have `access: write` locally; read-only users just get a fetched copy. Structurally the same deal as the main `wiki/` row below — feel free to correct an entry by hand, then run "Lint" (it now also checks connector-backed indexes, respecting the same read/write split). Scoped entirely to that connector; never blended into `wiki/`. |
|
||||||
| `wiki/decisions/` | Agent-generated, **edit with care** | Same as the rest of `wiki/` mechanically, but these are append-only by convention: correct a typo or a misattributed name freely, and don't rewrite a decision's context or rationale to match a later view — record a superseding decision instead, so the history survives. |
|
|
||||||
| `wiki/` (pages, `index.md`, `overview.md`, `log.md`, `error-book.md`, `entities/`, `graph/`) | Agent-generated, **you can freely edit** | This is the one place the agent both writes and expects you might too. Feel free to correct a page by hand — just keep the frontmatter fields intact (or update `last_updated`), and run Lint afterward if you touched something the index/graph/log reference. |
|
| `wiki/` (pages, `index.md`, `overview.md`, `log.md`, `error-book.md`, `entities/`, `graph/`) | Agent-generated, **you can freely edit** | This is the one place the agent both writes and expects you might too. Feel free to correct a page by hand — just keep the frontmatter fields intact (or update `last_updated`), and run Lint afterward if you touched something the index/graph/log reference. |
|
||||||
| `outputs/okf/`, `outputs/starlight/` | Agent, **fully regenerated** | Don't hand-edit — these are gitignored build artifacts, silently overwritten the next time you export. If something's wrong, fix the wiki page it came from and re-export. |
|
| `outputs/okf/`, `outputs/starlight/` | Agent, **fully regenerated** | Don't hand-edit — these are gitignored build artifacts, silently overwritten the next time you export. If something's wrong, fix the wiki page it came from and re-export. |
|
||||||
| `outputs/teaching/<topic>/` | Agent, semi-persistent state | `plan.md`/`progress.md` the teaching skill reads and writes across sessions. You can look at them any time; hand-editing is possible but may confuse "what's next" tracking — safer to tell the agent what you want changed and let it update the files. |
|
| `outputs/teaching/<topic>/` | Agent, semi-persistent state | `plan.md`/`progress.md` the teaching skill reads and writes across sessions. You can look at them any time; hand-editing is possible but may confuse "what's next" tracking — safer to tell the agent what you want changed and let it update the files. |
|
||||||
|
|
@ -787,14 +540,10 @@ generated page will be overwritten.
|
||||||
| Say... | What happens | Skill |
|
| Say... | What happens | Skill |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| "Set up a new wiki like this one in \<folder\>" | Bootstraps a fresh, empty KB with this schema | `ckb-init` |
|
| "Set up a new wiki like this one in \<folder\>" | Bootstraps a fresh, empty KB with this schema | `ckb-init` |
|
||||||
| "Pull the ckb repo into \<folder\> and set up the wiki" | Clones the template repo to a scratch dir, then bootstraps an empty KB from it | `ckb-init` |
|
|
||||||
| "Ingest" / "Sync the wiki" / "Update the wiki" | Processes `raw/inbox/` into structured `wiki/` pages | `ckb-ingest` |
|
| "Ingest" / "Sync the wiki" / "Update the wiki" | Processes `raw/inbox/` into structured `wiki/` pages | `ckb-ingest` |
|
||||||
| "Record a decision: ..." / "we decided ..." | Writes a numbered decision record under `wiki/decisions/` | `ckb-decide` |
|
|
||||||
| "What did we decide about X" / "who decided X" / "what's still open" | Answers from the decision records, with who/when/status attached | `ckb-decide` |
|
|
||||||
| "Lint" | Health-checks the wiki, auto-fixes what it safely can | `ckb-lint` |
|
| "Lint" | Health-checks the wiki, auto-fixes what it safely can | `ckb-lint` |
|
||||||
| "Reset the wiki" / "Make this a clean template" | **Destructive.** Deletes accumulated knowledge and restores the empty scaffold, after an inventory and a typed confirmation | `ckb-reset` |
|
|
||||||
| "Sync changes" / "Sync with git" | Commits, pulls, resolves conflicts, pushes to `origin` | `ckb-sync-changes` |
|
| "Sync changes" / "Sync with git" | Commits, pulls, resolves conflicts, pushes to `origin` | `ckb-sync-changes` |
|
||||||
| "Quiz me on X" | One-off scored knowledge test | `ckb-quiz` |
|
| "Quiz me on X" | One-off scored knowledge test | `cbk-quiz` |
|
||||||
| "Teach me the wiki" / "Teach me about X" | Plans and runs a spaced-out course with progress tracking | `ckb-teach-me` |
|
| "Teach me the wiki" / "Teach me about X" | Plans and runs a spaced-out course with progress tracking | `ckb-teach-me` |
|
||||||
| "Onboard me on X" / "Where do I start with X" | Short guided reading order through the graph | `ckb-onboard-me` |
|
| "Onboard me on X" / "Where do I start with X" | Short guided reading order through the graph | `ckb-onboard-me` |
|
||||||
| "Give me a project summary" | Regenerates `PROJECT-OVERVIEW.md` | `ckb-project-summary` |
|
| "Give me a project summary" | Regenerates `PROJECT-OVERVIEW.md` | `ckb-project-summary` |
|
||||||
|
|
@ -802,5 +551,4 @@ generated page will be overwritten.
|
||||||
| "Export the wiki to Starlight" | Human-readable docs site at `outputs/starlight/` | `ckb-export-starlight` |
|
| "Export the wiki to Starlight" | Human-readable docs site at `outputs/starlight/` | `ckb-export-starlight` |
|
||||||
| "Upgrade the wiki" / "Check for a newer template version" | Checks template + wiki schema versions against the canonical repo, upgrades what you accept | `ckb-upgrade` |
|
| "Upgrade the wiki" / "Check for a newer template version" | Checks template + wiki schema versions against the canonical repo, upgrades what you accept | `ckb-upgrade` |
|
||||||
| "Index external sources" / "Index libs" | Builds/refreshes a self-contained index for each connector-backed `libs/<name>/` | `ckb-index-external` |
|
| "Index external sources" / "Index libs" | Builds/refreshes a self-contained index for each connector-backed `libs/<name>/` | `ckb-index-external` |
|
||||||
| Just ask a question | Answers from the wiki, using the index/TLDR/graph cascade, with caveats when a source is stale or contested | `ckb-retrieve` |
|
| Just ask a question | Answers from the wiki, using the index/TLDR/graph cascade | — (core query workflow) |
|
||||||
| "Who knows about X" / "Who owns X" | Answers from expertise/ownership edges in the graph | `ckb-retrieve` |
|
|
||||||
|
|
|
||||||
291
MANUAL.pl.md
291
MANUAL.pl.md
|
|
@ -6,9 +6,7 @@ To jest podręcznik dla *człowieka* korzystającego z Cascade Knowledge Base
|
||||||
(tego repozytorium) — nie dla agenta. Zasady działania samego agenta
|
(tego repozytorium) — nie dla agenta. Zasady działania samego agenta
|
||||||
znajdziesz w [AGENTS.md](AGENTS.md) / [CLAUDE.md](CLAUDE.md). Techniczny,
|
znajdziesz w [AGENTS.md](AGENTS.md) / [CLAUDE.md](CLAUDE.md). Techniczny,
|
||||||
funkcja-po-funkcji przegląd znajdziesz w [README.md](README.md) (lub
|
funkcja-po-funkcji przegląd znajdziesz w [README.md](README.md) (lub
|
||||||
[README.pl.md](README.pl.md)). Pełny schemat strony wraz z historią obu
|
[README.pl.md](README.pl.md)). Ten dokument jest zorientowany na zadania:
|
||||||
numerów wersji znajdziesz w [CHANGELOG.pl.md](CHANGELOG.pl.md). Ten dokument
|
|
||||||
jest zorientowany na zadania:
|
|
||||||
„chcę zrobić X — co mam powiedzieć i co się wtedy stanie?”
|
„chcę zrobić X — co mam powiedzieć i co się wtedy stanie?”
|
||||||
|
|
||||||
Wszędzie poniżej „powiedz” oznacza napisanie tego do dowolnego agenta AI,
|
Wszędzie poniżej „powiedz” oznacza napisanie tego do dowolnego agenta AI,
|
||||||
|
|
@ -51,15 +49,6 @@ Nigdy nie kopiuje rzeczywistej zawartości tego projektu (żadnych encji,
|
||||||
danych grafu, notatek). Otrzymujesz świeżą, pustą KB, gotową na pierwszy
|
danych grafu, notatek). Otrzymujesz świeżą, pustą KB, gotową na pierwszy
|
||||||
zrzut do `raw/inbox/`. Zobacz `.agents/skills/ckb-init/SKILL.md`.
|
zrzut do `raw/inbox/`. Zobacz `.agents/skills/ckb-init/SKILL.md`.
|
||||||
|
|
||||||
Szablon może pochodzić z dwóch miejsc: z plików tego repozytorium albo ze
|
|
||||||
świeżego, płytkiego klona kanonicznego repozytorium szablonu (lub dowolnego
|
|
||||||
forka/mirrora, którego URL podasz), pobranego do folderu roboczego.
|
|
||||||
Powiedz „pull the latest template and set up a KB in \<folder\>” — albo
|
|
||||||
uruchom skilla spoza jakiejkolwiek KB — a agent najpierw sklonuje
|
|
||||||
repozytorium, a dopiero potem zbuduje szkielet. Klon jest wyłącznie
|
|
||||||
roboczy: nowa KB dostaje własną historię gita (agent pyta przed
|
|
||||||
`git init`), a nie historię szablonu.
|
|
||||||
|
|
||||||
Jeśli docelowy folder wygląda już jak baza wiedzy (ma `wiki/` lub
|
Jeśli docelowy folder wygląda już jak baza wiedzy (ma `wiki/` lub
|
||||||
`AGENTS.md`), agent zatrzyma się i zapyta, zanim czegokolwiek dotknie — nie
|
`AGENTS.md`), agent zatrzyma się i zapyta, zanim czegokolwiek dotknie — nie
|
||||||
nadpisze po cichu istniejącej bazy wiedzy.
|
nadpisze po cichu istniejącej bazy wiedzy.
|
||||||
|
|
@ -107,11 +96,6 @@ podpięcia:
|
||||||
`index:` wskazujący na już zbudowany indeks — repozytorium git albo
|
`index:` wskazujący na już zbudowany indeks — repozytorium git albo
|
||||||
zasób współdzielony — dzięki czemu po prostu pobierasz to, co ktoś
|
zasób współdzielony — dzięki czemu po prostu pobierasz to, co ktoś
|
||||||
inny już zaindeksował, zamiast samodzielnie skanować żywe źródło.
|
inny już zaindeksował, zamiast samodzielnie skanować żywe źródło.
|
||||||
Każde uruchomienie najpierw sprawdza tę lokalizację: jeśli indeks już
|
|
||||||
tam jest, dostajesz go; jeśli go tam jeszcze nie ma (normalny stan,
|
|
||||||
zanim ktokolwiek z dostępem do zapisu to uruchomił), to nie błąd —
|
|
||||||
ten, kto ma dostęp do zapisu, tworzy go tam i publikuje przy swoim
|
|
||||||
kolejnym uruchomieniu.
|
|
||||||
- **Budowanie/odświeżanie jest opcjonalne, per osoba, per źródło.**
|
- **Budowanie/odświeżanie jest opcjonalne, per osoba, per źródło.**
|
||||||
Domyślnie każdy jest tylko-do-odczytu dla źródła typu konektor —
|
Domyślnie każdy jest tylko-do-odczytu dla źródła typu konektor —
|
||||||
agent nikogo nie przeskanuje żywego konektora w jego imieniu, jeśli
|
agent nikogo nie przeskanuje żywego konektora w jego imieniu, jeśli
|
||||||
|
|
@ -121,14 +105,6 @@ podpięcia:
|
||||||
niewidoczny dla współpracowników). To celowe: pozwala jednej lub dwóm
|
niewidoczny dla współpracowników). To celowe: pozwala jednej lub dwóm
|
||||||
osobom utrzymywać źródło dla całego zespołu, zamiast żeby każdy
|
osobom utrzymywać źródło dla całego zespołu, zamiast żeby każdy
|
||||||
redundantnie je skanował.
|
redundantnie je skanował.
|
||||||
- **Możesz ustawić, jak często ma być odświeżane.** Dodaj opcjonalne
|
|
||||||
`refresh_interval_days: 7` do `source.yaml` (domyślnie 30). Folder
|
|
||||||
zmieniający się codziennie potrzebuje krótszego okna niż kwartalne
|
|
||||||
archiwum, którego nikt nie tyka. Wtedy zarówno „index external
|
|
||||||
sources", jak i „Lint" powiedzą ci, kiedy źródło jest zaległe i o ile —
|
|
||||||
co ma największe znaczenie, jeśli masz do niego dostęp tylko do
|
|
||||||
odczytu, bo wiedza o tym, *które* źródło się przedawniło, pozwala
|
|
||||||
zapytać osobę, która je utrzymuje.
|
|
||||||
|
|
||||||
Niezależnie od sposobu, po podpięciu wystarczy normalnie zadawać pytania —
|
Niezależnie od sposobu, po podpięciu wystarczy normalnie zadawać pytania —
|
||||||
agent sprawdza najpierw twoją lokalną `wiki/`, potem przechodzi przez
|
agent sprawdza najpierw twoją lokalną `wiki/`, potem przechodzi przez
|
||||||
|
|
@ -168,17 +144,6 @@ Przykład:
|
||||||
> `raw/archive/2026-07-10/`. Na koniec przypomina o przejrzeniu wyniku i
|
> `raw/archive/2026-07-10/`. Na koniec przypomina o przejrzeniu wyniku i
|
||||||
> powiedzeniu „sync changes”, gdy będziesz zadowolony.
|
> powiedzeniu „sync changes”, gdy będziesz zadowolony.
|
||||||
|
|
||||||
Przy długim transkrypcie agent nie pisze po prostu jednej strony
|
|
||||||
podsumowania. Wyciąga wyszukiwalne pytanie, podsumowanie, rozwiązanie oraz
|
|
||||||
zaangażowane systemy i osoby — a pojedyncze fragmenty awansuje do własnych
|
|
||||||
znajdowalnych sekcji, jeśli inaczej przepadłyby wewnątrz podsumowania. Ta
|
|
||||||
ostatnia część ma celowy próg: fragment musi zawierać naprawdę konkretny
|
|
||||||
termin (flagę, komunikat błędu, klauzulę, numer wersji), mieć co najmniej
|
|
||||||
kilka zdań i być potwierdzony przez coś dalej w materiale. W przeciwnym razie
|
|
||||||
zostaje wtopiony w podsumowanie. Bez tego progu każdy akapit wygląda na wart
|
|
||||||
zacytowania, a strona wiki znów staje się transkryptem — co przekreśla sens
|
|
||||||
jego zingestowania.
|
|
||||||
|
|
||||||
Jeśli `raw/inbox/` jest puste, agent skanuje bezpośrednio `raw/` (nadal
|
Jeśli `raw/inbox/` jest puste, agent skanuje bezpośrednio `raw/` (nadal
|
||||||
pomijając `raw/archive/`, które zawiera już przetworzoną historię).
|
pomijając `raw/archive/`, które zawiera już przetworzoną historię).
|
||||||
|
|
||||||
|
|
@ -209,59 +174,6 @@ ingest to wchłonie. Dzięki temu wiki pozostaje napędzana zapotrzebowaniem:
|
||||||
rośnie wokół tego, o co faktycznie pytasz, a nie wokół wszystkiego, co
|
rośnie wokół tego, o co faktycznie pytasz, a nie wokół wszystkiego, co
|
||||||
teoretycznie dałoby się spisać.
|
teoretycznie dałoby się spisać.
|
||||||
|
|
||||||
Trwałe braki można też śledzić w `wiki/query-gaps.md`. Dobry wpis o luce jest
|
|
||||||
maleńki: pytanie, gdzie agent szukał i jakie najmniejsze źródło lub strona
|
|
||||||
sprawiłaby, że odpowiedź będzie dostępna następnym razem.
|
|
||||||
|
|
||||||
### D. Zapisz decyzję
|
|
||||||
|
|
||||||
Gdy zapada jakaś decyzja — wybór technologii, zmiana procesu, polityka —
|
|
||||||
powiedz:
|
|
||||||
|
|
||||||
> „Zapisz decyzję: przenosimy billing na Postgresa. Alice i Bob zdecydowali
|
|
||||||
> dzisiaj, bo zapytania raportowe zabijały MySQL-a."
|
|
||||||
|
|
||||||
Agent zapisze numerowany rekord pod `wiki/decisions/` z decyzją, autorami,
|
|
||||||
datą, uzasadnieniem, alternatywami i tym, czego dotyczy. Jeśli zastępuje
|
|
||||||
wcześniejszą decyzję, połączy obie w obu kierunkach i oznaczy starą jako
|
|
||||||
zastąpioną — nie ruszając jej uzasadnienia. O to, czego nie podasz, dopyta w
|
|
||||||
jednej turze; jeśli jesteś w środku pracy, powiedz to, a zapisze, co ma, i
|
|
||||||
wskaże, które pola zostawił otwarte.
|
|
||||||
|
|
||||||
Potem pytaj, jak chcesz:
|
|
||||||
|
|
||||||
> „Co zdecydowaliśmy w sprawie bazy danych billingu?"
|
|
||||||
> „Dlaczego używamy Postgresa?"
|
|
||||||
> „Kto to zdecydował i kiedy?"
|
|
||||||
> „Które decyzje są wciąż tylko propozycjami?"
|
|
||||||
> „Co zastąpiło decyzję 3?"
|
|
||||||
|
|
||||||
Odpowiedź zawsze przychodzi z informacją kto i kiedy, i wprost mówi, gdy
|
|
||||||
decyzja jest propozycją, a nie decyzją obowiązującą, albo została już
|
|
||||||
zastąpiona — żebyś nie działał na czymś, co nie obowiązuje. Zaimplementowane
|
|
||||||
przez skill `ckb-decide`.
|
|
||||||
|
|
||||||
Dwie rzeczy warte zapamiętania:
|
|
||||||
|
|
||||||
- **Decyzje są tylko do dopisywania.** „Właściwie zmieniliśmy zdanie" tworzy
|
|
||||||
*nową* decyzję zastępującą starą; nigdy nie edytuje uzasadnienia starej. To
|
|
||||||
celowe — historia jest tu sednem. Zwykłe błędy zapisu („powiedziałem Alice,
|
|
||||||
a było Anna") poprawiane są w miejscu.
|
|
||||||
- **Propozycja to nie decyzja.** Jeśli sprawa nie została rozstrzygnięta,
|
|
||||||
zapisywana jest jako `proposed` bez daty decyzji i pojawia się, gdy pytasz,
|
|
||||||
co jest jeszcze otwarte.
|
|
||||||
|
|
||||||
### E. Utwórz lokalny zakres projektu
|
|
||||||
|
|
||||||
Gdy jakiś temat, klient, system lub inicjatywa wraca często, poproś:
|
|
||||||
|
|
||||||
> „Utwórz zakres projektu dla integracji płatności."
|
|
||||||
|
|
||||||
Agent utworzy lub zaktualizuje zwykłą stronę Markdown pod `wiki/projects/`,
|
|
||||||
wymieniającą strony, encje, pliki z `raw/archive/`, indeksy konektorów i
|
|
||||||
obszary grafu, które należy przeszukać najpierw dla tego zakresu. Nadal masz
|
|
||||||
jedną lokalną wiki; to tylko daje powracającym pytaniom lepszy punkt startowy.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Utrzymanie porządku
|
## 3. Utrzymanie porządku
|
||||||
|
|
@ -284,92 +196,13 @@ To uruchamia przegląd kondycji całej wiki:
|
||||||
albo są archiwizowane
|
albo są archiwizowane
|
||||||
- uszkodzone krawędzie grafu są naprawiane lub usuwane
|
- uszkodzone krawędzie grafu są naprawiane lub usuwane
|
||||||
- brakujące/podwójne wpisy w indeksie i dzienniku są poprawiane
|
- brakujące/podwójne wpisy w indeksie i dzienniku są poprawiane
|
||||||
- konektorowe źródła, których indeks jest zaległy do odświeżenia, zostają
|
|
||||||
oflagowane wraz z informacją o ile — przydatne nawet jeśli masz do tego
|
|
||||||
źródła dostęp tylko do odczytu, bo mówi ci, kogo dopytać
|
|
||||||
- **strony, których źródło faktycznie się zmieniło**, zostają oflagowane —
|
|
||||||
patrz niżej
|
|
||||||
- **cytaty, których nie ma już w źródle**, na które się powołują, zostają
|
|
||||||
oflagowane
|
|
||||||
- powtarzające się problemy systemowe trafiają do `wiki/error-book.md`
|
- powtarzające się problemy systemowe trafiają do `wiki/error-book.md`
|
||||||
|
|
||||||
Te dwa ostatnie warto zrozumieć, bo to różnica między „ta strona jest stara"
|
Naprawia samodzielnie to, co może zrobić bezpiecznie, a resztę zgłasza do
|
||||||
a „ta strona jest błędna".
|
twojej decyzji. Podobnie jak Ingest, na koniec przypomina o przejrzeniu i
|
||||||
|
|
||||||
Każda strona zapisuje skrót materiału, z którego powstała. Nieaktualność
|
|
||||||
liczona datą to przypuszczenie: strona napisana rok temu może być wciąż
|
|
||||||
całkowicie poprawna. Skrót nie jest przypuszczeniem — agent przelicza go i
|
|
||||||
albo źródło jest co do bajtu tym, przeciwko czemu stronę napisano, albo ktoś
|
|
||||||
je zmienił. Gdy źródło się zmienia, strona na nim zbudowana trafia na szczyt
|
|
||||||
listy, przed wszystko, co się jedynie zestarzało.
|
|
||||||
|
|
||||||
Strony cytują też swoje źródła wprost, w sekcji `## Crux` — kilka dosłownych
|
|
||||||
linijek niosących właściwe twierdzenie, pod streszczeniem agenta. Wynikają z
|
|
||||||
tego dwie rzeczy. Gdy zadajesz pytanie, agent często może odpowiedzieć z
|
|
||||||
cytatu zamiast ponownie czytać całe źródło i pokazać ci słowa, a nie swoją
|
|
||||||
parafrazę. A gdy cytat przestaje zgadzać się ze źródłem, to strona twierdzi —
|
|
||||||
w cudzysłowie — coś, czego jej dowód już nie mówi. To najmocniejsze
|
|
||||||
znalezisko lintu i agent nigdy nie „naprawi" go, po cichu dopasowując cytat
|
|
||||||
do źródła.
|
|
||||||
|
|
||||||
Połowa wykrywająca działa jako skrypt Python tylko-do-odczytu
|
|
||||||
(`scripts/lint_report.py`), więc ta sama wiki zawsze daje tę samą listę
|
|
||||||
znalezisk — agent czyta ten raport, a potem wykonuje części wymagające
|
|
||||||
osądu (supersesja, niejednoznaczne sieroty, wpisy do księgi błędów oraz
|
|
||||||
decyzja, co naprawić, a co oddać tobie). Naprawia samodzielnie to, co może
|
|
||||||
zrobić bezpiecznie, a resztę zgłasza do twojej decyzji. Podobnie jak Ingest, na koniec przypomina o przejrzeniu i
|
|
||||||
synchronizacji. Zaimplementowane przez skill `ckb-lint` —
|
synchronizacji. Zaimplementowane przez skill `ckb-lint` —
|
||||||
`.agents/skills/ckb-lint/SKILL.md`.
|
`.agents/skills/ckb-lint/SKILL.md`.
|
||||||
|
|
||||||
### Zaczynanie od zera: reset do czystego szablonu
|
|
||||||
|
|
||||||
Czasem chcesz zachować *kształt* bazy wiedzy bez jej zawartości — zwykle
|
|
||||||
dlatego, że to repozytorium pełni też rolę szablonu przekazywanego innym, a
|
|
||||||
zdążyło zebrać decyzje, podsumowania sesji i strony encji, które nie powinny
|
|
||||||
z nim wędrować.
|
|
||||||
|
|
||||||
> „Zresetuj wiki.” / „Zrób z tego czysty szablon.”
|
|
||||||
|
|
||||||
To jedyne polecenie w tym repozytorium, które **celowo usuwa wiedzę**, więc
|
|
||||||
jest zbudowane tak, żeby trudno było je uruchomić przez przypadek:
|
|
||||||
|
|
||||||
1. **Najpierw szuka punktu przywracania.** Jeśli drzewo robocze jest
|
|
||||||
„brudne”, zatrzymuje się i proponuje commit — po resecie wszystko, co
|
|
||||||
zacommitowane, jest o jedno `git checkout` stąd, a wszystko
|
|
||||||
niezacommitowane po prostu znika. Może też otagować commit
|
|
||||||
(`pre-reset-<data>`), żebyś nie musiał trzymać hasha w głowie.
|
|
||||||
2. **Pyta, jak daleko sięgnąć.** Sześć poziomów wybieranych osobno: wiedza w
|
|
||||||
wiki, historia `workload/`, materiał źródłowy w `raw/`, `outputs/`,
|
|
||||||
źródła zewnętrzne i zainstalowane moduły. Domyślnie włączony jest tylko
|
|
||||||
pierwszy. `libs/`, `linked/` i moduły domyślnie na *nie* — `linked/`
|
|
||||||
zawiera dowiązania do cudzych baz wiedzy, więc usuwa dowiązanie, ale
|
|
||||||
nigdy nie podąża za nim.
|
|
||||||
3. **Liczy, zanim zapyta.** Dostajesz inwentarz — ile stron, ile rekordów
|
|
||||||
decyzji (wymienionych z numerem i tytułem), ile krawędzi grafu, plus
|
|
||||||
wszystko oznaczone `retention: high` — i jedną linijkę o tym, co
|
|
||||||
przetrwa.
|
|
||||||
4. **Wymaga wpisania frazy**, nie „tak”. A jeśli w odpowiedzi zmienisz
|
|
||||||
zakres, przeliczy wszystko i zapyta ponownie, bo zgodziłeś się na
|
|
||||||
konkretną liczbę, a liczba się zmieniła.
|
|
||||||
5. **Weryfikuje po wszystkim**, uruchamiając lint, zanim powie, że się
|
|
||||||
udało.
|
|
||||||
|
|
||||||
Odtwarza dokładnie to, co utworzyłby `ckb-init`: te same katalogi, te same
|
|
||||||
pliki szkieletu, ten sam `kb_schema_version`. Opróżnienie treści nie cofa
|
|
||||||
wersji schematu.
|
|
||||||
|
|
||||||
Czego nie rusza nigdy, z potwierdzeniem czy bez: warstwy szablonu
|
|
||||||
(`AGENTS.md`, `.agents/`, `LICENSE`, `VERSION`, dokumentacja) oraz `src/`,
|
|
||||||
gdzie leżą niezależne repozytoria kodu, do których usuwania to polecenie nie
|
|
||||||
ma żadnego tytułu.
|
|
||||||
|
|
||||||
Jedna celowa osobliwość: w odróżnieniu od każdego innego skilla ten **nie**
|
|
||||||
zapisuje notatki sesji w `workload/` — byłby to pierwszy wpis w katalogu,
|
|
||||||
który właśnie opróżnił. Informuje o tym w raporcie.
|
|
||||||
|
|
||||||
Zaimplementowane przez skill `ckb-reset` —
|
|
||||||
`.agents/skills/ckb-reset/SKILL.md`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Synchronizacja — z samym sobą i z innymi ludźmi
|
## 4. Synchronizacja — z samym sobą i z innymi ludźmi
|
||||||
|
|
@ -456,35 +289,6 @@ Powiedz:
|
||||||
|
|
||||||
> „Upgrade the wiki.” / „Check for a newer template version.”
|
> „Upgrade the wiki.” / „Check for a newer template version.”
|
||||||
|
|
||||||
### Z jakiego kanału pobierasz
|
|
||||||
|
|
||||||
Repozytorium szablonu utrzymuje trzy gałęzie i domyślnie dostajesz tę
|
|
||||||
stabilną:
|
|
||||||
|
|
||||||
| Gałąź | Czym jest | Kto powinien na niej być |
|
|
||||||
|---|---|---|
|
|
||||||
| `main` | **Stabilna** — wydany szablon | Ty, o ile nie masz powodu, żeby być gdzie indziej |
|
|
||||||
| `test` | **Kandydat do wydania** — walidowany, zanim trafi do `main` | Pomagasz walidować wydanie albo potrzebujesz poprawki, która weszła, ale nie została wydana |
|
|
||||||
| `experimental` | **Rozwojowa** — bieżąca praca, może być zepsuta albo wycofana | Rozwijasz sam szablon |
|
|
||||||
|
|
||||||
Żeby użyć innej, po prostu powiedz której:
|
|
||||||
|
|
||||||
> „Upgrade from the test branch.” / „Check experimental for updates.” /
|
|
||||||
> „Switch this KB back to the stable channel.”
|
|
||||||
|
|
||||||
To, co wybierzesz, zostaje — jest zapisane w `ckb.yaml`, więc kolejny upgrade
|
|
||||||
zostanie na tym samym kanale, zamiast po cichu ściągnąć cię z powrotem na
|
|
||||||
`main`. Tak samo przy tworzeniu: *„zainicjuj z gałęzi experimental”*.
|
|
||||||
|
|
||||||
Na jedno warto uważać. Jeśli śledzisz `test` albo `experimental`, twoja KB
|
|
||||||
może mieć wersję, której `main` jeszcze nie wydało. Sprawdzenie względem
|
|
||||||
`main` nie znajdzie wtedy nic nowszego — agent powie ci, że **wyprzedzasz**, a
|
|
||||||
nie że jesteś aktualny, bo to dwie różne sytuacje. Powrót stamtąd na `main` to
|
|
||||||
*downgrade*: może usunąć skille i cofnąć schemat poniżej tego, pod co napisano
|
|
||||||
twoje strony. Zostaniesz poproszony o wyraźne potwierdzenie, a gdy treść
|
|
||||||
przestałaby być zgodna z własnym zadeklarowanym schematem — operacja zostanie
|
|
||||||
odmówiona.
|
|
||||||
|
|
||||||
Sprawdzane są dwie zupełnie odrębne rzeczy, i każda, obie albo żadna może
|
Sprawdzane są dwie zupełnie odrębne rzeczy, i każda, obie albo żadna może
|
||||||
coś wykazać:
|
coś wykazać:
|
||||||
|
|
||||||
|
|
@ -554,46 +358,11 @@ Po prostu zapytaj, zwykłym językiem:
|
||||||
|
|
||||||
> „Co wiemy o ryzyku migracji w Q3?”
|
> „Co wiemy o ryzyku migracji w Q3?”
|
||||||
|
|
||||||
Agent najpierw czyta `wiki/index.md`, żeby znaleźć odpowiednie strony. Jeśli
|
Agent najpierw czyta `wiki/index.md`, żeby znaleźć odpowiednie strony,
|
||||||
istnieje pasujący zakres projektu pod `wiki/projects/`, przeszukuje najpierw
|
sprawdza ich jednolinijkowy `tldr` przed załadowaniem pełnej strony,
|
||||||
ten zakres. Potem sprawdza jednolinijkowe pola `tldr`, w razie potrzeby
|
przechodzi po grafie wiedzy w poszukiwaniu powiązanych faktów i sięga do
|
||||||
uruchamia dokładne wyszukiwanie lokalne dla literalnych tokenów, rozszerza
|
`linked/`/`libs/`, jeśli lokalna wiki nic nie ma. Dostajesz odpowiedź
|
||||||
kontekst wokół dopasowanych sekcji, przechodzi po grafie wiedzy w poszukiwaniu
|
opartą na tym, co faktycznie zostało spisane, a nie na domysłach.
|
||||||
powiązanych faktów i sięga do `linked/`/`libs/`, jeśli lokalna wiki nic nie ma.
|
|
||||||
Dostajesz odpowiedź opartą na tym, co faktycznie zostało spisane, a nie na
|
|
||||||
domysłach.
|
|
||||||
|
|
||||||
Dwie rzeczy warte wiedzenia jako użytkownik:
|
|
||||||
|
|
||||||
- **Przeszukuje też `raw/inbox/`.** Coś, co wrzuciłeś dziś rano i czego jeszcze
|
|
||||||
nie zingestowałeś, nadal może odpowiedzieć na twoje pytanie. Agent powie ci,
|
|
||||||
kiedy odpowiedź pochodzi z niezingestowanego materiału, co jednocześnie
|
|
||||||
sygnalizuje, że „Ingest" jest zaległy.
|
|
||||||
- **Odpowiedzi noszą własne zastrzeżenia.** Jeśli strona stojąca za odpowiedzią
|
|
||||||
przekroczyła okno świeżości, ma niską pewność albo została przeczytana z
|
|
||||||
zapisanego indeksu konektora zamiast z żywego źródła, odpowiedź mówi o tym
|
|
||||||
obok danego twierdzenia. Jeśli dwie strony są ze sobą sprzeczne, a żadna nie
|
|
||||||
została jeszcze oznaczona jako zastąpiona, też o tym usłyszysz. Chodzi o to,
|
|
||||||
żebyś nigdy nie musiał sam czytać frontmatteru, by wiedzieć, na ile zaufać
|
|
||||||
temu, co właśnie dostałeś.
|
|
||||||
|
|
||||||
Gdy nadal nie ma odpowiedzi, agent powinien powiedzieć, czego brakuje, i albo
|
|
||||||
dodać/zaproponować krótki wpis w `wiki/query-gaps.md`, albo zasugerować
|
|
||||||
najmniejsze źródło do wrzucenia do `raw/inbox/`.
|
|
||||||
|
|
||||||
### Pytanie, kto się na czymś zna
|
|
||||||
|
|
||||||
> „Kto zna się na ścieżce przywracania checkpointów?" / „Kto jest właścicielem
|
|
||||||
> usługi billingowej?"
|
|
||||||
|
|
||||||
Na te pytania odpowiada bezpośrednio graf wiedzy, a nie wyszukiwanie nazwisk po
|
|
||||||
słowach kluczowych. Ingest zapisuje krawędź eksperctwa lub własności, gdy
|
|
||||||
materiał źródłowy faktycznie pokazuje, że ktoś odpowiada na pytania w danym
|
|
||||||
temacie albo ma zadeklarowaną odpowiedzialność za niego — a nie na podstawie
|
|
||||||
obecności na spotkaniu czy nazwy stanowiska. Jeśli nikt nie ma jeszcze
|
|
||||||
zapisanej krawędzi, agent wraca do tego, kogo zarchiwizowane źródła pokazują
|
|
||||||
jako odpowiadającego na tego rodzaju pytania, i mówi ci, że wnioskuje, a nie
|
|
||||||
raportuje.
|
|
||||||
|
|
||||||
### Nauka z wiki
|
### Nauka z wiki
|
||||||
|
|
||||||
|
|
@ -604,7 +373,7 @@ raportuje.
|
||||||
Zostaniesz zapytany o liczbę pytań i format (otwarte / jednokrotnego
|
Zostaniesz zapytany o liczbę pytań i format (otwarte / jednokrotnego
|
||||||
wyboru), a następnie przejdziesz przez nie jedno po drugim, z natychmiastową
|
wyboru), a następnie przejdziesz przez nie jedno po drugim, z natychmiastową
|
||||||
informacją zwrotną i bieżącym wynikiem. Nic nie jest zapisywane potem — to
|
informacją zwrotną i bieżącym wynikiem. Nic nie jest zapisywane potem — to
|
||||||
jednorazowy sprawdzian. `.agents/skills/ckb-quiz/SKILL.md`.
|
jednorazowy sprawdzian. `.agents/skills/cbk-quiz/SKILL.md`.
|
||||||
|
|
||||||
**Prawdziwy kurs, rozłożony w czasie** — powiedz:
|
**Prawdziwy kurs, rozłożony w czasie** — powiedz:
|
||||||
|
|
||||||
|
|
@ -698,7 +467,6 @@ Przykład:
|
||||||
> connector: sharepoint
|
> connector: sharepoint
|
||||||
> location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
> location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
||||||
> description: "Wspólny folder raportów zespołu finansowego"
|
> description: "Wspólny folder raportów zespołu finansowego"
|
||||||
> refresh_interval_days: 7
|
|
||||||
> ```
|
> ```
|
||||||
> *potem mówisz „Index external sources.”*
|
> *potem mówisz „Index external sources.”*
|
||||||
>
|
>
|
||||||
|
|
@ -748,18 +516,9 @@ dodaje blok `index:` do współdzielonego `source.yaml`:
|
||||||
index:
|
index:
|
||||||
store: git
|
store: git
|
||||||
location: "https://github.com/finance-team/index-cache.git"
|
location: "https://github.com/finance-team/index-cache.git"
|
||||||
# ref: main — opcjonalnie: przypina branch, tag albo podścieżkę w tym miejscu
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Za pierwszym razem, gdy ktokolwiek uruchomi „index external sources” po
|
Teraz, kiedy *ktokolwiek* powie „index external sources”, agent najpierw
|
||||||
dodaniu tego bloku, `https://github.com/finance-team/index-cache.git` jest
|
|
||||||
puste — to oczekiwane, nie błąd. Każde uruchomienie sprawdza je najpierw:
|
|
||||||
użytkownicy tylko-do-odczytu zobaczą po prostu „nic jeszcze nie
|
|
||||||
opublikowano, zapytaj administratora”; to uruchomienie administratora
|
|
||||||
faktycznie je tworzy, ponieważ uruchomienie z dostępem do zapisu zawsze
|
|
||||||
przebudowuje indeks z żywego konektora i wypycha wynik do tej lokalizacji,
|
|
||||||
niezależnie od tego, czy coś tam wcześniej było. Od tego momentu, kiedy
|
|
||||||
*ktokolwiek* powie „index external sources”, agent najpierw
|
|
||||||
pobiera to, co już zostało opublikowane — użytkownicy tylko-do-odczytu
|
pobiera to, co już zostało opublikowane — użytkownicy tylko-do-odczytu
|
||||||
zatrzymują się w tym miejscu; administrator dodatkowo przebudowuje indeks
|
zatrzymują się w tym miejscu; administrator dodatkowo przebudowuje indeks
|
||||||
z żywego konektora i wypycha odświeżoną wersję do tej samej lokalizacji,
|
z żywego konektora i wypycha odświeżoną wersję do tej samej lokalizacji,
|
||||||
|
|
@ -783,32 +542,15 @@ Jeśli ręcznie edytujesz stronę wiki, dobrą praktyką jest uruchomienie
|
||||||
potem „Lint”, żeby indeks/dziennik/graf pozostały spójne z tym, co
|
potem „Lint”, żeby indeks/dziennik/graf pozostały spójne z tym, co
|
||||||
zmieniłeś.
|
zmieniłeś.
|
||||||
|
|
||||||
Jest jeden wyjątek działający w drugą stronę. Na każdej stronie, którą agent
|
|
||||||
*regeneruje* — indeks konektora, mapa kodu — wszystko, co napiszesz, zwykle
|
|
||||||
ginie przy następnej przebudowie. Dlatego każda taka strona kończy się sekcją
|
|
||||||
`## Notes`, której żaden skill nigdy nie tknie:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
<!-- Twoje. Żaden skill tego nie nadpisuje. -->
|
|
||||||
```
|
|
||||||
|
|
||||||
Pisz tam, co chcesz — że ten dokument jest nieaktualny, że osoba w nim
|
|
||||||
wymieniona już nie pracuje, kogo naprawdę zapytać. Treść jest przenoszona
|
|
||||||
przez przebudowy co do bajtu. Wszystko, co napiszesz *powyżej* tego nagłówka
|
|
||||||
na stronie generowanej, zostanie nadpisane.
|
|
||||||
|
|
||||||
| Lokalizacja | Kto zwykle to zapisuje | Uwagi |
|
| Lokalizacja | Kto zwykle to zapisuje | Uwagi |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `raw/inbox/`, luźne pliki w `raw/` | **Tylko ty** | Agent tylko czyta, archiwizuje i przenosi rzeczy tutaj — nigdy nie tworzy treści w `raw/` sam. |
|
| `raw/inbox/`, luźne pliki w `raw/` | **Tylko ty** | Agent tylko czyta, archiwizuje i przenosi rzeczy tutaj — nigdy nie tworzy treści w `raw/` sam. |
|
||||||
| `raw/archive/<data>/` | Agent | Automatycznie zarchiwizowana kopia tego, co wrzuciłeś do `raw/inbox/`, uporządkowana według daty ingestu. Nie umieszczaj tu plików ręcznie — pozwól, żeby zrobił to Ingest, tak by data i powiązanie z wpisem w dzienniku były poprawne. |
|
| `raw/archive/<data>/` | Agent | Automatycznie zarchiwizowana kopia tego, co wrzuciłeś do `raw/inbox/`, uporządkowana według daty ingestu. Nie umieszczaj tu plików ręcznie — pozwól, żeby zrobił to Ingest, tak by data i powiązanie z wpisem w dzienniku były poprawne. |
|
||||||
| `linked/<name>/` | **Ty** (tworzysz dowiązanie symboliczne) | Wskazuje na rzeczywiste pliki innej KB, które żyją i są edytowane *w tamtym repozytorium* — nigdy tutaj. Agent nigdy nie może zapisywać wewnątrz `linked/`. |
|
| `linked/<name>/` | **Ty** (tworzysz dowiązanie symboliczne) | Wskazuje na rzeczywiste pliki innej KB, które żyją i są edytowane *w tamtym repozytorium* — nigdy tutaj. Agent nigdy nie może zapisywać wewnątrz `linked/`. |
|
||||||
| `libs/<name>/` (kopia git, bez `source.yaml`) | **Ty** (robisz `git clone`) | Zamrożona kopia zewnętrznej KB. Aktualizujesz ją, ponownie pobierając to repozytorium samodzielnie, a nie ręcznie edytując pliki tutaj. Agent nigdy nie może zapisywać wewnątrz niej. |
|
| `libs/<name>/` (kopia git, bez `source.yaml`) | **Ty** (robisz `git clone`) | Zamrożona kopia zewnętrznej KB. Aktualizujesz ją, ponownie pobierając to repozytorium samodzielnie, a nie ręcznie edytując pliki tutaj. Agent nigdy nie może zapisywać wewnątrz niej. |
|
||||||
| `libs/<name>/source.yaml` (konektor) | **Tylko ty** | Deklaruje konektor, lokalizację, opcjonalnie jak często ma być odświeżany (`refresh_interval_days:`) i opcjonalnie gdzie znajduje się współdzielony/wcześniej zbudowany indeks (`index:`). Agent go czyta, ale nigdy nie zapisuje — tak jak wszystko inne nadrzędne. |
|
| `libs/<name>/source.yaml` (konektor) | **Tylko ty** | Deklaruje konektor, lokalizację i opcjonalnie gdzie znajduje się współdzielony/wcześniej zbudowany indeks (`index:`). Agent go czyta, ale nigdy nie zapisuje — tak jak wszystko inne nadrzędne. |
|
||||||
| `libs/<name>/source.local.yaml` (konektor) | **Ty** (albo agent, tylko gdy wyraźnie poprosisz o zostanie/przestanie bycia administratorem tego źródła) | Osobiste, per-komputer ustawienie `access: write`/`read` — nigdy niecommitowane, nigdy niewidoczne dla innych. Brak = tylko do odczytu, domyślnie. |
|
| `libs/<name>/source.local.yaml` (konektor) | **Ty** (albo agent, tylko gdy wyraźnie poprosisz o zostanie/przestanie bycia administratorem tego źródła) | Osobiste, per-komputer ustawienie `access: write`/`read` — nigdy niecommitowane, nigdy niewidoczne dla innych. Brak = tylko do odczytu, domyślnie. |
|
||||||
| `libs/<name>/{index.md,entities/,graph/,log.md}` (konektor) | Generowane przez agenta, **możesz swobodnie edytować** | Własny indeks agenta dla tego jednego źródła konektora, budowany/odświeżany przez „Index external sources” — ale tylko jeśli masz lokalnie `access: write`; użytkownicy tylko-do-odczytu dostają po prostu pobraną kopię. Strukturalnie ta sama zasada jak przy wierszu `wiki/` poniżej — śmiało popraw wpis ręcznie, a potem uruchom „Lint” (teraz sprawdza też indeksy oparte na konektorach, respektując ten sam podział odczyt/zapis). Ograniczone wyłącznie do tego konektora; nigdy nie wmieszane w `wiki/`. **Przebudowę przetrwa tylko `## Notes`** — trzymaj tam wszystko, co chcesz zachować. |
|
| `libs/<name>/{index.md,entities/,graph/,log.md}` (konektor) | Generowane przez agenta, **możesz swobodnie edytować** | Własny indeks agenta dla tego jednego źródła konektora, budowany/odświeżany przez „Index external sources” — ale tylko jeśli masz lokalnie `access: write`; użytkownicy tylko-do-odczytu dostają po prostu pobraną kopię. Strukturalnie ta sama zasada jak przy wierszu `wiki/` poniżej — śmiało popraw wpis ręcznie, a potem uruchom „Lint” (teraz sprawdza też indeksy oparte na konektorach, respektując ten sam podział odczyt/zapis). Ograniczone wyłącznie do tego konektora; nigdy nie wmieszane w `wiki/`. |
|
||||||
| `wiki/decisions/` | Generowane przez agenta, **edytuj ostrożnie** | Mechanicznie tak samo jak reszta `wiki/`, ale te strony są z założenia tylko do dopisywania: popraw swobodnie literówkę czy źle przypisane nazwisko, ale nie przepisuj kontekstu ani uzasadnienia decyzji pod późniejszy pogląd — zapisz zamiast tego decyzję zastępującą, żeby historia przetrwała. |
|
|
||||||
| `wiki/` (strony, `index.md`, `overview.md`, `log.md`, `error-book.md`, `entities/`, `graph/`) | Generowane przez agenta, **możesz swobodnie edytować** | To jedyne miejsce, w którym zarówno agent zapisuje, jak i spodziewa się, że ty też możesz. Śmiało popraw stronę ręcznie — zachowaj tylko pola frontmatteru (lub zaktualizuj `last_updated`) i uruchom potem Lint, jeśli dotknąłeś czegoś, do czego odwołuje się indeks/graf/dziennik. |
|
| `wiki/` (strony, `index.md`, `overview.md`, `log.md`, `error-book.md`, `entities/`, `graph/`) | Generowane przez agenta, **możesz swobodnie edytować** | To jedyne miejsce, w którym zarówno agent zapisuje, jak i spodziewa się, że ty też możesz. Śmiało popraw stronę ręcznie — zachowaj tylko pola frontmatteru (lub zaktualizuj `last_updated`) i uruchom potem Lint, jeśli dotknąłeś czegoś, do czego odwołuje się indeks/graf/dziennik. |
|
||||||
| `outputs/okf/`, `outputs/starlight/` | Agent, **w pełni regenerowane** | Nie edytuj ręcznie — to zignorowane przez git artefakty budowania, cicho nadpisywane przy każdym kolejnym eksporcie. Jeśli coś jest nie tak, popraw stronę wiki, z której to pochodzi, i wyeksportuj ponownie. |
|
| `outputs/okf/`, `outputs/starlight/` | Agent, **w pełni regenerowane** | Nie edytuj ręcznie — to zignorowane przez git artefakty budowania, cicho nadpisywane przy każdym kolejnym eksporcie. Jeśli coś jest nie tak, popraw stronę wiki, z której to pochodzi, i wyeksportuj ponownie. |
|
||||||
| `outputs/teaching/<topic>/` | Agent, stan półtrwały | `plan.md`/`progress.md`, które skill do nauczania czyta i zapisuje między sesjami. Możesz je oglądać kiedy chcesz; ręczna edycja jest możliwa, ale może pomieszać śledzenie „co dalej” — bezpieczniej powiedzieć agentowi, co chcesz zmienić, i pozwolić mu zaktualizować pliki. |
|
| `outputs/teaching/<topic>/` | Agent, stan półtrwały | `plan.md`/`progress.md`, które skill do nauczania czyta i zapisuje między sesjami. Możesz je oglądać kiedy chcesz; ręczna edycja jest możliwa, ale może pomieszać śledzenie „co dalej” — bezpieczniej powiedzieć agentowi, co chcesz zmienić, i pozwolić mu zaktualizować pliki. |
|
||||||
|
|
@ -825,14 +567,10 @@ na stronie generowanej, zostanie nadpisane.
|
||||||
| Powiedz... | Co się dzieje | Skill |
|
| Powiedz... | Co się dzieje | Skill |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| „Set up a new wiki like this one in \<folder\>” | Zakłada świeżą, pustą KB z tym schematem | `ckb-init` |
|
| „Set up a new wiki like this one in \<folder\>” | Zakłada świeżą, pustą KB z tym schematem | `ckb-init` |
|
||||||
| „Pull the ckb repo into \<folder\> and set up the wiki” | Klonuje repozytorium szablonu do katalogu roboczego, a potem zakłada z niego pustą KB | `ckb-init` |
|
|
||||||
| „Ingest” / „Sync the wiki” / „Update the wiki” | Przetwarza `raw/inbox/` na ustrukturyzowane strony `wiki/` | `ckb-ingest` |
|
| „Ingest” / „Sync the wiki” / „Update the wiki” | Przetwarza `raw/inbox/` na ustrukturyzowane strony `wiki/` | `ckb-ingest` |
|
||||||
| „Zapisz decyzję: ...” / „zdecydowaliśmy ...” | Zapisuje numerowany rekord decyzji pod `wiki/decisions/` | `ckb-decide` |
|
|
||||||
| „Co zdecydowaliśmy w sprawie X” / „kto zdecydował X” / „co jest otwarte” | Odpowiada z zapisów decyzji, z autorem, datą i statusem | `ckb-decide` |
|
|
||||||
| „Lint” | Sprawdza kondycję wiki, automatycznie naprawia to, co bezpiecznie może | `ckb-lint` |
|
| „Lint” | Sprawdza kondycję wiki, automatycznie naprawia to, co bezpiecznie może | `ckb-lint` |
|
||||||
| „Zresetuj wiki” / „Zrób z tego czysty szablon” | **Destrukcyjne.** Usuwa zgromadzoną wiedzę i odtwarza pusty szkielet, po inwentarzu i potwierdzeniu wpisaną frazą | `ckb-reset` |
|
|
||||||
| „Sync changes” / „Sync with git” | Commituje, pobiera, rozwiązuje konflikty, wypycha do `origin` | `ckb-sync-changes` |
|
| „Sync changes” / „Sync with git” | Commituje, pobiera, rozwiązuje konflikty, wypycha do `origin` | `ckb-sync-changes` |
|
||||||
| „Quiz me on X” | Jednorazowy, punktowany sprawdzian wiedzy | `ckb-quiz` |
|
| „Quiz me on X” | Jednorazowy, punktowany sprawdzian wiedzy | `cbk-quiz` |
|
||||||
| „Teach me the wiki” / „Teach me about X” | Planuje i prowadzi rozłożony w czasie kurs ze śledzeniem postępu | `ckb-teach-me` |
|
| „Teach me the wiki” / „Teach me about X” | Planuje i prowadzi rozłożony w czasie kurs ze śledzeniem postępu | `ckb-teach-me` |
|
||||||
| „Onboard me on X” / „Where do I start with X” | Krótka, prowadzona kolejność czytania po grafie | `ckb-onboard-me` |
|
| „Onboard me on X” / „Where do I start with X” | Krótka, prowadzona kolejność czytania po grafie | `ckb-onboard-me` |
|
||||||
| „Give me a project summary” | Regeneruje `PROJECT-OVERVIEW.md` | `ckb-project-summary` |
|
| „Give me a project summary” | Regeneruje `PROJECT-OVERVIEW.md` | `ckb-project-summary` |
|
||||||
|
|
@ -840,5 +578,4 @@ na stronie generowanej, zostanie nadpisane.
|
||||||
| „Export the wiki to Starlight” | Czytelna dla człowieka strona dokumentacji w `outputs/starlight/` | `ckb-export-starlight` |
|
| „Export the wiki to Starlight” | Czytelna dla człowieka strona dokumentacji w `outputs/starlight/` | `ckb-export-starlight` |
|
||||||
| „Upgrade the wiki” / „Check for a newer template version” | Sprawdza wersje szablonu i schematu wiki względem kanonicznego repozytorium, aktualizuje to, co zaakceptujesz | `ckb-upgrade` |
|
| „Upgrade the wiki” / „Check for a newer template version” | Sprawdza wersje szablonu i schematu wiki względem kanonicznego repozytorium, aktualizuje to, co zaakceptujesz | `ckb-upgrade` |
|
||||||
| „Index external sources” / „Index libs” | Buduje/odświeża samodzielny indeks dla każdego `libs/<name>/` opartego na konektorze | `ckb-index-external` |
|
| „Index external sources” / „Index libs” | Buduje/odświeża samodzielny indeks dla każdego `libs/<name>/` opartego na konektorze | `ckb-index-external` |
|
||||||
| Po prostu zadaj pytanie | Odpowiedź z wiki, przy użyciu kaskady indeks/TLDR/graf, z zastrzeżeniami gdy źródło jest nieaktualne lub sprzeczne | `ckb-retrieve` |
|
| Po prostu zadaj pytanie | Odpowiedź z wiki, przy użyciu kaskady indeks/TLDR/graf | — (podstawowy przepływ zapytań) |
|
||||||
| „Kto wie o X" / „Kto jest właścicielem X" | Odpowiedź z krawędzi eksperctwa/własności w grafie | `ckb-retrieve` |
|
|
||||||
|
|
|
||||||
202
OPENSPEC.md
202
OPENSPEC.md
|
|
@ -1,202 +0,0 @@
|
||||||
# OpenSpec in this knowledge base
|
|
||||||
|
|
||||||
*Read this in: **English** | [Polski](OPENSPEC.pl.md)*
|
|
||||||
|
|
||||||
How to add [OpenSpec](https://openspec.dev) to a Cascade KB that documents
|
|
||||||
software you're building, and how it fits alongside the wiki.
|
|
||||||
|
|
||||||
This is a human guide. The agent's own rules live in the `software` module —
|
|
||||||
[.agents/modules/software/](.agents/modules/software/README.md) — which must be
|
|
||||||
installed before any of this applies. Say **"install the software module"**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contents
|
|
||||||
|
|
||||||
1. [What OpenSpec is for here](#1-what-openspec-is-for-here)
|
|
||||||
2. [Read this before you run `openspec init`](#2-read-this-before-you-run-openspec-init)
|
|
||||||
3. [Installing](#3-installing)
|
|
||||||
4. [The two spec levels](#4-the-two-spec-levels)
|
|
||||||
5. [Daily workflow](#5-daily-workflow)
|
|
||||||
6. [How specs reach the wiki](#6-how-specs-reach-the-wiki)
|
|
||||||
7. [Keeping the levels honest](#7-keeping-the-levels-honest)
|
|
||||||
8. [Quick reference](#8-quick-reference)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. What OpenSpec is for here
|
|
||||||
|
|
||||||
The wiki records three different things about software, and it's worth keeping
|
|
||||||
them apart:
|
|
||||||
|
|
||||||
| Question | Where it lives | Who writes it |
|
|
||||||
|---|---|---|
|
|
||||||
| What *is* the code? | `wiki/entities/` — `repository` and `component` pages | `ckb-code-map` |
|
|
||||||
| What *should* it do? | `openspec/specs/` | `ckb-spec` + OpenSpec |
|
|
||||||
| What did we *choose*, and why? | `wiki/decisions/` | `ckb-decide` |
|
|
||||||
|
|
||||||
OpenSpec owns the middle row: a spec states a standing contract, and a change
|
|
||||||
proposal states what should become true next. You write the spec before the
|
|
||||||
code, the agent implements against it, and the archived change becomes part of
|
|
||||||
the permanent record.
|
|
||||||
|
|
||||||
If something doesn't constrain future behaviour, it isn't a spec — it's a
|
|
||||||
decision. Say "record a decision" instead.
|
|
||||||
|
|
||||||
## 2. Read this before you run `openspec init`
|
|
||||||
|
|
||||||
**Never run `openspec init` in the knowledge base root.** Run it inside
|
|
||||||
`src/<repo>/`.
|
|
||||||
|
|
||||||
This isn't stylistic caution. `openspec init` writes tool integration files into
|
|
||||||
`.claude/skills/`, and it adds marker blocks to `AGENTS.md` / `CLAUDE.md`. In
|
|
||||||
this repository both of those are load-bearing:
|
|
||||||
|
|
||||||
- `.claude/skills` is a **symlink** to `.agents/skills`. Anything OpenSpec writes
|
|
||||||
there lands in your KB's own skill set.
|
|
||||||
- `CLAUDE.md` is a **symlink** to `AGENTS.md` — the KB's system prompt. They are
|
|
||||||
one file, so a marker block written to "both" is written twice to the same
|
|
||||||
place.
|
|
||||||
|
|
||||||
Run it at the root and you get OpenSpec's per-repo workflow instructions mixed
|
|
||||||
into the rules governing your whole knowledge base. Run it in `src/<repo>/` and
|
|
||||||
everything stays where it belongs: the repo gets its own `.claude/`, its own
|
|
||||||
`AGENTS.md`, and its specs travel with its code.
|
|
||||||
|
|
||||||
The KB root's `openspec/` directory is **not** an OpenSpec install (§4), so it
|
|
||||||
never needs `init`.
|
|
||||||
|
|
||||||
## 3. Installing
|
|
||||||
|
|
||||||
Needs **Node.js 20.19.0+**.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g @fission-ai/openspec@latest # or pnpm / yarn / bun
|
|
||||||
openspec --version
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, per repository:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd src/<repo>
|
|
||||||
openspec init
|
|
||||||
```
|
|
||||||
|
|
||||||
After upgrading the CLI later, run `openspec update` in each repo to refresh its
|
|
||||||
generated instruction files.
|
|
||||||
|
|
||||||
**Uninstalling from a repo** means removing its `openspec/` directory, its
|
|
||||||
generated tool files, and the OpenSpec marker blocks from that repo's
|
|
||||||
`AGENTS.md` / `CLAUDE.md` by hand — the CLI doesn't unwind those for you.
|
|
||||||
|
|
||||||
## 4. The two spec levels
|
|
||||||
|
|
||||||
Specs live in two places on purpose.
|
|
||||||
|
|
||||||
**KB root — `openspec/`** — cross-cutting capabilities that span several `src/`
|
|
||||||
repos. The *what* and *why* that no single repo owns.
|
|
||||||
|
|
||||||
This layer is managed by the `ckb-spec` skill, **not** by the OpenSpec CLI.
|
|
||||||
There's no code beneath it and no repo for it to govern, so OpenSpec's
|
|
||||||
repo-shaped workflow doesn't apply. It's tracked in the KB's git history, like
|
|
||||||
every other piece of knowledge here.
|
|
||||||
|
|
||||||
**Per repo — `src/<repo>/openspec/`** — how that repo implements those
|
|
||||||
capabilities. The *how*. This is OpenSpec's native case: managed by the CLI and
|
|
||||||
its own instructions, travelling with the code and riding along in that repo's
|
|
||||||
pull requests.
|
|
||||||
|
|
||||||
The agent defers to OpenSpec here and won't substitute a workflow of its own. If
|
|
||||||
the CLI isn't installed it says so rather than improvising.
|
|
||||||
|
|
||||||
## 5. Daily workflow
|
|
||||||
|
|
||||||
From inside a repo that's been initialized, the loop is
|
|
||||||
**propose → apply → archive**:
|
|
||||||
|
|
||||||
| Step | Say / run | What happens |
|
|
||||||
|---|---|---|
|
|
||||||
| Propose | `/opsx:propose <what you want>` | Proposal, spec delta, design and tasks written as Markdown under `openspec/changes/<id>/` |
|
|
||||||
| Review | `openspec show <id>`, `openspec validate <id>` | Read the delta; validation checks structure and checks modified requirements against the specs they'd replace |
|
|
||||||
| Apply | `/opsx:apply` | The agent implements against the spec |
|
|
||||||
| Archive | `/opsx:archive` or `openspec archive <id>` | The change merges into the main specs and moves to `changes/archive/` |
|
|
||||||
|
|
||||||
Useful alongside: `openspec list`, `openspec status`, `openspec view` (an
|
|
||||||
interactive dashboard).
|
|
||||||
|
|
||||||
For the KB-root layer there's no CLI — just say **"write a cross-cutting spec
|
|
||||||
for X"** and `ckb-spec` handles it.
|
|
||||||
|
|
||||||
## 6. How specs reach the wiki
|
|
||||||
|
|
||||||
Three bridges, all handled by `ckb-spec`. This is what stops OpenSpec from being
|
|
||||||
a parallel universe next to your knowledge base.
|
|
||||||
|
|
||||||
**Archived change → decision record.** An applied-and-archived change is a
|
|
||||||
decision that was made and acted on. The agent *offers* to record it under
|
|
||||||
`wiki/decisions/` — the proposal's reasoning becomes Context and Rationale, the
|
|
||||||
delta becomes the Decision, rejected options become Alternatives considered.
|
|
||||||
|
|
||||||
It offers rather than doing it automatically, deliberately. Not every routine
|
|
||||||
change deserves a permanent numbered record, and a decisions log padded with
|
|
||||||
them stops being worth reading.
|
|
||||||
|
|
||||||
**Spec → entity page.** Current KB-root specs surface in `wiki/entities/` as thin
|
|
||||||
`type: spec` pages, so questions like "what is this supposed to do?" get answered
|
|
||||||
from the index without opening the spec tree. The spec file stays the source of
|
|
||||||
truth — the wiki page is a pointer, not a copy.
|
|
||||||
|
|
||||||
**Level → level.** Root specs and repo specs link to each other (§7).
|
|
||||||
|
|
||||||
## 7. Keeping the levels honest
|
|
||||||
|
|
||||||
Two spec levels means two places a statement can live, and therefore two places
|
|
||||||
it can quietly diverge. One rule prevents that:
|
|
||||||
|
|
||||||
> A root spec lists every repo spec implementing it in `implemented_by:`.
|
|
||||||
> A repo spec names its parent in `implements:`.
|
|
||||||
> **Always set both sides.**
|
|
||||||
|
|
||||||
Those links become graph edges, which turns divergence into a visible finding
|
|
||||||
rather than a silent second source of truth.
|
|
||||||
|
|
||||||
Say **"sync the specs"** (or run `lint`) and you get four checks:
|
|
||||||
|
|
||||||
1. Root specs with no implementer — *specified, but nobody's building it.*
|
|
||||||
2. Repo specs whose parent is missing or archived — *building against something
|
|
||||||
that's no longer true.*
|
|
||||||
3. One-sided links, either direction.
|
|
||||||
4. Root specs older than the mapped commit of every repo implementing them —
|
|
||||||
*possible drift, worth a look.*
|
|
||||||
|
|
||||||
None of these are auto-fixed. Each is a statement about intent, and only you know
|
|
||||||
which side is right.
|
|
||||||
|
|
||||||
## 8. Quick reference
|
|
||||||
|
|
||||||
| You want to | Say / run |
|
|
||||||
|---|---|
|
|
||||||
| Enable all of this | "install the software module" |
|
|
||||||
| Add OpenSpec to a repo | `cd src/<repo> && openspec init` |
|
|
||||||
| Map a repo into the wiki | "map `src/<repo>`" |
|
|
||||||
| Write a cross-cutting spec | "write a cross-cutting spec for X" |
|
|
||||||
| Propose a change in a repo | `/opsx:propose <what>` |
|
|
||||||
| Check spec health | "sync the specs" |
|
|
||||||
| Turn an archived change into a decision | "record this change as a decision" |
|
|
||||||
|
|
||||||
**Never:** run `openspec init` at the KB root · `git add` anything under `src/` ·
|
|
||||||
edit a repo's OpenSpec files to match this KB's conventions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
- [OpenSpec documentation](https://openspec.dev/docs/installation) ·
|
|
||||||
[Fission-AI/OpenSpec](https://github.com/Fission-AI/OpenSpec) ·
|
|
||||||
[@fission-ai/openspec on npm](https://www.npmjs.com/package/@fission-ai/openspec)
|
|
||||||
- Design records: [D-0001](wiki/decisions/0001-opt-in-file-based-kb-modules.md),
|
|
||||||
[D-0002](wiki/decisions/0002-software-module-design.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Licensed under the Apache License, Version 2.0 — see [LICENSE](LICENSE).*
|
|
||||||
209
OPENSPEC.pl.md
209
OPENSPEC.pl.md
|
|
@ -1,209 +0,0 @@
|
||||||
# OpenSpec w tej bazie wiedzy
|
|
||||||
|
|
||||||
*Read this in: [English](OPENSPEC.md) | **Polski***
|
|
||||||
|
|
||||||
Jak dodać [OpenSpec](https://openspec.dev) do Cascade KB, które dokumentuje
|
|
||||||
tworzone przez Ciebie oprogramowanie, i jak to współgra z wiki.
|
|
||||||
|
|
||||||
To przewodnik dla człowieka. Zasady dla samego agenta są w module `software` —
|
|
||||||
[.agents/modules/software/](.agents/modules/software/README.md) — który musi być
|
|
||||||
zainstalowany, zanim cokolwiek z poniższych zacznie obowiązywać. Powiedz:
|
|
||||||
**„zainstaluj moduł software”**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Spis treści
|
|
||||||
|
|
||||||
1. [Po co tu OpenSpec](#1-po-co-tu-openspec)
|
|
||||||
2. [Przeczytaj to, zanim uruchomisz `openspec init`](#2-przeczytaj-to-zanim-uruchomisz-openspec-init)
|
|
||||||
3. [Instalacja](#3-instalacja)
|
|
||||||
4. [Dwa poziomy specyfikacji](#4-dwa-poziomy-specyfikacji)
|
|
||||||
5. [Codzienna praca](#5-codzienna-praca)
|
|
||||||
6. [Jak specyfikacje trafiają do wiki](#6-jak-specyfikacje-trafiają-do-wiki)
|
|
||||||
7. [Pilnowanie spójności poziomów](#7-pilnowanie-spójności-poziomów)
|
|
||||||
8. [Szybki przegląd](#8-szybki-przegląd)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Po co tu OpenSpec
|
|
||||||
|
|
||||||
Wiki zapisuje o oprogramowaniu trzy różne rzeczy i warto trzymać je osobno:
|
|
||||||
|
|
||||||
| Pytanie | Gdzie mieszka | Kto zapisuje |
|
|
||||||
|---|---|---|
|
|
||||||
| Czym *jest* kod? | `wiki/entities/` — strony `repository` i `component` | `ckb-code-map` |
|
|
||||||
| Co *powinien* robić? | `openspec/specs/` | `ckb-spec` + OpenSpec |
|
|
||||||
| Co *wybraliśmy* i dlaczego? | `wiki/decisions/` | `ckb-decide` |
|
|
||||||
|
|
||||||
OpenSpec odpowiada za środkowy wiersz: specyfikacja opisuje obowiązujący
|
|
||||||
kontrakt, a propozycja zmiany — co ma stać się prawdą w następnej kolejności.
|
|
||||||
Piszesz specyfikację przed kodem, agent implementuje pod nią, a zarchiwizowana
|
|
||||||
zmiana staje się częścią trwałego zapisu.
|
|
||||||
|
|
||||||
Jeśli coś nie ogranicza przyszłego zachowania, to nie jest specyfikacja, tylko
|
|
||||||
decyzja. Powiedz wtedy „zapisz decyzję”.
|
|
||||||
|
|
||||||
## 2. Przeczytaj to, zanim uruchomisz `openspec init`
|
|
||||||
|
|
||||||
**Nigdy nie uruchamiaj `openspec init` w katalogu głównym bazy wiedzy.**
|
|
||||||
Uruchamiaj go w `src/<repo>/`.
|
|
||||||
|
|
||||||
To nie jest ostrożność na wszelki wypadek. `openspec init` zapisuje pliki
|
|
||||||
integracji z narzędziami w `.claude/skills/` oraz dodaje bloki znacznikowe do
|
|
||||||
`AGENTS.md` / `CLAUDE.md`. W tym repozytorium oba te miejsca są nośne:
|
|
||||||
|
|
||||||
- `.claude/skills` to **dowiązanie symboliczne** do `.agents/skills`. Cokolwiek
|
|
||||||
OpenSpec tam zapisze, ląduje w zestawie skilli Twojej bazy wiedzy.
|
|
||||||
- `CLAUDE.md` to **dowiązanie symboliczne** do `AGENTS.md` — systemowego prompta
|
|
||||||
bazy. To jeden i ten sam plik, więc blok zapisany „do obu” zapisuje się
|
|
||||||
dwukrotnie w to samo miejsce.
|
|
||||||
|
|
||||||
Uruchomienie w katalogu głównym wmiesza instrukcje OpenSpec dla pojedynczego
|
|
||||||
repozytorium w reguły rządzące całą bazą wiedzy. Uruchomienie w `src/<repo>/`
|
|
||||||
zostawia wszystko na swoim miejscu: repozytorium dostaje własne `.claude/`,
|
|
||||||
własny `AGENTS.md`, a jego specyfikacje podróżują razem z kodem.
|
|
||||||
|
|
||||||
Katalog `openspec/` w korzeniu bazy **nie jest** instalacją OpenSpec (§4), więc
|
|
||||||
nigdy nie wymaga `init`.
|
|
||||||
|
|
||||||
## 3. Instalacja
|
|
||||||
|
|
||||||
Wymaga **Node.js 20.19.0 lub nowszego**.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g @fission-ai/openspec@latest # albo pnpm / yarn / bun
|
|
||||||
openspec --version
|
|
||||||
```
|
|
||||||
|
|
||||||
Następnie, dla każdego repozytorium:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd src/<repo>
|
|
||||||
openspec init
|
|
||||||
```
|
|
||||||
|
|
||||||
Po późniejszej aktualizacji CLI uruchom w każdym repozytorium `openspec update`,
|
|
||||||
żeby odświeżyć wygenerowane pliki instrukcji.
|
|
||||||
|
|
||||||
**Odinstalowanie z repozytorium** oznacza ręczne usunięcie katalogu `openspec/`,
|
|
||||||
wygenerowanych plików narzędziowych oraz bloków znacznikowych OpenSpec z
|
|
||||||
`AGENTS.md` / `CLAUDE.md` tego repozytorium — CLI tego za Ciebie nie cofa.
|
|
||||||
|
|
||||||
## 4. Dwa poziomy specyfikacji
|
|
||||||
|
|
||||||
Specyfikacje mieszkają w dwóch miejscach i jest to zamierzone.
|
|
||||||
|
|
||||||
**Korzeń bazy — `openspec/`** — przekrojowe zdolności obejmujące kilka
|
|
||||||
repozytoriów w `src/`. To *co* i *dlaczego*, którego nie posiada żadne pojedyncze
|
|
||||||
repozytorium.
|
|
||||||
|
|
||||||
Tą warstwą zarządza skill `ckb-spec`, a **nie** CLI OpenSpec. Nie ma pod nią
|
|
||||||
kodu ani repozytorium, którym miałaby rządzić, więc workflow OpenSpec — skrojony
|
|
||||||
pod repozytorium — tu nie pasuje. Warstwa ta jest śledzona w historii gita bazy,
|
|
||||||
tak jak każda inna wiedza.
|
|
||||||
|
|
||||||
**Per repozytorium — `src/<repo>/openspec/`** — jak to repozytorium realizuje te
|
|
||||||
zdolności. To *jak*. Rodzimy przypadek OpenSpec: zarządzany przez CLI i jego
|
|
||||||
własne instrukcje, podróżujący z kodem i biorący udział w pull requestach tego
|
|
||||||
repozytorium.
|
|
||||||
|
|
||||||
Agent oddaje tu pole OpenSpec i nie podstawia własnego workflow. Jeśli CLI nie
|
|
||||||
jest zainstalowane, powie to wprost, zamiast improwizować.
|
|
||||||
|
|
||||||
## 5. Codzienna praca
|
|
||||||
|
|
||||||
W zainicjalizowanym repozytorium pętla wygląda tak:
|
|
||||||
**propose → apply → archive**.
|
|
||||||
|
|
||||||
| Krok | Powiedz / uruchom | Co się dzieje |
|
|
||||||
|---|---|---|
|
|
||||||
| Propozycja | `/opsx:propose <co chcesz zbudować>` | Propozycja, delta specyfikacji, projekt i zadania zapisane jako Markdown w `openspec/changes/<id>/` |
|
|
||||||
| Przegląd | `openspec show <id>`, `openspec validate <id>` | Czytasz deltę; walidacja sprawdza strukturę oraz zmodyfikowane wymagania wobec specyfikacji, które mają zastąpić |
|
|
||||||
| Wdrożenie | `/opsx:apply` | Agent implementuje pod specyfikację |
|
|
||||||
| Archiwizacja | `/opsx:archive` lub `openspec archive <id>` | Zmiana scala się z główną specyfikacją i trafia do `changes/archive/` |
|
|
||||||
|
|
||||||
Przydatne obok: `openspec list`, `openspec status`, `openspec view`
|
|
||||||
(interaktywny pulpit).
|
|
||||||
|
|
||||||
Dla warstwy w korzeniu bazy nie ma CLI — po prostu powiedz **„napisz przekrojową
|
|
||||||
specyfikację dla X”**, a zajmie się tym `ckb-spec`.
|
|
||||||
|
|
||||||
## 6. Jak specyfikacje trafiają do wiki
|
|
||||||
|
|
||||||
Trzy mostki, wszystkie obsługiwane przez `ckb-spec`. To one sprawiają, że
|
|
||||||
OpenSpec nie staje się równoległym światem obok bazy wiedzy.
|
|
||||||
|
|
||||||
**Zarchiwizowana zmiana → rekord decyzji.** Wdrożona i zarchiwizowana zmiana to
|
|
||||||
decyzja, którą podjęto i wykonano. Agent *proponuje* zapisanie jej w
|
|
||||||
`wiki/decisions/` — uzasadnienie z propozycji staje się Kontekstem i Uzasadnieniem,
|
|
||||||
delta staje się Decyzją, a odrzucone opcje — Rozważanymi alternatywami.
|
|
||||||
|
|
||||||
Proponuje, a nie robi tego automatycznie, i jest to celowe. Nie każda rutynowa
|
|
||||||
zmiana zasługuje na trwały, numerowany rekord, a dziennik decyzji zapchany nimi
|
|
||||||
przestaje być wart czytania.
|
|
||||||
|
|
||||||
**Specyfikacja → strona encji.** Aktualne specyfikacje z korzenia bazy pojawiają
|
|
||||||
się w `wiki/entities/` jako cienkie strony `type: spec`, dzięki czemu pytania w
|
|
||||||
rodzaju „co to ma robić?” da się odpowiedzieć z indeksu, bez otwierania drzewa
|
|
||||||
specyfikacji. Źródłem prawdy pozostaje plik specyfikacji — strona wiki jest
|
|
||||||
wskaźnikiem, nie kopią.
|
|
||||||
|
|
||||||
**Poziom → poziom.** Specyfikacje z korzenia i z repozytoriów linkują się
|
|
||||||
nawzajem (§7).
|
|
||||||
|
|
||||||
## 7. Pilnowanie spójności poziomów
|
|
||||||
|
|
||||||
Dwa poziomy specyfikacji to dwa miejsca, w których może mieszkać to samo
|
|
||||||
stwierdzenie — a więc i dwa, w których może się po cichu rozjechać. Zapobiega
|
|
||||||
temu jedna zasada:
|
|
||||||
|
|
||||||
> Specyfikacja z korzenia wymienia każdą implementującą ją specyfikację
|
|
||||||
> repozytorium w `implemented_by:`.
|
|
||||||
> Specyfikacja repozytorium wskazuje rodzica w `implements:`.
|
|
||||||
> **Zawsze ustawiaj obie strony.**
|
|
||||||
|
|
||||||
Te odnośniki stają się krawędziami grafu, co zamienia rozjazd w widoczne
|
|
||||||
znalezisko, zamiast w cichy drugi ośrodek prawdy.
|
|
||||||
|
|
||||||
Powiedz **„zsynchronizuj specyfikacje”** (albo uruchom `lint`), a dostaniesz
|
|
||||||
cztery kontrole:
|
|
||||||
|
|
||||||
1. Specyfikacje z korzenia bez implementacji — *opisane, ale nikt tego nie buduje.*
|
|
||||||
2. Specyfikacje repozytorium, których rodzic zniknął lub trafił do archiwum —
|
|
||||||
*budujesz pod coś, co przestało obowiązywać.*
|
|
||||||
3. Jednostronne odnośniki, w dowolnym kierunku.
|
|
||||||
4. Specyfikacje z korzenia starsze niż zmapowany commit każdego repozytorium,
|
|
||||||
które je implementuje — *możliwy rozjazd, wart sprawdzenia.*
|
|
||||||
|
|
||||||
Żadna z tych rzeczy nie jest naprawiana automatycznie. Każda jest stwierdzeniem
|
|
||||||
o intencji, a tylko Ty wiesz, która strona ma rację.
|
|
||||||
|
|
||||||
## 8. Szybki przegląd
|
|
||||||
|
|
||||||
| Chcesz | Powiedz / uruchom |
|
|
||||||
|---|---|
|
|
||||||
| Włączyć to wszystko | „zainstaluj moduł software” |
|
|
||||||
| Dodać OpenSpec do repozytorium | `cd src/<repo> && openspec init` |
|
|
||||||
| Zmapować repozytorium do wiki | „zmapuj `src/<repo>`” |
|
|
||||||
| Napisać przekrojową specyfikację | „napisz przekrojową specyfikację dla X” |
|
|
||||||
| Zaproponować zmianę w repozytorium | `/opsx:propose <co>` |
|
|
||||||
| Sprawdzić stan specyfikacji | „zsynchronizuj specyfikacje” |
|
|
||||||
| Zamienić zarchiwizowaną zmianę w decyzję | „zapisz tę zmianę jako decyzję” |
|
|
||||||
|
|
||||||
**Nigdy:** nie uruchamiaj `openspec init` w korzeniu bazy · nie rób `git add`
|
|
||||||
niczego w `src/` · nie przerabiaj plików OpenSpec repozytorium pod konwencje tej
|
|
||||||
bazy.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Źródła
|
|
||||||
|
|
||||||
- [Dokumentacja OpenSpec](https://openspec.dev/docs/installation) ·
|
|
||||||
[Fission-AI/OpenSpec](https://github.com/Fission-AI/OpenSpec) ·
|
|
||||||
[@fission-ai/openspec na npm](https://www.npmjs.com/package/@fission-ai/openspec)
|
|
||||||
- Rekordy projektowe: [D-0001](wiki/decisions/0001-opt-in-file-based-kb-modules.md),
|
|
||||||
[D-0002](wiki/decisions/0002-software-module-design.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Udostępniane na licencji Apache License 2.0 — zobacz [LICENSE](LICENSE).*
|
|
||||||
220
README.md
220
README.md
|
|
@ -14,37 +14,6 @@ how to create a wiki, add knowledge, keep it tidy, sync with others, and
|
||||||
worked examples for every use case — see [MANUAL.md](MANUAL.md)
|
worked examples for every use case — see [MANUAL.md](MANUAL.md)
|
||||||
([Polski](MANUAL.pl.md)).
|
([Polski](MANUAL.pl.md)).
|
||||||
|
|
||||||
If this KB documents software you're building, the optional `software` module
|
|
||||||
adds `src/` repositories and spec-driven development — see
|
|
||||||
[OPENSPEC.md](OPENSPEC.md) ([Polski](OPENSPEC.pl.md)).
|
|
||||||
|
|
||||||
For the page schema in full, and for how both version numbers got where they
|
|
||||||
are, see [CHANGELOG.md](CHANGELOG.md) ([Polski](CHANGELOG.pl.md)).
|
|
||||||
|
|
||||||
### Release channels
|
|
||||||
|
|
||||||
The template repo keeps three branches. They are not interchangeable:
|
|
||||||
|
|
||||||
| Branch | What it is | Who should track it |
|
|
||||||
|---|---|---|
|
|
||||||
| `main` | **Stable** — the released template | Everyone, by default |
|
|
||||||
| `test` | **Release candidate** — validated before merging to `main` | Anyone helping validate a release, or needing a landed-but-unshipped fix |
|
|
||||||
| `experimental` | **Development** — active work, may be broken or reverted | People developing the template itself |
|
|
||||||
|
|
||||||
Both `ckb-init` and `ckb-upgrade` default to `main`. To use another channel,
|
|
||||||
just say which: *"initialize from the test branch"*, *"check experimental for
|
|
||||||
updates"*, *"switch this KB back to the stable channel"*. The branch a KB
|
|
||||||
tracks is recorded in `ckb.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
template:
|
|
||||||
repo: https://git.wierzbowa.cloud/michal/ckb.git
|
|
||||||
branch: main
|
|
||||||
```
|
|
||||||
|
|
||||||
A KB with no `ckb.yaml` (or no `template:` block) is treated as tracking
|
|
||||||
`main`, which is what every KB predating this convention was doing anyway.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
@ -62,9 +31,6 @@ A KB with no `ckb.yaml` (or no `template:` block) is treated as tracking
|
||||||
│ ├── overview.md # High-level map
|
│ ├── overview.md # High-level map
|
||||||
│ ├── log.md # Root rollup change log
|
│ ├── log.md # Root rollup change log
|
||||||
│ ├── error-book.md # Compilation errors & derived constraints
|
│ ├── error-book.md # Compilation errors & derived constraints
|
||||||
│ ├── query-gaps.md # Failed or missing-answer questions for future ingest
|
|
||||||
│ ├── projects/ # Optional local query scopes
|
|
||||||
│ ├── decisions/ # Numbered, append-only decision records + own index.md & log.md
|
|
||||||
│ ├── entities/ # Typed entity pages (people, projects, concepts) + own index.md
|
│ ├── entities/ # Typed entity pages (people, projects, concepts) + own index.md
|
||||||
│ └── graph/ # Edge lists and relationship data + own index.md
|
│ └── graph/ # Edge lists and relationship data + own index.md
|
||||||
└── workload/ # Session summaries & decisions
|
└── workload/ # Session summaries & decisions
|
||||||
|
|
@ -106,23 +72,6 @@ Implemented as a Claude Code Skill — see
|
||||||
actually invoked. Distinct from the `ckb-sync-changes` skill, which is a
|
actually invoked. Distinct from the `ckb-sync-changes` skill, which is a
|
||||||
pure git-level operation with no wiki synthesis.
|
pure git-level operation with no wiki synthesis.
|
||||||
|
|
||||||
For long conversations, meeting notes, transcripts, or chat exports, ingest
|
|
||||||
uses a structured distillation rather than treating the whole file as one
|
|
||||||
undifferentiated blob: searchable question, short summary, resolution or
|
|
||||||
decision, systems/code references, people involved, and high-signal excerpts
|
|
||||||
that deserve to stay findable on their own.
|
|
||||||
|
|
||||||
"High-signal" is an explicit test, not a judgment call, or every excerpt
|
|
||||||
looks worth keeping and the page becomes a second copy of the transcript.
|
|
||||||
An excerpt earns its own retrievable section only if it carries a term rare
|
|
||||||
across the wiki (checked with `rg -c` — a distinguishing search handle, not
|
|
||||||
a word already on twenty pages), runs to roughly 200 characters or more, and
|
|
||||||
is corroborated by something later in the material agreeing with, acting on,
|
|
||||||
or correcting it. Fail any one and the content still lands in the page, just
|
|
||||||
inside the summary rather than as its own unit. Promoted excerpts carry
|
|
||||||
their parent heading or thread question with them, so they read
|
|
||||||
unambiguously alone.
|
|
||||||
|
|
||||||
### Lazy-Loading Index with "Use When" Triggers
|
### Lazy-Loading Index with "Use When" Triggers
|
||||||
`wiki/index.md` is a routing table. Each entry has a **Use when** column
|
`wiki/index.md` is a routing table. Each entry has a **Use when** column
|
||||||
listing trigger keywords. The agent reads the index first (stays in context),
|
listing trigger keywords. The agent reads the index first (stays in context),
|
||||||
|
|
@ -134,100 +83,6 @@ Every page carries a one-sentence `tldr` in frontmatter. When querying, the
|
||||||
agent reads TLDRs first. If the TLDR already answers the question, the full
|
agent reads TLDRs first. If the TLDR already answers the question, the full
|
||||||
body is never loaded. Fallback chain: TLDR → body → raw source.
|
body is never loaded. Fallback chain: TLDR → body → raw source.
|
||||||
|
|
||||||
### Local Project Scopes
|
|
||||||
For recurring teams, clients, systems, or initiatives, the wiki can keep
|
|
||||||
plain Markdown scope pages under `wiki/projects/`. A scope page lists the
|
|
||||||
wiki pages, entities, raw/archive sources, connector-backed libs, outputs,
|
|
||||||
and graph areas that should be searched first for that project. This gives
|
|
||||||
the same practical benefit as a project workspace in a larger retrieval
|
|
||||||
system while staying local, transparent, and editable with any text editor.
|
|
||||||
|
|
||||||
Scopes only narrow the first pass. If the scoped search does not answer the
|
|
||||||
question, the agent falls back to the full cascade.
|
|
||||||
|
|
||||||
### Local Hybrid Retrieval
|
|
||||||
When index/TLDR routing is not enough, the agent can combine several local
|
|
||||||
signals before answering:
|
|
||||||
- exact text search with `rg` for error strings, commands, flags, filenames,
|
|
||||||
hostnames, ticket IDs, and other literal tokens — including across
|
|
||||||
`raw/inbox/`, so material dropped an hour ago and not yet ingested can
|
|
||||||
still answer a question (and flags that an ingest is overdue)
|
|
||||||
- semantic/entity matches from page titles, TLDRs, project scopes, and graph
|
|
||||||
relationships
|
|
||||||
- freshness and confidence metadata, so stale or weak pages are treated with
|
|
||||||
care
|
|
||||||
- context expansion around a matched section, so answers are grounded in the
|
|
||||||
neighboring headings and paragraphs rather than a lone snippet
|
|
||||||
|
|
||||||
Each signal produces its own ranked list, and the lists are then fused
|
|
||||||
rather than resolved by picking a favourite: every candidate scores
|
|
||||||
`weight / (k + rank)` summed across the lists it appears in, so a page
|
|
||||||
ranked third by three signals beats one ranked first by a single signal.
|
|
||||||
`k` is 10, deliberately smaller than the 60 rank fusion is usually quoted
|
|
||||||
with — 60 is tuned for retrievers returning hundreds of candidates and
|
|
||||||
flattens all scores into near-identical values against the dozen a local
|
|
||||||
wiki produces. Literal-token queries up-weight the exact-match list, since
|
|
||||||
no amount of title similarity should outrank a match on the exact string
|
|
||||||
someone pasted.
|
|
||||||
|
|
||||||
Fused candidates are then deduplicated by claim — a wiki page, the
|
|
||||||
`raw/archive/` file it cites, and a connector page pointing back at it are
|
|
||||||
three hits for one fact, not three sources — and reranked 0–10 on how well
|
|
||||||
each answers the literal question rather than how well it matches the
|
|
||||||
question's wording. Same agent, deliberate second pass, no separate model.
|
|
||||||
|
|
||||||
The result is normalized internally as an evidence packet: source path,
|
|
||||||
matched claim, date/freshness, confidence/quality, relationship or scope
|
|
||||||
hints, and which signals each candidate was fused from. No server, vector
|
|
||||||
database, or dedicated client is required.
|
|
||||||
|
|
||||||
### Answer Caveats
|
|
||||||
Metadata the wiki already tracks is stated in the answer itself, not just
|
|
||||||
consulted while building it. When a page grounding an answer is past its
|
|
||||||
`freshness_window_days`, carries low `confidence`/`quality`, rests on
|
|
||||||
un-ingested `raw/inbox/` material, or was checked against a cached
|
|
||||||
connector index rather than a live re-fetch, the answer says so next to
|
|
||||||
the claim it qualifies. Conflicts between two live pages are surfaced the
|
|
||||||
same way even when neither carries `superseded_by` yet. The failure mode
|
|
||||||
this closes is answering confidently *from* a stale page without passing
|
|
||||||
that on.
|
|
||||||
|
|
||||||
### Expertise & Ownership Lookups
|
|
||||||
"Who knows about X" and "who owns X" are direct graph lookups rather than
|
|
||||||
full-text guesses. Ingest records `has_expertise_in` edges when someone
|
|
||||||
demonstrably answers questions or explains decisions on a topic, and
|
|
||||||
`owns` edges for declared responsibility over a system, area, or decision
|
|
||||||
— both from demonstrated evidence only, never inferred from meeting
|
|
||||||
attendance or job title. Where no edge exists yet, retrieval falls back to
|
|
||||||
authorship evidence and says which of the two grounded the answer, since
|
|
||||||
an inferred expert is a weaker claim than a recorded one.
|
|
||||||
|
|
||||||
### Decision Records
|
|
||||||
`wiki/decisions/` holds one numbered page per decision (`NNNN-slug.md`):
|
|
||||||
what was decided, `decided_by` whom, `decided_on` what date, why, the
|
|
||||||
alternatives that lost, and what it `affects`. A `status` field
|
|
||||||
(`proposed`/`accepted`/`rejected`/`superseded`/`reversed`) says whether it is
|
|
||||||
actually in force, and an optional `review_on` date marks it for revisiting.
|
|
||||||
|
|
||||||
Decision pages are **append-only**. When the choice changes, a *new* decision
|
|
||||||
supersedes the old one — `supersedes`/`superseded_by` are set on both sides,
|
|
||||||
the old page's status becomes `superseded` or `reversed`, and its original
|
|
||||||
context and rationale stay untouched. That is what makes "why is it like
|
|
||||||
this?" answerable years later, and what turns "we use Postgres" into "we use
|
|
||||||
Postgres, and before that MySQL, changed in September because of reporting".
|
|
||||||
`decided_by` and `affects` also become graph edges, so "who decided X" and
|
|
||||||
"what decisions touch Y" are direct lookups. The `ckb-decide` skill records
|
|
||||||
decisions and answers questions about them; `ckb-lint` checks their structure
|
|
||||||
(status vocabulary, required dates, two-sided supersession, unique numbers,
|
|
||||||
overdue reviews).
|
|
||||||
|
|
||||||
### Query Gaps
|
|
||||||
If the cascade cannot answer a question, the agent records or proposes a
|
|
||||||
short entry in `wiki/query-gaps.md`: what was asked, where it looked, and the
|
|
||||||
smallest source or page that would close the gap. This makes failed searches
|
|
||||||
useful demand signals for the next ingest instead of disappearing into chat
|
|
||||||
history.
|
|
||||||
|
|
||||||
### Page Frontmatter Schema
|
### Page Frontmatter Schema
|
||||||
Every wiki page uses YAML frontmatter. `type` is required; the rest are optional:
|
Every wiki page uses YAML frontmatter. `type` is required; the rest are optional:
|
||||||
|
|
||||||
|
|
@ -243,8 +98,6 @@ superseded_by: path/to/new.md
|
||||||
last_updated: YYYY-MM-DD
|
last_updated: YYYY-MM-DD
|
||||||
freshness_window_days: 90 # Days before considered stale
|
freshness_window_days: 90 # Days before considered stale
|
||||||
retention: high|medium|low
|
retention: high|medium|low
|
||||||
source_fingerprint: sha256:3f9a2c1e # digest of the source this page was built from
|
|
||||||
source_checked: YYYY-MM-DD # when that digest was last verified
|
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -255,40 +108,17 @@ source_checked: YYYY-MM-DD # when that digest was last verified
|
||||||
- **supersedes / superseded_by** — when new info replaces old, link them
|
- **supersedes / superseded_by** — when new info replaces old, link them
|
||||||
- **freshness_window_days** — pages older than this get flagged during lint
|
- **freshness_window_days** — pages older than this get flagged during lint
|
||||||
- **retention** — low pages are archived after 2× freshness window
|
- **retention** — low pages are archived after 2× freshness window
|
||||||
- **source_fingerprint / source_checked** — a digest of the material the page
|
|
||||||
was synthesized from, plus when it was last confirmed. A freshness window is
|
|
||||||
a guess that a source *might* have moved; a fingerprint is a fact about
|
|
||||||
whether it *did*, and lint checks it mechanically.
|
|
||||||
|
|
||||||
#### Reserved body sections
|
`wiki/index.md` alone also carries `kb_schema_version` (e.g. `"1.1"`), declaring
|
||||||
Four `##` headings mean something specific everywhere in the KB:
|
|
||||||
|
|
||||||
| Section | What it holds |
|
|
||||||
|---|---|
|
|
||||||
| `## Sources` | one bullet per source, each with a fingerprint |
|
|
||||||
| `## Crux` | verbatim quotes from those sources — evidence, never paraphrase |
|
|
||||||
| `## Notes` | human-authored and **protected**: no skill rewrites it, ever |
|
|
||||||
|
|
||||||
`## Crux` is what lets a question be answered from the page instead of from
|
|
||||||
the archive: a summary can drift silently, a quote either still matches its
|
|
||||||
source or it doesn't. `## Notes` is the inverse guarantee — on pages the agent
|
|
||||||
regenerates (connector indexes, code maps), it is the one place an annotation
|
|
||||||
survives the next rebuild.
|
|
||||||
|
|
||||||
`wiki/index.md` alone also carries `kb_schema_version` (currently `"1.5"`), declaring
|
|
||||||
which revision of this schema the wiki was authored against — bump minor for
|
which revision of this schema the wiki was authored against — bump minor for
|
||||||
additive optional fields, major for breaking changes.
|
additive optional fields, major for breaking changes.
|
||||||
|
|
||||||
### Entity Extraction & Knowledge Graph
|
### Entity Extraction & Knowledge Graph
|
||||||
During ingest, the agent extracts typed entities (people, projects, libraries,
|
During ingest, the agent extracts typed entities (people, projects, libraries,
|
||||||
concepts, systems) and stores them as pages in `wiki/entities/`. Typed
|
concepts, systems) and stores them as pages in `wiki/entities/`. Typed
|
||||||
relationships are recorded in `wiki/graph/edges.json`, using a closed
|
relationships (`uses`, `depends_on`, `caused`, `contradicts`, `supersedes`)
|
||||||
vocabulary where each verb is defined by the question it answers (see
|
are recorded in `wiki/graph/edges.json`. Queries can walk the graph to
|
||||||
`wiki/graph/index.md`) — structural ones (`part_of`, `uses`, `depends_on`,
|
discover connected pages (e.g. "what depends on Redis?").
|
||||||
`produces`, `configures`, `validates`, `implements`, `caused`, `contradicts`,
|
|
||||||
`supersedes`) plus people-to-topic ones (`has_expertise_in`, `owns`). Queries can walk the
|
|
||||||
graph to discover connected pages (e.g. "what depends on Redis?") or to
|
|
||||||
answer "who knows about X" directly.
|
|
||||||
|
|
||||||
### Recursive Index & Log Convention
|
### Recursive Index & Log Convention
|
||||||
Any `wiki/` subdirectory that groups multiple pages (`entities/`, `graph/`,
|
Any `wiki/` subdirectory that groups multiple pages (`entities/`, `graph/`,
|
||||||
|
|
@ -308,15 +138,7 @@ mirror locally:
|
||||||
connector: sharepoint
|
connector: sharepoint
|
||||||
location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
||||||
description: "Finance team's shared reports folder"
|
description: "Finance team's shared reports folder"
|
||||||
refresh_interval_days: 7 # optional, defaults to 30
|
|
||||||
```
|
```
|
||||||
`refresh_interval_days` tunes cadence per source — a folder that churns
|
|
||||||
daily deserves a shorter window than a quarterly archive that barely moves
|
|
||||||
— and sets the `freshness_window_days` stamped on that source's generated
|
|
||||||
pages. Both "index external sources" and "Lint" report a source that's
|
|
||||||
overdue against it, and say by how much, so a read-only user knows which
|
|
||||||
admin to chase rather than quietly trusting a copy three weeks past due.
|
|
||||||
|
|
||||||
Say "index external sources" and the agent walks it, resolving `connector`
|
Say "index external sources" and the agent walks it, resolving `connector`
|
||||||
to whatever live tool is available that session (a connected Microsoft
|
to whatever live tool is available that session (a connected Microsoft
|
||||||
365/Google Drive MCP tool, or `WebFetch` for a plain URL), and builds a
|
365/Google Drive MCP tool, or `WebFetch` for a plain URL), and builds a
|
||||||
|
|
@ -334,18 +156,13 @@ Two refinements on top of that:
|
||||||
- **Shared, pre-built indexes.** `source.yaml` can add an optional `index:`
|
- **Shared, pre-built indexes.** `source.yaml` can add an optional `index:`
|
||||||
block declaring *where the already-built index lives* — a git repo, or a
|
block declaring *where the already-built index lives* — a git repo, or a
|
||||||
shared resource such as a network path or another connector-reachable
|
shared resource such as a network path or another connector-reachable
|
||||||
location:
|
location — so most people just fetch what's already there instead of
|
||||||
|
building it themselves:
|
||||||
```yaml
|
```yaml
|
||||||
index:
|
index:
|
||||||
store: git # git | shared
|
store: git # git | shared
|
||||||
location: "https://github.com/org/finance-index-cache.git"
|
location: "https://github.com/org/finance-index-cache.git"
|
||||||
ref: main # optional — branch, tag, or subpath hint within that store
|
|
||||||
```
|
```
|
||||||
Every run checks that location: if it already has an index, fetch it —
|
|
||||||
most people just read what's already there instead of building it
|
|
||||||
themselves. If it doesn't yet, that's the normal first-time state, not an
|
|
||||||
error: a write-access user's very next run is what creates and publishes
|
|
||||||
it there, with no separate "initialize" step.
|
|
||||||
- **Read vs. write, per user, per source.** Whether *this* user can
|
- **Read vs. write, per user, per source.** Whether *this* user can
|
||||||
actually rebuild an index (versus only read a fetched/published one) is a
|
actually rebuild an index (versus only read a fetched/published one) is a
|
||||||
separate, local, gitignored `libs/<name>/source.local.yaml` — read-only
|
separate, local, gitignored `libs/<name>/source.local.yaml` — read-only
|
||||||
|
|
@ -376,9 +193,6 @@ Periodically (or on demand), the agent health-checks the wiki:
|
||||||
- **Orphan detection** — finds pages with no inbound links
|
- **Orphan detection** — finds pages with no inbound links
|
||||||
- **Graph consistency** — verifies all edges point to existing entities
|
- **Graph consistency** — verifies all edges point to existing entities
|
||||||
- **Index/log consistency** — verifies every subdirectory has an index.md and no change is double-logged
|
- **Index/log consistency** — verifies every subdirectory has an index.md and no change is double-logged
|
||||||
- **Source fingerprints** — recomputes each cited source's digest and flags pages whose evidence has actually changed, not merely aged
|
|
||||||
- **Crux verbatimness** — flags a quoted excerpt that no longer appears in the source it cites
|
|
||||||
- **Connector cadence** — flags a connector-backed source whose generated index is overdue against its `refresh_interval_days`, and by how much
|
|
||||||
- **Error Book** — records systemic issues with root cause and fix
|
- **Error Book** — records systemic issues with root cause and fix
|
||||||
|
|
||||||
Auto-fixes what it can (broken links, missing backlinks, stale flags), and
|
Auto-fixes what it can (broken links, missing backlinks, stale flags), and
|
||||||
|
|
@ -445,13 +259,9 @@ internal schema (`confidence`/`quality`/`retention`/`supersedes`/dual-linking)
|
||||||
that OKF doesn't natively understand. Implemented as a Claude Code Skill —
|
that OKF doesn't natively understand. Implemented as a Claude Code Skill —
|
||||||
see `.agents/skills/ckb-export-okf/SKILL.md` — rather than baked into
|
see `.agents/skills/ckb-export-okf/SKILL.md` — rather than baked into
|
||||||
`CLAUDE.md`/`AGENTS.md`, so the mapping ruleset only loads into context when
|
`CLAUDE.md`/`AGENTS.md`, so the mapping ruleset only loads into context when
|
||||||
actually invoked. A deterministic Python script
|
actually invoked. `outputs/okf/` is gitignored — it's a fully-regenerated
|
||||||
(`scripts/export_okf.py`) does the whole transform — frontmatter remapping,
|
build artifact, so each machine/tool regenerates it on demand rather than
|
||||||
link rewriting, index and log regeneration, and an OKF conformance pass over
|
carrying it in git history.
|
||||||
its own output — so the export is reproducible rather than re-reasoned page
|
|
||||||
by page; the agent runs it and relays the report. `outputs/okf/` is
|
|
||||||
gitignored — it's a fully-regenerated build artifact, so each machine/tool
|
|
||||||
regenerates it on demand rather than carrying it in git history.
|
|
||||||
|
|
||||||
### Starlight Export (on demand)
|
### Starlight Export (on demand)
|
||||||
The wiki can also be exported into an Astro + Starlight-consumable form at
|
The wiki can also be exported into an Astro + Starlight-consumable form at
|
||||||
|
|
@ -466,16 +276,6 @@ report. Also on-demand and skill-only — see
|
||||||
`.agents/skills/ckb-export-starlight/SKILL.md`. Like `outputs/okf/`,
|
`.agents/skills/ckb-export-starlight/SKILL.md`. Like `outputs/okf/`,
|
||||||
`outputs/starlight/` is gitignored as a regenerated build artifact.
|
`outputs/starlight/` is gitignored as a regenerated build artifact.
|
||||||
|
|
||||||
### Decision Log (on demand)
|
|
||||||
Say "record a decision: ..." (or just "we decided ...") to file a numbered
|
|
||||||
decision record; ask "what did we decide about X", "who decided that",
|
|
||||||
"which decisions are still proposed", or "what superseded decision 3" to get
|
|
||||||
it back with the who, when, and status attached. Recording gathers the
|
|
||||||
missing fields in a single round rather than an interview, wires the
|
|
||||||
supersession links in both directions, adds `decided_by`/`affects` graph
|
|
||||||
edges, and logs to `wiki/decisions/log.md`. See
|
|
||||||
`.agents/skills/ckb-decide/SKILL.md`.
|
|
||||||
|
|
||||||
### Guided Onboarding Tours (on demand)
|
### Guided Onboarding Tours (on demand)
|
||||||
Ask "onboard me on X" (or "where do I start with X", "mini tour of X") to get
|
Ask "onboard me on X" (or "where do I start with X", "mini tour of X") to get
|
||||||
a short, read-only guided reading order: an overview paragraph plus an
|
a short, read-only guided reading order: an overview paragraph plus an
|
||||||
|
|
@ -508,7 +308,7 @@ one-off, scored knowledge check: the agent reads the relevant pages,
|
||||||
generates open or multiple-choice questions grounded in specific wiki
|
generates open or multiple-choice questions grounded in specific wiki
|
||||||
facts, runs them one at a time with immediate feedback and a running
|
facts, runs them one at a time with immediate feedback and a running
|
||||||
score, and closes with a verdict. Stateless — nothing is saved between
|
score, and closes with a verdict. Stateless — nothing is saved between
|
||||||
runs. See `.agents/skills/ckb-quiz/SKILL.md`.
|
runs. See `.agents/skills/cbk-quiz/SKILL.md`.
|
||||||
|
|
||||||
### Guided Teaching Curriculum (on demand)
|
### Guided Teaching Curriculum (on demand)
|
||||||
Ask to be taught the wiki ("teach me the wiki", "teach me about X", "run a
|
Ask to be taught the wiki ("teach me the wiki", "teach me about X", "run a
|
||||||
|
|
|
||||||
236
README.pl.md
236
README.pl.md
|
|
@ -15,37 +15,6 @@ przewodnik — jak stworzyć wiki, dodawać wiedzę, utrzymywać porządek,
|
||||||
synchronizować się z innymi i przykłady dla każdego przypadku użycia —
|
synchronizować się z innymi i przykłady dla każdego przypadku użycia —
|
||||||
znajdziesz w [MANUAL.pl.md](MANUAL.pl.md) ([English](MANUAL.md)).
|
znajdziesz w [MANUAL.pl.md](MANUAL.pl.md) ([English](MANUAL.md)).
|
||||||
|
|
||||||
Jeśli ta baza dokumentuje tworzone przez Ciebie oprogramowanie, opcjonalny moduł
|
|
||||||
`software` dodaje repozytoria w `src/` i pracę sterowaną specyfikacją — zobacz
|
|
||||||
[OPENSPEC.pl.md](OPENSPEC.pl.md) ([English](OPENSPEC.md)).
|
|
||||||
|
|
||||||
Pełny schemat strony oraz historię obu numerów wersji znajdziesz w
|
|
||||||
[CHANGELOG.pl.md](CHANGELOG.pl.md) ([English](CHANGELOG.md)).
|
|
||||||
|
|
||||||
### Kanały wydawnicze
|
|
||||||
|
|
||||||
Repozytorium szablonu utrzymuje trzy gałęzie. Nie są wymienne:
|
|
||||||
|
|
||||||
| Gałąź | Czym jest | Kto powinien ją śledzić |
|
|
||||||
|---|---|---|
|
|
||||||
| `main` | **Stabilna** — wydany szablon | Wszyscy, domyślnie |
|
|
||||||
| `test` | **Kandydat do wydania** — walidowany przed scaleniem do `main` | Każdy, kto pomaga walidować wydanie albo potrzebuje poprawki, która weszła, ale jeszcze nie została wydana |
|
|
||||||
| `experimental` | **Rozwojowa** — bieżąca praca, może być zepsuta albo wycofana | Osoby rozwijające sam szablon |
|
|
||||||
|
|
||||||
Zarówno `ckb-init`, jak i `ckb-upgrade` domyślnie używają `main`. Żeby użyć
|
|
||||||
innego kanału, po prostu powiedz którego: *„zainicjuj z gałęzi test"*,
|
|
||||||
*„sprawdź experimental pod kątem aktualizacji"*, *„przełącz tę KB z powrotem
|
|
||||||
na kanał stabilny"*. Śledzona gałąź jest zapisana w `ckb.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
template:
|
|
||||||
repo: https://git.wierzbowa.cloud/michal/ckb.git
|
|
||||||
branch: main
|
|
||||||
```
|
|
||||||
|
|
||||||
KB bez `ckb.yaml` (albo bez bloku `template:`) jest traktowana jako śledząca
|
|
||||||
`main` — czyli dokładnie to, co i tak robiła każda KB sprzed tej konwencji.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Struktura katalogów
|
## Struktura katalogów
|
||||||
|
|
@ -63,7 +32,6 @@ KB bez `ckb.yaml` (albo bez bloku `template:`) jest traktowana jako śledząca
|
||||||
│ ├── overview.md # Mapa wysokiego poziomu
|
│ ├── overview.md # Mapa wysokiego poziomu
|
||||||
│ ├── log.md # Główny dziennik zmian (rollup)
|
│ ├── log.md # Główny dziennik zmian (rollup)
|
||||||
│ ├── error-book.md # Błędy kompilacji i wyprowadzone ograniczenia
|
│ ├── error-book.md # Błędy kompilacji i wyprowadzone ograniczenia
|
||||||
│ ├── decisions/ # Numerowane, tylko-dopisywane zapisy decyzji + własny index.md i log.md
|
|
||||||
│ ├── entities/ # Typowane strony encji (osoby, projekty, koncepcje) + własny index.md
|
│ ├── entities/ # Typowane strony encji (osoby, projekty, koncepcje) + własny index.md
|
||||||
│ └── graph/ # Listy krawędzi i dane relacji + własny index.md
|
│ └── graph/ # Listy krawędzi i dane relacji + własny index.md
|
||||||
└── workload/ # Podsumowania sesji i decyzje
|
└── workload/ # Podsumowania sesji i decyzje
|
||||||
|
|
@ -107,25 +75,6 @@ się do kontekstu tylko wtedy, gdy jest faktycznie wywoływana. Odrębne od
|
||||||
skilla `ckb-sync-changes`, który jest czysto operacją na poziomie gita, bez
|
skilla `ckb-sync-changes`, który jest czysto operacją na poziomie gita, bez
|
||||||
syntezy wiki.
|
syntezy wiki.
|
||||||
|
|
||||||
Dla długich rozmów, notatek ze spotkań, transkryptów lub eksportów czatu
|
|
||||||
ingest używa strukturalnej destylacji, zamiast traktować cały plik jako jedną
|
|
||||||
niezróżnicowaną bryłę: wyszukiwalne pytanie, krótkie podsumowanie,
|
|
||||||
rozwiązanie lub decyzja, odniesienia do systemów/kodu, zaangażowane osoby
|
|
||||||
oraz fragmenty o wysokiej wartości, które zasługują na to, by pozostać
|
|
||||||
znajdowalne samodzielnie.
|
|
||||||
|
|
||||||
„Wysoka wartość" to jawny test, a nie ocena uznaniowa — inaczej każdy
|
|
||||||
fragment wygląda na wart zachowania, a strona staje się drugą kopią
|
|
||||||
transkryptu. Fragment zasługuje na własną wyszukiwalną sekcję tylko wtedy,
|
|
||||||
gdy zawiera termin rzadki w całej wiki (sprawdzane przez `rg -c` — wyróżniający
|
|
||||||
uchwyt wyszukiwania, a nie słowo już obecne na dwudziestu stronach), ma około
|
|
||||||
200 znaków lub więcej i jest potwierdzony przez coś dalej w materiale, co się
|
|
||||||
z nim zgadza, działa na jego podstawie lub go koryguje. Niespełnienie choćby
|
|
||||||
jednego warunku oznacza, że treść nadal trafia na stronę, tylko wewnątrz
|
|
||||||
podsumowania, a nie jako osobna jednostka. Awansowane fragmenty zabierają ze
|
|
||||||
sobą nagłówek nadrzędny lub pytanie wątku, żeby dały się jednoznacznie
|
|
||||||
czytać samodzielnie.
|
|
||||||
|
|
||||||
### Leniwie ładowany indeks z wyzwalaczami „Use When"
|
### Leniwie ładowany indeks z wyzwalaczami „Use When"
|
||||||
`wiki/index.md` to tabela routingu. Każdy wpis ma kolumnę **Use when** z
|
`wiki/index.md` to tabela routingu. Każdy wpis ma kolumnę **Use when** z
|
||||||
listą słów kluczowych wyzwalających. Agent najpierw czyta indeks (pozostaje
|
listą słów kluczowych wyzwalających. Agent najpierw czyta indeks (pozostaje
|
||||||
|
|
@ -138,106 +87,6 @@ Podczas zapytania agent najpierw czyta TLDR-y. Jeśli TLDR już odpowiada na
|
||||||
pytanie, pełna treść nigdy nie jest ładowana. Łańcuch odwoławczy: TLDR →
|
pytanie, pełna treść nigdy nie jest ładowana. Łańcuch odwoławczy: TLDR →
|
||||||
treść → surowe źródło.
|
treść → surowe źródło.
|
||||||
|
|
||||||
### Lokalne zakresy projektów
|
|
||||||
Dla powracających zespołów, klientów, systemów lub inicjatyw wiki może
|
|
||||||
trzymać zwykłe strony zakresów w formacie Markdown pod `wiki/projects/`.
|
|
||||||
Strona zakresu wymienia strony wiki, encje, źródła z `raw/archive/`,
|
|
||||||
konektorowe `libs/`, wyjścia i obszary grafu, które należy przeszukać
|
|
||||||
najpierw dla danego projektu. Daje to tę samą praktyczną korzyść co
|
|
||||||
przestrzeń robocza projektu w większym systemie wyszukiwania, pozostając
|
|
||||||
lokalnym, przejrzystym i edytowalnym w dowolnym edytorze tekstu.
|
|
||||||
|
|
||||||
Zakresy zawężają tylko pierwsze przejście. Jeśli wyszukiwanie w zakresie nie
|
|
||||||
odpowiada na pytanie, agent wraca do pełnej kaskady.
|
|
||||||
|
|
||||||
### Lokalne wyszukiwanie hybrydowe
|
|
||||||
Gdy routing po indeksie/TLDR nie wystarcza, agent może połączyć kilka
|
|
||||||
lokalnych sygnałów przed odpowiedzią:
|
|
||||||
- dokładne wyszukiwanie tekstu przez `rg` dla komunikatów błędów, komend,
|
|
||||||
flag, nazw plików, nazw hostów, identyfikatorów zgłoszeń i innych
|
|
||||||
literalnych tokenów — również w `raw/inbox/`, więc materiał wrzucony
|
|
||||||
godzinę temu i jeszcze nie zingestowany nadal może odpowiedzieć na pytanie
|
|
||||||
(i sygnalizuje, że ingest jest zaległy)
|
|
||||||
- dopasowania semantyczne/encyjne z tytułów stron, TLDR-ów, zakresów
|
|
||||||
projektów i relacji w grafie
|
|
||||||
- metadane świeżości i pewności, dzięki którym nieaktualne lub słabe strony
|
|
||||||
są traktowane ostrożnie
|
|
||||||
- rozszerzenie kontekstu wokół dopasowanej sekcji, aby odpowiedzi były
|
|
||||||
osadzone w sąsiadujących nagłówkach i akapitach, a nie w samotnym urywku
|
|
||||||
|
|
||||||
Każdy sygnał tworzy własną listę rankingową, a listy są następnie łączone,
|
|
||||||
zamiast rozstrzygania przez wybór ulubionego sygnału: każdy kandydat zbiera
|
|
||||||
`weight / (k + rank)` zsumowane po listach, na których występuje, więc strona
|
|
||||||
na trzecim miejscu w trzech listach wygrywa ze stroną pierwszą w jednej.
|
|
||||||
`k` wynosi 10, celowo mniej niż zwykle cytowane 60 — 60 jest dostrojone do
|
|
||||||
wyszukiwarek zwracających setki kandydatów i spłaszcza wszystkie wyniki do
|
|
||||||
niemal identycznych wartości przy kilkunastu, które daje lokalna wiki.
|
|
||||||
Zapytania o literalne tokeny podnoszą wagę listy dokładnych dopasowań,
|
|
||||||
ponieważ żadne podobieństwo tytułu nie powinno wyprzedzić trafienia w dokładny
|
|
||||||
ciąg, który ktoś wklejił.
|
|
||||||
|
|
||||||
Połączeni kandydaci są następnie deduplikowani według twierdzenia — strona
|
|
||||||
wiki, plik z `raw/archive/`, który cytuje, i strona konektora wskazująca na
|
|
||||||
nią to trzy trafienia dla jednego faktu, nie trzy źródła — i przerankowani w
|
|
||||||
skali 0–10 według tego, jak dobrze odpowiadają na dosłownie zadane pytanie, a
|
|
||||||
nie jak dobrze pasują do jego sformułowania. Ten sam agent, świadome drugie
|
|
||||||
przejście, bez osobnego modelu.
|
|
||||||
|
|
||||||
Wynik jest wewnętrznie normalizowany jako pakiet dowodowy: ścieżka źródła,
|
|
||||||
dopasowane twierdzenie, data/świeżość, pewność/jakość, wskazówki o relacjach
|
|
||||||
lub zakresie oraz informacja, z których sygnałów każdy kandydat został
|
|
||||||
połączony. Nie jest wymagany żaden serwer, baza wektorowa ani dedykowany
|
|
||||||
klient.
|
|
||||||
|
|
||||||
### Zastrzeżenia w odpowiedziach
|
|
||||||
Metadane, które wiki już śledzi, są podawane w samej odpowiedzi, a nie tylko
|
|
||||||
sprawdzane przy jej budowaniu. Gdy strona stanowiąca podstawę odpowiedzi
|
|
||||||
przekroczyła `freshness_window_days`, ma niską `confidence`/`quality`, opiera
|
|
||||||
się na niezingestowanym materiale z `raw/inbox/` lub została sprawdzona
|
|
||||||
względem zapisanego w pamięci indeksu konektora, a nie żywego źródła —
|
|
||||||
odpowiedź mówi o tym obok twierdzenia, którego to dotyczy. Konflikty między
|
|
||||||
dwiema aktywnymi stronami są ujawniane tak samo, nawet jeśli żadna nie ma
|
|
||||||
jeszcze `superseded_by`. Zamyka to tryb awarii polegający na pewnej
|
|
||||||
odpowiedzi *z* nieaktualnej strony bez przekazania tej informacji dalej.
|
|
||||||
|
|
||||||
### Wyszukiwanie ekspertów i właścicieli
|
|
||||||
„Kto wie o X" i „kto jest właścicielem X" to bezpośrednie zapytania do grafu,
|
|
||||||
a nie zgadywanie po pełnym tekście. Ingest zapisuje krawędzie
|
|
||||||
`has_expertise_in`, gdy ktoś wykazuje się odpowiadaniem na pytania lub
|
|
||||||
wyjaśnianiem decyzji w danym temacie, oraz krawędzie `owns` dla zadeklarowanej
|
|
||||||
odpowiedzialności za system, obszar lub decyzję — oba wyłącznie na podstawie
|
|
||||||
wykazanych dowodów, nigdy wnioskowane z obecności na spotkaniu czy ze
|
|
||||||
stanowiska. Gdy krawędzi jeszcze nie ma, wyszukiwanie wraca do dowodów
|
|
||||||
autorstwa i mówi, które z dwóch stanowiło podstawę odpowiedzi, bo domniemany
|
|
||||||
ekspert to słabsze twierdzenie niż zapisany.
|
|
||||||
|
|
||||||
### Zapisy decyzji
|
|
||||||
`wiki/decisions/` zawiera jedną numerowaną stronę na decyzję
|
|
||||||
(`NNNN-slug.md`): co zostało zdecydowane, przez kogo (`decided_by`), kiedy
|
|
||||||
(`decided_on`), dlaczego, jakie alternatywy odpadły i czego decyzja dotyczy
|
|
||||||
(`affects`). Pole `status`
|
|
||||||
(`proposed`/`accepted`/`rejected`/`superseded`/`reversed`) mówi, czy decyzja
|
|
||||||
faktycznie obowiązuje, a opcjonalna data `review_on` oznacza ją do przeglądu.
|
|
||||||
|
|
||||||
Strony decyzji są **tylko do dopisywania**. Gdy wybór się zmienia, *nowa*
|
|
||||||
decyzja zastępuje starą — `supersedes`/`superseded_by` ustawiane są po obu
|
|
||||||
stronach, status starej zmienia się na `superseded` lub `reversed`, a jej
|
|
||||||
pierwotny kontekst i uzasadnienie pozostają nietknięte. To właśnie sprawia,
|
|
||||||
że „dlaczego jest tak, jak jest?" da się odpowiedzieć po latach, i zamienia
|
|
||||||
„używamy Postgresa" w „używamy Postgresa, a wcześniej MySQL-a, zmienione we
|
|
||||||
wrześniu z powodu raportowania". `decided_by` i `affects` stają się też
|
|
||||||
krawędziami grafu, więc „kto zdecydował o X" i „jakie decyzje dotyczą Y" to
|
|
||||||
bezpośrednie wyszukania. Skill `ckb-decide` zapisuje decyzje i odpowiada na
|
|
||||||
pytania o nie; `ckb-lint` sprawdza ich strukturę (słownik statusów, wymagane
|
|
||||||
daty, dwustronne zastępowanie, unikalne numery, zaległe przeglądy).
|
|
||||||
|
|
||||||
### Luki w zapytaniach
|
|
||||||
Jeśli kaskada nie potrafi odpowiedzieć na pytanie, agent zapisuje lub proponuje
|
|
||||||
krótki wpis w `wiki/query-gaps.md`: o co pytano, gdzie szukał i jakie
|
|
||||||
najmniejsze źródło lub strona zamknęłaby lukę. Dzięki temu nieudane
|
|
||||||
wyszukiwania stają się użytecznym sygnałem zapotrzebowania dla następnego
|
|
||||||
ingestu, zamiast przepadać w historii czatu.
|
|
||||||
|
|
||||||
### Schemat frontmatteru strony
|
### Schemat frontmatteru strony
|
||||||
Każda strona wiki używa frontmatteru YAML. Pole `type` jest wymagane; reszta
|
Każda strona wiki używa frontmatteru YAML. Pole `type` jest wymagane; reszta
|
||||||
jest opcjonalna:
|
jest opcjonalna:
|
||||||
|
|
@ -254,8 +103,6 @@ superseded_by: path/to/new.md
|
||||||
last_updated: YYYY-MM-DD
|
last_updated: YYYY-MM-DD
|
||||||
freshness_window_days: 90 # Liczba dni, po których treść uznaje się za nieaktualną
|
freshness_window_days: 90 # Liczba dni, po których treść uznaje się za nieaktualną
|
||||||
retention: high|medium|low
|
retention: high|medium|low
|
||||||
source_fingerprint: sha256:3f9a2c1e # skrót źródła, z którego zbudowano tę stronę
|
|
||||||
source_checked: YYYY-MM-DD # kiedy ten skrót był ostatnio zweryfikowany
|
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -273,27 +120,8 @@ source_checked: YYYY-MM-DD # kiedy ten skrót był ostatnio zweryfiko
|
||||||
podczas lintowania
|
podczas lintowania
|
||||||
- **retention** — strony o niskim priorytecie są archiwizowane po 2× oknie
|
- **retention** — strony o niskim priorytecie są archiwizowane po 2× oknie
|
||||||
świeżości
|
świeżości
|
||||||
- **source_fingerprint / source_checked** — skrót materiału, z którego
|
|
||||||
zsyntetyzowano stronę, oraz data ostatniego potwierdzenia. Okno świeżości to
|
|
||||||
przypuszczenie, że źródło *mogło* się zmienić; skrót to fakt, czy *się
|
|
||||||
zmieniło* — i lint sprawdza go mechanicznie.
|
|
||||||
|
|
||||||
#### Zastrzeżone sekcje treści
|
Sam `wiki/index.md` dodatkowo zawiera `kb_schema_version` (np. `"1.1"`),
|
||||||
Cztery nagłówki `##` mają w całej KB ściśle określone znaczenie:
|
|
||||||
|
|
||||||
| Sekcja | Co zawiera |
|
|
||||||
|---|---|
|
|
||||||
| `## Sources` | po jednym punkcie na źródło, każdy ze skrótem |
|
|
||||||
| `## Crux` | dosłowne cytaty z tych źródeł — dowód, nigdy parafraza |
|
|
||||||
| `## Notes` | pisane przez człowieka i **chronione**: żaden skill ich nie nadpisuje |
|
|
||||||
|
|
||||||
`## Crux` pozwala odpowiedzieć na pytanie ze strony zamiast z archiwum:
|
|
||||||
streszczenie może po cichu odpłynąć od źródła, cytat albo wciąż się z nim
|
|
||||||
zgadza, albo nie. `## Notes` daje odwrotną gwarancję — na stronach
|
|
||||||
regenerowanych przez agenta (indeksy konektorów, mapy kodu) to jedyne miejsce,
|
|
||||||
w którym adnotacja przetrwa kolejną przebudowę.
|
|
||||||
|
|
||||||
Sam `wiki/index.md` dodatkowo zawiera `kb_schema_version` (obecnie `"1.5"`),
|
|
||||||
deklarujący, według której wersji tego schematu wiki została napisana —
|
deklarujący, według której wersji tego schematu wiki została napisana —
|
||||||
zwiększaj wersję pomniejszą dla dodatkowych opcjonalnych pól, główną dla
|
zwiększaj wersję pomniejszą dla dodatkowych opcjonalnych pól, główną dla
|
||||||
zmian łamiących kompatybilność.
|
zmian łamiących kompatybilność.
|
||||||
|
|
@ -301,14 +129,9 @@ zmian łamiących kompatybilność.
|
||||||
### Ekstrakcja encji i graf wiedzy
|
### Ekstrakcja encji i graf wiedzy
|
||||||
Podczas ingestu agent wydobywa typowane encje (osoby, projekty, biblioteki,
|
Podczas ingestu agent wydobywa typowane encje (osoby, projekty, biblioteki,
|
||||||
koncepcje, systemy) i zapisuje je jako strony w `wiki/entities/`. Typowane
|
koncepcje, systemy) i zapisuje je jako strony w `wiki/entities/`. Typowane
|
||||||
relacje są zapisywane w `wiki/graph/edges.json` przy użyciu zamkniętego
|
relacje (`uses`, `depends_on`, `caused`, `contradicts`, `supersedes`) są
|
||||||
słownika, w którym każdy czasownik jest zdefiniowany przez pytanie, na jakie
|
zapisywane w `wiki/graph/edges.json`. Zapytania mogą przechodzić po grafie,
|
||||||
odpowiada (patrz `wiki/graph/index.md`) — strukturalne (`part_of`, `uses`,
|
aby odkrywać powiązane strony (np. „co zależy od Redis?").
|
||||||
`depends_on`, `produces`, `configures`, `validates`, `implements`, `caused`,
|
|
||||||
`contradicts`, `supersedes`) oraz łączące osoby z tematami
|
|
||||||
(`has_expertise_in`, `owns`). Zapytania mogą przechodzić po grafie,
|
|
||||||
aby odkrywać powiązane strony (np. „co zależy od Redis?") albo bezpośrednio
|
|
||||||
odpowiadać na „kto wie o X".
|
|
||||||
|
|
||||||
### Rekurencyjna konwencja indeksu i dziennika
|
### Rekurencyjna konwencja indeksu i dziennika
|
||||||
Każdy podkatalog `wiki/`, który grupuje wiele stron (`entities/`, `graph/`,
|
Każdy podkatalog `wiki/`, który grupuje wiele stron (`entities/`, `graph/`,
|
||||||
|
|
@ -328,16 +151,7 @@ URL albo inny konektor — którego nie chcesz w pełni kopiować lokalnie:
|
||||||
connector: sharepoint
|
connector: sharepoint
|
||||||
location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
location: "https://contoso.sharepoint.com/sites/Finance/Shared Documents/Reports"
|
||||||
description: "Wspólny folder raportów zespołu finansowego"
|
description: "Wspólny folder raportów zespołu finansowego"
|
||||||
refresh_interval_days: 7 # opcjonalne, domyślnie 30
|
|
||||||
```
|
```
|
||||||
`refresh_interval_days` dostraja częstotliwość per źródło — folder zmieniający
|
|
||||||
się codziennie zasługuje na krótsze okno niż kwartalne archiwum, które prawie
|
|
||||||
nie drgnie — i ustawia `freshness_window_days` nadawane generowanym stronom
|
|
||||||
tego źródła. Zarówno „index external sources", jak i „Lint" raportują źródło
|
|
||||||
zaległe względem tej wartości i mówią o ile, żeby użytkownik z dostępem tylko
|
|
||||||
do odczytu wiedział, kogo zapytać, zamiast po cichu polegać na kopii sprzed
|
|
||||||
trzech tygodni.
|
|
||||||
|
|
||||||
Powiedz „index external sources", a agent go przeskanuje, dopasowując
|
Powiedz „index external sources", a agent go przeskanuje, dopasowując
|
||||||
`connector` do dowolnego żywego narzędzia dostępnego w danej sesji
|
`connector` do dowolnego żywego narzędzia dostępnego w danej sesji
|
||||||
(połączonego narzędzia MCP do Microsoft 365/Google Drive, albo `WebFetch`
|
(połączonego narzędzia MCP do Microsoft 365/Google Drive, albo `WebFetch`
|
||||||
|
|
@ -355,19 +169,14 @@ Dwa rozszerzenia na tym fundamencie:
|
||||||
- **Współdzielone, wcześniej zbudowane indeksy.** `source.yaml` może
|
- **Współdzielone, wcześniej zbudowane indeksy.** `source.yaml` może
|
||||||
dodać opcjonalny blok `index:`, który deklaruje, *gdzie już zbudowany
|
dodać opcjonalny blok `index:`, który deklaruje, *gdzie już zbudowany
|
||||||
indeks się znajduje* — repozytorium git albo zasób współdzielony, np.
|
indeks się znajduje* — repozytorium git albo zasób współdzielony, np.
|
||||||
ścieżka sieciowa lub inna lokalizacja dostępna przez konektor:
|
ścieżka sieciowa lub inna lokalizacja dostępna przez konektor — dzięki
|
||||||
|
czemu większość osób po prostu pobiera to, co już jest, zamiast budować
|
||||||
|
to samodzielnie:
|
||||||
```yaml
|
```yaml
|
||||||
index:
|
index:
|
||||||
store: git # git | shared
|
store: git # git | shared
|
||||||
location: "https://github.com/org/finance-index-cache.git"
|
location: "https://github.com/org/finance-index-cache.git"
|
||||||
ref: main # opcjonalnie — branch, tag albo podpowiedź co do podścieżki w tym miejscu
|
|
||||||
```
|
```
|
||||||
Każde uruchomienie najpierw sprawdza tę lokalizację: jeśli indeks już
|
|
||||||
tam jest, zostaje pobrany — większość osób po prostu czyta to, co już
|
|
||||||
jest, zamiast budować to samodzielnie. Jeśli go tam jeszcze nie ma, to
|
|
||||||
normalny stan przy pierwszym uruchomieniu, a nie błąd: kolejne
|
|
||||||
uruchomienie użytkownika z dostępem do zapisu jest tym, które go tam
|
|
||||||
tworzy i publikuje — bez osobnego kroku „inicjalizacji".
|
|
||||||
- **Odczyt vs. zapis, per użytkownik, per źródło.** Czy *ten* użytkownik
|
- **Odczyt vs. zapis, per użytkownik, per źródło.** Czy *ten* użytkownik
|
||||||
może faktycznie przebudować indeks (a nie tylko czytać pobraną/
|
może faktycznie przebudować indeks (a nie tylko czytać pobraną/
|
||||||
opublikowaną wersję) to odrębne, lokalne, ignorowane przez git
|
opublikowaną wersję) to odrębne, lokalne, ignorowane przez git
|
||||||
|
|
@ -405,13 +214,6 @@ Okresowo (lub na żądanie) agent sprawdza kondycję wiki:
|
||||||
istniejące encje
|
istniejące encje
|
||||||
- **Spójność indeksu/dziennika** — weryfikuje, czy każdy podkatalog ma
|
- **Spójność indeksu/dziennika** — weryfikuje, czy każdy podkatalog ma
|
||||||
index.md i czy żadna zmiana nie jest podwójnie logowana
|
index.md i czy żadna zmiana nie jest podwójnie logowana
|
||||||
- **Skróty źródeł** — przelicza skrót każdego cytowanego źródła i oznacza
|
|
||||||
strony, których dowód faktycznie się zmienił, a nie tylko się zestarzał
|
|
||||||
- **Dosłowność sekcji Crux** — oznacza cytat, którego nie ma już w źródle,
|
|
||||||
na które się powołuje
|
|
||||||
- **Częstotliwość konektorów** — oznacza konektorowe źródło, którego
|
|
||||||
generowany indeks jest zaległy względem `refresh_interval_days`, wraz z
|
|
||||||
informacją o ile
|
|
||||||
- **Księga błędów (Error Book)** — zapisuje systemowe problemy wraz z
|
- **Księga błędów (Error Book)** — zapisuje systemowe problemy wraz z
|
||||||
przyczyną i naprawą
|
przyczyną i naprawą
|
||||||
|
|
||||||
|
|
@ -491,14 +293,10 @@ bogatszego wewnętrznego schematu
|
||||||
którego OKF natywnie nie rozumie. Zaimplementowane jako Claude Code Skill —
|
którego OKF natywnie nie rozumie. Zaimplementowane jako Claude Code Skill —
|
||||||
zobacz `.agents/skills/ckb-export-okf/SKILL.md` — zamiast być zaszytym w
|
zobacz `.agents/skills/ckb-export-okf/SKILL.md` — zamiast być zaszytym w
|
||||||
`CLAUDE.md`/`AGENTS.md`, dzięki czemu zestaw reguł mapowania ładuje się do
|
`CLAUDE.md`/`AGENTS.md`, dzięki czemu zestaw reguł mapowania ładuje się do
|
||||||
kontekstu tylko wtedy, gdy jest faktycznie wywoływany. Cały transform —
|
kontekstu tylko wtedy, gdy jest faktycznie wywoływany. `outputs/okf/` jest
|
||||||
przemapowanie frontmatteru, przepisanie linków, regeneracja indeksów i
|
w `.gitignore` — to w pełni regenerowalny artefakt budowania, więc każda
|
||||||
dzienników oraz sprawdzenie zgodności z OKF na własnym wyjściu — wykonuje
|
maszyna/narzędzie regeneruje go na żądanie zamiast przenosić go w historii
|
||||||
deterministyczny skrypt Python (`scripts/export_okf.py`), więc eksport jest
|
gita.
|
||||||
odtwarzalny zamiast wyprowadzany na nowo strona po stronie; agent uruchamia
|
|
||||||
skrypt i przekazuje raport. `outputs/okf/` jest w `.gitignore` — to w pełni
|
|
||||||
regenerowalny artefakt budowania, więc każda maszyna/narzędzie regeneruje go
|
|
||||||
na żądanie zamiast przenosić go w historii gita.
|
|
||||||
|
|
||||||
### Eksport Starlight (na żądanie)
|
### Eksport Starlight (na żądanie)
|
||||||
Wiki może też zostać wyeksportowana do formy skonsumowalnej przez Astro +
|
Wiki może też zostać wyeksportowana do formy skonsumowalnej przez Astro +
|
||||||
|
|
@ -515,16 +313,6 @@ zobacz `.agents/skills/ckb-export-starlight/SKILL.md`. Podobnie jak
|
||||||
`outputs/okf/`, `outputs/starlight/` jest w `.gitignore` jako regenerowalny
|
`outputs/okf/`, `outputs/starlight/` jest w `.gitignore` jako regenerowalny
|
||||||
artefakt budowania.
|
artefakt budowania.
|
||||||
|
|
||||||
### Dziennik decyzji (na żądanie)
|
|
||||||
Powiedz „zapisz decyzję: ..." (albo po prostu „zdecydowaliśmy ..."), żeby
|
|
||||||
utworzyć numerowany rekord decyzji; zapytaj „co zdecydowaliśmy w sprawie X",
|
|
||||||
„kto to zdecydował", „które decyzje są wciąż propozycjami" albo „co zastąpiło
|
|
||||||
decyzję 3", żeby dostać ją z powrotem wraz z autorem, datą i statusem.
|
|
||||||
Zapisywanie zbiera brakujące pola w jednej turze zamiast wywiadu, ustawia
|
|
||||||
powiązania zastępowania w obu kierunkach, dodaje krawędzie grafu
|
|
||||||
`decided_by`/`affects` i zapisuje do `wiki/decisions/log.md`. Zobacz
|
|
||||||
`.agents/skills/ckb-decide/SKILL.md`.
|
|
||||||
|
|
||||||
### Prowadzone wycieczki wprowadzające (na żądanie)
|
### Prowadzone wycieczki wprowadzające (na żądanie)
|
||||||
Poproś „oprowadź mnie po X" (lub „od czego zacząć z X", „krótka wycieczka
|
Poproś „oprowadź mnie po X" (lub „od czego zacząć z X", „krótka wycieczka
|
||||||
po X") aby otrzymać krótką, tylko-do-odczytu prowadzoną kolejność czytania:
|
po X") aby otrzymać krótką, tylko-do-odczytu prowadzoną kolejność czytania:
|
||||||
|
|
@ -562,7 +350,7 @@ strony, generuje pytania otwarte lub jednokrotnego wyboru oparte na
|
||||||
konkretnych faktach z wiki, przeprowadza je jedno po drugim z natychmiastową
|
konkretnych faktach z wiki, przeprowadza je jedno po drugim z natychmiastową
|
||||||
informacją zwrotną i bieżącym wynikiem, a na koniec podaje werdykt.
|
informacją zwrotną i bieżącym wynikiem, a na koniec podaje werdykt.
|
||||||
Bezstanowy — nic nie jest zapisywane między uruchomieniami. Zobacz
|
Bezstanowy — nic nie jest zapisywane między uruchomieniami. Zobacz
|
||||||
`.agents/skills/ckb-quiz/SKILL.md`.
|
`.agents/skills/cbk-quiz/SKILL.md`.
|
||||||
|
|
||||||
### Prowadzony program nauczania (na żądanie)
|
### Prowadzony program nauczania (na żądanie)
|
||||||
Poproś o naukę z wiki („teach me the wiki", „naucz mnie o X", „przeprowadź
|
Poproś o naukę z wiki („teach me the wiki", „naucz mnie o X", „przeprowadź
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.9.0
|
1.1.0
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
# Decisions
|
|
||||||
|
|
||||||
Decision records — one page per decision, newest number last. Each entry
|
|
||||||
mirrors the linked page's `tldr`, prefixed with its current status.
|
|
||||||
|
|
||||||
Filenames are `NNNN-short-slug.md`, numbered sequentially from `0001`, and
|
|
||||||
numbers are never reused: a decision that is reversed or superseded keeps its
|
|
||||||
number and its page, and the newer decision points back at it.
|
|
||||||
|
|
||||||
*(No decisions recorded yet — say "record a decision" to add the first one.)*
|
|
||||||
|
|
||||||
## Status vocabulary
|
|
||||||
|
|
||||||
| Status | Meaning |
|
|
||||||
|---|---|
|
|
||||||
| `proposed` | Under discussion; not yet in force. |
|
|
||||||
| `accepted` | In force. The default for a decision that was actually made. |
|
|
||||||
| `rejected` | Considered and turned down. Kept so the reasoning isn't relitigated. |
|
|
||||||
| `superseded` | Replaced by a later decision; `superseded_by` names it. |
|
|
||||||
| `reversed` | Undone by a later decision that went back to the prior state; `superseded_by` names it. |
|
|
||||||
|
|
||||||
## Open questions
|
|
||||||
|
|
||||||
Decisions still at `proposed`, and any decision whose `review_on` date has
|
|
||||||
passed, are the ones worth chasing. `ckb-lint` reports both.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# Decisions Change Log
|
|
||||||
|
|
||||||
Changes to pages under `wiki/decisions/` are recorded here rather than in
|
|
||||||
`wiki/log.md`, per the Recursive Index & Log Convention — each change gets
|
|
||||||
exactly one home log. `wiki/log.md` carries a pointer line when this log
|
|
||||||
absorbs a change.
|
|
||||||
|
|
||||||
Reverse chronological order, most recent first.
|
|
||||||
|
|
||||||
*(No entries yet.)*
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
type: error-book
|
type: error-book
|
||||||
tldr: Table of known ingest/lint errors, root causes, and the constraints derived from them.
|
tldr: Table of known ingest/lint errors, root causes, and the constraints derived from them.
|
||||||
last_updated: 2026-09-20
|
last_updated: 2026-07-13
|
||||||
---
|
---
|
||||||
|
|
||||||
# Error Book
|
# Error Book
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"version": 1, "last_updated": "2026-09-20", "edges": []}
|
|
||||||
|
|
@ -2,47 +2,6 @@
|
||||||
|
|
||||||
Edge list and relationship data for the knowledge graph, keyed by entity page.
|
Edge list and relationship data for the knowledge graph, keyed by entity page.
|
||||||
|
|
||||||
`edges.json` holds typed relationships between pages. Created on the first
|
* `edges.json` - Typed relationships (`uses`, `depends_on`, `caused`, `contradicts`, `supersedes`) between entity pages. Created on the first ingest that extracts entities.
|
||||||
ingest that extracts entities.
|
|
||||||
|
|
||||||
## Edge vocabulary
|
|
||||||
|
|
||||||
Each verb exists because it answers a question retrieval actually gets asked.
|
|
||||||
If a proposed edge doesn't answer one of these, it doesn't belong in the graph
|
|
||||||
— put it in prose on the page instead.
|
|
||||||
|
|
||||||
| Verb | Question it answers | Written by |
|
|
||||||
|---|---|---|
|
|
||||||
| `part_of` | Where does this live? What is it a piece of? | `ckb-ingest`, `ckb-code-map` |
|
|
||||||
| `uses` | What does this reach for at runtime? | `ckb-ingest`, `ckb-index-external` |
|
|
||||||
| `depends_on` | What breaks if I change this? | `ckb-ingest`, `ckb-code-map` |
|
|
||||||
| `produces` | Where does this output come from? | `ckb-ingest` |
|
|
||||||
| `configures` | What changes this thing's behaviour? | `ckb-ingest` |
|
|
||||||
| `validates` | What checks, tests, or judges this? | `ckb-ingest` |
|
|
||||||
| `implements` | What contract must this honour? | `ckb-ingest`, `ckb-spec` |
|
|
||||||
| `caused` | Why did this happen? | `ckb-ingest` |
|
|
||||||
| `contradicts` | What disagrees with this, unresolved? | `ckb-ingest`, `ckb-lint` |
|
|
||||||
| `supersedes` | What replaced this, and what did it replace? | `ckb-ingest`, `ckb-decide`, `ckb-lint` |
|
|
||||||
| `decided_by` | Who made this call? | `ckb-decide` |
|
|
||||||
| `affects` | What does this decision constrain? | `ckb-decide` |
|
|
||||||
| `has_expertise_in` | Who can answer questions on this? | `ckb-ingest` |
|
|
||||||
| `owns` | Who is responsible for this? | `ckb-ingest`, `ckb-code-map` |
|
|
||||||
| `mentioned_in` | Which source document discusses this? | `ckb-index-external` (lib indexes only) |
|
|
||||||
|
|
||||||
Conventions:
|
|
||||||
|
|
||||||
* **One direction per relationship.** `part_of`, `supersedes`, `depends_on`,
|
|
||||||
and `uses` are canonical; don't also record the inverse (`contains`,
|
|
||||||
`superseded_by`, …) as a second edge. Frontmatter carries the inverse where
|
|
||||||
a page needs to state it (`superseded_by:`), and traversal reads edges both
|
|
||||||
ways regardless.
|
|
||||||
* **Evidence, not inference.** `has_expertise_in` and `owns` are recorded only
|
|
||||||
from demonstrated evidence — someone attending a meeting is not expertise,
|
|
||||||
and a job title is not ownership. The same applies to every other verb: an
|
|
||||||
absent edge beats a fabricated one.
|
|
||||||
* **In-degree is a retrieval signal.** How many edges point *at* a page is a
|
|
||||||
rough measure of how central it is, and `ckb-retrieve` fuses it as one
|
|
||||||
ranked list among several. That only works if edges are recorded honestly;
|
|
||||||
padding the graph degrades search rather than improving it.
|
|
||||||
|
|
||||||
*(No edges recorded yet — populated on the next ingest.)*
|
*(No edges recorded yet — populated on the next ingest.)*
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
kb_schema_version: "1.5"
|
kb_schema_version: "1.1"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Knowledge Base Index
|
# Knowledge Base Index
|
||||||
|
|
@ -11,9 +11,6 @@ kb_schema_version: "1.5"
|
||||||
| [Overview](overview.md) | High-level map of the knowledge base | Getting started, understanding the structure |
|
| [Overview](overview.md) | High-level map of the knowledge base | Getting started, understanding the structure |
|
||||||
| [Log](log.md) | Chronological record of root-level changes | Reviewing recent modifications |
|
| [Log](log.md) | Chronological record of root-level changes | Reviewing recent modifications |
|
||||||
| [Error Book](error-book.md) | Known compilation errors and fixes | Debugging a bad ingest or lint issue |
|
| [Error Book](error-book.md) | Known compilation errors and fixes | Debugging a bad ingest or lint issue |
|
||||||
| [Projects](projects/index.md) | Optional local query scopes grouping related wiki pages, sources, libs, and graph areas | Narrowing search to a team, topic, client, system, or initiative |
|
|
||||||
| [Query Gaps](query-gaps.md) | Questions the wiki could not answer yet, used to drive demand-driven ingest | Tracking failed searches, missing knowledge, future ingest targets |
|
|
||||||
| [Decisions](decisions/index.md) | Numbered decision records — what was decided, by whom, when, why, and what it superseded | Asking why something is the way it is, who decided it, when, what changed it, or what is still open |
|
|
||||||
| [Entities](entities/index.md) | Typed entity pages (people, projects, libraries, concepts) | Looking up a specific person, project, library, or concept |
|
| [Entities](entities/index.md) | Typed entity pages (people, projects, libraries, concepts) | Looking up a specific person, project, library, or concept |
|
||||||
| [Graph](graph/index.md) | Edge lists and relationship data between entities | Finding what depends on / relates to a given entity |
|
| [Graph](graph/index.md) | Edge lists and relationship data between entities | Finding what depends on / relates to a given entity |
|
||||||
|
|
||||||
|
|
@ -25,11 +22,3 @@ extracted during ingest. See [Entities index](entities/index.md) /
|
||||||
graph index.
|
graph index.
|
||||||
|
|
||||||
*(Agent populates this as entities are extracted.)*
|
*(Agent populates this as entities are extracted.)*
|
||||||
|
|
||||||
## Decision Records
|
|
||||||
|
|
||||||
Pages in `decisions/` are numbered decision records (`NNNN-slug.md`). Unlike
|
|
||||||
entity pages, they are append-only: a decision is never rewritten to match a
|
|
||||||
later change of mind — a new decision supersedes it and both stay on the
|
|
||||||
record. See [Decisions index](decisions/index.md) /
|
|
||||||
[[decisions/index]]. Say "record a decision" to add one.
|
|
||||||
|
|
|
||||||
15
wiki/log.md
15
wiki/log.md
|
|
@ -2,18 +2,3 @@
|
||||||
|
|
||||||
All modifications to the local `wiki/` directory are recorded here
|
All modifications to the local `wiki/` directory are recorded here
|
||||||
in reverse chronological order (most recent first).
|
in reverse chronological order (most recent first).
|
||||||
|
|
||||||
Each entry uses this format:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## [YYYY-MM-DD HH:MM] - [ACTION TYPE]
|
|
||||||
- **File Affected:** `wiki/path/to/file.md`
|
|
||||||
- **Description:** Brief summary of what knowledge or structure changed.
|
|
||||||
- **Source:** Chat conversation, raw file, URL, or skill name.
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
Changes to pages under `wiki/decisions/` live in `wiki/decisions/log.md`
|
|
||||||
instead, per the Recursive Index & Log Convention.
|
|
||||||
|
|
||||||
*(No entries yet.)*
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
type: overview
|
type: overview
|
||||||
tldr: High-level map of the cascade knowledge base's directory structure, frontmatter schema, and layering rules.
|
tldr: High-level map of the cascade knowledge base's directory structure, frontmatter schema, and layering rules.
|
||||||
last_updated: 2026-09-21
|
last_updated: 2026-07-13
|
||||||
---
|
---
|
||||||
|
|
||||||
# Knowledge Base Overview
|
# Knowledge Base Overview
|
||||||
|
|
@ -26,55 +26,19 @@ When the same entity exists in multiple layers, the local version wins.
|
||||||
│ └── inbox/ # Drop zone for unprocessed material
|
│ └── inbox/ # Drop zone for unprocessed material
|
||||||
├── tmp/ # Temporary files, caches (gitignored)
|
├── tmp/ # Temporary files, caches (gitignored)
|
||||||
├── wiki/ # Local structured wiki (agent-managed)
|
├── wiki/ # Local structured wiki (agent-managed)
|
||||||
│ ├── query-gaps.md # Failed or missing-answer questions for demand-driven ingest
|
│ ├── entities/ # Typed entity pages
|
||||||
│ ├── projects/ # Optional local query scopes
|
│ └── graph/ # Edge lists and relationship data
|
||||||
│ ├── decisions/ # Numbered, append-only decision records
|
|
||||||
│ ├── entities/ # Typed entity pages
|
|
||||||
│ └── graph/ # Edge lists and relationship data
|
|
||||||
└── workload/ # Summaries of discussions and decisions
|
└── workload/ # Summaries of discussions and decisions
|
||||||
```
|
```
|
||||||
|
|
||||||
## Page Frontmatter
|
## Page Frontmatter
|
||||||
Every wiki page carries YAML frontmatter with a required `type` field, plus
|
Every wiki page carries YAML frontmatter with a required `type` field, plus
|
||||||
optional `resource`, `tldr`, `confidence`, `quality`, `supersedes`,
|
optional `resource`, `tldr`, `confidence`, `quality`, `supersedes`,
|
||||||
`freshness_window_days`, `retention`, `source_fingerprint`, and
|
`freshness_window_days`, and `retention`. `wiki/index.md` additionally
|
||||||
`source_checked`. Pages with `type: decision` add
|
|
||||||
`status`, `decided_on`, `decided_by`, `affects`, and `review_on`. `wiki/index.md` additionally
|
|
||||||
declares `kb_schema_version` for the bundle as a whole.
|
declares `kb_schema_version` for the bundle as a whole.
|
||||||
See AGENTS.md for the full schema.
|
See AGENTS.md for the full schema.
|
||||||
|
|
||||||
## Reserved Page Sections
|
|
||||||
Three `##` headings mean the same thing on every page in every layer:
|
|
||||||
|
|
||||||
* **`## Sources`** — one bullet per source, each carrying a fingerprint
|
|
||||||
(`sha256:` for a local file, `etag:`/`mtime:` for a connector item) and the
|
|
||||||
date it was last checked. This is what makes drift detectable mechanically
|
|
||||||
rather than by calendar.
|
|
||||||
* **`## Crux`** — verbatim excerpts from those sources, attributed to the
|
|
||||||
bullet they came from. Evidence, never paraphrase: a quote either still
|
|
||||||
matches its source or it doesn't.
|
|
||||||
* **`## Notes`** — human-authored and **protected**. No skill rewrites,
|
|
||||||
reflows, or drops it; regeneration preserves it byte-for-byte.
|
|
||||||
|
|
||||||
## Decision Records
|
|
||||||
`wiki/decisions/` holds one numbered page per decision (`NNNN-slug.md`),
|
|
||||||
recording what was decided, by whom, on what date, why, and which earlier
|
|
||||||
decision it supersedes or reverses. Decision pages are **append-only**: the
|
|
||||||
substance is never rewritten to match a later change of mind — a new decision
|
|
||||||
supersedes the old one and both stay on the record, so "why is it like this?"
|
|
||||||
keeps its original answer. Decision-specific frontmatter is `status`,
|
|
||||||
`decided_on`, `decided_by`, and optionally `affects` and `review_on`; the
|
|
||||||
existing `supersedes`/`superseded_by` pair carries the history. The
|
|
||||||
`ckb-decide` skill records them and answers questions about them.
|
|
||||||
|
|
||||||
## Recursive Indexes
|
## Recursive Indexes
|
||||||
`entities/`, `graph/`, `decisions/`, and optional topic folders such as
|
`entities/` and `graph/` each carry their own `index.md` so navigation stays
|
||||||
`projects/` each carry their own `index.md` so navigation stays lazy — read `wiki/index.md`
|
lazy — read `wiki/index.md` first, then only descend into a subdirectory
|
||||||
first, then only descend into a subdirectory index if its contents are
|
index if its contents are relevant to the current task.
|
||||||
relevant to the current task.
|
|
||||||
|
|
||||||
## Local-First Retrieval Aids
|
|
||||||
Project scope pages in `wiki/projects/` can group related sources so a query
|
|
||||||
starts narrow before falling back to the full cascade. `wiki/query-gaps.md`
|
|
||||||
records questions the wiki could not answer yet, turning failed searches into
|
|
||||||
small ingest targets.
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# Projects
|
|
||||||
|
|
||||||
Optional local query scopes. A project page groups related wiki pages,
|
|
||||||
entity pages, raw/archive source paths, connector-backed libs, and graph
|
|
||||||
areas so retrieval starts from the most relevant slice of the cascade.
|
|
||||||
|
|
||||||
*(No project scopes yet — create one when a topic, team, client, system, or
|
|
||||||
initiative starts recurring often enough to deserve a default search scope.)*
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
---
|
|
||||||
type: log
|
|
||||||
tldr: Local queue of questions the wiki could not answer yet, used to drive demand-driven ingest.
|
|
||||||
confidence: 1.0
|
|
||||||
quality: 0.8
|
|
||||||
last_updated: 2026-09-20
|
|
||||||
freshness_window_days: 30
|
|
||||||
retention: medium
|
|
||||||
---
|
|
||||||
|
|
||||||
# Query Gaps
|
|
||||||
|
|
||||||
Use this page for questions that the cascade could not answer from
|
|
||||||
`wiki/`, `linked/`, or `libs/`. Each entry should stay short and point to
|
|
||||||
the smallest missing source or page that would close the gap.
|
|
||||||
|
|
||||||
## Open
|
|
||||||
|
|
||||||
*(No query gaps recorded yet.)*
|
|
||||||
|
|
||||||
## Resolved
|
|
||||||
|
|
||||||
*(Move entries here after an ingest, page update, or connector index makes
|
|
||||||
the answer available.)*
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
# 2026-09-21 Session Summary
|
|
||||||
|
|
||||||
## [2026-09-21] Analysis — Graft (trailhq/Graft) vs. this KB structure
|
|
||||||
|
|
||||||
- **Type:** read-only analysis. No `wiki/` writes, so no `wiki/log.md` entry.
|
|
||||||
- **Read:** `wiki/index.md`, `wiki/graph/index.md`, `wiki/entities/index.md`,
|
|
||||||
`.agents/skills/ckb-retrieve/SKILL.md`, `.agents/skills/ckb-lint/SKILL.md`,
|
|
||||||
`.agents/modules/software/skills/ckb-code-map/SKILL.md`, repo tree.
|
|
||||||
- **External source:** <https://github.com/trailhq/Graft> (README, fetched 2026-09-21).
|
|
||||||
- **Finding:** Graft is a *derived, disposable* code-context graph (tree-sitter pass +
|
|
||||||
optional LLM pass, gitignored, regenerated per developer); CKB is a *durable, curated*
|
|
||||||
knowledge layer over non-regenerable material. Architectures are not competitors.
|
|
||||||
- **Seven portable ideas identified**, ranked: (1) verbatim `## Crux`/evidence excerpts
|
|
||||||
in pages; (2) protected `## Notes` block on agent-regenerated pages; (3) content-hash
|
|
||||||
fingerprints on `## Sources` for mechanical staleness; (4) cheap deterministic
|
|
||||||
pre-pass + a `ckb check` freshness signal at session start; (5) graph in-degree as a
|
|
||||||
rank-fusion signal in `ckb-retrieve`; (6) blast-radius pass during ingest;
|
|
||||||
(7) complete + question-oriented edge vocabulary in `wiki/graph/index.md`.
|
|
||||||
- **Defect spotted:** `wiki/graph/index.md` documents `uses`/`depends_on`/`caused`/
|
|
||||||
`contradicts`/`supersedes` but omits `part_of`, which `ckb-code-map` Step 6 writes.
|
|
||||||
- **Rejected as non-portable:** gitignored/regenerable store, MCP server + CLI daemon,
|
|
||||||
tree-sitter parsing, statusline/hooks, telemetry.
|
|
||||||
- **Status:** analysis only, no changes proposed to disk yet. Awaiting user decision on
|
|
||||||
which ideas to implement.
|
|
||||||
- **Git:** branch `main` clean at session start; this summary is the only new file.
|
|
||||||
|
|
||||||
## [2026-09-21] Implementation — Graft ideas 1-7 on branch `graft-ideas`
|
|
||||||
|
|
||||||
Branched from `main` at c998489. Schema bumped 1.4 -> 1.5 (additive; every 1.4
|
|
||||||
page stays valid). VERSION 1.7.0 -> 1.8.0.
|
|
||||||
|
|
||||||
| # | Idea | Where it landed |
|
|
||||||
|---|---|---|
|
|
||||||
| 1 | `## Crux` verbatim evidence | `AGENTS.md` §2, `ckb-ingest` Step 5, `ckb-decide`, `ckb-retrieve` Step 6 shortcut, lint check 13 |
|
|
||||||
| 2 | Protected `## Notes` | `AGENTS.md` §2, `ckb-index-external` regeneration rule, `ckb-code-map`, lint check 14 |
|
|
||||||
| 3 | Source fingerprints | `source_fingerprint`/`source_checked` frontmatter, `ckb-ingest`, `ckb-index-external` (etag/mtime/sha256), `ckb-decide`, lint check 12 |
|
|
||||||
| 4 | Cheap pre-pass + `ckb check` | `lint_report.py --quick`, wired into Rule E at session start; `ckb-index-external` skips unchanged docs by fingerprint |
|
|
||||||
| 5 | In-degree rank fusion | `ckb-retrieve` Step 3 builds the list, Step 4 fuses at weight ~0.5; `in_degree()` in the lint script surfaces hubs |
|
|
||||||
| 6 | Blast radius | new `ckb-ingest` Step 4 (reverse graph walk, confirms/extends/contradicts/untouched + owners), reported in Step 8 |
|
|
||||||
| 7 | Edge vocabulary | `wiki/graph/index.md` rewritten as a question-per-verb table; added `part_of` (the live inconsistency with `ckb-code-map`), `produces`, `configures`, `validates`, `implements` |
|
|
||||||
|
|
||||||
Also updated for consistency: `ckb-init`/`ckb-reset` (graph vocabulary is now
|
|
||||||
scaffold contract, copied verbatim), `ckb-upgrade` (new step b2, the 1.4->1.5
|
|
||||||
migration), `ckb-export-okf` (two new passthrough fields), `README.md`,
|
|
||||||
`README.pl.md`, `MANUAL.md`, `MANUAL.pl.md`, `wiki/overview.md`.
|
|
||||||
|
|
||||||
**Verification:** new lint checks exercised against a synthetic fixture in the
|
|
||||||
scratchpad — a good page (clean), a page with a stale digest + missing file +
|
|
||||||
missing fingerprint + a fabricated quote, and a page with a paraphrased Crux.
|
|
||||||
All five findings fired, the good page produced none. Repo's own wiki lints
|
|
||||||
clean; both exporters run clean.
|
|
||||||
|
|
||||||
**Not done:** ideas beyond 1-7 (Graft's token-budgeted `map` view was idea 8
|
|
||||||
and out of scope). No wiki content was fingerprinted, because this wiki is an
|
|
||||||
empty scaffold — the conventions take effect on the next ingest.
|
|
||||||
|
|
||||||
## [2026-09-21] Docs — CHANGELOG.md / CHANGELOG.pl.md
|
|
||||||
|
|
||||||
Added a bilingual changelog + schema reference at the repo root, following the
|
|
||||||
existing `.md`/`.pl.md` doc convention. Contents: the full current page schema
|
|
||||||
(frontmatter for all pages and for decisions, the three reserved body sections,
|
|
||||||
the closed edge vocabulary with a "since" column, the reserved scaffold), the
|
|
||||||
KB schema version history 1.1-1.5, the template VERSION history 1.0.0-1.8.0,
|
|
||||||
and a migration section.
|
|
||||||
|
|
||||||
History reconstructed from git, not from memory: `VERSION` and
|
|
||||||
`wiki/index.md` were read back at each commit that changed them, and each
|
|
||||||
release's diff was inspected to describe what it actually contained.
|
|
||||||
|
|
||||||
Two facts worth keeping:
|
|
||||||
- There was never a schema 1.0. Versioning began at 1.1 (c56348b, 2026-07-13).
|
|
||||||
- Template 1.4.0 and 1.5.0 were never published — VERSION jumped 1.3.0 -> 1.6.0
|
|
||||||
on 2026-09-01. Recorded as a note rather than papered over.
|
|
||||||
- Connector-backed libs shipped in template 1.1.0 but the schema only recorded
|
|
||||||
them at 1.2, nine days later; the table's schema column shows what was in
|
|
||||||
effect after each release, with a footnote explaining the lag.
|
|
||||||
|
|
||||||
Registered the new files with `ckb-init` (copied verbatim into a new KB) and
|
|
||||||
`ckb-upgrade` (taken wholesale from upstream, never merged — upstream is
|
|
||||||
authoritative about its own history). Cross-linked from README.md,
|
|
||||||
README.pl.md, MANUAL.md, MANUAL.pl.md.
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
# 2026-09-22 Session Summary
|
|
||||||
|
|
||||||
## [2026-09-22] Reset requested, then cancelled
|
|
||||||
|
|
||||||
`/ckb-reset` was invoked. Ran Step 0 (clean tree, restore point `2c4d57a`) and
|
|
||||||
the Step 2 inventory, which found this KB already effectively a clean template
|
|
||||||
— 0 entity pages, 0 project scopes, 0 decision records, 0 graph edges, nothing
|
|
||||||
in `raw/`, `outputs/`, `libs/`, `linked/` beyond `.gitadd` placeholders, no
|
|
||||||
modules installed, no `src/`. The only tier-1/2 content was 3 `wiki/log.md`
|
|
||||||
entries (the schema 1.5 migration trail) and one workload summary.
|
|
||||||
|
|
||||||
**Nothing was deleted.** The typed confirmation was never given — the user
|
|
||||||
moved on to merging and syncing instead. Flagged at the time: `main` was
|
|
||||||
already a clean template at schema 1.4, and clearing the log would leave the
|
|
||||||
scaffold declaring 1.5 with no in-wiki record of how it got there.
|
|
||||||
|
|
||||||
## [2026-09-22] Merge and sync
|
|
||||||
|
|
||||||
- Working tree was already clean; nothing to commit.
|
|
||||||
- `main` was an ancestor of `graft-ideas`, so the merge fast-forwarded — no
|
|
||||||
merge commit, both commits preserved individually, no conflicts.
|
|
||||||
- Pushed `main` (c998489 -> 2c4d57a) to **both** remotes: `origin`
|
|
||||||
(git.wierzbowa.cloud/michal/ckb) and `codeberg`
|
|
||||||
(codeberg.org/Valdec/llm-wiki-cascade).
|
|
||||||
- Schema 1.5 and template 1.8.0 are now the published state on both.
|
|
||||||
|
|
||||||
**Left alone deliberately:** branch `graft-ideas` (now identical to `main`,
|
|
||||||
safe to delete whenever), and branch `feature/external-source-connectors`,
|
|
||||||
which holds one commit (`f3dbce7`) not present on either remote and is
|
|
||||||
unrelated to this work.
|
|
||||||
|
|
||||||
## [2026-09-22] Reset executed — Tier 1
|
|
||||||
|
|
||||||
Re-ran `/ckb-reset` from the post-sync state (the earlier inventory could not
|
|
||||||
carry over: the counts had moved). Confirmed with the typed phrase, Tier 1 only.
|
|
||||||
|
|
||||||
- **Deleted:** 3 `wiki/log.md` entries — the schema 1.5 migration trail. That
|
|
||||||
was the whole of Tier 1; there were no entity pages, project scopes, decision
|
|
||||||
records or graph edges to remove.
|
|
||||||
- **Restored:** `wiki/log.md` to its header-and-format-example form. Nothing
|
|
||||||
else was rewritten — `index.md`, `overview.md`, `graph/index.md` and
|
|
||||||
`decisions/index.md` already matched what `ckb-init` produces at schema 1.5.
|
|
||||||
- **Deliberately not bumped:** `wiki/graph/edges.json` (`last_updated:
|
|
||||||
2026-09-20`, array already empty) and `wiki/overview.md` (`last_updated:
|
|
||||||
2026-09-21`). Both dates are accurate as they stand; bumping them would
|
|
||||||
assert changes that did not happen.
|
|
||||||
- **Preserved:** Tier 2 (both workload summaries), the entire template layer,
|
|
||||||
and schema 1.5 / template 1.8.0.
|
|
||||||
- **Verification:** lint returned 0 findings across `wiki/` and `libs/`.
|
|
||||||
- **Restore point:** `7d3fa54`, clean and pushed to both remotes before the
|
|
||||||
reset ran.
|
|
||||||
|
|
||||||
**Rule B exception.** The reset is a change inside `wiki/` and would normally
|
|
||||||
require a `wiki/log.md` entry. Logging "emptied the log" into the log it just
|
|
||||||
emptied defeats the purpose, so the record lives here instead — the same
|
|
||||||
reasoning `ckb-reset` applies to its Rule D exception. The migration this
|
|
||||||
removed from the log remains documented in `CHANGELOG.md` and in commits
|
|
||||||
`474630e`/`2c4d57a`, published on both remotes.
|
|
||||||
|
|
||||||
## [2026-09-22] Release channels — three branches, branch-aware init and upgrade
|
|
||||||
|
|
||||||
Added `test` and `experimental` branches alongside `main`, and taught
|
|
||||||
`ckb-init`/`ckb-upgrade` to source from any of them.
|
|
||||||
|
|
||||||
**Channel semantics:** `main` = stable/released, `test` = release candidate,
|
|
||||||
`experimental` = development. Default everywhere is `main`.
|
|
||||||
|
|
||||||
**Selection is per-invocation and sticky.** The user names a branch in words
|
|
||||||
("initialize from the test branch", "check experimental for updates", "switch
|
|
||||||
back to stable"); the choice is then persisted to `ckb.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
template:
|
|
||||||
repo: https://git.wierzbowa.cloud/michal/ckb.git
|
|
||||||
branch: main
|
|
||||||
```
|
|
||||||
|
|
||||||
Persistence was a judgment call, not an explicit ask. Without it, a KB
|
|
||||||
bootstrapped from `experimental` would be silently pulled back to `main` by its
|
|
||||||
next upgrade — mixing channels without anyone noticing. Absent file or absent
|
|
||||||
block both mean `main`, so every pre-existing KB behaves exactly as before.
|
|
||||||
|
|
||||||
**The non-obvious consequence, handled explicitly:** a KB tracking `test` or
|
|
||||||
`experimental` can sit on a VERSION `main` has not released. Comparing against
|
|
||||||
`main` finds nothing newer, which the old code would have reported as "up to
|
|
||||||
date" — true but misleading. `ckb-upgrade` Step 2 now reports "ahead" instead,
|
|
||||||
and treats a move back to `main` as a downgrade requiring explicit
|
|
||||||
confirmation, blocked outright when `kb_schema_version` would drop below what
|
|
||||||
local content is written against.
|
|
||||||
|
|
||||||
**Files touched:** `ckb-init` (Step 3 channel table, Step 7 writes `ckb.yaml`,
|
|
||||||
Step 12 reports the channel), `ckb-upgrade` (new Step 0, Step 2 ahead-case,
|
|
||||||
Step 6 persists, Step 8 reports, 5 new edge cases), `ckb-module` (must not
|
|
||||||
clobber the `template:` block), `AGENTS.md` routing, both READMEs, both
|
|
||||||
MANUALs, both CHANGELOGs. VERSION 1.8.0 -> 1.9.0. Schema unchanged at 1.5 —
|
|
||||||
this is tooling, not a content contract.
|
|
||||||
Loading…
Add table
Reference in a new issue