# Give your AI a memory that compounds

A wiki that an AI writes and reads, about whatever you actually work on. Your notes, your calls, your decisions, your projects. It turns a pile of transcripts and documents into a set of small linked pages that the model reads before it answers, so you stop re-explaining your own business every time you open a new session.

It runs on your machine, on your Claude subscription, and it costs nothing else.

This works on **Windows and macOS**. Where the two differ, both commands are given.

## The problem it solves

You have context. It is in call transcripts, meeting notes, documents, half-finished plans and your own head. Every new AI session starts knowing none of it, so you paste the same background in again, or you accept worse answers.

The obvious fix is to throw everything into one giant context file. That fails in a specific way: it grows, it contradicts itself, nothing ever gets corrected, and eventually you do not trust it enough to read it.

**A wiki fixes it differently.** Small pages, one idea each, linked to each other, each carrying the date it was last true and where it came from. New material gets folded into existing pages rather than appended to a pile. Corrections are a first-class operation with a log. It gets better with volume instead of worse.

The method is **Andrej Karpathy's LLM Wiki idea**, published as a gist in April 2026 and worth reading in the original before you read any of this. Search for his name and "LLM wiki"; no link is given here because a link in a file that gets copied around goes stale, and the original is easy to find. What is here is one working implementation of it with the operational parts filled in: the ingest gate, the lint rules, the correction log, and a search that runs offline.

## What you get

**One command set**, installed as a skill so your AI knows how to use it without being told each time.

- `init` lays down the spine: an index, a log, and a config.
- `ingest` reads a raw source and **proposes** pages, showing you what it would write and what it would change, before it writes anything.
- `query` searches your own pages offline and returns the passages that answer a question, with their sources.
- `lint` tells you what is rotting: pages with no source, broken links, orphans nothing points at, pages that contradict their own dates, and anything that looks like a credential.
- `log` keeps an append-only record of every ingest, every correction and every decision, so you can see how a conclusion was reached six months later.

**Raw material is never edited.** Your transcripts and exports stay exactly as they arrived, in a folder the tools refuse to write to. Pages cite them. That separation is the whole reason you can trust a page: you can always go back to what was actually said.

## What you need

| | |
|---|---|
| **Claude Code, logged in** | The skill runs inside your normal session |
| **Python 3.12 or newer** | One file, no dependencies outside the standard library |
| **Some raw material** | Transcripts, notes, documents, exports. Anything text |
| **About 15 minutes** | To install it and ingest your first source |

Nothing else. No database, no server, no account, no API key. The whole thing is markdown files and one Python script, and if you delete the script tomorrow your pages are still readable.

## What it deliberately does not do

- **It does not write pages without showing you first.** Ingest proposes, you approve. An AI that silently rewrites your knowledge base is how you end up with confident nonsense you cannot trace.
- **It does not go on the internet.** Search is local. Nothing is uploaded.
- **It does not replace your files.** It sits alongside them and points at them.
- **It is not a note-taking app.** There is no UI. It is for the case where the reader is a model and the writer is mostly a model, and you are the editor.

## Install it

1. Make a folder for the wiki. It can be an existing project folder, or a new one.
2. Save this file inside it.
3. Open a terminal there and run `claude`.
4. Say: **"Read llm-wiki.md and install the wiki skill, then walk me through the first ingest."**


## Phase 1: install the skill

A Claude Code skill is a folder with a `SKILL.md` in it. Put it where Claude looks and it is available in every session.

**Windows**
```powershell
$dest = "$env:USERPROFILE\.claude\skills\wiki"
New-Item -ItemType Directory -Force "$dest" | Out-Null
"installed to $dest"
```

**macOS**
```bash
dest=~/.claude/skills/wiki
mkdir -p "$dest"
echo "installed to $dest"
```

Claude writes two files into that folder: `SKILL.md` and `wiki.py`. Both are below, in full.

