wiki-lint

Use when auditing a wiki for health issues — contradictions between pages, orphan pages, broken cross-references, stale claims, missing pages, or coverage gaps. Run after every 5-10 ingests.

kfchou/wiki-skills70 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: wiki-lint
description: Use when auditing a wiki for health issues — contradictions between pages, orphan pages, broken cross-references, stale claims, missing pages, or coverage gaps. Run after every 5-10 ingests.
license: MIT
---

# Wiki Lint

Audit the wiki as a four-phase pipeline that keeps page bodies out of the main context: a
deterministic script (Phase 1) and per-cluster subagents (Phase 2) do the heavy reading; the
main thread only assembles findings. Produce a categorized report, offer concrete fixes, log
the operation.

## Pre-condition

Find `SCHEMA.md` (search from cwd upward, or `~/wikis/`). If not found, tell the user to run `wiki-init` first. Read it to get wiki root path and conventions.

**Link style.** Read `link_style:` from `SCHEMA.md`. If the field is missing or `<wiki-root>/config/link-style.md` does not exist, default to `obsidian` (`[[slug]]`). Otherwise, read `<wiki-root>/config/link-style.md`. All link scanning below uses the `## Parse` rule from that file (which may match more than one form). The lint report you write is itself a wiki page, so its cross-references use the `## Emit` rule.

## Why the pipeline

The old whole-wiki pass loaded every page body into one context — it nuked the context window
as the wiki grew. Instead:

- **Phase 1** runs `bin/lint-mechanical.py`, which computes the deterministic checks (graph
  and string properties) with **no LLM and no bodies in your context**, and emits the
  tag-cluster list for Phase 2.
