ckb/.agents/skills/ckb-retrieve/SKILL.md
Michał Kopeć 474630e2bb Adopt seven ideas from trailhq/Graft; schema 1.4 -> 1.5
Graft keeps a derived, disposable code graph in sync with a content hash
rather than a calendar, and keeps a protected block on every regenerated
node. This KB is the opposite kind of store — durable, curated, built from
material that cannot be regenerated — but several of Graft's mechanisms
port cleanly, and two of them close real gaps here.

Schema 1.5 is additive: every 1.4 page remains valid.

1. `## Crux` — verbatim source excerpts alongside the synthesis. A summary
   can drift silently; a quote either still matches its source or it does
   not. Lets `ckb-retrieve` ground an answer without a round-trip to the
   archive, and makes drift mechanically detectable.
2. `## Notes` — human-authored and protected everywhere. Closes a real
   gap: `ckb-index-external` regenerates connector pages wholesale, so an
   annotation written there was previously destroyed on the next refresh.
3. `source_fingerprint`/`source_checked` — a digest of the material a page
   was built from. Freshness by date says a page has aged; a fingerprint
   says whether its evidence moved. Most valuable for connector-backed
   libs, where documents change with no notice.
4. `lint_report.py --quick` — a deterministic one-line session-start
   signal, wired into Rule E next to the existing `git status` check.
5. In-degree as a rank-fusion signal in `ckb-retrieve`, weighted below 1.0:
   centrality is a prior, not evidence.
6. Blast radius — a new `ckb-ingest` step walking the graph backwards from
   touched entities to find what the incoming material contradicts, before
   writing anything. Ingest was additive-first, which is how a wiki
   accumulates two pages that quietly disagree.
7. Edge vocabulary in `wiki/graph/index.md` rewritten as a question per
   verb, and completed: `part_of` was written by `ckb-code-map` but never
   declared. Added `produces`, `configures`, `validates`, `implements`.

Lint gains checks 12 (fingerprint drift), 13 (crux verbatimness) and 14
(the protected-Notes rule), verified against a synthetic fixture covering
stale digests, missing sources, fabricated quotes and paraphrased evidence.

Not adopted: the gitignored regenerable store, the MCP server and CLI
daemon, tree-sitter parsing, statusline hooks, telemetry.

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

336 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

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