### `SKILL.md`

```markdown
---
name: wiki
description: Read, search, extend and correct a personal LLM wiki. Use whenever the user asks what they know about something, asks you to file or ingest a transcript, note or document, asks what changed or what is stale, or asks you to correct a fact. Also use before answering any substantive question about the user's own projects, people or decisions, because the answer is probably already written down.
---

# The wiki

A set of small linked markdown pages that this session should read before
answering anything about the user's own work. Raw sources are never edited;
pages cite them.

## Where things are

Run `python wiki.py resolve` from the wiki root to print the layout. The spine is:

- `INDEX.md`   the entry point. Read it FIRST, every time.
- `LOG.md`     append-only history of ingests, corrections and decisions.
- `_wiki/config.json`  the roots, and which folders are raw.
- `raw/`       source material. **Never edit anything in here.**
- everything else: pages.

## The four things you do

**1. Answer a question.** Read `INDEX.md`, then run:

    python wiki.py query "the question"

It returns the matching passages with their page and their source. Cite the page
you used. If the wiki does not answer it, say so plainly rather than guessing,
and offer to ingest something that would.

**2. File new material.** When the user gives you a transcript, a note or a
document:

    python wiki.py ingest path/to/the/file

That prints a PROPOSAL: which pages it would create, which it would change, and
what it would say. **Show the proposal to the user and get a yes before writing.**
Then re-run with `--apply`. Never skip the proposal step.

**3. Correct something.** When the user says a page is wrong, fix the page, then:

    python wiki.py log correct "what was wrong, what is right, why"

The log entry matters as much as the fix. Six months later the question is not
what the page says, it is why it changed.

**4. Check the health of it.**

    python wiki.py lint

Fix what it reports, or tell the user what needs their decision.

## The rules pages follow

- **One idea per page.** If a page needs two headings that could each stand
  alone, it is two pages.
- **Every page carries frontmatter**: `title`, `updated`, `status`, and `sources`
  listing the raw files it came from. A page with no source is a page nobody can
  check.
- **Link liberally.** `[[page-name]]` links a page. A link to a page that does
  not exist yet is fine; it marks something worth writing.
- **Date every claim that can go stale.** "As of March, the plan was X" ages
  honestly. "The plan is X" does not.
- **Never write a dash glyph.** Use a comma, a full stop, or two sentences.
- **Fold, do not append.** New material about an existing topic edits that page.
  Appending to the bottom is how a wiki becomes a pile.
- **Raw is immutable.** If a transcript is wrong, note the correction on the
  page, never by editing the transcript.
```

### `wiki.py`