- **Phase 2** dispatches one subagent per tag cluster. Each reads only its cluster's pages
  (bodies live in the *subagent's* context, discarded on return) and reports the cross-page
  and body-level findings. You aggregate compact findings, never bodies.

Phase 1 checks are whole-wiki exhaustive. Phase 2 checks are **cluster-scoped**: a page that
shares no tag with any other page is not body-checked (approach A's accepted recall limit —
see *Phase 2*). Run lint periodically (every 5–10 ingests) so the scoped passes accumulate
coverage.

## Process

### Phase 0 — regenerate the index

`python bin/generate-index.py` so the index reflects current page frontmatter.

### Phase 1 — deterministic checks (script, no LLM)

Run `python bin/lint-mechanical.py` and parse its JSON. It returns:

```json
{ "findings": { "broken_links": [...], "missing_frontmatter": [...], "orphans": [...],
                "slug_collisions": [...], "stale_date": [...], "missing_concept": [...] },
  "clusters": [ {"pages": ["slug-a", "slug-b"], "split": false}, ... ] }
```

These map to report sections directly:

- **🔴 Broken links** — `broken_links` (`{page, link}`).
- **🔴 Missing frontmatter** — `missing_frontmatter` (`{page, missing:[...]}`). `overview.md`
  lives in `wiki/`, not `wiki/pages/`, and is exempt.
- **🟡 Orphan pages** — `orphans` (`{page}`): zero inbound `[[slug]]` links.
- **🟡 Colliding slugs** — `slug_collisions` (`{token, pages:[...]}`): a bare slug clashing
  with qualified ones (`mercury` vs `mercury-element`). Flag the pair; never auto-rename —
  renaming is a `wiki-merge` split.
- **🟡 Stale claims** — `stale_date` (`{page}`): not updated in 90 days and the body still
  says `current`/`latest`/`recent`/`state-of-the-art` or names a year ≥2 years old.
- **🔵 Missing concept pages** — `missing_concept` (`{slug, count}`): a `[[slug]]` referenced
  3+ times with no page.

Do **not** re-derive these by reading pages yourself — the script is the source of truth.

### Phase 2 — cluster subagents (contradictions + body-level checks)

For each cluster in the Phase 1 `clusters` list, dispatch a subagent (parallel fan-out,
batched to bound concurrency). Give it the cluster's page paths and this instruction:

> Read these pages. Report, **within these pages only**, as a compact list — each finding as
> `{type, pages:[slug,...], detail}`, or `clean` if none:
> - `contradiction` — two pages describe the same entity with conflicting dates, counts,
>   names, or relationships.
> - `missing-xref` — two pages clearly discuss the same entity but don't `[[link]]` each
>   other.
> - `merge-candidate` — two pages describe the *same concept under different slugs*
>   (near-duplicate titles / near-identical Description sections).
> - `chronological-section` — a page body carries date-stamped journal headers (`## [Month]`,
>   `**[Month] N update`) that should be integrated in-place.
> - `addable-line-range` — a footnote citing a *text-addressable* raw file (markdown /
>   plaintext / code / cached HTML) with no `L<start>-<end>` token. Exempt: PDFs, transcripts,
>   live URLs — never flag those.
> Return findings only — no page bodies.

**Aggregate** all subagent findings in the main thread and **dedup** on
`(type, sorted(pages), detail-entity)` — a page in K tags sits in K clusters, so the same
finding can surface more than once.

**Coverage note:** because subagents only see their cluster, body-level findings are
cluster-scoped. Pages sharing no tag with any other page are not body-checked. This is the
accepted limit of tag-cluster scoping; Phase 1 (graph/string checks) still covers every page.

### Phase 3 — coverage gaps, then assemble the report

First, the one body-level check that needs no page bodies: read `wiki/overview.md` and flag
**🔵 Coverage gaps** — open questions that a web search or a new ingest could answer.

Then write `wiki/pages/lint-<today>.md` (do not ask permission — always write this), merging
Phase 1 findings, the deduped Phase 2 findings, and the coverage gaps. The template below
shows slug references in obsidian form for readability; in the actual report you write, every
slug reference must follow the `## Emit` rule from `config/link-style.md`:

```markdown
---
title: Lint Report <today>
category: Maintenance
summary: <N> errors, <N> warnings, <N> info
tags: [lint, maintenance]
sources: []
created: <today>
updated: <today>
---

# Lint Report — <today>

## Summary
- 🔴 Errors: N
- 🟡 Warnings: N
- 🔵 Info: N
<!-- note here if any cluster was split (Phase 1 `split: true`): contradiction recall is
     reduced for those pages — they were too large a tag group to check together -->

## 🔴 Broken Links
- [[source-page]] references [[missing-slug]] — does not exist
  Fix: create the page or remove the reference

## 🔴 Missing Frontmatter
- [[page]] is missing: title, updated

## 🟡 Orphan Pages
- [[slug]] — no inbound links
  Fix: add link from [[related-page]], or delete if no longer relevant

## 🟡 Contradictions
- [[page-a]] says: "<claim>"
- [[page-b]] says: "<conflicting claim>"
  Recommendation: <which to trust, or "investigate further">

## 🟡 Stale Claims
- [[page]] last updated <date>, contains "latest" — may be outdated
  Fix: re-verify claims or add a "as of <date>" qualifier

## 🟡 Colliding Slugs
- [[mercury]] and [[mercury-element]] share base token "mercury" — possible undisambiguated homonym
  Fix: qualify the bare slug via wiki-merge split (e.g. mercury → mercury-planet)

## 🟡 Chronological Update Sections
- [[page]] has date-stamped journal headers — integrate the updates in-place

## 🔵 Missing Concept Pages
- [[slug]] referenced N times but no page exists
  Fix: run wiki-ingest or create a stub

## 🔵 Coverage Gaps
- Open question from overview.md: "<question>"
  Suggestion: search for <X> or ingest <source type>

## 🔵 Missing Cross-References
- [[page-a]] and [[page-b]] both discuss <entity> but don't link to each other

## 🔵 Merge Candidates
- [[page-a]] and [[page-b]] appear to describe the same concept under two slugs
  Fix: run wiki-merge to consolidate into one page (rewrites inbound links)

## 🔵 Addable Line-Ranges
- [[page]] [^3] cites text source [[markdown-source]] with no line-range
  Fix: add an `L<start>-<end>` token so wiki-audit can verify it deterministically
```

The lint report carries `category: Maintenance` in its frontmatter, so it lands in the index
automatically — do not hand-edit `index.md`. Run `python bin/generate-index.py`.

### Offer concrete fixes

For each fixable category, offer:
- **Broken links:** "Remove the broken slug references? (I'll show each change before writing)"
- **Missing cross-references:** "Add the missing links between these page pairs?" (emit in the wiki's link_style)
- **Colliding slugs / merge candidates:** "These look like the same concept (or a homonym needing disambiguation) — want to run `wiki-merge` to consolidate or split them?" Do not fix these in `wiki-lint`; hand off to `wiki-merge`.
- **Orphan page tags:** "Add `status: orphan` to frontmatter of orphan pages?"
- **Missing frontmatter:** "Add missing frontmatter fields with placeholder values?"

Show the exact diff for each change before writing. Apply only after confirmation.

### Record the operation

Per SCHEMA's **Operation Log & Commit Convention**:
- **Git wiki:** suggest a commit (default Conventional Commits `fix:` when fixes were
  applied, `chore:` for a report-only run, plus the trailer); commit on confirmation.
  ```
  fix: resolve <what lint fixed>

  Wiki-Op: lint
  ```
- **Non-git wiki:** append to `wiki/log.md`:
  ```
  ## [<today>] lint | <N errors> errors, <N warnings> warnings, <N info> info
  Report: <slug reference to lint-<today>, in the wiki's link_style>
  Fixed: <list what was auto-fixed, or "none">
  ```

More AI & ML skills

← All AI & ML skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY