LLM Wiki Manager
LLM wiki kit
Created by sametbrr
Published in sametbrr/llm-wiki-manager. Publisher page
73 GitHub stars
What it is
Agent Skills plugin for a persistent LLM-managed markdown wiki — ingest, cross-reference, cited queries and lint with 5 stdlib scripts
AutoGineer overview, from the brain’s own files
LLM Wiki Manager is an agent skill and plugin designed to implement the Karpathy LLM Wiki pattern. It shifts the paradigm from traditional RAG—where an LLM re-reads raw documents on every query—to a persistent, compounding knowledge base. The system maintains a clear division of labor: the user curates raw source files in a dedicated folder, while the agent manages the wiki layer, handling synthesis, cross-referencing, indexing, and bookkeeping. It includes idempotent Python scripts for maintenance tasks like linting, logging, and schema evolution, ensuring that knowledge remains structured and traceable over time.
How it works
Memory model: LLM wiki
AutoGineer overview, from the brain’s own files
The system organizes data into a three-layer model: a schema file for conventions, a raw folder for immutable source documents, and a wiki folder for synthesized content. The agent uses a set of Python scripts to manage this structure. When a user provides a source, the agent ingests it, summarizes the content, updates entity and concept pages, and logs the action. It supports advanced operations like semantic updates to propagate changes across multiple pages, automated health checks that generate dated reports, and multi-wiki routing to bridge project-specific and global knowledge bases.
| Folder | Role | Files |
|---|---|---|
| .github | agent-config | 2 |
| skills | agent-instructions | 27 |
| tests | tests | 1 |
| .claude-plugin | folder | 1 |
| .codex-plugin | folder | 1 |
The agent reads
- CLAUDE.md evidence: README.md
The agent writes
- CHANGELOG.md evidence: AGENTS.md
- AGENTS.md evidence: README.md
- CLAUDE.md evidence: README.md
Requirements
Works with: Claude Code, OpenAI Codex, GitHub Copilot, Cursor, Kiro, OpenCode, Amp, Cline, JetBrains Junie, Devin (ex-Windsurf)
Set it up
- Download the brain as a ZIP and unzip it where you keep your notes or project: https://mp-staging.autogineer.com/agent-brains/sametbrr/llm-wiki-manager/download
Claude Code reads CLAUDE.md from this folder automatically.
cd llm-wiki-manager && claude
AutoGineer scanned these files at commit 28ad285.
Created by sametbrr. Published in sametbrr/llm-wiki-manager. Licence: MIT License.
Usage
AutoGineer overview, from the brain’s own files
- Bootstrap a new wiki environment in a research folder.
- Ingest PDF or text documents into the wiki.
- Query the wiki for synthesized answers with citations.
- Perform semantic updates to propagate information changes across the wiki.
- Run automated health checks to identify broken links or orphan pages.
- Route knowledge between project-specific and global wikis.
- “Set up an LLM wiki here. Topic: history of nutrition science.”
- The agent scaffolds the raw/ and wiki/ folders and initializes the schema file.
- “Add this PDF to the wiki.”
- The agent reads the source, writes a summary, updates relevant entity pages, and logs the ingestion.
- “Smith 2024 supersedes Keys 1980, update the wiki.”
- The agent performs a semantic sweep across all pages to update the superseded claim and logs the change.
- “Lint the wiki.”
- The agent runs a health check, generates a dated report in the reports folder, and updates the index.
Documentation by sametbrr
Image: Release Image: License: MIT Image: Agent Skills
Quick Start • Installation • Usage • Update mode • Multi-wiki • Compatibility
LLM Wiki Manager
An Agent Skill and plugin for Claude Code and Codex for building and maintaining a personal LLM-managed wiki — a persistent, compounding knowledge base where the LLM does all the writing, cross-referencing, and bookkeeping while you curate sources and ask questions.
🇹🇷 Türkçe için README.tr.md
Quick Start
claude plugin marketplace add sametbrr/skill-hub && claude plugin install llm-wiki-manager@skill-hub # Claude Code
codex plugin marketplace add sametbrr/skill-hub && codex plugin add llm-wiki-manager@skill-hub # CodexStart a new session in your research folder:
mkdir ~/research/my-topic && cd ~/research/my-topic && claude # or: codex
> "Set up an LLM wiki here. Topic: history of nutrition science."Features
Instead of RAG — where the LLM rediscovers answers from raw documents on every query — this pattern has the LLM compile raw sources into a persistent, interlinked markdown wiki. Each new source enriches existing pages. Cross-references are built eagerly. Contradictions are flagged. Knowledge compounds over time.
Implements Karpathy's LLM Wiki pattern as a full agent skill with 9 operating modes (including multi-wiki routing and migrate), 5 idempotent Python scripts, 8 page templates, and 11 reference documents.
Without this pattern With this pattern
──────────────────── ──────────────────
Query 1 → re-read 50 docs Query 1 → read compiled wiki (already synthesized)
Query 2 → re-read 50 docs Query 2 → read updated wiki (cross-refs already there)
Query 3 → re-read 50 docs Query 3 → read updated wiki (contradictions already flagged)Requirements
Claude Code, OpenAI Codex, or any agentskills.io-compatible agent
Python 3.9+ (stdlib only, for the 5 included scripts — no pip install needed)
Installation
Option 1 — Claude Code plugin (recommended)
claude plugin marketplace add sametbrr/skill-hub
claude plugin install llm-wiki-manager@skill-hubOption 2 — Codex plugin
codex plugin marketplace add sametbrr/skill-hub
codex plugin add llm-wiki-manager@skill-hubOption 3 — GitHub CLI, any agent (gh CLI with gh skill)
gh skill install sametbrr/llm-wiki-manager llm-wiki-manager --agent claude-code --scope user # or --agent codexOption 4 — manual copy: copy skills/llm-wiki-manager/ into ~/.claude/skills/ or ~/.agents/skills/.
After installing, start a new session. The skill loads automatically when relevant.
Usage
The skill auto-detects which mode applies from natural language. No slash commands needed.
> "Set up an LLM wiki here" # Bootstrap: scaffold raw/, wiki/ and the schema file
> "Add this PDF to the wiki" # Ingest: summarize, cross-reference, index, log
> "What does the wiki say about X?" # Query: answer with citations, offer to file back
> "Smith 2024 supersedes Keys 1980, update the wiki" # Update: sweep every page that paraphrases the claim
> "Lint the wiki" # Lint: dated health report in wiki/reports/
> "We should always do X going forward" # Schema-evolve: record a convention for future sessions
> "Add this to my global wiki" # Multi-wiki: route between project and global wiki
> "How does this pattern work?" # Teach: explain the pattern and compare it with RAGModes
| Mode | Trigger examples | What happens |
|---|---|---|
| Bootstrap | "Set up a wiki", "start a knowledge base here" | Scaffolds raw/, wiki/ and the schema file (AGENTS.md + a CLAUDE.md pointer) from templates |
| Ingest | "Add this PDF to the wiki", "I just read X, file it" | Reads source → writes summary → updates entity/concept pages → indexes → logs |
| Query | "What does the wiki say about X?", "Compare X and Y" | Reads index → candidate pages → synthesizes answer with citations → offers to file back |
| Update | "Smith 2024 supersedes Keys 1980, update the wiki" | Semantic sweep across all pages → diff-before-write per page → single log entry |
| Lint | "Health check the wiki", "anything broken?" | Runs lint_wiki.py → auto-saves wiki/reports/lint-YYYY-MM-DD.md → auto-tracks in index and log |
| Schema-evolve | "We should always do X going forward" | Updates the schema file (AGENTS.md / CLAUDE.md) so future sessions inherit the convention |
| Multi-wiki | "Add this to my global wiki", "promote this page to global" | Routes between project wiki and global wiki using the External Wiki: declaration in project CLAUDE.md |
| Teach | "How does this pattern work?", "explain the LLM wiki idea" | Explains the pattern, compares with RAG, walks through a concrete example |
Full walkthrough
# 1. Go to your research folder
mkdir ~/research/my-topic && cd ~/research/my-topic && claude
# 2. Bootstrap the wiki
> "Set up an LLM wiki here. Topic: history of nutrition science."
# 3. Drop a source
cp ~/Downloads/pollan-2008.pdf raw/
# 4. Ingest it
> "Ingest Pollan's In Defense of Food"
# 5. Ask questions
> "What does the wiki say about nutritionism?"
# 6. Health check (auto-saves dated report to wiki/reports/)
> "Lint the wiki"The three-layer model
your-wiki/
├── AGENTS.md # Schema — conventions for this wiki (co-evolved over time)
├── CLAUDE.md # One line: @AGENTS.md (Claude Code reads CLAUDE.md, Codex reads AGENTS.md)
├── raw/ # YOUR layer — immutable sources you curate. LLM reads, never writes.
└── wiki/ # LLM layer — all pages written and maintained by the LLM
├── index.md # Content catalog (updated on every ingest)
├── log.md # Append-only operation log (greppable)
├── hot.md # Hot cache — most recently ingested sources and active references
├── sources/ # One summary page per ingested source
├── entities/ # People, organizations, places, products
├── concepts/ # Ideas, theories, frameworks, terms
├── notes/ # Filed-back query answers and loose pages
└── reports/ # Auto-generated dated lint reports (wiki/reports/lint-YYYY-MM-DD.md)Division of labor:
| You do | The LLM does |
|---|---|
| Curate sources (decide what to read) | Read sources end-to-end |
| Ask questions, steer direction | Write summaries, entity and concept pages |
| Review the wiki, follow links | Update cross-references in-place |
| Decide what matters | Maintain index.md and log.md |
Own raw/ | Flag contradictions, surface gaps |
You almost never write wiki pages by hand. The LLM does the bookkeeping — that's what makes the wiki compound instead of collapse.
Core disciplines
LLM owns
wiki/. You ownraw/. No exceptions.Every operation logs to
log.mdviaappend_log.py. Greppable:grep "^## \[" log.md | tail -20Every new or updated page touches
index.mdviaupdate_index.py. Stale index = wiki that feels lost.Cross-reference aggressively. When a source mentions an entity that already has a page, update that page. Don't leave connections implicit.
Cite back to
raw/. Every claim is traceable to a specific source file.Flag contradictions, don't overwrite. New source disagrees with old claim? Both stay, marked with their source, with a
> [!warning] Sources disagreecallout.Schema lives in
CLAUDE.md. When a convention works, write it down. The next session starts informed.
What's inside
Scripts (Python stdlib, no dependencies, all idempotent)
| Script | Purpose |
|---|---|
scripts/init_wiki.py | Scaffold a new wiki — creates raw/, wiki/, the schema file (AGENTS.md + CLAUDE.md pointer, or keeps an existing one), index.md, log.md, and hot.md. Idempotent. |
scripts/append_log.py | Append a ## [YYYY-MM-DD] action | title entry to log.md. Supports flexible log path detection. |
scripts/update_index.py | Add or update an entry under a category in index.md. Case-insensitive categories, keeps section order and content, follows the index link style (markdown or [[wikilinks]]), new categories go before ## Meta. |
scripts/lint_wiki.py | Health check. Detects orphan pages, broken links and index drift in both standard markdown and Obsidian wiki-link format ([[page#heading]], ![[embed.png]] included). Default: writes wiki/reports/lint-<today>.md and auto-tracks. Run --stdout for terminal output. |
scripts/migrate_wiki.py | Schema upgrade (v1 → v2). Deduplicates index.md, moves dated changelog blocks from hot.md into log.md, stamps the schema version. Idempotent. |
Templates
| Template | Used for |
|---|---|
wiki-CLAUDE.md.tmpl | The schema file dropped into a fresh wiki |
source-summary.md.tmpl | One ingested source — claims, methodology, cross-links, open questions |
entity-page.md.tmpl | People, organizations, places, products |
concept-page.md.tmpl | Ideas, frameworks, theories, terms |
comparison-page.md.tmpl | "X vs Y" pages, often filed-back query answers |
index.md.tmpl | Initial content catalog |
log.md.tmpl | Initial log with bootstrap entry |
hot.md.tmpl | Initial hot cache — rewritten after each ingest with latest sources and active references |
Reference docs
Eleven detailed workflow documents in references/:
philosophy.md · architecture.md · bootstrap-workflow.md · ingest-workflow.md · query-workflow.md · update-workflow.md · lint-workflow.md · migrate-workflow.md · schema-design-guide.md · multi-wiki-routing.md · teaching-mode.md
The skill reads these selectively — you don't need to. They're there to give the LLM depth on each mode.
Update mode
Standard ingest already handles contradictions on a single page (Disputes section). Update mode is for when a new source supersedes a claim that's paraphrased across multiple pages — the same idea written four different ways in four different files.
Scenario: Smith 2024 reanalysis shows Keys 1980's seven-countries study cherry-picked data.
The Keys r=0.87 claim appears as:
concepts/saturated-fat.md → "Keys found r=0.87 across seven countries"
entities/ancel-keys.md → "famous for showing strong correlation"
concepts/heart-disease.md → "saturated fat is a primary driver per Keys"
concepts/dietary-policy.md → "the saturated fat hypothesis drove decades of policy"
Update mode:
1. Semantic sweep — finds all four (grep won't, LLM will)
2. Shows scope: "4 pages affected. Proceed?"
3. Diff-before-write per page — y/n/skip/edit per change
4. Per-page strategy: revise / disputes / annotate (not one-size-fits-all)
5. One log entry tying all four edits to Smith 2024This is change propagation, not change tracking. The log and frontmatter track what was edited. Update mode ensures the edit reaches everywhere it should.
Auto-dated lint reports
lint_wiki.py with no flags:
Writes
wiki/reports/lint-YYYY-MM-DD.md(overwrites same-day run — idempotent daily)Adds a
Reportsindex entry automaticallyAppends a
lint | Health checklog entry automaticallyExits with code 1 if any block-severity issue found (useful for CI)
Reports accumulate as a longitudinal health record. git log wiki/reports/ shows how wiki quality evolved over time.
Override flags: --stdout (terminal, no tracking), --no-track (write file, skip index/log), --report PATH (custom path).
Multi-wiki
Most users start with one wiki. Once you have two — say, a per-project wiki at the working directory plus a long-lived global "second brain" (often an existing Obsidian vault) — the skill routes writes between them based on a single declaration in the project's CLAUDE.md.
~/projects/x-project/ ← active project (current working directory)
├── CLAUDE.md ← project schema — declares the global wiki path
├── raw/ ← project sources
└── wiki/ ← project wiki
~/Documents/obsidian/ ← global wiki (long-lived, exists across projects)
├── CLAUDE.md ← global schema
├── raw/
└── wiki/One-time setup
Add this to the project's CLAUDE.md (or ask the agent to do it):
## External Wiki
Global knowledge base: ~/Documents/obsidian/
### Routing rules
- Project-specific code decisions, architecture, bugs, configuration → this project's `wiki/`
- Concepts, frameworks, patterns, ideas applicable beyond this project → global wiki
- When in doubt, ask before writing
- Scripts always need `--path` flag pointing to the right wiki root
### Cross-wiki links
- Use absolute paths (`~/...`) when linking from one wiki to the other.
- Never use relative paths that cross wiki boundaries.Four canonical scenarios
| # | Scenario | Trigger | What the agent does |
|---|---|---|---|
| A | Write to global while in a project | "Add JWT refresh rotation to my global wiki" | Reads project CLAUDE.md → resolves global path → writes to global, project wiki untouched |
| B | Pull from global into a project | "What does the global wiki say about rate limiting? Apply it to /api/search" | Reads global pages → synthesizes recommendation → writes a project-specific page that links to the global one (never copies) |
| C | Promote a page from project to global | "concepts/event-sourcing.md has matured, promote it" | Moves content to global → leaves a one-line redirect stub at the project path → updates both indexes and logs |
| D | Lint both wikis at once | "Lint both wikis" | Runs lint_wiki.py --path against each → reads both reports → returns one summary |
Full walkthroughs of all four scenarios in references/multi-wiki-routing.md.
Compatibility
| Tool | Install | Schema file it reads |
|---|---|---|
| Claude Code | plugin (claude plugin install llm-wiki-manager@skill-hub) or ~/.claude/skills/ | CLAUDE.md |
| OpenAI Codex | plugin (codex plugin add llm-wiki-manager@skill-hub) or ~/.agents/skills/ | AGENTS.md |
| GitHub Copilot, Cursor, Gemini CLI, … | gh skill install … --agent <name> | usually AGENTS.md |
A wiki that keeps its schema in AGENTS.md with a one-line CLAUDE.md (@AGENTS.md) works in all of them; init_wiki.py sets that up.
Related
Karpathy's LLM Wiki gist — the original idea
agentskills.io — open standard spec
Contributing
Questions and ideas: Discussions
Bugs: issue tracker
Pull requests: read the contribution guide first
Security reports: follow the security policy, not a public issue
License
MIT — see LICENSE.
<div align="center">
</div>
skills/llm-wiki-manager/scripts/lint_wiki.py
Shown as plain text.
#!/usr/bin/env python3
"""
lint_wiki.py — Health check an LLM-managed wiki.
Catches mechanical issues only (broken links, orphan pages, index drift, stub
pages, log gaps). Semantic issues — stale claims, unflagged contradictions,
missing pages on cross-cutting entities — are out of scope; the LLM has to
spot those by reading.
By default writes a dated report to wiki/reports/lint-YYYY-MM-DD.md, then
auto-tracks it: adds an index entry under "Reports" and appends a log entry.
Re-running on the same day overwrites the day's report (idempotent daily).
Usage:
python lint_wiki.py # default: wiki/reports/lint-<today>.md + auto-track
python lint_wiki.py --path /path/to/wiki-root
python lint_wiki.py --stdout # print to stdout, no file, no tracking
python lint_wiki.py --report /tmp/lint.md # custom path; auto-track only if inside wiki/
python lint_wiki.py --no-track # write report file but skip index + log updates
python lint_wiki.py --stub-words 30 --log-gap-days 60 # tune thresholds
Output is markdown, organized by severity (block, quality, suggestion).
Exit code 1 if any block-severity issue found.
"""
from __future__ import annotations
import argparse
import datetime as dt
import re
import subprocess
import sys
from collections import defaultdict
from pathlib import Path
LINK_PATTERN = re.compile(
r"\[(?P<text>[^\]]+)\]\((?P<url>[^)\s]+)(?:\s+\"[^\"]*\")?\)"
)
# Obsidian wiki-links: [[slug]], [[slug|alias]], [[slug#heading]], [[slug#^block]],
# [[folder/slug]] and embeds ![[image.png]] / ![[note]]. Group 1 is the raw target
# (before the alias); wikilink_target() strips the #heading part.
WIKILINK_PATTERN = re.compile(r"!?\[\[([^\]|]+)(?:\|[^\]]*)?\]\]")
LOG_DATE_PATTERN = re.compile(r"^## \[(\d{4}-\d{2}-\d{2})\]")
INDEX_LINK_PATTERN = re.compile(
r"^\s*-\s*\[([^\]]+)\]\(([^)]+)\)"
)
# Obsidian-style [[slug]] or [[slug|alias]] wiki-links in index entries
INDEX_WIKILINK_PATTERN = re.compile(
r"^\s*-\s*\[\[([^\]|]+)(?:\|[^\]]*)?\]\]"
)
FENCED_CODE = re.compile(r"^(```|~~~).*?^\1[^\n]*$", re.M | re.S)
INLINE_CODE = re.compile(r"`[^`\n]+`")
def strip_code(text: str) -> str:
"""Drop fenced blocks and inline code spans: links shown as code are examples, not links."""
return INLINE_CODE.sub("", FENCED_CODE.sub("", text))
def wikilink_target(raw: str) -> str | None:
"""Target of a wiki-link without its #heading / #^block part; None for same-page anchors."""
target = raw.split("#", 1)[0].strip()
return target or None
def resolve_wikilink(target: str, wiki_dir: Path) -> tuple[str, Path]:
"""
Resolve a wiki-link target to a file. Notes ([[slug]], [[slug.md]], [[dir/slug]])
are searched under wiki/; attachments with another extension ([[img.png]]) are
searched under the whole wiki root (wiki/ and raw/). Unresolved targets map to
a nominal path that doesn't exist, so the caller reports them as broken.
"""
suffix = Path(target).suffix.lower()
if suffix and suffix != ".md":
name = Path(target).name
for base in (wiki_dir, wiki_dir.parent):
matches = list(base.rglob(name))
if matches:
return f"[[{target}]]", matches[0].resolve()
return f"[[{target}]]", (wiki_dir / target).resolve()
slug = target[:-3] if suffix == ".md" else target
matches = list(wiki_dir.rglob(f"{slug}.md"))
if matches:
return f"[[{slug}]]", matches[0].resolve()
return f"[[{slug}]]", (wiki_dir / f"{slug}.md").resolve()
# Schema version the bundled templates/conventions correspond to.
# Kept in sync with migrate_wiki.py's registry. The script can be invoked from
# any cwd (e.g. via subprocess), so make the script dir importable first.
sys.path.insert(0, str(Path(__file__).resolve().parent))
try:
from migrate_wiki import EXPECTED_SCHEMA_VERSION
except ImportError:
EXPECTED_SCHEMA_VERSION = 2
from schema_file import resolve_schema_file # noqa: E402
def is_external(url: str) -> bool:
return url.startswith(("http://", "https://", "mailto:", "ftp://"))
def parse_frontmatter(text: str) -> dict | None:
"""
Minimal YAML frontmatter parser (stdlib only). Supports `key: value`,
inline lists `tags: [a, b]`, and block lists:
tags:
- a
- b
Returns None when there is no frontmatter or it can't be parsed.
"""
if not text.startswith("---\n"):
return None
end = text.find("\n---", 4)
if end == -1:
return None
body = text[4:end]
data: dict = {}
current_list_key: str | None = None
try:
for line in body.splitlines():
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
if stripped.startswith("- ") and current_list_key:
data[current_list_key].append(stripped[2:].strip().strip("'\""))
continue
if ":" not in stripped:
return None
key, _, value = stripped.partition(":")
key = key.strip()
value = value.split("#", 1)[0].strip()
if value == "":
data[key] = []
current_list_key = key
elif value.startswith("[") and value.endswith("]"):
items = [v.strip().strip("'\"") for v in value[1:-1].split(",")]
data[key] = [v for v in items if v]
current_list_key = None
else:
data[key] = value.strip("'\"")
current_list_key = None
except (ValueError, KeyError, AttributeError, IndexError):
return None
return data
def is_anchor_only(url: str) -> bool:
return url.startswith("#")
def find_md_files(wiki_dir: Path) -> list[Path]:
"""All .md files under wiki/, excluding structural files and auto-generated reports.
Skips:
- wiki/index.md, wiki/log.md, wiki/hot.md (structural meta files)
- wiki/reports/* (auto-generated lint/audit artifacts; tracked separately)
"""
skip_names = {"index.md", "log.md", "hot.md"}
reports_dir = (wiki_dir / "reports").resolve()
out: list[Path] = []
for p in wiki_dir.rglob("*.md"):
if p.name in skip_names:
continue
try:
p.resolve().relative_to(reports_dir)
continue # path is inside reports/, skip
except ValueError:
pass
out.append(p)
return out
def collect_links(md_path: Path, wiki_dir: Path) -> list[tuple[str, Path]]:
"""
Returns list of (raw_url, resolved_path) for all internal links in the file.
Handles both standard markdown links [text](url) and Obsidian [[slug]] wiki-links.
Unresolved wiki-links resolve to the nominal path wiki/<slug>.md, which does
not exist — so check_broken_links reports them instead of skipping silently.
"""
text = strip_code(md_path.read_text(encoding="utf-8", errors="replace"))
out: list[tuple[str, Path]] = []
for m in LINK_PATTERN.finditer(text):
url = m.group("url")
if is_external(url) or is_anchor_only(url):
continue
target = url.split("#", 1)[0]
if not target:
continue
resolved = (md_path.parent / target).resolve()
out.append((url, resolved))
for m in WIKILINK_PATTERN.finditer(text):
target = wikilink_target(m.group(1))
if target is None:
continue # [[#heading]]: same-page anchor
out.append(resolve_wikilink(target, wiki_dir))
return out
def check_wikilink_collisions(md_files: list[Path], wiki_dir: Path) -> list[dict]:
"""
Wiki-link slugs that resolve to more than one file under wiki/.
The first match wins at link time, so collisions are silent ambiguity.
"""
collisions: list[dict] = []
seen: set[str] = set()
for md in md_files:
text = md.read_text(encoding="utf-8", errors="replace")
for m in WIKILINK_PATTERN.finditer(text):
target = wikilink_target(m.group(1))
if target is None or Path(target).suffix.lower() not in ("", ".md"):
continue
slug = target[:-3] if target.lower().endswith(".md") else target
if slug in seen:
continue
matches = list(wiki_dir.rglob(f"{slug}.md"))
if len(matches) > 1:
seen.add(slug)
collisions.append({
"slug": slug,
"from": str(md),
"matches": [str(p) for p in matches],
})
return collisions
def check_broken_links(
md_files: list[Path], wiki_dir: Path, raw_dir: Path,
) -> tuple[list[dict], list[dict]]:
"""
Two outputs:
- broken: links pointing to non-existent files inside the wiki repo
- raw_missing: links to raw/ files that don't exist
"""
broken: list[dict] = []
raw_missing: list[dict] = []
for md in md_files:
for url, resolved in collect_links(md, wiki_dir):
if not resolved.exists():
# Categorize: is the target inside raw/ ?
try:
resolved.relative_to(raw_dir)
raw_missing.append({
"from": str(md),
"url": url,
"resolved": str(resolved),
})
except ValueError:
broken.append({
"from": str(md),
"url": url,
"resolved": str(resolved),
})
return broken, raw_missing
def check_orphans(md_files: list[Path], wiki_dir: Path, root: Path) -> list[Path]:
"""
Pages with zero inbound links from any other page in wiki/ or from structural meta files.
Returns list of orphan page paths.
"""
referenced: set[Path] = set()
content_resolved = {p.resolve() for p in md_files}
# Scan content pages + structural meta files that contain links.
# Try both standard (wiki/index.md) and flat-vault (root/index.md) locations.
candidate_files = list(md_files)
for meta in [
wiki_dir / "index.md", root / "index.md",
wiki_dir / "hot.md", wiki_dir / "overview.md",
]:
if meta.exists() and meta.resolve() not in content_resolved:
candidate_files.append(meta)
for md in candidate_files:
for _url, resolved in collect_links(md, wiki_dir):
try:
if resolved.exists():
referenced.add(resolved)
except OSError:
continue
orphans = [p for p in md_files if p.resolve() not in referenced]
return orphans
def check_index_drift(
md_files: list[Path], wiki_dir: Path,
) -> tuple[list[Path], list[dict]]:
"""
Returns (pages_missing_from_index, dead_index_entries).
Checks wiki/index.md first (standard layout), then root/index.md (flat layout).
"""
for candidate in [wiki_dir / "index.md", wiki_dir.parent / "index.md"]:
if candidate.exists():
index_md = candidate
index_dir = candidate.parent
break
else:
return md_files, []
text = index_md.read_text(encoding="utf-8", errors="replace")
# Collect targets the index points to.
indexed_targets: set[Path] = set()
dead: list[dict] = []
for line in text.splitlines():
# Standard markdown links: [Title](path.md)
m = INDEX_LINK_PATTERN.match(line)
if m:
title, url = m.group(1), m.group(2)
if is_external(url) or is_anchor_only(url):
continue
target = (index_dir / url.split("#", 1)[0]).resolve()
if target.exists():
indexed_targets.add(target)
else:
dead.append({"title": title, "url": url, "resolved": str(target)})
continue
# Obsidian wiki-links: [[slug]] or [[slug|alias]]
wm = INDEX_WIKILINK_PATTERN.match(line)
if wm:
target = wikilink_target(wm.group(1))
if target is None:
continue
label, resolved = resolve_wikilink(target, wiki_dir)
if resolved.exists():
indexed_targets.add(resolved)
else:
# An index entry must point at a real page: a dead [[slug]] in
# the catalog is drift, same as a dead markdown link.
dead.append({"title": label, "url": label, "resolved": str(resolved)})
missing = [p for p in md_files if p.resolve() not in indexed_targets]
return missing, dead
def check_index_duplicates(wiki_dir: Path) -> list[dict]:
"""
Index entries pointing at the same file more than once (usually a page
listed under several categories). One page = one index entry.
"""
for candidate in [wiki_dir / "index.md", wiki_dir.parent / "index.md"]:
if candidate.exists():
index_md = candidate
index_dir = candidate.parent
break
else:
return []
occurrences: dict[Path, list[str]] = defaultdict(list)
category = "(no category)"
for line in index_md.read_text(encoding="utf-8", errors="replace").splitlines():
if line.startswith("## "):
category = line[3:].strip()
continue
m = INDEX_LINK_PATTERN.match(line)
if m:
url = m.group(2)
if is_external(url) or is_anchor_only(url):
continue
target = (index_dir / url.split("#", 1)[0]).resolve()
occurrences[target].append(category)
continue
wm = INDEX_WIKILINK_PATTERN.match(line)
if wm:
raw_target = wikilink_target(wm.group(1))
if raw_target is None:
continue
_, target = resolve_wikilink(raw_target, wiki_dir)
occurrences[target].append(category)
return [
{"target": str(target), "count": len(cats), "categories": cats}
for target, cats in occurrences.items()
if len(cats) > 1
]
def check_hot_health(wiki_dir: Path, max_words: int) -> list[dict]:
"""
hot.md is a ~500-word cache, rewritten on every ingest — not a log.
Flags: word count over threshold, and 3+ dated `## [YYYY-MM-DD]` blocks
(a sign the file is accumulating changelog entries that belong in log.md).
"""
for candidate in [wiki_dir / "hot.md", wiki_dir.parent / "hot.md"]:
if candidate.exists():
hot_path = candidate
break
else:
return []
text = hot_path.read_text(encoding="utf-8", errors="replace")
body = text
if body.startswith("---\n"):
end = body.find("\n---", 4)
if end != -1:
body = body[end + 4:]
findings: list[dict] = []
words = len(body.split())
if words > max_words:
findings.append({
"path": str(hot_path),
"issue": f"{words} words (threshold {max_words}) — rewrite, don't append",
})
dated_blocks = sum(
1 for line in body.splitlines() if LOG_DATE_PATTERN.match(line)
)
if dated_blocks >= 3:
findings.append({
"path": str(hot_path),
"issue": (
f"{dated_blocks} dated `## [...]` blocks — hot.md is turning into "
"a second log; move them to log.md and rewrite hot.md"
),
})
return findings
def check_tag_health(
md_files: list[Path], max_tags: int,
) -> tuple[list[dict], list[dict], int]:
"""
Frontmatter tag hygiene:
- single_use: tags appearing on exactly one page (keywords, not classifiers)
- overtagged: pages with more than max_tags tags
- unparsed: count of files whose frontmatter could not be parsed (skipped)
"""
tag_pages: dict[str, list[Path]] = defaultdict(list)
overtagged: list[dict] = []
unparsed = 0
for md in md_files:
text = md.read_text(encoding="utf-8", errors="replace")
if not text.startswith("---\n"):
continue
fm = parse_frontmatter(text)
if fm is None:
unparsed += 1
continue
tags = fm.get("tags")
if not isinstance(tags, list):
continue
for tag in tags:
tag_pages[tag].append(md)
if len(tags) > max_tags:
overtagged.append({"path": str(md), "count": len(tags), "tags": tags})
single_use = [
{"tag": tag, "page": str(pages[0])}
for tag, pages in sorted(tag_pages.items())
if len(pages) == 1
]
return single_use, overtagged, unparsed
def check_schema_version(root: Path, schema_override: str | None = None) -> dict | None:
"""
Compare the schema file's (AGENTS.md or CLAUDE.md) schema_version stamp against
what this skill version expects. Unstamped wikis count as v1. Returns a finding
dict when the wiki is behind, else None.
"""
claude_md = resolve_schema_file(root, schema_override)
current = 1
if claude_md.exists():
fm = parse_frontmatter(claude_md.read_text(encoding="utf-8", errors="replace"))
if fm and str(fm.get("schema_version", "")).isdigit():
current = int(fm["schema_version"])
if current < EXPECTED_SCHEMA_VERSION:
return {
"current": current,
"expected": EXPECTED_SCHEMA_VERSION,
"hint": "run scripts/migrate_wiki.py --path <wiki-root> (dry-run) to see the upgrade steps",
}
return None
def check_stub_pages(md_files: list[Path], min_words: int) -> list[dict]:
"""Pages with fewer than min_words of body text (excluding frontmatter)."""
stubs: list[dict] = []
for md in md_files:
text = md.read_text(encoding="utf-8", errors="replace")
# Strip YAML frontmatter
if text.startswith("---\n"):
end = text.find("\n---\n", 4)
if end != -1:
text = text[end + 5:]
words = len(text.split())
if words < min_words:
stubs.append({"path": str(md), "words": words})
return stubs
def check_log_gaps(wiki_dir: Path, gap_days: int) -> list[dict]:
"""Look for stretches of >gap_days between log entries in log.md."""
for candidate in [wiki_dir / "log.md", wiki_dir.parent / "log.md"]:
if candidate.exists():
log_path = candidate
break
else:
return []
text = log_path.read_text(encoding="utf-8", errors="replace")
dates: list[dt.date] = []
for line in text.splitlines():
m = LOG_DATE_PATTERN.match(line)
if m:
try:
dates.append(dt.date.fromisoformat(m.group(1)))
except ValueError:
continue
if len(dates) < 2:
return []
dates.sort()
gaps: list[dict] = []
for i in range(1, len(dates)):
delta = (dates[i] - dates[i - 1]).days
if delta > gap_days:
gaps.append({
"from": dates[i - 1].isoformat(),
"to": dates[i].isoformat(),
"days": delta,
})
return gaps
def check_slug_conventions(md_files: list[Path]) -> list[Path]:
"""Filenames that aren't lowercase-with-hyphens (excluding _-prefixed)."""
bad: list[Path] = []
pattern = re.compile(r"^[a-z0-9][a-z0-9\-]*\.md$")
for md in md_files:
name = md.name
if name.startswith("_"):
continue
if not pattern.match(name):
bad.append(md)
return bad
def render_report(results: dict, root: Path, thresholds: dict) -> str:
"""Render the lint report as markdown."""
today = dt.date.today().isoformat()
lines: list[str] = []
lines.append(f"# Lint report\n")
lines.append(f"Wiki root: `{root}`")
lines.append(f"Date: {today}\n")
block_count = (
len(results["broken_links"])
+ len(results["raw_missing"])
+ len(results["index_dead"])
)
quality_count = (
len(results["orphans"])
+ len(results["index_missing"])
+ len(results["stubs"])
+ len(results["slug_mismatch"])
+ len(results["index_duplicates"])
+ len(results["hot_health"])
+ len(results["overtagged"])
+ len(results["wikilink_collisions"])
)
suggestion_count = (
len(results["log_gaps"])
+ len(results["single_use_tags"])
+ (1 if results["schema_version"] else 0)
)
lines.append(
f"Summary: **{block_count} block**, **{quality_count} quality**, "
f"**{suggestion_count} suggestion**.\n"
)
# BLOCK
lines.append("## Block (fix without asking)\n")
if not block_count:
lines.append("None. ✓\n")
else:
if results["broken_links"]:
lines.append(f"### Broken links ({len(results['broken_links'])})\n")
lines.append(
"Markdown links and `[[wiki-links]]` pointing to files that don't "
"exist inside the wiki.\n"
)
for entry in results["broken_links"]:
lines.append(
f"- in `{entry['from']}`: `{entry['url']}` → `{entry['resolved']}`"
)
lines.append("")
if results["raw_missing"]:
lines.append(f"### Wiki citing missing `raw/` files ({len(results['raw_missing'])})\n")
for entry in results["raw_missing"]:
lines.append(
f"- in `{entry['from']}`: `{entry['url']}` → `{entry['resolved']}`"
)
lines.append("")
if results["index_dead"]:
lines.append(f"### Dead index entries ({len(results['index_dead'])})\n")
lines.append("`wiki/index.md` references files that don't exist.\n")
for entry in results["index_dead"]:
if entry["url"].startswith("[["):
lines.append(f"- `{entry['url']}`")
else:
lines.append(f"- [{entry['title']}]({entry['url']})")
lines.append("")
# QUALITY
lines.append("## Quality (propose fixes, apply with approval)\n")
if not quality_count:
lines.append("None. ✓\n")
else:
if results["orphans"]:
lines.append(f"### Orphan pages ({len(results['orphans'])})\n")
lines.append("Pages with no inbound links from any other page or from the index.\n")
for p in results["orphans"]:
lines.append(f"- `{p}`")
lines.append("")
if results["index_missing"]:
lines.append(f"### Pages missing from index ({len(results['index_missing'])})\n")
for p in results["index_missing"]:
lines.append(f"- `{p}`")
lines.append("")
if results["stubs"]:
lines.append(
f"### Stub pages — under {thresholds['stub_words']} words "
f"({len(results['stubs'])})\n"
)
for s in results["stubs"]:
lines.append(f"- `{s['path']}` ({s['words']} words)")
lines.append("")
if results["slug_mismatch"]:
lines.append(f"### Slug convention mismatch ({len(results['slug_mismatch'])})\n")
lines.append(
"Filenames not matching `lowercase-with-hyphens.md`.\n"
)
for p in results["slug_mismatch"]:
lines.append(f"- `{p}`")
lines.append("")
if results["index_duplicates"]:
lines.append(
f"### Duplicate index entries ({len(results['index_duplicates'])})\n"
)
lines.append(
"The same page is listed more than once in the index. "
"One page = one entry; pick the best category and drop the rest.\n"
)
for d in results["index_duplicates"]:
cats = ", ".join(d["categories"])
lines.append(f"- `{d['target']}` — {d['count']} entries ({cats})")
lines.append("")
if results["hot_health"]:
lines.append(f"### hot.md health ({len(results['hot_health'])})\n")
lines.append(
"hot.md is a ~500-word cache, rewritten entirely on every ingest.\n"
)
for h in results["hot_health"]:
lines.append(f"- `{h['path']}`: {h['issue']}")
lines.append("")
if results["overtagged"]:
lines.append(
f"### Pages over {thresholds['max_tags']} tags "
f"({len(results['overtagged'])})\n"
)
lines.append(
"Tags are classifiers, not keywords — trim to the canonical list "
"in the schema file (AGENTS.md / CLAUDE.md).\n"
)
for t in results["overtagged"]:
lines.append(f"- `{t['path']}` ({t['count']} tags: {', '.join(t['tags'])})")
lines.append("")
if results["wikilink_collisions"]:
lines.append(
f"### Ambiguous wiki-link slugs ({len(results['wikilink_collisions'])})\n"
)
lines.append(
"These `[[slugs]]` match more than one file; the first match wins "
"silently. Rename to unambiguous slugs.\n"
)
for c in results["wikilink_collisions"]:
lines.append(
f"- `[[{c['slug']}]]` (first seen in `{c['from']}`): "
+ ", ".join(f"`{m}`" for m in c["matches"])
)
lines.append("")
# SUGGESTIONS
lines.append("## Suggestions (informational)\n")
if not suggestion_count:
lines.append("None. ✓\n")
else:
if results["log_gaps"]:
lines.append(
f"### Log gaps over {thresholds['log_gap_days']} days "
f"({len(results['log_gaps'])})\n"
)
for g in results["log_gaps"]:
lines.append(f"- {g['from']} → {g['to']} ({g['days']} days)")
lines.append("")
if results["single_use_tags"]:
lines.append(
f"### Single-use tags ({len(results['single_use_tags'])})\n"
)
lines.append(
"A tag on exactly one page is a keyword, not a classifier — "
"move it to the page body or merge it into a canonical tag.\n"
)
for s in results["single_use_tags"]:
lines.append(f"- `{s['tag']}` (only on `{s['page']}`)")
lines.append("")
if results["schema_version"]:
sv = results["schema_version"]
lines.append("### Wiki schema behind skill version\n")
lines.append(
f"- Wiki is at schema v{sv['current']}, skill expects "
f"v{sv['expected']} — {sv['hint']}"
)
lines.append("")
if results.get("tag_unparsed"):
lines.append(
f"*Note: frontmatter could not be parsed in "
f"{results['tag_unparsed']} file(s); tag checks skipped them.*\n"
)
# Reminder of out-of-scope checks
lines.append("---\n")
lines.append("**Not checked here (LLM responsibility):** stale claims, ")
lines.append("unflagged contradictions, missing pages on cross-cutting entities, ")
lines.append("schema drift between the schema file (`AGENTS.md` / `CLAUDE.md`) and actual practice.\n")
return "\n".join(lines)
def auto_track(
report_path: Path,
root: Path,
block_total: int,
quality_count: int,
suggestion_count: int,
today: str,
) -> None:
"""Update index.md and log.md to record this report.
Best-effort: failures are reported but don't abort lint.
Only called when report is being written inside wiki/reports/.
"""
scripts_dir = Path(__file__).resolve().parent
summary = (
f"{block_total} block, {quality_count} quality, {suggestion_count} suggestion"
)
index_title = f"Lint {today}"
log_title = "Health check"
# Path relative to wiki/ for the index entry
try:
rel = report_path.resolve().relative_to((root / "wiki").resolve())
except ValueError:
return
# update_index.py
res = subprocess.run(
[
sys.executable, str(scripts_dir / "update_index.py"),
"--path", str(root),
"--category", "Reports",
"--title", index_title,
"--page-path", str(report_path),
"--summary", summary,
],
capture_output=True, text=True, check=False,
)
if res.returncode != 0:
print(f"warning: update_index failed: {res.stderr.strip()}", file=sys.stderr)
# append_log.py
details = f"Wrote wiki/{rel.as_posix()}. {summary}."
res = subprocess.run(
[
sys.executable, str(scripts_dir / "append_log.py"),
"--path", str(root),
"--action", "lint",
"--title", log_title,
"--details", details,
"--date", today,
],
capture_output=True, text=True, check=False,
)
if res.returncode != 0:
print(f"warning: append_log failed: {res.stderr.strip()}", file=sys.stderr)
def main() -> int:
parser = argparse.ArgumentParser(
description="Health check an LLM-managed wiki.",
)
parser.add_argument(
"--path", default=".",
help="Wiki root directory (default: current directory).",
)
parser.add_argument(
"--report", default=None,
help="Custom report path. Default: wiki/reports/lint-<today>.md.",
)
parser.add_argument(
"--stdout", action="store_true",
help="Print report to stdout, do not write a file (no auto-track).",
)
parser.add_argument(
"--no-track", action="store_true",
help="Write the report file but skip the auto index/log updates.",
)
parser.add_argument(
"--stub-words", type=int, default=50,
help="Pages under this many body words are flagged as stubs (default: 50).",
)
parser.add_argument(
"--log-gap-days", type=int, default=30,
help="Log gaps longer than this many days are flagged (default: 30).",
)
parser.add_argument(
"--hot-max-words", type=int, default=700,
help="hot.md over this many body words is flagged (default: 700).",
)
parser.add_argument(
"--max-tags", type=int, default=4,
help="Pages with more frontmatter tags than this are flagged (default: 4).",
)
parser.add_argument(
"--schema-file", default=None,
help="Schema file (default: auto-detect AGENTS.md / CLAUDE.md).",
)
args = parser.parse_args()
root = Path(args.path).expanduser().resolve()
wiki_dir = root / "wiki"
raw_dir = root / "raw"
if not wiki_dir.exists():
print(f"error: no wiki/ directory at {root}", file=sys.stderr)
return 1
today = dt.date.today().isoformat()
md_files = find_md_files(wiki_dir)
broken, raw_missing = check_broken_links(md_files, wiki_dir, raw_dir)
orphans = check_orphans(md_files, wiki_dir, root)
index_missing, index_dead = check_index_drift(md_files, wiki_dir)
stubs = check_stub_pages(md_files, args.stub_words)
log_gaps = check_log_gaps(wiki_dir, args.log_gap_days)
slug_mismatch = check_slug_conventions(md_files)
index_duplicates = check_index_duplicates(wiki_dir)
hot_health = check_hot_health(wiki_dir, args.hot_max_words)
single_use_tags, overtagged, tag_unparsed = check_tag_health(md_files, args.max_tags)
wikilink_collisions = check_wikilink_collisions(md_files, wiki_dir)
schema_version = check_schema_version(root, args.schema_file)
results = {
"broken_links": broken,
"raw_missing": raw_missing,
"orphans": orphans,
"index_missing": index_missing,
"index_dead": index_dead,
"stubs": stubs,
"log_gaps": log_gaps,
"slug_mismatch": slug_mismatch,
"index_duplicates": index_duplicates,
"hot_health": hot_health,
"single_use_tags": single_use_tags,
"overtagged": overtagged,
"tag_unparsed": tag_unparsed,
"wikilink_collisions": wikilink_collisions,
"schema_version": schema_version,
}
report = render_report(
results, root,
thresholds={
"stub_words": args.stub_words,
"log_gap_days": args.log_gap_days,
"max_tags": args.max_tags,
},
)
block_total = len(broken) + len(raw_missing) + len(index_dead)
quality_count = (
len(orphans) + len(index_missing) + len(stubs) + len(slug_mismatch)
+ len(index_duplicates) + len(hot_health) + len(overtagged)
+ len(wikilink_collisions)
)
suggestion_count = (
len(log_gaps) + len(single_use_tags) + (1 if schema_version else 0)
)
# Decide where the report goes.
if args.stdout:
print(report)
else:
if args.report:
report_path = Path(args.report).expanduser().resolve()
else:
report_path = (wiki_dir / "reports" / f"lint-{today}.md").resolve()
report_path.parent.mkdir(parents=True, exist_ok=True)
report_path.write_text(report, encoding="utf-8")
print(f"Report written to {report_path}")
# Auto-track only when the report lives inside wiki/ — custom paths
# outside the wiki are treated as one-off and not tracked.
if not args.no_track:
try:
report_path.relative_to(wiki_dir.resolve())
except ValueError:
pass # outside wiki/, skip tracking
else:
auto_track(
report_path, root,
block_total, quality_count, suggestion_count, today,
)
return 1 if block_total > 0 else 0
if __name__ == "__main__":
sys.exit(main())
40 files
- .claude-plugin/plugin.json
- .codex-plugin/plugin.json
- .github/dependabot.yml
- .github/workflows/release.yml
- .gitignore
- AGENTS.md
- ATTRIBUTION.md
- CHANGELOG.md
- CLAUDE.md
- LICENSE
- README.md
- README.tr.md
- skills/llm-wiki-manager/SKILL.md
- skills/llm-wiki-manager/assets/examples/healthy-wiki-tree.txt
- skills/llm-wiki-manager/assets/templates/comparison-page.md.tmpl
- skills/llm-wiki-manager/assets/templates/concept-page.md.tmpl
- skills/llm-wiki-manager/assets/templates/entity-page.md.tmpl
- skills/llm-wiki-manager/assets/templates/hot.md.tmpl
- skills/llm-wiki-manager/assets/templates/index.md.tmpl
- skills/llm-wiki-manager/assets/templates/log.md.tmpl
- skills/llm-wiki-manager/assets/templates/source-summary.md.tmpl
- skills/llm-wiki-manager/assets/templates/wiki-CLAUDE.md.tmpl
- skills/llm-wiki-manager/references/architecture.md
- skills/llm-wiki-manager/references/bootstrap-workflow.md
- skills/llm-wiki-manager/references/ingest-workflow.md
- skills/llm-wiki-manager/references/lint-workflow.md
- skills/llm-wiki-manager/references/migrate-workflow.md
- skills/llm-wiki-manager/references/multi-wiki-routing.md
- skills/llm-wiki-manager/references/philosophy.md
- skills/llm-wiki-manager/references/query-workflow.md
- skills/llm-wiki-manager/references/schema-design-guide.md
- skills/llm-wiki-manager/references/teaching-mode.md
- skills/llm-wiki-manager/references/update-workflow.md
- skills/llm-wiki-manager/scripts/append_log.py
- skills/llm-wiki-manager/scripts/init_wiki.py
- skills/llm-wiki-manager/scripts/lint_wiki.py
- skills/llm-wiki-manager/scripts/migrate_wiki.py
- skills/llm-wiki-manager/scripts/schema_file.py
- skills/llm-wiki-manager/scripts/update_index.py
- tests/test_scripts.py
Advantages
- Compounding knowledge base that synthesizes information rather than re-reading raw documents on every query. evidence: README.md
- Automated bookkeeping and cross-referencing reduce manual maintenance effort. evidence: README.md
- Idempotent scripts ensure reliable operations without external dependencies. evidence: README.md
Limitations
- Requires strict adherence to the division of labor between the raw source folder and the LLM-managed wiki folder. evidence: README.md
- Multi-wiki routing requires manual configuration of paths in the schema file. evidence: README.md
Security
Risk level: No risk found
This brain runs
Agent instructions that run or fetch something (8)
AGENTS.md:`tests/test_scripts.py`: functional tests that run every script against temp wikis.AGENTS.md:`python3 -m unittest discover -s tests`AGENTS.md:Codex: install from a local marketplace in an isolated `CODEX_HOME`;skills/llm-wiki-manager/SKILL.md:Run them by path relative to this skill's directory, and always pass `--path <wiki-root>`.skills/llm-wiki-manager/SKILL.md:If it is lower than the version this skill expects, offer one sentence before starting the requested work: *"This wiki is at schema v1, the skill expects v2 — run `scripts/migrate_wiki.py` first?"* Don't block the requested operation if the user declines;skills/llm-wiki-manager/SKILL.md:If the section is missing and a multi-wiki operation is requested, **run Schema-evolve first** to add it, then proceed.skills/llm-wiki-manager/SKILL.md:Some users run two wikis: a per-project wiki at the working directory and a long-lived global wiki (often an existing Obsidian vault) at a fixed path.skills/llm-wiki-manager/SKILL.md:unstamped = v1), shows a **dry-run** of the mechanical steps plus the manual (LLM) steps, applies with `--apply` only.
Attribution and licence
- Created by
- sametbrr
- Declared authors
- sametbrr (declared in .claude-plugin/plugin.json)
- Publisher
- Samet Birer (Community)
- Source
- sametbrr/llm-wiki-manager · commit 28ad285
- Licence
- MIT License (files served here)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by sametbrr.
Download
The ZIP holds the files AutoGineer serves, with ATTRIBUTION.md and the licence. Download llm-wiki-manager.zip