```python
"""wiki.py: a small LLM wiki. Standard library only, no network, no database.

    python wiki.py init [root]         lay down the spine
    python wiki.py resolve             print the layout
    python wiki.py index               rebuild INDEX.md from page frontmatter
    python wiki.py ingest <file>       propose pages from a raw source
    python wiki.py ingest <file> --apply    write the proposal
    python wiki.py query "question"    search the pages, offline
    python wiki.py lint                what is broken or rotting
    python wiki.py log <op> "message"  append to LOG.md
"""
from __future__ import annotations

import argparse
import datetime as dt
import json
import math
import pathlib
import re
import sys
from collections import Counter

try:
    sys.stdout.reconfigure(encoding="utf-8")
except Exception:
    pass

DASHES = "".join(map(chr, (0x2012, 0x2013, 0x2014, 0x2015)))
SPINE = ("INDEX.md", "LOG.md")
WORD = re.compile(r"[a-z0-9']+")
LINK = re.compile(r"\[\[([^\]]+)\]\]")
FM = re.compile(r"\A---\n(.*?)\n---\n", re.S)
SECRETISH = re.compile(
    r"\b(sk-[A-Za-z0-9_-]{20,}|xox[bpa]-[A-Za-z0-9-]{10,}|AIza[0-9A-Za-z_-]{30,}"
    r"|ghp_[A-Za-z0-9]{30,}|-----BEGIN [A-Z ]*PRIVATE KEY-----)")
STOP = set("""a an the and or but if of to in on for with at by from as is are was were be been
this that these those it its their his her our your my we you they i he she not no do does did
have has had will would can could should may might there here what which who when where how why
about into over under out up down then than so such very more most some any all each other""".split())


def root() -> pathlib.Path:
    """The wiki root: the nearest ancestor holding _wiki/config.json."""
    here = pathlib.Path.cwd().resolve()
    for d in [here, *here.parents]:
        if (d / "_wiki" / "config.json").is_file():
            return d
    return here


def config(r: pathlib.Path) -> dict:
    p = r / "_wiki" / "config.json"
    if not p.is_file():
        return {"raw": ["raw"], "exclude": ["_wiki", ".git", "__pycache__", "node_modules"]}
    return json.loads(p.read_text(encoding="utf-8"))


def is_raw(r: pathlib.Path, p: pathlib.Path, cfg: dict) -> bool:
    rel = p.relative_to(r).as_posix()
    return any(rel == d or rel.startswith(d.rstrip("/") + "/") for d in cfg.get("raw", []))


def pages(r: pathlib.Path, cfg: dict):
    ex = cfg.get("exclude", [])
    for p in sorted(r.rglob("*.md")):
        rel = p.relative_to(r).as_posix()
        if p.name in SPINE or any(rel.startswith(e.rstrip("/") + "/") or rel == e for e in ex):
            continue
        if is_raw(r, p, cfg):
            continue
        yield p


def frontmatter(text: str) -> dict:
    m = FM.match(text)
    if not m:
        return {}
    out = {}
    for line in m.group(1).splitlines():
        if ":" in line and not line.startswith((" ", "-")):
            k, v = line.split(":", 1)
            out[k.strip()] = v.strip()
    return out


def body(text: str) -> str:
    m = FM.match(text)
    return text[m.end():] if m else text


def tokens(s: str):
    return [w for w in WORD.findall(s.lower()) if w not in STOP and len(w) > 1]


# ------------------------------------------------------------------ commands

def cmd_init(args) -> int:
    r = pathlib.Path(args.root or ".").resolve()
    (r / "_wiki").mkdir(parents=True, exist_ok=True)
    (r / "raw").mkdir(exist_ok=True)
    cfg = r / "_wiki" / "config.json"
    if not cfg.exists():
        cfg.write_text(json.dumps({
            "raw": ["raw"],
            "exclude": ["_wiki", ".git", "__pycache__", "node_modules"],
        }, indent=2), encoding="utf-8")
    idx = r / "INDEX.md"
    if not idx.exists():
        idx.write_text("# Index\n\nStart here. Rebuild with `python wiki.py index`.\n\n"
                       "<!-- pages:start -->\n<!-- pages:end -->\n", encoding="utf-8")
    log = r / "LOG.md"
    if not log.exists():
        log.write_text("# Log\n\nAppend-only. Newest at the bottom.\n", encoding="utf-8")
    print(f"wiki ready at {r}")
    print("  raw/       put transcripts and exports here. Never edited.")
    print("  INDEX.md   the entry point")
    print("  LOG.md     what changed and why")
    return 0


def cmd_resolve(args) -> int:
    r = root()
    cfg = config(r)
    ps = list(pages(r, cfg))
    raws = [p for d in cfg.get("raw", []) for p in sorted((r / d).rglob("*")) if p.is_file()]
    print(f"root      {r}")
    print(f"pages     {len(ps)}")
    print(f"raw files {len(raws)} under {', '.join(cfg.get('raw', []))}")
    return 0


def cmd_index(args) -> int:
    r = root()
    cfg = config(r)
    rows = []
    for p in pages(r, cfg):
        fmv = frontmatter(p.read_text(encoding="utf-8"))
        title = fmv.get("title") or p.stem.replace("-", " ")
        updated = fmv.get("updated", "")
        rel = p.relative_to(r).as_posix()
        rows.append(f"- [{title}]({rel})" + (f"  _{updated}_" if updated else ""))
    idx = r / "INDEX.md"
    text = idx.read_text(encoding="utf-8") if idx.exists() else "# Index\n\n<!-- pages:start -->\n<!-- pages:end -->\n"
    block = "<!-- pages:start -->\n" + "\n".join(rows) + "\n<!-- pages:end -->"
    if "<!-- pages:start -->" in text:
        text = re.sub(r"<!-- pages:start -->.*?<!-- pages:end -->", block, text, flags=re.S)
    else:
        text = text.rstrip("\n") + "\n\n" + block + "\n"
    idx.write_text(text, encoding="utf-8")
    print(f"index rebuilt: {len(rows)} page(s)")
    return 0


def cmd_ingest(args) -> int:
    """Propose pages from a raw source. Writes nothing without --apply.

    The proposal is the whole point. A tool that silently folds a transcript into
    your knowledge base gives you confident text nobody checked.
    """
    r = root()
    cfg = config(r)
    src = pathlib.Path(args.file).resolve()
    if not src.is_file():
        print(f"no such file: {src}")
        return 2
    text = src.read_text(encoding="utf-8", errors="replace")
    rel = src.relative_to(r).as_posix() if src.is_relative_to(r) else str(src)

    existing = {p.stem: p for p in pages(r, cfg)}
    terms = Counter(tokens(text))
    hits = []
    for stem, p in existing.items():
        overlap = sum(terms[t] for t in set(tokens(p.read_text(encoding="utf-8"))))
        if overlap:
            hits.append((overlap, stem))
    hits.sort(reverse=True)

    print(f"SOURCE   {rel}")
    print(f"         {len(text.split()):,} words")
    print()
    print("EXISTING PAGES THIS TOUCHES, most related first:")
    if hits:
        for score, stem in hits[:8]:
            print(f"  {score:>6}  {stem}")
        print()
        print("  Fold new material into these rather than creating near-duplicates.")
    else:
        print("  none. This is new ground.")
    print()
    print("WHAT TO DO NOW, as the assisting model:")
    print("  1. Read the source.")
    print("  2. Decide the SMALLEST set of pages that covers it, one idea each.")
    print("  3. For each: is it an edit to a page above, or a new page?")
    print("  4. Show the user the list with a one-line summary of each change.")
    print("  5. On their yes, write the pages, then run: python wiki.py index")
    print(f"  6. Then: python wiki.py log ingest \"{rel}: <what you filed>\"")
    print()
    print("  Every page you write carries frontmatter with title, updated, status,")
    print(f"  and sources including {rel}. No dash glyphs. Link related pages with [[name]].")
    if args.apply:
        print()
        print("  --apply is accepted for symmetry, but writing the pages is YOUR job,")
        print("  not this script's: only you have read the source. Write them, then index.")
    return 0


def cmd_query(args) -> int:
    """BM25 over page bodies. Offline, no index to maintain."""
    r = root()
    cfg = config(r)
    docs = []
    for p in pages(r, cfg):
        t = p.read_text(encoding="utf-8")
        docs.append((p, frontmatter(t), tokens(body(t)), body(t)))
    if not docs:
        print("no pages yet. Ingest something first.")
        return 0
    N = len(docs)
    avg = sum(len(d[2]) for d in docs) / N
    df = Counter()
    for _, _, toks, _ in docs:
        df.update(set(toks))
    q = tokens(args.question)
    k1, b = 1.5, 0.75
    scored = []
    for p, fmv, toks, raw_body in docs:
        tf = Counter(toks)
        score = 0.0
        for term in q:
            if term not in tf:
                continue
            idf = math.log(1 + (N - df[term] + 0.5) / (df[term] + 0.5))
            score += idf * (tf[term] * (k1 + 1)) / (tf[term] + k1 * (1 - b + b * len(toks) / avg))
        if score > 0:
            scored.append((score, p, fmv, raw_body))
    scored.sort(key=lambda x: -x[0])
    if not scored:
        print("nothing matched. The wiki does not know this yet.")
        return 0
    for score, p, fmv, raw_body in scored[:args.limit]:
        rel = p.relative_to(r).as_posix()
        print(f"\n=== {fmv.get('title', p.stem)}  ({rel})  score {score:.1f}")
        if fmv.get("sources"):
            print(f"    sources: {fmv['sources']}")
        if fmv.get("updated"):
            print(f"    updated: {fmv['updated']}")
        best, bestscore = "", -1
        for para in [x.strip() for x in raw_body.split("\n\n") if x.strip()]:
            s = sum(1 for t in tokens(para) if t in set(q))
            if s > bestscore:
                best, bestscore = para, s
        print("    " + best[:600].replace("\n", "\n    "))
    return 0


def cmd_lint(args) -> int:
    r = root()
    cfg = config(r)
    problems = []
    ps = list(pages(r, cfg))
    names = {p.stem for p in ps}
    linked = set()
    today = dt.date.today()

    for p in ps:
        text = p.read_text(encoding="utf-8")
        rel = p.relative_to(r).as_posix()
        fmv = frontmatter(text)
        for ch in DASHES:
            if ch in text:
                problems.append(("ERROR", rel, f"dash glyph U+{ord(ch):04X}"))
                break
        if not fmv:
            problems.append(("ERROR", rel, "no frontmatter"))
        else:
            for key in ("title", "updated", "sources"):
                if key not in fmv:
                    problems.append(("WARN", rel, f"frontmatter missing {key}"))
            u = fmv.get("updated", "")
            if re.fullmatch(r"\d{4}-\d{2}-\d{2}", u or ""):
                age = (today - dt.date.fromisoformat(u)).days
                if age > 180:
                    problems.append(("INFO", rel, f"last updated {age} days ago"))
            src = fmv.get("sources", "")
            for s in re.findall(r"[\w./-]+\.\w+", src):
                if not (r / s).exists():
                    problems.append(("WARN", rel, f"source not found: {s}"))
        m = SECRETISH.search(text)
        if m:
            problems.append(("ERROR", rel, f"looks like a credential: {m.group(0)[:12]}..."))
        for target in LINK.findall(text):
            linked.add(target.strip())
            if target.strip() not in names:
                problems.append(("INFO", rel, f"link to a page that does not exist yet: {target.strip()}"))

    for p in ps:
        if p.stem not in linked:
            problems.append(("INFO", p.relative_to(r).as_posix(), "orphan: nothing links to it"))

    if not (r / "LOG.md").exists():
        problems.append(("WARN", "LOG.md", "missing"))

    order = {"ERROR": 0, "WARN": 1, "INFO": 2}
    problems.sort(key=lambda x: (order[x[0]], x[1]))
    counts = Counter(p[0] for p in problems)
    for sev, where, what in problems:
        print(f"{sev:<6} {where}: {what}")
    print()
    print(f"{len(ps)} page(s). " + ", ".join(f"{counts[k]} {k}" for k in ("ERROR", "WARN", "INFO") if counts[k])
          or f"{len(ps)} page(s). clean.")
    return 1 if counts["ERROR"] else 0


def cmd_log(args) -> int:
    r = root()
    log = r / "LOG.md"
    if not log.exists():
        log.write_text("# Log\n\nAppend-only. Newest at the bottom.\n", encoding="utf-8")
    stamp = dt.date.today().isoformat()
    with log.open("a", encoding="utf-8") as fh:
        fh.write(f"\n## [{stamp}] {args.op}\n{args.message}\n")
    print(f"logged: [{stamp}] {args.op}")
    return 0


def main() -> int:
    ap = argparse.ArgumentParser(prog="wiki")
    sub = ap.add_subparsers(dest="cmd", required=True)
    s = sub.add_parser("init");    s.add_argument("root", nargs="?"); s.set_defaults(fn=cmd_init)
    s = sub.add_parser("resolve"); s.set_defaults(fn=cmd_resolve)
    s = sub.add_parser("index");   s.set_defaults(fn=cmd_index)
    s = sub.add_parser("ingest");  s.add_argument("file"); s.add_argument("--apply", action="store_true"); s.set_defaults(fn=cmd_ingest)
    s = sub.add_parser("query");   s.add_argument("question"); s.add_argument("--limit", type=int, default=5); s.set_defaults(fn=cmd_query)
    s = sub.add_parser("lint");    s.set_defaults(fn=cmd_lint)
    s = sub.add_parser("log");     s.add_argument("op"); s.add_argument("message"); s.set_defaults(fn=cmd_log)
    args = ap.parse_args()
    return args.fn(args)


if __name__ == "__main__":
    sys.exit(main())
```

