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>
18 KiB
| name | description |
|---|---|
| ckb-lint | Health-check the wiki/ — conformance, freshness, confidence decay, retention sweep, supersession detection, orphan detection, graph consistency, index/log consistency, and error-book entries — auto-fixing what it safely can and reporting the rest, then reminding the user to review and sync to origin. Also runs the same checks against each connector-backed libs/<name>/'s own generated index (see ckb-index-external), plus a source.yaml validity check, without ever touching a git-copy lib or a connector's source.yaml itself. Use when the user says "Lint", "health-check the wiki", "check the wiki", or asks for a periodic/scheduled wiki health check. This is the maintenance workflow routed by CLAUDE.md/AGENTS.md, distinct from `ckb-ingest` (which adds new content) and `ckb-sync-changes` (a pure git-level operation with no wiki synthesis at all). |
Lint skill
Purpose
Health-check the local, mutable wiki/ for structural rot: missing or
malformed frontmatter, stale pages, decayed confidence, orphaned or
archivable content, broken graph edges, and index/log drift. This is
maintenance, not growth — it never adds new knowledge the way ckb-ingest
does; it only checks and repairs the shape of what's already there.
Implemented as a skill (rather than living inline in
CLAUDE.md/AGENTS.md) so the full checklist only loads into context when
actually invoked — on demand, or on a schedule if the user has set one up.
The detection half of this checklist is mechanical — a fixed set of frontmatter, date, link, graph, and index/log rules over a tree that keeps growing — so it runs as a read-only Python script rather than as per-page model reasoning (Step 0 below). What the script cannot decide is what this skill is actually for: which findings to auto-fix, which to leave for the user, whether two pages genuinely supersede one another, and whether a cluster of findings is a systemic pattern worth an error-book entry. Read the script's report, then do that work.
This skill only ever writes within wiki/ (including moves into
wiki/archived/) and, for any connector-backed libs/<name>/ (one with a
source.yaml — see CLAUDE.md/AGENTS.md directory contract) where this user has
local access: write (see ckb-index-external), within that connector's
own agent-owned generated index (index.md/entities//graph//log.md).
For a connector-backed libs/<name>/ where this user is read-only (the
default), findings against that index are reported only, never auto-fixed
— the same access gate ckb-index-external applies to rebuilding applies
here too. It never touches linked/, a git-copy libs/<name>/, or a
connector's source.yaml/source.local.yaml — those stay immutable or
user-owned — and it never touches this repo's own git remote; pushing the
resulting changes is a separate, explicit step (see the final step below).
Trigger phrases
Use this skill when the user says:
- "Lint"
- "health-check the wiki" / "check the wiki"
- "run the periodic/scheduled wiki check"
Do not use this skill for "Ingest" / "Sync the wiki" (that's
ckb-ingest — turning raw material into new wiki pages) or for "sync
changes" / "sync with git" (that's ckb-sync-changes — reconciling this
repo's own commit history with origin, no wiki content involved).
How to run this skill
Work through each check in order. A malformed page breaks every later check's ability to reason about it, so conformance comes first.
Checks 1-9 below are written against wiki/, but the same checks also run
a second time against each connector-backed libs/<name>/'s own generated
index — scoped entirely to that one connector's index.md/entities//
graph//log.md. Auto-fixes there only happen where this user has local
access: write for that source (see ckb-index-external); everywhere
read-only, findings are reported but not applied. Findings and fixes there
are logged in that connector's own log.md, never in wiki/log.md. A
git-copy libs/<name>/ (no source.yaml) and any source.yaml/
source.local.yaml are never touched by any of these checks.
0 — Run the checker script
From the repository root:
python3 "<skill-dir>/scripts/lint_report.py"
Resolve <skill-dir> to this skill's own directory. Flags: --scope wiki|libs|all (default all) to limit which trees are checked; --today YYYY-MM-DD to pin the date for reproducible runs; --json for a
machine-readable report; --quick for the one-line session-start summary
Rule E calls (conformance, freshness, and source drift only — it prints a
single line and does none of the work below). Exit code is 0 when nothing was found and 1
when there are findings — read the report either way.
The script is strictly read-only: it writes nothing, moves nothing, and
runs no git commands. It covers the mechanical detection in checks 1, 2,
3, 4, 6, 7, 8, 10, 12, and 13 below — plus the structural half of check 5
(decision records) — across wiki/ and every connector-backed
libs/<name>/ (reporting each one's access: level so you know where a
fix is even permitted). Check 9, the semantic half of check 5, and every
auto-fix-vs-report decision, are yours.
The numbered checks below stay as the reference for what each finding means and what to do about it — you don't need to re-derive the detection by hand. Do read the pages the script flags: a finding is a pointer to a page that needs a decision, not the decision itself.
1 — Conformance check
The script flags every non-reserved .md file under wiki/ (i.e.
excluding index.md and log.md) whose frontmatter is unparseable,
absent, or missing a non-empty type. Treat flagged pages as unreliable
input for the checks below rather than guessing at their intended
type/content, and fix the frontmatter before acting on any later finding
about the same page.
2 — Freshness check
The script reports every page whose last_updated exceeds its
freshness_window_days, and by how much. Suggest the user confirm or
update the content — don't silently rewrite stale content yourself.
3 — Confidence decay
The script reports pages already below 0.3 confidence (and any
unparseable value). Decaying confidence on pages not reinforced by a new
source since the last check is a write and a judgment call, so it stays
yours — the script only surfaces where the floor has been crossed.
4 — Retention sweep
The script reports retention: low pages older than 2x their
freshness_window_days as archive candidates. Move them into
wiki/archived/ — never delete, always move — and log each move (see
Rule B in CLAUDE.md/AGENTS.md) with a note explaining why. Check each
candidate against recent use before moving it (see Edge cases).
5 — Supersession and decision records
The script checks the structure of every type: decision page: a status
from the vocabulary, a decided_on date where the status implies one, a
decided_by (unknown counts — an omitted field doesn't), affects targets
and supersession links that resolve, supersession set on both sides,
status: superseded/reversed matched by a superseded_by, unique
four-digit decision numbers, and any review_on date that has passed. Fix
those directly — they're mechanical. A review_on that has come due is not a
defect, though: report it so the user can decide whether to revisit, and never
change a decision's substance yourself (ckb-decide → decisions are
append-only).
The semantic half stays yours: when two pages appear to cover the same
entity or the same choice, check for contradictions. If one is clearly
newer/better corroborated, add supersedes / superseded_by links between
them. Preserve the older page rather than deleting it, but mark it stale. For
two decisions, that judgment is exactly what the script can't make — it can
tell you a link is one-sided, not that a link should exist.
6 — Orphan detection
The script reports pages with no inbound links ([[wikilinks]] or
[markdown](path.md) references from anywhere else in the tree). For each
one, either add backlinks from relevant pages where an obvious connection
exists, or move the orphan to wiki/archived/ with a log note if no
natural backlink does — that choice is the judgment the script leaves you.
7 — Graph consistency
The script reports every edge in wiki/graph/edges.json whose from/to
does not resolve to an existing page (plus malformed edges and invalid
JSON). Remove or fix them; note what was removed rather than silently
dropping entries.
8 — Index/log consistency
The script reports subdirectories holding pages but no index.md, index
files that don't list a page sitting next to them, and any change recorded
in both a subdirectory log.md and the root wiki/log.md (per the
Recursive Index & Log Convention). Fix missing index entries and duplicate
log entries directly — these are the safest auto-fixes on the list.
9 — Error Book entry
Record any systemic issue found above (a repeated broken pattern, a
recurring format mismatch, the same kind of orphan appearing again) in
wiki/error-book.md with its root cause, the fix applied, and the derived
constraint that should prevent recurrence. A one-off, isolated issue
doesn't need an Error Book entry — this is for patterns, not incidents.
10 — External source config check
The script validates each libs/<name>/source.yaml (non-empty connector
and location; non-empty store and location inside any index: block;
a positive integer refresh_interval_days) and flags a libs/<name>/ that
ambiguously holds both real content files and a source.yaml. All of this
is report only — source.yaml is never edited by any skill, and the
ambiguous-content case is a configuration conflict for the user to resolve,
not something to guess at.
It also reports any connector-backed source whose generated index is
overdue — newest last_updated in libs/<name>/ older than its
refresh_interval_days (default 30) — with how overdue it is, since a
source two days past a 7-day interval is a different situation from one six
months past a 30-day one. Relay that the same way whether or not this user
has write access: a read-only user can't fix it, but knowing which source to
chase the admin about is the actionable part. Never re-index here; that's
ckb-index-external's job, and suggesting it is as far as this check goes.
Don't flag a missing libs/<name>/source.local.yaml as an issue — its
absence is the correct, read-only default (see ckb-index-external), not
a gap to report or fix. The script follows the same rule: it reads that
file only to label each connector access: read-only / access: write in
its report.
11 — Installed module checks
If ckb.yaml lists installed modules, run whatever extra checks those
modules define, and skip this step entirely when the file is absent or
empty. Modules are optional by design: their absence is never a finding.
For the software module specifically:
- Stale code maps — a
type: repositorypage whosecommitis behind the currentHEADof itssrc/clone. Report how far behind; suggestckb-code-map. A missing clone is not a finding —src/is gitignored and expected to be empty on a fresh checkout. - Spec drift — the four checks
ckb-specdefines: root specs with emptyimplemented_by, repo-level specs implementing a missing or archived parent, one-sidedimplements/implemented_bylinks, and root specs older than the mapped commit of every repo implementing them. - Remote-less repos — a mapped repository page with no
resource:, meaning a gitignored clone that exists nowhere else.
All report-only. Never auto-fix any of these: each is a statement about intent, and only the user knows which side is correct.
12 — Source fingerprint drift
The script recomputes the sha256 of every local file cited in a page's
## Sources and compares it against the digest recorded there. Three
findings come out of it, and they are not the same problem:
CHANGED since this page was built— the file the page was synthesized from has been edited. This outranks every freshness finding on the list: check 2 says a page has aged, this says its evidence has moved. Read both, and either re-verify the page's claims against the new content and re-stamp the digest, or flag it forckb-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## Sourcesbullet 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'ssource_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.
## Cruxhas 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. ## Cruxwith 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, 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.
Final step — Remind to review and sync
This is always the last step, every time this skill makes any change at
all to wiki/. Close with a short reminder — do not sync or push
anything yourself here:
"Lint complete — see
wiki/log.mdfor what changed (auto-fixed: ...; flagged for your review: ...). Please review the changes, and once you're happy with them, say 'sync changes' to commit and push them toorigin."
If any connector-backed libs/<name>/ was also touched, mention it
separately (its own log.md, not wiki/log.md) rather than folding it
into the same sentence.
If the lint pass made no changes at all (clean bill of health, nothing to fix or flag), skip this reminder — there's nothing to review or sync.
Edge cases
- Wiki is empty or near-empty — report that there's little to lint
yet; don't fabricate findings to look thorough. A
total findings: 0report is a valid outcome; say so plainly rather than hunting for something to say. - A page's frontmatter is unparseable (not just missing
type, but invalid YAML) — the script reports it under conformance and skips it in the later checks rather than reasoning from a half-parsed page. Surface it prominently and fix the frontmatter first; a page can't be judged stale, orphaned, or archivable until it parses. - Retention sweep would archive a page that's clearly still in active
use (e.g. linked from a very recent
workload/entry) — flag it for the user to confirm rather than auto-archiving; recency of use can override a stalelast_updated/retentioncombination. - Supersession is ambiguous (two pages disagree and neither is clearly newer/better corroborated) — report the conflict rather than guessing which one wins.
- Repeated run with nothing changed since the last lint — produces the
same report each time (pass
--todayto 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.
Licensed under the Apache License, Version 2.0 — see LICENSE at the repository root, or http://www.apache.org/licenses/LICENSE-2.0.