ckb/CHANGELOG.md
Michał Kopeć 6c0d70976c Add release channels: main, test, experimental
The template repo now keeps three branches with fixed meanings — main is
stable, test is the release candidate, experimental is development — and
ckb-init/ckb-upgrade can source from any of them instead of only main.

Selection is per-invocation, in words the user already uses ("initialize
from the test branch", "check experimental for updates", "switch back to
stable"), and sticky: the resolved repo and branch are written to a
template: block in ckb.yaml. Without persistence, a KB bootstrapped from
experimental would be silently pulled back to main by its next upgrade.
A missing file or missing block both mean main, so every KB predating
this convention behaves exactly as before.

One consequence needed explicit handling. A KB tracking test or
experimental can sit on a VERSION main has not released yet, so comparing
it against main finds nothing newer — which the version check would have
reported as "up to date". That is true and misleading. ckb-upgrade now
reports it as "ahead", and treats a move back to main as a downgrade:
explicitly confirmed, with the specific losses named, and blocked
outright where kb_schema_version would drop below what local pages are
already written against.

ckb-module is told not to clobber the template: block — a module install
that silently reset a KB's channel would change what its next upgrade
pulls, which is not a module's business.

Documented in both READMEs, both MANUALs and both CHANGELOGs. VERSION
1.8.0 -> 1.9.0; kb_schema_version stays 1.5, since this is tooling rather
than a content contract.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 08:29:45 +02:00

370 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).