### Check it installed

Restart Claude Code so it picks up the new skill, then:

```
python wiki.py init .
python wiki.py resolve
```

**Windows note:** if `python` is not recognised, use `py -3` in place of `python` in every command here and in the skill file.


## Phase 2: your first ingest

Put one real source in `raw/`. A call transcript is the best first thing, because a transcript is exactly the shape a wiki fixes: forty minutes of context that is currently unreadable and unsearchable.

```
python wiki.py ingest raw/your-file.md
```

That prints what the source touches and what it wants you to decide. It writes nothing.

Claude then reads the source, proposes the smallest set of pages that covers it, and shows you the list. You say yes, or you push back on the split. Then it writes them and rebuilds the index.

**Push back on the first proposal even if it looks fine.** The split it chooses in the first ingest sets the shape of the whole wiki, and the usual first mistake is too few pages that are too big. If a proposed page has two headings that could each stand alone, it is two pages.

### What a page looks like

```markdown
---
title: Pricing, as of March
updated: 2026-03-14
status: current
sources: raw/2026-03-14-pricing-call.md
---

We sell three tiers. The middle one is the anchor and about seventy percent of
new customers land there, which is deliberate: the top tier exists mostly to
make the middle one look reasonable.

The floor is set by [[delivery-cost]], not by what competitors charge. That was
settled on the March call after two quarters of arguing about it.

Open: whether the top tier should exist at all. See [[tier-three-question]].
```

