Review Knowledge

SkillAI & models

Audit the knowledge/ base and the .claude/ skills, agents and workflows for staleness, self-contradiction, and drift from their own stated rules — four independent adversarial auditors (two lenses over two trees) verify every load-bearing claim against the current tree, cross-examine each other within their tree, and reach a consensus on if/what needs changing. Use periodically, after a run of deliveries that changed build config, target layout or the skills themselves, or whenever you suspect the docs have aged out of true.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Review Knowledge skill

What this skill tells your AI

The instructions your AI receives, as published by adamayoung/tmdb in .claude/skills/review-knowledge/SKILL.md and read by ahel’s review.

Audit knowledge/ and .claude/ against reality. Both are a cache of currently-true facts (knowledge/README.mdMaintenance & retention), and caches go stale silently: writes are engineered here (/capture-knowledge, /deliver's capture phase) but retirements are not, so truth decays exactly where the code moves fastest — Makefile, .github/workflows/ci.yml, Package.swift, target layout, toolchain pins.

Four independent adversarial auditors — an accuracy lens and a structure lens over each of the two trees — audit them, cross-examine each other's findings within their tree, and converge on a consensus. You adjudicate only what survives disputed.

The base's own entries are the thing under suspicion. An entry that reads confidently and cites a file is exactly the kind that goes stale unnoticed — confidence is not currency here. Verify against the tree or drop the claim.

Agent Behaviour Contract

The point of this skill: do these by default, without being reminded.

  1. Two lenses × two trees, one Workflow. Run the embedded Workflow below. It fans out four auditors in parallel — each lens against each tree — every one pinned to the opus model, then runs a cross-examination round paired within each tree, so the two lenses challenge each other on the same material. Invoking this skill is itself the opt-in to call Workflow. A tree whose pair doesn't both return is reported unreconciled, never as consensus. The cross-examination stays on fable — do not unify the two tiers. The rounds are not symmetric: a refutation is written to the permanent refutation memory (item 2) and silently suppresses that finding on every later audit, so a weak refuter costs far more than a weak auditor. An audit miss, by contrast, is re-derivable next run. See ADR-0020.
  2. Consult the refutation memory first. Before reporting, grep knowledge/skill-improvement-log.md for · refuted entries and drop any finding already settled there whose Reconsider when condition is unmet. Say in the report how many you dropped this way. Re-deriving a settled refutation costs a full audit cycle and buys nothing.
  3. Verify, never trust the prose. Every finding must be checked against the actual tree (Read/Grep/Bash) and cite file:line. A finding sourced only from reading the knowledge base itself is inadmissible — that is the failure mode being audited.
  4. Critics are read-only. They audit and report. They do not edit knowledge/, do not fix anything, and do not open PRs. Applying is the conductor's job, after the user approves.
  5. "Nothing needs changing" is a real, respectable outcome. A critic that finds a file accurate must say so and name what it checked. Do not manufacture findings to look thorough — a padded audit trains the next one to be ignored.
  6. Adjudicate only genuine deadlock. After cross-examination, findings both critics confirm are consensus and need no debate from you. Resolve only what remains disputed, with a stated rationale grounded in the tree.
  7. Report before you fix. Present the consensus, get the user's go-ahead, then apply. Never silently rewrite the knowledge base on the strength of an audit.

Scope

Two trees. First, everything under knowledge/:

FileWhat decay looks like here
gotchas.mdA trap that was fixed upstream but still reads as live; a tooling pin, path, or filter that moved; three generations of truth accreted into one entry.
tmdb-api-notes.mdA live-API behaviour that changed; a field that is no longer nullable; a claim never verified against a real response.
decisions/Numbering collisions; a status still saying "targets X" after X shipped; an ADR superseded in fact with no forward link; the index out of sync.
delivery-retros.mdOver its ~12-entry rolling window; prose whose lesson is already folded into a skill (spent — distil it).
skill-improvement-log.mdA deferred/rejected entry whose "Reconsider when" condition has already been met. This is the highest-cost staleness in the base: the recurring-pattern scan reads this file as dedup memory, so a stale entry actively misinforms it.
next-major.mdAn item that shipped, or one whose "breaking" premise no longer holds. It is a queue: anything still listed after its major version tagged is a process failure, not a backlog item.
README.mdThe stated policy no longer matching what the files actually do.

Also in scope: CLAUDE.md and everything under .claude/ — skills, agents, workflows/, the topic docs under docs/, and .github/CODE_REVIEW.md. This tree is larger and more normative than knowledge/, decays the same way, and until 2026-08-12 had no periodic audit at all — an audit that month found most of its defects here, not in knowledge/.

WhereWhat decay looks like here
A skill's proseA make target, CI job name, path, test filter or tool version that moved; a count quoted from another file.
A rule stated in two placesThe copies drift; one silently becomes wrong. Prefer one owner and a pointer.
A rule with no enforcementStated as advice where a gate, hook or tools allowlist could carry it — this repo's recurring failure (#368).
Precedence clauses"If X and this file disagree, the file wins" — check the file actually says what X assumes, or X's rule is inert.
Two rules sharing one keyTwo mandated report lines with the same name, one retro slot: the loser vanishes while the slot still looks filled.
A skill's handoffSkill A delegates to B without passing the argument B needs, so B falls back to a default A just forbade.

When the base and CLAUDE.md disagree, determine which is right from the tree — the base is often the one that already diagnosed the truth, and nobody propagated it upstream.

Run the audit (Workflow)

Four auditors run as a single Workflow so the model and effort are guaranteed per agentopus for the audit round, fable for the cross-examination (Agent Behaviour Contract item 1) — and each verdict is schema-validated rather than free-text. No args are needed — the scope is the repository itself.

export const meta = {
  name: 'review-knowledge-critics',
  description: 'Four adversarial Opus auditors (2 lenses x 2 trees), then a Fable cross-examination',
  phases: [
    { title: 'Audit', detail: 'four opus/high auditors: each lens over each tree' },
    { title: 'Cross-examine', detail: 'four fable/high refuters: lenses refute each other, paired within a tree', model: 'fable' },
  ],
  model: 'opus',
}

const SEVERITY = `Grade by CONSEQUENCE IF THE ENTRY IS TRUSTED AS WRITTEN, not by your confidence:
- critical: acting on this entry causes real harm or wasted work — it states a falsehood about the current tree, contradicts another entry, or (in skill-improvement-log.md) misinforms the dedup scan that reads it as memory.
- major: materially misleading — a stale path/pin/filter, an ADR status or number defect, a policy the base states but does not follow, a missing cross-reference that hides a correction.
- minor: hygiene — ordering, duplication, a cosmetic defect, a window one entry over.
If unsure whether a finding is real, set "confidence" to low/medium and say so in the claim — do NOT inflate or deflate severity to express doubt.`

const LENSES = [
  {
    key: 'accuracy',
    title: 'Accuracy & Staleness',
    brief: `Assume the text is lying to you. For every load-bearing factual claim, check it against the CURRENT tree and flag what no longer holds. Prioritise claims about things that move: Makefile targets and variables, .github/workflows/ci.yml (toolchain pins, job names, lint scopes, test filters), Package.swift (targets, dependencies, exclude lists), test-target layout and file locations, swiftlint/swiftformat pins, skill and tool names, and live-API behaviours. Hunt specifically for: text describing a FIXED problem as still live; passages that contradict EACH OTHER; passages that contradict CLAUDE.md; and text that has accreted several generations of truth instead of being rewritten to the present. Read git log for the PRs a passage cites — one whose fix shipped should have been retired. A cited file:line that no longer points at what it claims is itself a defect, and so is a claimed mechanism (a git hook, a make target, a tool, a skill) that does not exist — verify a mechanism by looking for it, never by trusting the sentence.`,
  },
  {
    key: 'structure',
    title: 'Structure, Policy Compliance & Gaps',
    brief: `Audit the tree against its OWN stated rules, and against what its siblings say about it. Hunt for: duplicate or out-of-order headings, numbering collisions, index drift, broken or missing cross-references, orphaned files, statuses that lag reality, content filed in the wrong place, and windows that have drifted. Then ask what is MISSING: a decision made but never recorded, a recurring trap with no entry, a deferred item with no trigger that would ever surface it, a rule stated where nothing enforces it. Check the tree applies its own hygiene rules TO ITSELF.`,
  },
]

// Both lenses run against BOTH trees — four audits. Widening the scope prose
// without widening these briefs is how this skill silently audited only half
// the repo while its own scope table claimed otherwise (#443).
const TREES = [
  {
    key: 'knowledge',
    title: 'the engineering knowledge base at knowledge/',
    scope: `Everything under knowledge/: gotchas.md, tmdb-api-notes.md, decisions/ (the ADRs, their README index, and 0000-template.md), delivery-retros.md, skill-improvement-log.md, next-major.md, README.md. Its own rules live in knowledge/README.md — rolling windows, retire-what-is-no-longer-true, ADR immutability and superseding, do-not-pre-split, dated grep-friendly headings, and CITE THE PR THAT DID THE WORK, NOT THE ISSUE (this repo's numbers interleave issues and PRs, so check each cited number with \`gh api\`) — and in knowledge/decisions/README.md: numbering, the 0000-template shape, and status upkeep, where an unreleased CHANGELOG section is NOT a release, only a tag is.`,
  },
  {
    key: 'claude',
    title: 'the operating instructions in CLAUDE.md, .claude/ and .github/CODE_REVIEW.md',
    scope: `CLAUDE.md, the topic docs under .claude/docs/, every SKILL.md and reference file under .claude/skills/, .claude/agents/*.md, .claude/workflows/*.js, .claude/settings.json, and .github/CODE_REVIEW.md. This tree is LARGER and MORE NORMATIVE than knowledge/ and decays the same way. Its decay modes, in the order they have actually bitten this repo: a rule stated in two or three places where the copies drift apart; a rule stated as advice where a gate, hook or frontmatter tools-allowlist could enforce it; a precedence clause ("if your memory and the file disagree, the file wins") pointing at a file that no longer says what the caller assumes, leaving the rule inert; two rules sharing one key, so the loser vanishes while the slot still looks filled; a skill delegating to another without passing the argument that skill needs, so it silently falls back to a default the caller just forbade; a phase mandated to write state that no phase actually writes; and a claimed mechanism that does not exist.`,
  },
]

const FINDING_SCHEMA = {
  type: 'object',
  additionalProperties: false,
  properties: {
    lens: { type: 'string' },
    verdict: { type: 'string', enum: ['healthy', 'needs-fixes', 'materially-stale'] },
    findings: {
      type: 'array',
      items: {
        type: 'object',
        additionalProperties: false,
        properties: {
          id: { type: 'string', description: 'short stable slug, e.g. "xcode-pin-stale"' },
          severity: { type: 'string', enum: ['critical', 'major', 'minor'] },
          confidence: { type: 'string', enum: ['high', 'medium', 'low'] },
          claim: { type: 'string', description: 'one-line statement of the defect' },
          location: { type: 'string', description: 'the file:line at fault (in either audited tree)' },
          evidence: { type: 'string', description: 'how you VERIFIED it against the tree — the file:line or command whose output proves the entry wrong' },
          fix: { type: 'string', description: 'the concrete change: rewrite / retire / renumber / relocate, and to what' },
        },
        required: ['id', 'severity', 'confidence', 'claim', 'location', 'evidence', 'fix'],
      },
    },
    verifiedAccurate: { type: 'string', description: 'load-bearing claims you checked that ARE still true — say what you checked and how' },
  },
  required: ['lens', 'verdict', 'findings'],
}

const REBUTTAL_SCHEMA = {
  type: 'object',
  additionalProperties: false,
  properties: {
    assessments: {
      type: 'array',
      items: {
        type: 'object',
        additionalProperties: false,
        properties: {
          id: { type: 'string', description: "the other critic's finding id" },
          position: { type: 'string', enum: ['confirm', 'refute', 'amend'] },
          reasoning: { type: 'string', description: 'evidence from the tree — not an opinion' },
          amendedSeverity: { type: 'string', enum: ['critical', 'major', 'minor'], description: 'only when position is amend' },
        },
        required: ['id', 'position', 'reasoning'],
      },
    },
    missedByBoth: { type: 'string', description: 'anything the other report made you realise BOTH of you missed' },
  },
  required: ['assessments'],
}

const READONLY = `You are READ-ONLY. Read the repo freely (Read/Grep/Bash for git log, grep, ls) but do NOT edit any file, do not fix anything, and do not run builds or tests (slow and unnecessary). `

phase('Audit')
const JOBS = TREES.flatMap((tree) => LENSES.map((lens) => ({ tree, lens })))
const audits = await parallel(JOBS.map(({ tree, lens }) => () =>
  agent(
    `You are an ADVERSARIAL auditor of ${tree.title} in this repo, working the "${lens.title}" lens.\n\n` +
    `SCOPE — audit ONLY this tree, and cite every finding inside it:\n${tree.scope}\n\n` +
    `LENS:\n${lens.brief}\n\n` +
    `${READONLY}\n\n` +
    `EVERY finding must be VERIFIED against the current tree and cite the evidence that proves it — the file:line or command output that contradicts the text. A finding derived only from reading the documentation is inadmissible; that credulity is the exact failure mode you are auditing. Equally: do NOT manufacture findings to look thorough. If a file is accurate, say so in "verifiedAccurate" and name what you checked.\n\n` +
    `SEVERITY RUBRIC:\n${SEVERITY}`,
    { label: `audit:${tree.key}:${lens.key}`, phase: 'Audit', model: 'opus', effort: 'high', schema: FINDING_SCHEMA }
  ).then((v) => v && { ...v, lens: lens.title, key: lens.key, tree: tree.key, treeTitle: tree.title })
))

const live = audits.filter(Boolean)

// Cross-examine WITHIN each tree, so the two lenses challenge each other on the
// same material. A tree with fewer than two survivors is reported UNRECONCILED
// rather than passed off as consensus — a dead auditor is not a clean bill.
//
// This round stays on `fable` while the audit round above runs on `opus`, and
// the asymmetry is deliberate: a refutation here is written to the PERMANENT
// refutation memory in skill-improvement-log.md and suppresses that finding on
// every later audit, whereas a missed finding upstream is re-derived next run.
// Do not unify the two tiers without reading ADR-0020.
const groups = TREES.map((tree) => ({ tree, members: live.filter((a) => a.tree === tree.key) }))
const unreconciled = groups.filter((g) => g.members.length < 2).map((g) => g.tree.key)
unreconciled.forEach((k) => log(`WARNING: ${k} had fewer than 2 auditors return — its findings are UNRECONCILED, not consensus`))
if (live.length === 0) return { audits: [], rebuttals: [], unreconciled, degraded: true }

phase('Cross-examine')
const rebuttals = await parallel(
  groups.filter((g) => g.members.length === 2).flatMap((g) =>
    g.members.map((mine, i) => () => {
      const theirs = g.members[1 - i]
      return agent(
        `An audit of ${g.tree.title} was run through the "${mine.lens}" lens — those findings are YOURS, reproduced below. Another independent auditor worked the "${theirs.lens}" lens over the same tree. Your job now is to CROSS-EXAMINE their findings — try to refute each one.\n\n` +
        `YOUR findings (the "${mine.lens}" lens):\n${JSON.stringify(mine.findings, null, 2)}\n\n` +
        `THEIR findings (the "${theirs.lens}" lens), which you must assess:\n${JSON.stringify(theirs.findings, null, 2)}\n\n` +
        `For each, independently verify it against the tree and take a position: "confirm" (you checked, it holds), "refute" (you checked, it is wrong or the text is actually fine — say what they misread), or "amend" (real, but the severity or the proposed fix is wrong — give the corrected one).\n\n` +
        `Default to REFUTE when the evidence is thin. A finding that cannot be independently reproduced from the tree should not survive into the consensus. Do not confirm out of collegiality.\n\n` +
        `${READONLY}\n\n` +
        `Finally, in "missedByBoth", note anything their report made you realise you BOTH missed.`,
        // `fable` is deliberate and load-bearing — refutations are permanent. See ADR-0020.
        { label: `cross:${g.tree.key}:${mine.key}`, phase: 'Cross-examine', model: 'fable', effort: 'high', schema: REBUTTAL_SCHEMA }
      ).then((r) => r && { by: mine.lens, tree: g.tree.key, ...r })
    })
  )
)

return { audits: live, rebuttals: rebuttals.filter(Boolean), unreconciled, degraded: unreconciled.length > 0 }

To iterate on the script, edit the file path the Workflow tool returns and re-invoke with { scriptPath } rather than resending it.

Reach the consensus

The critics have already done the reconciling work — read it, don't redo it:

  1. Consensus findings — raised by one critic and confirmed by the other. These are settled. Do not re-litigate them; carry them at the confirmed severity (or the amended one, if the cross-examiner amended and justified it).
  2. Refuted findings — dropped. Report them as raised and refuted with the refutation's reasoning and write them into knowledge/skill-improvement-log.md as · refuted entries (its five-field shape; the Rationale carries the tree evidence, the Reconsider when the condition that would revive the claim). A refutation recorded only in a PR body does not exist: the next audit greps knowledge/, finds nothing, and re-raises it cold — observed on 2026-08-13, when a finding refuted in #444 came back on the very next run. Like every other edit, these entries are written in Apply, after the user's go-ahead — and they are worth writing even when the user declines every fix, because the memory is what stops the next run re-deriving them.
  3. Disputed — a finding whose rebuttal is itself unconvincing (asserts rather than evidences). This is the only place you adjudicate. Verify it yourself against the tree and make the call, stating what you checked.
  4. missedByBoth — fold in anything either critic surfaced here; verify it yourself first, since by definition neither audited it properly.
  5. If a critic died (degraded: true), say so plainly. A single unreconciled audit is a weaker result, not an equivalent one — offer to re-run.

Present a consensus table: finding · severity · agreement (confirmed / adjudicated / refuted) · the fix. Then state the verdict for the base as a whole, and be willing to conclude no changes needed.

Record the run — even a clean one. Append a one-line run record to skill-improvement-log.md, in the fixed shape that file's header defines: agent count, total tokens, and consensus findings by severity. Like the · refuted entries above, it is written in Apply, after the user's go-ahead — and, unlike them, it is written even when there is nothing else to apply, so a clean run's Apply step is this line alone. If the user declines outright, record the decline in the line rather than omitting it.

A run that finds nothing otherwise leaves no trace at all, and ADR-0020's revisit trigger compares the counts of two consecutive runs — so a missing line, or one in a different shape, breaks the comparison that decision rests on.

Apply — only after the user agrees

Present the consensus and stop. On the go-ahead:

  • Group the fixes into one PR unless the user says otherwise. Branch off main first — never edit on main (CLAUDE.mdBranching).
  • Rewrite to the present tense, don't append corrections. The retention policy is describe-the-present: an entry that has accreted "…update: actually…" layers should be rewritten as one entry that states what is true now. Git history is the archive.
  • Retire, don't hedge. When a trap was fixed upstream, delete the entry. Keep only the part that is still load-bearing (typically: the invariant somebody must maintain so it doesn't come back).
  • ADR corrections are exempt from immutability: fixing a stale status, a broken link, or a numbering collision is not a change of mind. A change of decision still needs a new ADR that supersedes the old one.
  • If the audit found a class of staleness rather than instances, fix the trigger too — an audit that only patches entries guarantees the next audit finds the same class again.
  • A finding about the code is not a docs edit. When an entry is stale because the tree is wrong rather than the prose — the documented invariant is right and the code stopped honouring it — editing the entry to match the code launders a defect into a description. File it per .github/ISSUE_FILING.md and leave the entry stating the invariant, with the issue number beside it.

Close with what changed, what was deliberately left, and anything needing a human call.

Relationship to other skills

  • /capture-knowledge writes entries; this skill retires them. They are the two halves of keeping the base a cache rather than an archive.
  • /deliver runs /capture-knowledge pre-PR, and its capture phase carries the targeted staleness sweep (when a diff touches build config, re-check the entries citing it). This skill is the untargeted, periodic counterpart — it catches decay no single delivery's diff would have pointed at.
  • /review-plan is the same adversarial-critics-then-consensus shape applied to a plan instead of the knowledge base.

Signals

GitHub stars
176
Forks
47
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
review-knowledge
Source
github.com/adamayoung/tmdb