207 lines
7.2 KiB
Python
207 lines
7.2 KiB
Python
#!/usr/bin/env -S uv run --script
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = []
|
|
# ///
|
|
"""
|
|
init_wiki.py — Bootstrap or upgrade an LLM Wiki structure in a project.
|
|
|
|
Plain init creates the directory layout and drops in templates for SCHEMA.md,
|
|
index.md, log.md, the page template, and the optional graph layer
|
|
(graph/ontology.yaml, graph/README.md, graph/.gitignore). It is idempotent:
|
|
re-running won't clobber existing files.
|
|
|
|
`--upgrade` mode is for wikis bootstrapped under an older plugin version. It
|
|
does the same idempotent file creation, then inspects the existing SCHEMA.md
|
|
for sections introduced in newer versions and prints clear instructions for
|
|
what to merge by hand. It never overwrites SCHEMA.md — the schema is
|
|
co-evolved with the user.
|
|
|
|
Usage:
|
|
python init_wiki.py <project-root> [--wiki-dir wiki] [--raw-dir raw] [--upgrade]
|
|
|
|
Examples:
|
|
python init_wiki.py .
|
|
python init_wiki.py . --upgrade
|
|
python init_wiki.py ~/research --wiki-dir kb --raw-dir sources
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
from datetime import date
|
|
|
|
|
|
SKILL_ROOT = Path(__file__).resolve().parent.parent
|
|
TEMPLATES = SKILL_ROOT / "assets"
|
|
|
|
|
|
SUBDIRS = ["sources", "entities", "concepts", "synthesis", "graph"]
|
|
|
|
# Markers used by --upgrade to detect SCHEMA.md sections introduced in
|
|
# specific plugin versions. Each entry: (heading_marker, version_label,
|
|
# template_anchor, blurb).
|
|
SCHEMA_SECTION_MARKERS = [
|
|
{
|
|
"marker": "## Optional graph metadata",
|
|
"version": "0.3.0",
|
|
"anchor": "## Optional graph metadata",
|
|
"label": "Optional graph metadata (Frontmatter section)",
|
|
},
|
|
{
|
|
"marker": "## Graph layer",
|
|
"version": "0.3.0",
|
|
"anchor": "## Graph layer",
|
|
"label": "Graph layer (canonical-vs-generated artifact policy)",
|
|
},
|
|
{
|
|
"marker": "Graph lint + extract",
|
|
"version": "0.3.0",
|
|
"anchor": "- Graph lint + extract: after every ingest that adds typed `graph.relationships`.",
|
|
"label": "Graph lint + extract cadence (Lint cadence section)",
|
|
},
|
|
]
|
|
|
|
|
|
def copy_template(src: Path, dst: Path, substitutions: dict | None = None) -> bool:
|
|
"""Copy a template file to dst. Returns True if file was created, False if it already existed."""
|
|
if dst.exists():
|
|
return False
|
|
text = src.read_text()
|
|
if substitutions:
|
|
for key, value in substitutions.items():
|
|
text = text.replace(key, value)
|
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
dst.write_text(text)
|
|
return True
|
|
|
|
|
|
def detect_schema_gaps(schema_path: Path) -> list[dict]:
|
|
"""Return the SCHEMA_SECTION_MARKERS entries missing from the user's SCHEMA.md."""
|
|
if not schema_path.exists():
|
|
return []
|
|
text = schema_path.read_text(encoding="utf-8")
|
|
return [m for m in SCHEMA_SECTION_MARKERS if m["marker"] not in text]
|
|
|
|
|
|
def print_schema_upgrade_guidance(schema_path: Path, gaps: list[dict]) -> None:
|
|
template_path = TEMPLATES / "SCHEMA.md.template"
|
|
print()
|
|
print("=" * 64)
|
|
print(f"Upgrade required: {schema_path}")
|
|
print("=" * 64)
|
|
print(
|
|
"Your SCHEMA.md predates one or more sections introduced by newer\n"
|
|
"plugin versions. The graph layer itself is opt-in, but to make Claude\n"
|
|
"aware of it, merge the sections below by hand. SCHEMA.md is co-evolved\n"
|
|
"with you — this script never overwrites it."
|
|
)
|
|
print()
|
|
print("Missing sections:")
|
|
for m in gaps:
|
|
print(f" - [{m['version']}] {m['label']}")
|
|
print()
|
|
print(f"Reference template: {template_path}")
|
|
print(
|
|
"Diff your SCHEMA.md against the template and copy the missing\n"
|
|
"sections in. Or run /wiki:upgrade and Claude will propose the edits\n"
|
|
"interactively (one section at a time, never silent)."
|
|
)
|
|
|
|
|
|
def init_wiki(project_root: Path, wiki_dir: str, raw_dir: str, upgrade: bool = False) -> None:
|
|
project_root = project_root.resolve()
|
|
if not project_root.exists():
|
|
print(f"Error: project root does not exist: {project_root}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
wiki = project_root / wiki_dir
|
|
raw = project_root / raw_dir
|
|
|
|
mode = "Upgrading" if upgrade else "Initializing"
|
|
print(f"{mode} LLM Wiki in: {project_root}")
|
|
print(f" Wiki directory: {wiki}")
|
|
print(f" Raw directory: {raw}")
|
|
print()
|
|
|
|
created = []
|
|
skipped = []
|
|
|
|
# Create wiki subdirs
|
|
for subdir in SUBDIRS:
|
|
d = wiki / subdir
|
|
if not d.exists():
|
|
d.mkdir(parents=True)
|
|
created.append(f"{wiki_dir}/{subdir}/")
|
|
else:
|
|
skipped.append(f"{wiki_dir}/{subdir}/")
|
|
|
|
# Create raw + raw/assets
|
|
for d, label in [(raw, raw_dir), (raw / "assets", f"{raw_dir}/assets")]:
|
|
if not d.exists():
|
|
d.mkdir(parents=True)
|
|
created.append(f"{label}/")
|
|
else:
|
|
skipped.append(f"{label}/")
|
|
|
|
# Copy templates
|
|
template_map = [
|
|
("SCHEMA.md.template", wiki / "SCHEMA.md"),
|
|
("index.md.template", wiki / "index.md"),
|
|
("log.md.template", wiki / "log.md"),
|
|
("page.md.template", wiki / ".page-template.md"),
|
|
("ontology.yaml.template", wiki / "graph" / "ontology.yaml"),
|
|
("graph_README.md.template", wiki / "graph" / "README.md"),
|
|
("graph_gitignore.template", wiki / "graph" / ".gitignore"),
|
|
]
|
|
for src_name, dst in template_map:
|
|
src = TEMPLATES / src_name
|
|
if not src.exists():
|
|
print(f"Warning: template missing: {src}", file=sys.stderr)
|
|
continue
|
|
if copy_template(src, dst):
|
|
created.append(str(dst.relative_to(project_root)))
|
|
else:
|
|
skipped.append(str(dst.relative_to(project_root)))
|
|
|
|
# Report
|
|
if created:
|
|
print("Created:")
|
|
for path in created:
|
|
print(f" + {path}")
|
|
if skipped:
|
|
print("Already existed (skipped):")
|
|
for path in skipped:
|
|
print(f" = {path}")
|
|
|
|
if upgrade:
|
|
gaps = detect_schema_gaps(wiki / "SCHEMA.md")
|
|
if gaps:
|
|
print_schema_upgrade_guidance(wiki / "SCHEMA.md", gaps)
|
|
else:
|
|
print()
|
|
print("SCHEMA.md is up to date with the current template — no manual merge needed.")
|
|
return
|
|
|
|
print()
|
|
print("Next steps:")
|
|
print(f" 1. Read {wiki_dir}/SCHEMA.md and customize it for your domain.")
|
|
print(f" 2. (Optional) Edit {wiki_dir}/graph/ontology.yaml to add domain-specific predicates.")
|
|
print(f" 3. Drop your first source into {raw_dir}/.")
|
|
print(f" 4. Ask Claude to ingest it.")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("project_root", type=Path, help="Project root directory.")
|
|
parser.add_argument("--wiki-dir", default="wiki", help="Name of the wiki subdirectory (default: wiki).")
|
|
parser.add_argument("--raw-dir", default="raw", help="Name of the raw sources subdirectory (default: raw).")
|
|
parser.add_argument("--upgrade", action="store_true",
|
|
help="Upgrade an existing wiki: add missing files idempotently and surface SCHEMA.md sections to merge by hand.")
|
|
args = parser.parse_args()
|
|
init_wiki(args.project_root, args.wiki_dir, args.raw_dir, upgrade=args.upgrade)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|