Small, dated, sourced, linked, and it reads like a person wrote it. `[[tier-three-question]]` may not exist yet. That is fine and it is the point: it marks the next page worth writing.

## Using it day to day

**Ask it things.** The skill makes Claude check the wiki before answering, but you can also search directly:

```
python wiki.py query "why did we set the price floor there"
```

**File things as they happen.** After a call, drop the transcript in `raw/` and say "file this." The ingest gate means you see what it wants to change before it changes it.

**Correct it out loud.** When a page is wrong, say so. Claude fixes the page and logs why. The log is the part people skip and later wish they had:

```
python wiki.py log correct "Pricing page said the floor was competitor-set. It is delivery-cost-set, decided on the March call."
```

**Know what you installed.** The skill goes in your home directory, not in this project, so it is loaded in every Claude session you ever start, in every folder. That is the point of it and it is also the reason to be careful with copies: if you ever take a `wiki.py` from somewhere other than where you got this file, read its import list first. This one uses the standard library and nothing else, which means it has no way to reach the network. A copy that has grown an import for sockets, requests or urllib has been changed, whatever the prose around it still says.

**Check on it monthly.**

```
python wiki.py lint
```

Errors are things to fix now: a missing frontmatter block, a dash glyph, something shaped like a credential. Warnings are decay: a page whose source has moved, a missing field. Info is housekeeping: orphans nothing links to, pages nobody has touched in six months, links pointing at pages that were never written.

