Compare commits
2 commits
c998489c9a
...
2c4d57acbc
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c4d57acbc | |||
| 474630e2bb |
25 changed files with 1604 additions and 60 deletions
|
|
@ -121,11 +121,25 @@ 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.
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -108,9 +108,34 @@ was chosen before the reporting requirements landed.
|
|||
|
||||
## Sources
|
||||
|
||||
Where this came from — a meeting, a thread, a `raw/archive/` file, a ticket.
|
||||
- `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 |
|
||||
|
|
@ -236,6 +261,14 @@ 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
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ The script prints, and you should summarize back to the user:
|
|||
| `tldr` | `description` | rename |
|
||||
| `resource` | `resource` | passthrough |
|
||||
| `last_updated` | `timestamp` | passthrough as-is (bare `YYYY-MM-DD` is valid ISO 8601 — no time-of-day is fabricated) |
|
||||
| `confidence`, `quality`, `retention`, `supersedes`, `superseded_by`, `freshness_window_days` | same keys | passthrough as OKF extension fields, which consumers must tolerate |
|
||||
| `confidence`, `quality`, `retention`, `supersedes`, `superseded_by`, `freshness_window_days`, `source_fingerprint`, `source_checked` | same keys | passthrough as OKF extension fields, which consumers must tolerate |
|
||||
| `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)* | `tags` | omitted — no source field to derive it from |
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ PASSTHROUGH_EXT = [
|
|||
"supersedes",
|
||||
"superseded_by",
|
||||
"freshness_window_days",
|
||||
"source_fingerprint",
|
||||
"source_checked",
|
||||
]
|
||||
# Keys with no valid home in an OKF bundle.
|
||||
DROPPED = {"kb_schema_version"}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,36 @@ stop being) a given source's admin (e.g. "make me the admin for the
|
|||
finance reports source" / "I don't want write access to X anymore"), never
|
||||
as a side effect of just running "index external sources."
|
||||
|
||||
## 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
|
||||
|
||||
### Step 1 — Find connector-backed libs
|
||||
|
|
@ -233,9 +263,38 @@ last_updated: YYYY-MM-DD
|
|||
freshness_window_days: 30 # this source's refresh_interval_days, or 30 if unset — shorter than a
|
||||
# typical wiki page, because external sources change without notice
|
||||
retention: medium
|
||||
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
|
||||
|
|
@ -289,9 +348,11 @@ authoritative (cascade priority); this page's only job is pointing back to
|
|||
*these specific documents* as additional evidence, which the wiki page may
|
||||
not have cited yet.
|
||||
|
||||
Record real relationships surfaced by a document between two entities
|
||||
(`uses`, `depends_on`, `caused`, `contradicts`) as edges in
|
||||
`libs/<name>/graph/edges.json`, same edge vocabulary `wiki/` uses. Also add
|
||||
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.
|
||||
|
|
@ -392,8 +453,19 @@ was read-only with nothing new to fetch), skip this reminder.
|
|||
user from seeing the last-known index.
|
||||
- **Re-running against an already-indexed source** — refresh existing
|
||||
document *and* entity/process pages in place (update
|
||||
`tldr`/`last_updated`/`confidence`, add newly-seen `mentioned_in` edges);
|
||||
never duplicate a document's or entity's page.
|
||||
`tldr`/`last_updated`/`confidence`/`source_fingerprint`/`source_checked`,
|
||||
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** —
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
upstream sources of truth — and it never touches this repo's own git
|
||||
remote; pushing the resulting changes is a separate, explicit step (see
|
||||
Step 7).
|
||||
Step 8).
|
||||
|
||||
## Trigger phrases
|
||||
|
||||
|
|
@ -60,8 +60,12 @@ than treated as final.
|
|||
Identify typed entities in the source material — people, projects,
|
||||
libraries, concepts, systems. Create entity pages at
|
||||
`wiki/entities/<entity-name>.md` if they don't already exist. Record typed
|
||||
relationships between entities — `uses`, `depends_on`, `caused`,
|
||||
`contradicts`, `supersedes` — as edge data in `wiki/graph/edges.json`.
|
||||
relationships between entities as edge data in `wiki/graph/edges.json`,
|
||||
using the closed vocabulary in `wiki/graph/index.md` — `part_of`, `uses`,
|
||||
`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
|
||||
evidences them: `has_expertise_in` when a person demonstrably answers
|
||||
|
|
@ -95,7 +99,35 @@ wiki/entity/source paths, exclusions, and refresh hints. Create one only
|
|||
when the source material shows a real repeated scope; don't manufacture
|
||||
scopes for one-off facts.
|
||||
|
||||
### Step 4 — Synthesize pages
|
||||
### 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
|
||||
gets frontmatter with:
|
||||
|
|
@ -110,6 +142,53 @@ gets frontmatter with:
|
|||
`CLAUDE.md`/`AGENTS.md` page schema) — set it once, based on the entity/content
|
||||
kind (person, project, concept, library, decision, playbook, ...).
|
||||
|
||||
Every synthesized page ends with the reserved body sections from the page
|
||||
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.
|
||||
|
|
@ -145,14 +224,14 @@ thread question or section heading it sat under. An excerpt that reads
|
|||
unambiguously on its own is the entire point; one that needs the
|
||||
surrounding transcript to make sense hasn't been extracted, only moved.
|
||||
|
||||
### Step 5 — Link and cross-reference
|
||||
### Step 6 — Link and cross-reference
|
||||
|
||||
Use **both** `[[Wikilinks]]` (Obsidian-compatible) and standard
|
||||
`[markdown](path.md)` links on every cross-reference, so the wiki works in
|
||||
Obsidian, GitHub, and CLI tools alike. Where useful, reference upstream
|
||||
files directly at `linked/<name>/...` or `libs/<name>/...`.
|
||||
|
||||
### Step 6 — Update index and log
|
||||
### Step 7 — Update index and log
|
||||
|
||||
Add new pages to the routing table in `wiki/index.md` with a **Use when**
|
||||
description. If the page lives in a subdirectory, also add it to that
|
||||
|
|
@ -170,10 +249,15 @@ also update `wiki/projects/index.md`. If ingest closes a previously
|
|||
recorded question in `wiki/query-gaps.md`, move that entry from Open to
|
||||
Resolved and mention the page or source that now answers it.
|
||||
|
||||
### Step 7 — Remind to review and sync
|
||||
### 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
|
||||
small or obviously correct:
|
||||
|
||||
|
|
@ -187,7 +271,7 @@ process), skip this reminder — there's nothing to review or sync.
|
|||
## Edge cases
|
||||
|
||||
- **Inbox and `raw/` both empty** — report that there's nothing to
|
||||
ingest. Don't touch `wiki/`, and skip the Step 7 reminder.
|
||||
ingest. Don't touch `wiki/`, and skip the Step 8 reminder.
|
||||
- **Item in inbox doesn't yield a clear entity or page** (too vague, pure
|
||||
scratch note with no durable fact) — don't force a page into existence.
|
||||
Still move the item to `raw/archive/<YYYY-MM-DD>/` since it's been
|
||||
|
|
|
|||
|
|
@ -217,6 +217,10 @@ actual project entity):
|
|||
should point somewhere else - it's what lets the new KB's own
|
||||
`ckb-upgrade` find template updates later.
|
||||
- `MANUAL.md` and `MANUAL.pl.md` - the task-oriented user guide.
|
||||
- `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
|
||||
same template version it was just cloned from.
|
||||
|
||||
|
|
@ -249,9 +253,11 @@ generic structure:
|
|||
- **`wiki/error-book.md`** - copy verbatim (already generic: empty table
|
||||
+ placeholder note). Set `last_updated` to today.
|
||||
- **`wiki/entities/index.md`** - header + placeholder note, no entries.
|
||||
- **`wiki/graph/index.md`** - header + pointer to `edges.json`, with a
|
||||
generic "Current graph coverage: (none yet)" line instead of this
|
||||
repo's specific bullet list.
|
||||
- **`wiki/graph/index.md`** - header + pointer to `edges.json`, the edge
|
||||
vocabulary table and its conventions **verbatim** (`ckb-ingest`,
|
||||
`ckb-decide`, `ckb-index-external`, and `ckb-lint` all write against that
|
||||
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/projects/index.md`** - header + placeholder note, no project
|
||||
scopes yet.
|
||||
|
|
|
|||
|
|
@ -77,13 +77,15 @@ 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. Exit code is `0` when nothing was found and `1`
|
||||
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, and 10 below — plus the structural half of check 5 (decision
|
||||
records) — across `wiki/` and every connector-backed
|
||||
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.
|
||||
|
|
@ -221,11 +223,78 @@ For the `software` module specifically:
|
|||
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 what can be done safely and mechanically: broken links, missing
|
||||
backlinks, stale flags, missing index entries, duplicate log entries,
|
||||
dangling graph edges. Report anything that needs a judgment call
|
||||
dangling graph edges, and a missing fingerprint on an otherwise-unflagged
|
||||
page. Report anything that needs a judgment call
|
||||
(supersession decisions, low-confidence content, ambiguous orphans) rather
|
||||
than guessing on the user's behalf.
|
||||
|
||||
|
|
@ -268,6 +337,13 @@ fix or flag), skip this reminder — there's nothing to review or sync.
|
|||
- **Repeated run with nothing changed since the last lint** — produces the
|
||||
same report each time (pass `--today` to pin the date if you need a
|
||||
byte-identical one); don't invent 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.
|
||||
|
|
|
|||
|
|
@ -15,14 +15,15 @@
|
|||
"""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]
|
||||
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) live here; the
|
||||
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
|
||||
|
|
@ -32,6 +33,7 @@ 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
|
||||
|
|
@ -136,6 +138,12 @@ def as_date(value):
|
|||
|
||||
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:
|
||||
|
|
@ -521,6 +529,124 @@ def check_sources(today):
|
|||
# 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"),
|
||||
|
|
@ -530,8 +656,15 @@ CHECKS = [
|
|||
("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)
|
||||
|
|
@ -546,14 +679,46 @@ def run_tree(root, today, label):
|
|||
"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()
|
||||
|
|
@ -582,6 +747,15 @@ def main():
|
|||
|
||||
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
|
||||
|
|
@ -597,6 +771,9 @@ def main():
|
|||
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:
|
||||
|
|
@ -607,6 +784,8 @@ def main():
|
|||
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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -131,8 +131,11 @@ Step 8 defines it** — that skill is the single source of truth for what a clea
|
|||
- `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`, `wiki/graph/index.md` —
|
||||
headers plus placeholder notes.
|
||||
- `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": []}`.
|
||||
|
|
|
|||
|
|
@ -126,6 +126,24 @@ 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
|
||||
|
|
@ -139,6 +157,12 @@ it appears in, where `rank` is its 1-based position in that list. Use
|
|||
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
|
||||
|
|
@ -184,6 +208,21 @@ changes the interpretation.
|
|||
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
|
||||
|
|
@ -256,6 +295,12 @@ that would close the gap. If you edit `wiki/query-gaps.md`, update
|
|||
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
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ neither:
|
|||
|
||||
- **Template layer** — the schema/tooling that defines *how* the KB
|
||||
behaves: `AGENTS.md`/`CLAUDE.md`, `.agents/skills/*`, `LICENSE`,
|
||||
`VERSION`, `README.md`/`README.pl.md`/`MANUAL.md`/`MANUAL.pl.md`, and the
|
||||
base `.gitignore` rules. Compared via the root `VERSION` file. Freely
|
||||
`VERSION`, `README.md`/`README.pl.md`/`MANUAL.md`/`MANUAL.pl.md`/
|
||||
`CHANGELOG.md`/`CHANGELOG.pl.md`, and the base `.gitignore` rules. Compared via the root `VERSION` file. Freely
|
||||
upgradable — these files hold no project-specific content.
|
||||
- **Content layer** — everything the KB has actually learned:
|
||||
`wiki/entities/`, `wiki/graph/edges.json`, `wiki/index.md`'s entity rows,
|
||||
|
|
@ -149,8 +149,11 @@ The template layer is exactly these paths — never anything under `wiki/`,
|
|||
symlink)
|
||||
- `LICENSE`
|
||||
- `VERSION`
|
||||
- `README.md`, `README.pl.md`, `MANUAL.md`, `MANUAL.pl.md` — whichever
|
||||
exist upstream (an older local KB may not have some of these yet)
|
||||
- `README.md`, `README.pl.md`, `MANUAL.md`, `MANUAL.pl.md`,
|
||||
`CHANGELOG.md`, `CHANGELOG.pl.md` — whichever exist upstream (an older
|
||||
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
|
||||
`tmp/ckb-upgrade-src/.agents/skills/`
|
||||
- `.agents/modules/<name>/**` for every optional module present upstream
|
||||
|
|
@ -296,6 +299,35 @@ present, and never alter the page's body text:
|
|||
vice versa) — add the missing form. Don't invent new cross-references
|
||||
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
|
||||
current value, once every page has been checked.
|
||||
|
||||
|
|
|
|||
46
AGENTS.md
46
AGENTS.md
|
|
@ -96,9 +96,34 @@ superseded_by: path/to/newer/page.md
|
|||
last_updated: YYYY-MM-DD
|
||||
freshness_window_days: 90
|
||||
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
|
||||
---
|
||||
```
|
||||
|
||||
### Page Body Sections
|
||||
Four body sections are reserved across the whole KB. All are optional, but
|
||||
where present they mean exactly this and nothing else:
|
||||
|
||||
- **`## Sources`** — where this page came from. One bullet per source, each
|
||||
carrying a fingerprint so drift is detectable by machine rather than by
|
||||
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
|
||||
|
|
@ -106,7 +131,7 @@ 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.4"`
|
||||
`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
|
||||
|
|
@ -201,11 +226,22 @@ or answer, append a short note to `workload/YYYY-MM-DD_summary.md`.
|
|||
|
||||
### Rule E: Session Start and End
|
||||
At session start, read `wiki/index.md`, the latest `workload/` summary, and
|
||||
run a cheap `git status` check. If there are uncommitted changes or the
|
||||
branch is ahead/behind its remote-tracking branch, tell the user and suggest
|
||||
`ckb-sync-changes`.
|
||||
run two cheap checks:
|
||||
|
||||
At session end, update `workload/` and repeat the same unsynchronized-change
|
||||
```bash
|
||||
git status --short --branch
|
||||
python3 .agents/skills/ckb-lint/scripts/lint_report.py --quick
|
||||
```
|
||||
|
||||
`git status` covers unsynchronized history — if there are uncommitted
|
||||
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
|
||||
|
|
|
|||
334
CHANGELOG.md
Normal file
334
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
# 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.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.
|
||||
|
||||
---
|
||||
|
||||
## Migrating between versions
|
||||
|
||||
Say **"upgrade the wiki"** or **"check for a newer template version"**. 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).
|
||||
341
CHANGELOG.pl.md
Normal file
341
CHANGELOG.pl.md
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
# 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.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.
|
||||
|
||||
---
|
||||
|
||||
## Migracja między wersjami
|
||||
|
||||
Powiedz **„upgrade the wiki"** albo **„check for a newer template version"**.
|
||||
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).
|
||||
44
MANUAL.md
44
MANUAL.md
|
|
@ -6,8 +6,9 @@ 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 —
|
||||
for the agent's own operating rules, see [AGENTS.md](AGENTS.md) /
|
||||
[CLAUDE.md](CLAUDE.md). For a feature-by-feature technical overview, see
|
||||
[README.md](README.md). This document is task-oriented: "I want to do X —
|
||||
what do I say, and what happens?"
|
||||
[README.md](README.md). For the page schema in full and the version
|
||||
histories behind it, see [CHANGELOG.md](CHANGELOG.md). This document is
|
||||
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
|
||||
using against this repo (Claude Code, or another agent that reads
|
||||
|
|
@ -273,8 +274,29 @@ This runs a health check across the whole wiki:
|
|||
- 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`
|
||||
|
||||
The last two are worth understanding, because they're the difference
|
||||
between "this page is old" and "this page is wrong".
|
||||
|
||||
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
|
||||
|
|
@ -692,6 +714,22 @@ 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/
|
||||
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 |
|
||||
|---|---|---|
|
||||
| `raw/inbox/`, loose files in `raw/` | **You, only** | The agent only reads, archives, and moves things here — it never originates content in `raw/` itself. |
|
||||
|
|
@ -700,7 +738,7 @@ graph stay in sync with what you changed.
|
|||
| `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.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/`. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
|
|
|
|||
45
MANUAL.pl.md
45
MANUAL.pl.md
|
|
@ -6,7 +6,9 @@ To jest podręcznik dla *człowieka* korzystającego z Cascade Knowledge Base
|
|||
(tego repozytorium) — nie dla agenta. Zasady działania samego agenta
|
||||
znajdziesz w [AGENTS.md](AGENTS.md) / [CLAUDE.md](CLAUDE.md). Techniczny,
|
||||
funkcja-po-funkcji przegląd znajdziesz w [README.md](README.md) (lub
|
||||
[README.pl.md](README.pl.md)). Ten dokument jest zorientowany na zadania:
|
||||
[README.pl.md](README.pl.md)). Pełny schemat strony wraz z historią obu
|
||||
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?”
|
||||
|
||||
Wszędzie poniżej „powiedz” oznacza napisanie tego do dowolnego agenta AI,
|
||||
|
|
@ -285,8 +287,31 @@ To uruchamia przegląd kondycji całej wiki:
|
|||
- 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`
|
||||
|
||||
Te dwa ostatnie warto zrozumieć, bo to różnica między „ta strona jest stara"
|
||||
a „ta strona jest błędna".
|
||||
|
||||
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
|
||||
|
|
@ -729,6 +754,22 @@ Jeśli ręcznie edytujesz stronę wiki, dobrą praktyką jest uruchomienie
|
|||
potem „Lint”, żeby indeks/dziennik/graf pozostały spójne z tym, co
|
||||
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 |
|
||||
|---|---|---|
|
||||
| `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. |
|
||||
|
|
@ -737,7 +778,7 @@ zmieniłeś.
|
|||
| `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.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/`. |
|
||||
| `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ć. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
|
|
|
|||
36
README.md
36
README.md
|
|
@ -18,6 +18,9 @@ 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)).
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
|
@ -216,6 +219,8 @@ superseded_by: path/to/new.md
|
|||
last_updated: YYYY-MM-DD
|
||||
freshness_window_days: 90 # Days before considered stale
|
||||
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
|
||||
---
|
||||
```
|
||||
|
||||
|
|
@ -226,17 +231,38 @@ retention: high|medium|low
|
|||
- **supersedes / superseded_by** — when new info replaces old, link them
|
||||
- **freshness_window_days** — pages older than this get flagged during lint
|
||||
- **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.
|
||||
|
||||
`wiki/index.md` alone also carries `kb_schema_version` (e.g. `"1.3"`), declaring
|
||||
#### Reserved body sections
|
||||
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
|
||||
additive optional fields, major for breaking changes.
|
||||
|
||||
### Entity Extraction & Knowledge Graph
|
||||
During ingest, the agent extracts typed entities (people, projects, libraries,
|
||||
concepts, systems) and stores them as pages in `wiki/entities/`. Typed
|
||||
relationships are recorded in `wiki/graph/edges.json` — structural ones
|
||||
(`uses`, `depends_on`, `caused`, `contradicts`, `supersedes`) plus
|
||||
people-to-topic ones (`has_expertise_in`, `owns`). Queries can walk the
|
||||
relationships are recorded in `wiki/graph/edges.json`, using a closed
|
||||
vocabulary where each verb is defined by the question it answers (see
|
||||
`wiki/graph/index.md`) — structural ones (`part_of`, `uses`, `depends_on`,
|
||||
`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.
|
||||
|
||||
|
|
@ -326,6 +352,8 @@ Periodically (or on demand), the agent health-checks the wiki:
|
|||
- **Orphan detection** — finds pages with no inbound links
|
||||
- **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
|
||||
- **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
|
||||
|
||||
|
|
|
|||
39
README.pl.md
39
README.pl.md
|
|
@ -19,6 +19,9 @@ 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)).
|
||||
|
||||
---
|
||||
|
||||
## Struktura katalogów
|
||||
|
|
@ -227,6 +230,8 @@ superseded_by: path/to/new.md
|
|||
last_updated: YYYY-MM-DD
|
||||
freshness_window_days: 90 # Liczba dni, po których treść uznaje się za nieaktualną
|
||||
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
|
||||
---
|
||||
```
|
||||
|
||||
|
|
@ -244,8 +249,27 @@ retention: high|medium|low
|
|||
podczas lintowania
|
||||
- **retention** — strony o niskim priorytecie są archiwizowane po 2× oknie
|
||||
ś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.
|
||||
|
||||
Sam `wiki/index.md` dodatkowo zawiera `kb_schema_version` (np. `"1.3"`),
|
||||
#### Zastrzeżone sekcje treści
|
||||
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 —
|
||||
zwiększaj wersję pomniejszą dla dodatkowych opcjonalnych pól, główną dla
|
||||
zmian łamiących kompatybilność.
|
||||
|
|
@ -253,9 +277,12 @@ zmian łamiących kompatybilność.
|
|||
### Ekstrakcja encji i graf wiedzy
|
||||
Podczas ingestu agent wydobywa typowane encje (osoby, projekty, biblioteki,
|
||||
koncepcje, systemy) i zapisuje je jako strony w `wiki/entities/`. Typowane
|
||||
relacje są zapisywane w `wiki/graph/edges.json` — strukturalne (`uses`,
|
||||
`depends_on`, `caused`, `contradicts`, `supersedes`) oraz łączące osoby z
|
||||
tematami (`has_expertise_in`, `owns`). Zapytania mogą przechodzić po grafie,
|
||||
relacje są zapisywane w `wiki/graph/edges.json` przy użyciu zamkniętego
|
||||
słownika, w którym każdy czasownik jest zdefiniowany przez pytanie, na jakie
|
||||
odpowiada (patrz `wiki/graph/index.md`) — strukturalne (`part_of`, `uses`,
|
||||
`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".
|
||||
|
||||
|
|
@ -354,6 +381,10 @@ Okresowo (lub na żądanie) agent sprawdza kondycję wiki:
|
|||
istniejące encje
|
||||
- **Spójność indeksu/dziennika** — weryfikuje, czy każdy podkatalog ma
|
||||
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
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1.7.0
|
||||
1.8.0
|
||||
|
|
|
|||
|
|
@ -2,9 +2,47 @@
|
|||
|
||||
Edge list and relationship data for the knowledge graph, keyed by entity page.
|
||||
|
||||
* `edges.json` - Typed relationships between entity pages. Created on the first ingest that extracts entities.
|
||||
* Structural: `uses`, `depends_on`, `caused`, `contradicts`, `supersedes`.
|
||||
* Decision edges: `decided_by` (decision page -> the person who made the call) and `affects` (decision page -> the entity, project, or system it constrains). These make "who decided X", "what decisions touch Y", and "what changed after Z" direct graph lookups. Written by `ckb-decide` when a decision is recorded.
|
||||
* People-to-topic: `has_expertise_in` (demonstrated ability to answer questions on a topic) and `owns` (declared responsibility for a system, area, or decision). These make "who knows about X" and "who owns X" a direct graph lookup rather than a full-text guess. Recorded only from demonstrated evidence, never inferred from attendance or job title.
|
||||
`edges.json` holds typed relationships between pages. Created on the first
|
||||
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.)*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
kb_schema_version: "1.4"
|
||||
kb_schema_version: "1.5"
|
||||
---
|
||||
|
||||
# Knowledge Base Index
|
||||
|
|
|
|||
18
wiki/log.md
18
wiki/log.md
|
|
@ -16,4 +16,20 @@ Each entry uses this format:
|
|||
Changes to pages under `wiki/decisions/` live in `wiki/decisions/log.md`
|
||||
instead, per the Recursive Index & Log Convention.
|
||||
|
||||
*(No entries yet.)*
|
||||
## [2026-09-21 22:41] - UPDATE (schema 1.4 -> 1.5)
|
||||
- **File Affected:** `wiki/index.md`
|
||||
- **Description:** Bumped `kb_schema_version` to `"1.5"`, which adds the reserved body sections (`## Sources`, `## Crux`, `## Notes`), the `source_fingerprint`/`source_checked` frontmatter fields, and a completed edge vocabulary. Additive only — every 1.4 page remains valid.
|
||||
- **Source:** Chat conversation (analysis of trailhq/Graft), branch `graft-ideas`.
|
||||
---
|
||||
|
||||
## [2026-09-21 22:41] - RESTRUCTURE
|
||||
- **File Affected:** `wiki/graph/index.md`
|
||||
- **Description:** Replaced the prose edge list with a closed vocabulary table defining each verb by the question it answers, and added the missing `part_of` (written by `ckb-code-map` but previously undocumented) plus `produces`, `configures`, `validates`, `implements`. Added conventions on edge direction, evidence-only edges, and in-degree as a retrieval signal.
|
||||
- **Source:** Chat conversation (analysis of trailhq/Graft), branch `graft-ideas`.
|
||||
---
|
||||
|
||||
## [2026-09-21 22:41] - UPDATE
|
||||
- **File Affected:** `wiki/overview.md`
|
||||
- **Description:** Documented the two new frontmatter fields and added a "Reserved Page Sections" section covering `## Sources`, `## Crux`, and the protected `## Notes`.
|
||||
- **Source:** Chat conversation (analysis of trailhq/Graft), branch `graft-ideas`.
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
type: overview
|
||||
tldr: High-level map of the cascade knowledge base's directory structure, frontmatter schema, and layering rules.
|
||||
last_updated: 2026-09-20
|
||||
last_updated: 2026-09-21
|
||||
---
|
||||
|
||||
# Knowledge Base Overview
|
||||
|
|
@ -37,11 +37,25 @@ When the same entity exists in multiple layers, the local version wins.
|
|||
## Page Frontmatter
|
||||
Every wiki page carries YAML frontmatter with a required `type` field, plus
|
||||
optional `resource`, `tldr`, `confidence`, `quality`, `supersedes`,
|
||||
`freshness_window_days`, and `retention`. Pages with `type: decision` add
|
||||
`freshness_window_days`, `retention`, `source_fingerprint`, and
|
||||
`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.
|
||||
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
|
||||
|
|
|
|||
81
workload/2026-09-21_summary.md
Normal file
81
workload/2026-09-21_summary.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# 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.
|
||||
Loading…
Add table
Reference in a new issue