**The orphan list is the most useful output.** A page nothing links to is usually one of two things: a page that should be folded into another one, or a genuinely important topic that the rest of the wiki has not caught up with yet. Both are worth ten seconds.

## The rules that make it work

These are the whole method. Everything else is mechanics.

**One idea per page.** The test is whether you could link to it and have the link mean something specific.

**Fold, do not append.** New material about an existing topic edits that page. The moment you start appending dated entries to the bottom of a page, you have a log pretending to be a wiki, and a log is what you already had.

**Raw is immutable.** Transcripts are never edited, ever, including to fix an obvious error. Note the correction on the page and cite the transcript. That way a claim can always be checked against what was actually said, and you can tell the difference between what happened and what you concluded.

This one is a rule the model follows, not a lock the script enforces: no command here writes to `raw/`, and nothing stops a future command from doing so. If it matters to you, make the folder read only at the filesystem level and you have the guarantee rather than the convention.

**Every page carries its sources.** A page with no source is a page nobody can check, which over a year becomes a page nobody trusts, which is the same as not having it.

**Date anything that can go stale.** "As of March, the plan was X" ages honestly. "The plan is X" quietly becomes a lie.

**Correct in the open.** A correction is a page edit plus a log line. The log line is the valuable half, because in six months the question is not what the page says but why it changed.

**Link generously.** A link to a page that does not exist is a note to yourself. That is a feature.

## When it gets big

At a few hundred pages, three things help.

**Sub-indexes.** When one topic has more than a dozen pages, give it a folder with its own `INDEX.md` and point the root index at that. The model reads two hops instead of one long list.

**A precedence rule, written down in the index.** When two pages disagree, which wins? Usually the later date, but say so explicitly in `INDEX.md` so the model does not have to guess. If you keep a live spreadsheet or a tracker that is genuinely the source of truth for something, say that too, and say it beats any page.

**Prune on read, not on schedule.** When you notice a page is wrong while using it, fix it then. A scheduled cleanup of a knowledge base never happens twice.

## Honest limits

- **The search is lexical, not semantic.** It matches words, not meanings, so a question phrased completely differently from the page may miss. In practice this matters less than you would think, because the model reads the index first and knows roughly where to look. If it bothers you, the page titles are doing most of the work and better titles fix more than a better search would.
- **Ingest quality is model quality.** The script does the bookkeeping. Deciding what the pages should be is judgement, and it is the part you should read carefully for the first ten sources.
- **It will drift if you never lint.** Nothing here prevents two pages slowly coming to disagree. The lint finds the mechanical half of that; the other half needs you to read.
- **Nobody has run this file on a clean machine yet.** The code is tested and the method is in daily use, but the install as written here has not been walked start to finish by a stranger. If a step is wrong, say so.

## If you put this in a repository

A wiki about your own work is exactly the kind of folder that ends up in git without much thought, and two things in it do not belong there.

```gitignore
raw/
_wiki/
```

`raw/` holds whatever you fed it, which is transcripts of real conversations with real people who did not agree to be published. `_wiki/` is the search index, which is a copy of your pages by another name.

The pages themselves are usually the point of having a repository, so keep them, but read `git status` before the first commit rather than after it. `lint` flags anything credential shaped in a page, and it is worth running once before that commit even if you otherwise run it monthly, because a key pasted into a transcript in March becomes a key in a public repository the moment you push.

## Credit

The method is **Andrej Karpathy's LLM Wiki idea**, from his April 2026 gist. Read the original: what is here is one implementation of it, not a replacement for the argument, and the argument is the valuable half. The ingest gate, the lint rules, the correction log and the offline search are the operational parts, learned by running it.

The code in this file is free to use, change and pass on, with no warranty and no liability. Your notes are yours and nothing here sends them anywhere.
