Agents configuration
This commit is contained in:
parent
c56348b15b
commit
3dabfa0c0b
12 changed files with 1081 additions and 292 deletions
170
.agents/skills/cascade-kb-init/SKILL.md
Normal file
170
.agents/skills/cascade-kb-init/SKILL.md
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
---
|
||||
name: cascade-kb-init
|
||||
description: Bootstrap a brand-new Cascade Knowledge Base - the same directory structure, AGENTS.md/CLAUDE.md system prompt, and empty wiki/ scaffold as this project - inside a target folder (typically empty, or a new project that doesn't have one yet). Use when the user asks to "set up a new wiki like this one", "initialize a new cascade KB", "bootstrap a wiki here", "create a knowledge base with this schema", or calls it a "wiki initializer". Do not confuse with a generic `init` skill that documents an existing codebase - this one creates the Cascade KB pattern itself, empty, ready for its first ingest.
|
||||
---
|
||||
|
||||
# Cascade KB init skill
|
||||
|
||||
## Purpose
|
||||
|
||||
Copy this project's Cascade Knowledge Base *schema* - not its content - into
|
||||
a new target folder: the directory structure, the `AGENTS.md`/`CLAUDE.md`
|
||||
system prompt that defines how the KB behaves, and the empty `wiki/`
|
||||
scaffold (routing table, overview, log, error book, entity/graph indexes).
|
||||
The result is a new, empty KB that behaves exactly like this one, ready for
|
||||
its first `raw/inbox/` drop and "Ingest."
|
||||
|
||||
This is a one-way copy from this repo's own `AGENTS.md`/`wiki/` template
|
||||
files into a different folder. It never reads or writes anything in this
|
||||
repo's `raw/`, `wiki/entities/`, `wiki/graph/edges.json`, or `outputs/` -
|
||||
those hold this project's actual accumulated knowledge, which is exactly
|
||||
what should *not* travel into a fresh KB.
|
||||
|
||||
## Trigger phrases
|
||||
|
||||
- "set up a new wiki like this one" / "initialize a new cascade KB"
|
||||
- "bootstrap a wiki here" / "create a knowledge base with this schema"
|
||||
- "wiki initializer" / "clone this KB structure into a new project"
|
||||
|
||||
## How to run this skill
|
||||
|
||||
### Step 1 - Confirm the target folder
|
||||
|
||||
Ask (if not already given): "Which folder should I initialize the new
|
||||
knowledge base in?" Resolve to an absolute path. This is a filesystem
|
||||
action outside the current repo, so confirm the resolved path back to the
|
||||
user before writing anything - do not assume a relative path means
|
||||
"somewhere under the current project."
|
||||
|
||||
If the folder doesn't exist yet, create it after confirming the path. If it
|
||||
exists, check its contents before doing anything else (Step 2).
|
||||
|
||||
### Step 2 - Don't clobber an existing KB or unrelated project
|
||||
|
||||
If the target already contains a `wiki/` directory, or an `AGENTS.md` /
|
||||
`CLAUDE.md`, stop and ask: "This folder already looks like it has a
|
||||
knowledge base (found `<what>`). Initializing here could overwrite it. Do
|
||||
you want to proceed anyway, pick a different folder, or only add whatever
|
||||
scaffold pieces are missing?" Never silently overwrite an existing
|
||||
`AGENTS.md` or populated `wiki/` tree.
|
||||
|
||||
If the target has other, unrelated files (e.g. it's an existing code
|
||||
project without a KB yet) - that's fine, proceed; the scaffold is added
|
||||
alongside them. Note any top-level name collisions (e.g. an existing
|
||||
`wiki/` folder used for something else) and ask before touching those
|
||||
specifically.
|
||||
|
||||
### Step 3 - Default skill set (no need to ask)
|
||||
|
||||
The bare scaffold (directory structure + `AGENTS.md`/`CLAUDE.md` + empty
|
||||
`wiki/` templates) is always included, and so is the reusable KB skill set
|
||||
- these operate purely on the `wiki/` structure, so they carry over
|
||||
cleanly and are part of "the schema" as far as this skill is concerned:
|
||||
`export-okf`, `export-starlight`, `sync-changes`, `extract-transcript`,
|
||||
`project-summary`. Don't ask about these - just include them.
|
||||
|
||||
`ghost-writer` and `clouddrift-docx` are not part of the default set (a
|
||||
general writing tool and a brand-specific export skill respectively, not
|
||||
KB-schema-native) - only include either if the user explicitly asks for it,
|
||||
e.g. "also bring over ghost-writer."
|
||||
|
||||
Record the final skill list (default five, plus anything explicitly added)
|
||||
- this affects Steps 6 and 7.
|
||||
|
||||
### Step 4 - Create the directory structure
|
||||
|
||||
Under the target folder, create:
|
||||
|
||||
```
|
||||
libs/
|
||||
linked/
|
||||
outputs/
|
||||
raw/inbox/
|
||||
raw/archive/
|
||||
tmp/
|
||||
wiki/entities/
|
||||
wiki/graph/
|
||||
workload/
|
||||
```
|
||||
|
||||
`libs/`, `linked/`, and `tmp/` are gitignored per the schema (Step 6) and
|
||||
stay empty. `raw/inbox/`, `raw/archive/`, and `workload/` are meant to be
|
||||
tracked but start empty - add a placeholder `.gitkeep` file to each so they
|
||||
survive a fresh `git init` + first commit rather than vanishing as empty
|
||||
directories.
|
||||
|
||||
### Step 5 - Write `AGENTS.md` and the `CLAUDE.md` symlink
|
||||
|
||||
Copy this repo's `AGENTS.md` verbatim into the target as `AGENTS.md` - it
|
||||
is already fully generic (no project-specific content; it *is* the
|
||||
schema definition). Then create `CLAUDE.md` in the target as a symlink to
|
||||
`AGENTS.md`, matching this repo's own convention (one source of truth,
|
||||
readable under either filename).
|
||||
|
||||
### Step 6 - Write the empty `wiki/` scaffold
|
||||
|
||||
Create these files in the target, using this repo's current versions as
|
||||
the template and stripping every reference to this project's actual
|
||||
content (Grant Thornton, Cloud Drift, specific entities, etc.) down to the
|
||||
generic structure:
|
||||
|
||||
- **`wiki/index.md`** - frontmatter with `kb_schema_version: "1.1"` only.
|
||||
Body: the routing table with just its four fixed infrastructure rows
|
||||
(Overview, Log, Error Book, Entities, Graph) and no entity rows, plus the
|
||||
"## Entity Pages" section with its placeholder note. Use today's date
|
||||
where the template needs one.
|
||||
- **`wiki/overview.md`** - copy verbatim from this repo (it's already
|
||||
generic - directory tree, cascade priority, frontmatter summary, no
|
||||
project content). Set `last_updated` to today.
|
||||
- **`wiki/log.md`** - header and explanation only, no entries.
|
||||
- **`wiki/error-book.md`** - copy verbatim (already generic: empty table
|
||||
+ placeholder note). Set `last_updated` to today.
|
||||
- **`wiki/entities/index.md`** - header + placeholder note, no entries.
|
||||
- **`wiki/graph/index.md`** - header + pointer to `edges.json`, with a
|
||||
generic "Current graph coverage: (none yet)" line instead of this
|
||||
repo's specific bullet list.
|
||||
- **`wiki/graph/edges.json`** - `{"version": 1, "last_updated": "<today>", "edges": []}`.
|
||||
|
||||
Do not carry over any entity pages, graph edges, log entries, or overview
|
||||
content specific to this project - the whole point is an empty KB with the
|
||||
same shape.
|
||||
|
||||
### Step 7 - Write `.gitignore`
|
||||
|
||||
Base rules (always): `libs/`, `linked/`, `tmp/`, `.DS_Store`. Since
|
||||
`export-starlight` and `export-okf` are in the default skill set (Step 3),
|
||||
also always add `outputs/starlight` and `outputs/okf` - both exist to be
|
||||
gitignored precisely because those two skills are present by default.
|
||||
|
||||
### Step 8 - Copy the skill set from Step 3
|
||||
|
||||
Copy each skill's folder from this repo's `.agents/skills/<name>/` into the
|
||||
target's `.agents/skills/<name>/` unchanged - the default five, plus
|
||||
anything explicitly added. Then create `.claude/skills` in the target as a
|
||||
symlink to `../.agents/skills`, matching this repo's convention - do this
|
||||
once, after copying the whole set, not per-skill.
|
||||
|
||||
### Step 9 - Report
|
||||
|
||||
Tell the user:
|
||||
- The resolved target path.
|
||||
- The directory tree created.
|
||||
- Whether `AGENTS.md`/`CLAUDE.md` were written or (per Step 2) skipped/merged.
|
||||
- Which skills were copied (the default five, plus anything explicitly added).
|
||||
- Next step: "Drop material into `raw/inbox/` and say 'Ingest' to populate the wiki for the first time."
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Target is the current repo itself, or a parent/ancestor of it** - refuse
|
||||
and explain why: this would either overwrite the live KB you're running
|
||||
from, or nest a KB inside itself. Ask for a genuinely separate path.
|
||||
- **Target is on a different filesystem/drive or requires elevated
|
||||
permissions** - if directory creation fails, report the exact error
|
||||
rather than retrying with escalated permissions.
|
||||
- **User wants only *some* of the wiki template files** (e.g. just the
|
||||
directory structure, no `AGENTS.md`) - honor that; the steps above are
|
||||
the default full scaffold, not an all-or-nothing bundle.
|
||||
- **This repo's own `AGENTS.md` or template `wiki/` files have since
|
||||
drifted from each other** (e.g. one mentions a directory the other
|
||||
doesn't) - fix the drift in *this* repo first if noticed, then copy the
|
||||
corrected version. Don't propagate a known inconsistency into a new KB.
|
||||
147
.agents/skills/export-starlight/SKILL.md
Normal file
147
.agents/skills/export-starlight/SKILL.md
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
---
|
||||
name: export-starlight
|
||||
description: Export the local wiki/ knowledge base into an Astro + Starlight-consumable form at outputs/starlight/, producing a human-readable documentation website. Use when the user asks to "export the wiki to Starlight", "generate the docs site", "export as Astro Starlight", "build a human-readable wiki site", or "publish the knowledge base as a website".
|
||||
---
|
||||
|
||||
# Export to Astro Starlight skill
|
||||
|
||||
## Purpose
|
||||
|
||||
Regenerate `outputs/starlight/` from the current `wiki/` tree so it can be
|
||||
built and served as an Astro + Starlight documentation website — a
|
||||
human-readable, browsable version of this knowledge base (unlike
|
||||
`export-okf`, which targets machine/tool consumption). This is a one-way,
|
||||
on-demand export — `wiki/` stays the authoritative source; `outputs/starlight/`
|
||||
is always a derived artifact of it, never edited by hand and never fed back
|
||||
in.
|
||||
|
||||
This skill only runs when explicitly invoked — it is deliberately not part
|
||||
of the always-loaded `CLAUDE.md`/`AGENTS.md` Ingest/Lint workflows.
|
||||
|
||||
The heavy lifting (frontmatter remapping, link rewriting, asset copying,
|
||||
sidebar generation) is done by a deterministic Python script, not by
|
||||
reading and rewriting every page by hand — the wiki now has 60+ pages, and
|
||||
a mechanical transform like this belongs in code, not in per-page model
|
||||
reasoning. Nothing in this skill needs an LLM to run correctly; the model's
|
||||
job is just to ask the two setup questions below, invoke the script, and
|
||||
relay its report.
|
||||
|
||||
## Trigger phrases
|
||||
|
||||
Use this skill when the user says things like:
|
||||
- "export the wiki to Starlight" / "export as Astro Starlight"
|
||||
- "generate the docs site" / "build the Starlight site"
|
||||
- "publish the knowledge base as a website"
|
||||
- "sync outputs/starlight"
|
||||
|
||||
## How to run this skill
|
||||
|
||||
### Step 1 — Ask the two setup questions, every time
|
||||
|
||||
The user has explicitly said they want to choose these on every invocation
|
||||
— do not assume a default or reuse an answer from a previous run. Use
|
||||
`AskUserQuestion` with:
|
||||
|
||||
1. **Export scope:**
|
||||
- "Full runnable scaffold" → `--mode full` — regenerates the whole
|
||||
`outputs/starlight/` project (package.json, astro.config.mjs with
|
||||
sidebar, tsconfig.json, src/content.config.ts, splash `index.mdx`)
|
||||
plus all content. Fully deletes and rebuilds `outputs/starlight/`.
|
||||
- "Content only" → `--mode content-only` — regenerates only
|
||||
`src/content/docs/**`, `public/assets/**`, and a `SIDEBAR-SNIPPET.md`
|
||||
with the sidebar config to paste in. Leaves any existing scaffold
|
||||
files (package.json, astro.config.mjs, tsconfig.json,
|
||||
content.config.ts, index.mdx) untouched — use this if the user is
|
||||
dropping the output into a Starlight project they already maintain
|
||||
elsewhere.
|
||||
|
||||
2. **Meta pages:**
|
||||
- "Include them" → add `--include-meta` — publishes `wiki/log.md` as a
|
||||
Change Log page and `wiki/error-book.md` as an Error Book page.
|
||||
- "Exclude them" → omit the flag — only entities, sources, graph, and
|
||||
overview get published.
|
||||
|
||||
### Step 2 — Run the script
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
python3 "<skill-dir>/scripts/export_starlight.py" --mode <full|content-only> [--include-meta]
|
||||
```
|
||||
|
||||
Resolve `<skill-dir>` to this skill's own directory. Optional extra flag:
|
||||
`--site-title "Custom Title"` overrides the auto-derived site title (which
|
||||
otherwise comes from `wiki/overview.md`'s H1 — a serviceable but generic
|
||||
default like "Knowledge Base Overview"). Consider offering this to the user
|
||||
rather than always silently accepting the default, since it's the one
|
||||
piece of branding a reader sees first.
|
||||
|
||||
### Step 3 — Relay the script's report
|
||||
|
||||
The script prints, and you should summarize back to the user:
|
||||
- Mode used and page count exported.
|
||||
- Assets (images) copied.
|
||||
- Which meta pages were skipped (if `--include-meta` was omitted).
|
||||
- Any unresolved `[[wikilinks]]` (bare wikilinks with no matching page
|
||||
title — these get silently degraded to plain text in the output; a
|
||||
small number is normal wiki-content noise, but flag the list so the user
|
||||
can fix the source page if it's not).
|
||||
- Any broken internal links the validation pass found (a link that was
|
||||
rewritten to a route with no corresponding generated file) — this is a
|
||||
real problem in the source `wiki/` content (a stale or malformed
|
||||
cross-reference), not a bug in the export, and is worth reporting back
|
||||
as something to fix at the source.
|
||||
- A reminder that `outputs/starlight/` has been regenerated on disk but
|
||||
not staged, committed, or `npm install`ed — those are separate steps for
|
||||
the user. For `--mode full`, the site is immediately runnable with
|
||||
`cd outputs/starlight && npm install && npm run dev`.
|
||||
|
||||
## What the transform does (for context, not something you need to re-derive)
|
||||
|
||||
- **Frontmatter:** `tldr` → `description`; the first `# H1` in the body
|
||||
becomes Starlight's required `title` (and is removed from the body, since
|
||||
Starlight renders `title` as the page heading itself — leaving the H1 in
|
||||
would duplicate it). `type`/`confidence`/`quality`/`retention`/
|
||||
`last_updated`/`resource` have no native Starlight field, so they're
|
||||
rendered as a `:::note[Knowledge base metadata]` aside at the top of the
|
||||
page body instead of being dropped.
|
||||
- **Badges:** a page gets a `sidebar.badge` — "Superseded" (if
|
||||
`superseded_by` is set), else "Stale" (if `last_updated` +
|
||||
`freshness_window_days` has elapsed), else "Low confidence" (if
|
||||
`confidence` < 0.5) — priority in that order, one badge max.
|
||||
- **Links:** dual-linked cross-references (`[text](path) ([[Wikilink]])`,
|
||||
or the older `[[Wikilink]] / [text](path)` ordering) collapse to just the
|
||||
markdown link, since Starlight has no wikilink concept. Bare `[[Wikilinks]]`
|
||||
are resolved against every page's H1 title and converted to a proper link
|
||||
where a match exists, otherwise degraded to plain text. Root-absolute
|
||||
`/wiki/...` links become site routes (`/wiki/entities/foo.md` →
|
||||
`/entities/foo/`; a link to a directory's `index.md` maps to the bare
|
||||
directory route, e.g. `/entities/`). `linked/...`/`libs/...` cascade
|
||||
references (pointing outside any self-contained export) become plain
|
||||
text noting the path, not a broken link.
|
||||
- **Assets:** person photos referenced as `../assets/people/x.jpg` are
|
||||
copied to `public/assets/people/x.jpg` and rewritten to `/assets/people/x.jpg`.
|
||||
- **Graph:** `wiki/graph/edges.json` is not copied verbatim (Starlight
|
||||
doesn't render JSON as a page) — its content is fully rendered as a
|
||||
Markdown table appended to `graph/index.md`, sorted by `from`, so no
|
||||
information is lost.
|
||||
- **Sidebar:** one `autogenerate` group per top-level `wiki/` subdirectory
|
||||
(so new pages added to `entities/`/`sources`/`graph`/any future topic
|
||||
folder show up automatically on the next export without touching the
|
||||
sidebar config), plus explicit entries for the handful of root-level
|
||||
pages (Overview, the routing table, and the two optional meta pages).
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **A future `wiki/<newtopic>/` subdirectory:** handled automatically —
|
||||
the script discovers top-level `wiki/` subdirectories dynamically and
|
||||
adds a sidebar `autogenerate` group for each; no script changes needed.
|
||||
- **Re-running with no wiki changes:** should produce byte-identical
|
||||
output (directory listings are sorted before writing). If asset copying
|
||||
or link rewriting ever introduces nondeterminism, that's a bug in the
|
||||
script, not expected behavior.
|
||||
- **A wiki page with no `tldr`:** `description` is simply omitted from
|
||||
that page's frontmatter — Starlight tolerates a missing description.
|
||||
- **A wiki page with no H1:** falls back to a slugified filename as the
|
||||
title (e.g. `foo-bar.md` → "Foo Bar"), same fallback rule `export-okf`
|
||||
uses.
|
||||
573
.agents/skills/export-starlight/scripts/export_starlight.py
Normal file
573
.agents/skills/export-starlight/scripts/export_starlight.py
Normal file
|
|
@ -0,0 +1,573 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Export wiki/ into an Astro + Starlight-consumable form at outputs/starlight/.
|
||||
|
||||
Usage:
|
||||
python3 export_starlight.py --mode {full,content-only} [--include-meta] [--today YYYY-MM-DD]
|
||||
|
||||
Run from the repository root (the directory containing wiki/ and outputs/).
|
||||
|
||||
--mode full regenerate the whole outputs/starlight/ project: scaffold
|
||||
(package.json, astro.config.mjs, tsconfig.json,
|
||||
src/content.config.ts, splash index.mdx) plus content.
|
||||
--mode content-only regenerate only src/content/docs/** and public/assets/**
|
||||
(and a SIDEBAR-SNIPPET.md), leaving any existing scaffold
|
||||
files untouched. Use this if dropping the output into a
|
||||
Starlight project you already have.
|
||||
--include-meta also publish wiki/log.md (as Change Log) and
|
||||
wiki/error-book.md (as Error Book). Omit to keep those
|
||||
internal-only.
|
||||
--today override "today" for staleness checks (default: real
|
||||
today). Mainly useful for reproducible test runs.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path.cwd()
|
||||
WIKI = REPO_ROOT / "wiki"
|
||||
OUT = REPO_ROOT / "outputs" / "starlight"
|
||||
|
||||
RESERVED_ROOT_ROUTES = {
|
||||
"index.md": "routing-table",
|
||||
"overview.md": "overview",
|
||||
"log.md": "changelog",
|
||||
"error-book.md": "error-book",
|
||||
}
|
||||
|
||||
|
||||
def yaml_quote(s):
|
||||
s = str(s).replace("\\", "\\\\").replace('"', '\\"')
|
||||
return f'"{s}"'
|
||||
|
||||
|
||||
def parse_frontmatter(text):
|
||||
"""Minimal parser for this repo's flat key: value frontmatter (no lists/nesting)."""
|
||||
if not text.startswith("---\n"):
|
||||
return {}, text
|
||||
end = text.find("\n---", 4)
|
||||
if end == -1:
|
||||
return {}, text
|
||||
fm_block = text[4:end]
|
||||
body = text[end + 4 :].lstrip("\n")
|
||||
fm = {}
|
||||
for line in fm_block.splitlines():
|
||||
line = line.strip()
|
||||
if not line or ":" not in line:
|
||||
continue
|
||||
key, _, value = line.partition(":")
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
|
||||
value = value[1:-1]
|
||||
fm[key] = value
|
||||
return fm, body
|
||||
|
||||
|
||||
def extract_h1(body):
|
||||
lines = body.splitlines()
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("# "):
|
||||
title = stripped[2:].strip()
|
||||
remaining = lines[:i] + lines[i + 1 :]
|
||||
# drop one immediately-following blank line so we don't leave a gap
|
||||
if i < len(remaining) and remaining[i].strip() == "":
|
||||
remaining = remaining[:i] + remaining[i + 1 :]
|
||||
return title, "\n".join(remaining).lstrip("\n")
|
||||
if stripped:
|
||||
break # first non-blank line isn't an H1 - stop looking
|
||||
return None, body
|
||||
|
||||
|
||||
def wiki_relpath_to_route(relpath):
|
||||
"""relpath is a path string relative to wiki/, e.g. 'entities/adam-betts.md'."""
|
||||
relpath = relpath.lstrip("/")
|
||||
if relpath in RESERVED_ROOT_ROUTES:
|
||||
return "/" + RESERVED_ROOT_ROUTES[relpath] + "/"
|
||||
p = Path(relpath)
|
||||
parts = list(p.parts)
|
||||
if parts and parts[-1].endswith(".md"):
|
||||
parts[-1] = parts[-1][:-3]
|
||||
if parts and parts[-1] == "index":
|
||||
parts = parts[:-1]
|
||||
if not parts:
|
||||
return "/"
|
||||
return "/" + "/".join(parts) + "/"
|
||||
|
||||
|
||||
def out_path_for(relpath):
|
||||
"""Where a wiki/<relpath> file's exported page lives under src/content/docs/."""
|
||||
if relpath in RESERVED_ROOT_ROUTES:
|
||||
return Path(RESERVED_ROOT_ROUTES[relpath] + ".md")
|
||||
return Path(relpath)
|
||||
|
||||
|
||||
def collect_wiki_pages():
|
||||
pages = [] # list of (relpath_str, absolute_path)
|
||||
for path in sorted(WIKI.rglob("*.md")):
|
||||
relpath = path.relative_to(WIKI).as_posix()
|
||||
pages.append((relpath, path))
|
||||
return pages
|
||||
|
||||
|
||||
def build_title_index(pages):
|
||||
"""title -> route, for resolving bare [[Wikilinks]]."""
|
||||
index = {}
|
||||
for relpath, path in pages:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
_, body = parse_frontmatter(text)
|
||||
title, _ = extract_h1(body)
|
||||
route = wiki_relpath_to_route(relpath)
|
||||
if title:
|
||||
index.setdefault(title, route)
|
||||
index.setdefault(title.lower(), route)
|
||||
slug = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")
|
||||
if slug:
|
||||
index.setdefault(slug, route)
|
||||
rel_no_ext = relpath[:-3] if relpath.endswith(".md") else relpath
|
||||
index.setdefault(rel_no_ext, route)
|
||||
index.setdefault(rel_no_ext.lower(), route)
|
||||
stem = Path(relpath).stem
|
||||
if stem != "index":
|
||||
index.setdefault(stem, route)
|
||||
index.setdefault(stem.lower(), route)
|
||||
return index
|
||||
|
||||
|
||||
WIKILINK_ADJACENT_RE = re.compile(r"(\[[^\]]+\]\([^)]+\))\s*\(\[\[[^\]]+\]\]\)")
|
||||
WIKILINK_BEFORE_LINK_RE = re.compile(r"\[\[[^\]]+\]\]\s*/\s*(\[[^\]]+\]\([^)]+\))")
|
||||
BARE_WIKILINK_RE = re.compile(r"\[\[([^\]]+)\]\]")
|
||||
WIKI_ABS_NON_MD_LINK_RE = re.compile(r"\[([^\]]+)\]\(/wiki/([^)#]+)(#[^)]*)?\)")
|
||||
WIKI_ABS_LINK_RE = re.compile(r"\]\(/wiki/([^)#]+)(#[^)]*)?\)")
|
||||
CASCADE_LINK_RE = re.compile(r"\[([^\]]+)\]\((?:linked|libs)/[^)]+\)")
|
||||
IMAGE_REL_RE = re.compile(r"!\[([^\]]*)\]\(\.\./assets/([^)]+)\)")
|
||||
|
||||
|
||||
def rewrite_body(body, title_index, unresolved_wikilinks, copied_assets):
|
||||
# 1. strip wikilinks that ride alongside a markdown link (either order:
|
||||
# "[text](path) ([[Wikilink]])" or the legacy "[[Wikilink]] / [text](path)")
|
||||
body = WIKILINK_ADJACENT_RE.sub(r"\1", body)
|
||||
body = WIKILINK_BEFORE_LINK_RE.sub(r"\1", body)
|
||||
|
||||
# 2. resolve (or strip) any remaining bare [[Wikilinks]]
|
||||
def _bare(m):
|
||||
text = m.group(1)
|
||||
route = title_index.get(text) or title_index.get(text.lower())
|
||||
if route:
|
||||
return f"[{text}]({route})"
|
||||
unresolved_wikilinks.append(text)
|
||||
return text
|
||||
|
||||
body = BARE_WIKILINK_RE.sub(_bare, body)
|
||||
|
||||
# 3. defuse /wiki/... links to non-Markdown source files not exported as pages
|
||||
def _abs_non_md_link(m):
|
||||
text = m.group(1)
|
||||
relpath = m.group(2)
|
||||
if relpath.endswith(".md"):
|
||||
return m.group(0)
|
||||
if relpath == "graph/edges.json":
|
||||
return f"`{text}` (rendered below)"
|
||||
return f"{text} (source file: `/wiki/{relpath}`)"
|
||||
|
||||
body = WIKI_ABS_NON_MD_LINK_RE.sub(_abs_non_md_link, body)
|
||||
|
||||
# 4. rewrite /wiki/... absolute links to site routes
|
||||
def _abs_link(m):
|
||||
relpath = m.group(1)
|
||||
frag = m.group(2) or ""
|
||||
route = wiki_relpath_to_route(relpath)
|
||||
if frag:
|
||||
route = route.rstrip("/") + "/" + frag
|
||||
return f"]({route})"
|
||||
|
||||
body = WIKI_ABS_LINK_RE.sub(_abs_link, body)
|
||||
|
||||
# 5. defuse linked/ and libs/ cascade references (not part of this export)
|
||||
def _cascade(m):
|
||||
text, target = m.group(1), m.group(0)
|
||||
path = re.search(r"\((?:linked|libs)/([^)]+)\)", target).group(0)[1:-1]
|
||||
return f"{text} (upstream reference: `{path}`, not included in this export)"
|
||||
|
||||
body = CASCADE_LINK_RE.sub(_cascade, body)
|
||||
|
||||
# 6. copy relative person/asset images and rewrite to /assets/...
|
||||
def _image(m):
|
||||
alt, rel = m.group(1), m.group(2)
|
||||
src = WIKI / "assets" / rel
|
||||
if src.is_file():
|
||||
dest = OUT / "public" / "assets" / rel
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(src, dest)
|
||||
copied_assets.append(rel)
|
||||
return f""
|
||||
|
||||
body = IMAGE_REL_RE.sub(_image, body)
|
||||
|
||||
return body
|
||||
|
||||
|
||||
def build_metadata_block(fm, today):
|
||||
lines = []
|
||||
meta_bits = []
|
||||
for label, key in (("Type", "type"), ("Confidence", "confidence"),
|
||||
("Quality", "quality"), ("Last updated", "last_updated"),
|
||||
("Retention", "retention")):
|
||||
if fm.get(key):
|
||||
meta_bits.append(f"**{label}:** {fm[key]}")
|
||||
if meta_bits:
|
||||
lines.append(":::note[Knowledge base metadata]")
|
||||
lines.append(" · ".join(meta_bits))
|
||||
lines.append(":::")
|
||||
lines.append("")
|
||||
|
||||
if fm.get("resource"):
|
||||
resource = fm["resource"]
|
||||
if resource.startswith("http://") or resource.startswith("https://"):
|
||||
lines.append(f":::note[Source]\n[{resource}]({resource})\n:::\n")
|
||||
else:
|
||||
lines.append(f":::note[Source]\n`{resource}`\n:::\n")
|
||||
|
||||
if fm.get("superseded_by"):
|
||||
lines.append(
|
||||
f":::caution[Superseded]\nThis page is superseded by "
|
||||
f"`{fm['superseded_by']}`.\n:::\n"
|
||||
)
|
||||
if fm.get("supersedes"):
|
||||
lines.append(
|
||||
f":::tip[Supersedes]\nThis page supersedes `{fm['supersedes']}`.\n:::\n"
|
||||
)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def compute_badge(fm, today):
|
||||
if fm.get("superseded_by"):
|
||||
return {"text": "Superseded", "variant": "danger"}
|
||||
try:
|
||||
if fm.get("last_updated") and fm.get("freshness_window_days"):
|
||||
last_updated = date.fromisoformat(fm["last_updated"])
|
||||
window = int(fm["freshness_window_days"])
|
||||
if (today - last_updated).days > window:
|
||||
return {"text": "Stale", "variant": "caution"}
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
if fm.get("confidence") and float(fm["confidence"]) < 0.5:
|
||||
return {"text": "Low confidence", "variant": "note"}
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def render_page(relpath, path, title_index, today, report):
|
||||
text = path.read_text(encoding="utf-8")
|
||||
fm, body = parse_frontmatter(text)
|
||||
h1_title, body = extract_h1(body)
|
||||
title = h1_title or Path(relpath).stem.replace("-", " ").title()
|
||||
|
||||
unresolved = []
|
||||
body = rewrite_body(body, title_index, unresolved, report["copied_assets"])
|
||||
report["unresolved_wikilinks"].extend(
|
||||
f"{relpath}: [[{t}]]" for t in unresolved
|
||||
)
|
||||
|
||||
meta_block = build_metadata_block(fm, today)
|
||||
badge = compute_badge(fm, today)
|
||||
|
||||
fm_lines = ["---", f"title: {yaml_quote(title)}"]
|
||||
if fm.get("tldr"):
|
||||
fm_lines.append(f"description: {yaml_quote(fm['tldr'])}")
|
||||
if badge:
|
||||
fm_lines.append("sidebar:")
|
||||
fm_lines.append(" badge:")
|
||||
fm_lines.append(f" text: {yaml_quote(badge['text'])}")
|
||||
fm_lines.append(f" variant: {badge['variant']}")
|
||||
fm_lines.append("---")
|
||||
|
||||
out_body = (meta_block + "\n" + body) if meta_block else body
|
||||
return "\n".join(fm_lines) + "\n\n" + out_body.strip() + "\n"
|
||||
|
||||
|
||||
def normalize_edges(raw_edges):
|
||||
if isinstance(raw_edges, dict):
|
||||
raw_edges = raw_edges.get("edges", [])
|
||||
if not isinstance(raw_edges, list):
|
||||
return []
|
||||
|
||||
normalized = []
|
||||
for edge in raw_edges:
|
||||
if not isinstance(edge, dict):
|
||||
continue
|
||||
normalized.append(
|
||||
{
|
||||
"from": edge.get("from") or edge.get("source") or "",
|
||||
"type": edge.get("type") or edge.get("relationship") or "",
|
||||
"to": edge.get("to") or edge.get("target") or "",
|
||||
"note": edge.get("note") or edge.get("evidence") or "",
|
||||
"source_files": edge.get("source_files") or [],
|
||||
}
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
def table_cell(value):
|
||||
return str(value).replace("|", "\\|").replace("\n", " ")
|
||||
|
||||
|
||||
def render_edges_table(edges):
|
||||
edges = normalize_edges(edges)
|
||||
if not edges:
|
||||
return "\n*No edges recorded yet.*\n"
|
||||
rows = sorted(edges, key=lambda e: (e.get("from", ""), e.get("type", "")))
|
||||
lines = ["", "## All relationships", "",
|
||||
"| From | Relationship | To | Evidence | Sources |", "|---|---|---|---|---|"]
|
||||
for e in rows:
|
||||
sources = ", ".join(f"`{table_cell(s)}`" for s in e.get("source_files", []))
|
||||
lines.append(
|
||||
f"| `{table_cell(e.get('from', ''))}` | {table_cell(e.get('type', ''))} | "
|
||||
f"`{table_cell(e.get('to', ''))}` | {table_cell(e.get('note', ''))} | {sources} |"
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def derive_site_title():
|
||||
overview = WIKI / "overview.md"
|
||||
if overview.is_file():
|
||||
_, body = parse_frontmatter(overview.read_text(encoding="utf-8"))
|
||||
title, _ = extract_h1(body)
|
||||
if title:
|
||||
return title
|
||||
return REPO_ROOT.name
|
||||
|
||||
|
||||
SPLASH_TEMPLATE = """---
|
||||
title: {title}
|
||||
description: {description}
|
||||
template: splash
|
||||
hero:
|
||||
tagline: {tagline}
|
||||
actions:
|
||||
- text: Browse entities
|
||||
link: /entities/
|
||||
icon: right-arrow
|
||||
- text: Browse sources
|
||||
link: /sources/
|
||||
icon: right-arrow
|
||||
- text: Knowledge base index
|
||||
link: /routing-table/
|
||||
icon: document
|
||||
variant: minimal
|
||||
---
|
||||
|
||||
import {{ CardGrid, Card }} from '@astrojs/starlight/components';
|
||||
|
||||
<CardGrid>
|
||||
\t<Card title="Entities" icon="document">
|
||||
\t\tPeople, organisations, products and concepts tracked in this knowledge base.
|
||||
\t</Card>
|
||||
\t<Card title="Sources" icon="open-book">
|
||||
\t\tOne-page summaries of ingested raw material, with provenance back to the original source.
|
||||
\t</Card>
|
||||
\t<Card title="Graph" icon="random">
|
||||
\t\tTyped relationships between entities and sources - who works for whom, what depends on what, what contradicts what.
|
||||
\t</Card>
|
||||
</CardGrid>
|
||||
"""
|
||||
|
||||
PACKAGE_JSON_TEMPLATE = """{{
|
||||
"name": "{name}",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {{
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview"
|
||||
}},
|
||||
"dependencies": {{
|
||||
"@astrojs/starlight": "^0.41.3",
|
||||
"astro": "^7.0.9"
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
TSCONFIG_TEMPLATE = """{
|
||||
"extends": "astro/tsconfigs/strict"
|
||||
}
|
||||
"""
|
||||
|
||||
CONTENT_CONFIG_TEMPLATE = """import { defineCollection } from 'astro:content';
|
||||
import { docsLoader } from '@astrojs/starlight/loaders';
|
||||
import { docsSchema } from '@astrojs/starlight/schema';
|
||||
|
||||
export const collections = {
|
||||
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
||||
};
|
||||
"""
|
||||
|
||||
|
||||
def build_sidebar_js(top_dirs, include_meta):
|
||||
items = []
|
||||
items.append(" { label: 'Knowledge Base Index', slug: 'routing-table' },")
|
||||
items.append(" { label: 'Overview', slug: 'overview' },")
|
||||
if include_meta:
|
||||
items.append(" { label: 'Error Book', slug: 'error-book' },")
|
||||
items.append(" { label: 'Change Log', slug: 'changelog' },")
|
||||
for d in sorted(top_dirs):
|
||||
label = d.replace("-", " ").title()
|
||||
items.append(
|
||||
f" {{ label: '{label}', items: [{{ autogenerate: {{ directory: '{d}' }} }}] }},"
|
||||
)
|
||||
return "[\n" + "\n".join(items) + "\n ]"
|
||||
|
||||
|
||||
def astro_config_template(site_title, top_dirs, include_meta):
|
||||
sidebar_js = build_sidebar_js(top_dirs, include_meta)
|
||||
return f"""import {{ defineConfig }} from 'astro/config';
|
||||
import starlight from '@astrojs/starlight';
|
||||
|
||||
export default defineConfig({{
|
||||
integrations: [
|
||||
starlight({{
|
||||
title: {yaml_quote(site_title)},
|
||||
sidebar: {sidebar_js},
|
||||
}}),
|
||||
],
|
||||
}});
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--mode", choices=["full", "content-only"], required=True)
|
||||
parser.add_argument("--include-meta", action="store_true")
|
||||
parser.add_argument("--today", default=None)
|
||||
parser.add_argument(
|
||||
"--site-title", default=None,
|
||||
help="Override the site title (default: derived from wiki/overview.md's H1)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not WIKI.is_dir():
|
||||
sys.exit(f"No wiki/ directory found under {REPO_ROOT} - run this from the repo root.")
|
||||
|
||||
today = date.fromisoformat(args.today) if args.today else date.today()
|
||||
|
||||
pages = collect_wiki_pages()
|
||||
title_index = build_title_index(pages)
|
||||
|
||||
docs_dir = OUT / "src" / "content" / "docs"
|
||||
assets_dir = OUT / "public" / "assets"
|
||||
|
||||
if args.mode == "full" and OUT.exists():
|
||||
shutil.rmtree(OUT)
|
||||
else:
|
||||
if docs_dir.exists():
|
||||
shutil.rmtree(docs_dir)
|
||||
if assets_dir.exists():
|
||||
shutil.rmtree(assets_dir)
|
||||
docs_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
report = {"copied_assets": [], "unresolved_wikilinks": [], "pages": 0, "skipped_meta": []}
|
||||
|
||||
top_dirs = set()
|
||||
edges_path = WIKI / "graph" / "edges.json"
|
||||
edges = json.loads(edges_path.read_text(encoding="utf-8")) if edges_path.is_file() else []
|
||||
|
||||
for relpath, path in pages:
|
||||
if relpath in ("log.md", "error-book.md") and not args.include_meta:
|
||||
report["skipped_meta"].append(relpath)
|
||||
continue
|
||||
|
||||
rendered = render_page(relpath, path, title_index, today, report)
|
||||
|
||||
if relpath == "graph/index.md":
|
||||
rendered = rendered.rstrip("\n") + "\n" + render_edges_table(edges)
|
||||
|
||||
dest = docs_dir / out_path_for(relpath)
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest.write_text(rendered, encoding="utf-8")
|
||||
report["pages"] += 1
|
||||
|
||||
parts = Path(relpath).parts
|
||||
if len(parts) > 1:
|
||||
top_dirs.add(parts[0])
|
||||
|
||||
site_title = args.site_title or derive_site_title()
|
||||
|
||||
if args.mode == "full":
|
||||
(OUT / "package.json").write_text(
|
||||
PACKAGE_JSON_TEMPLATE.format(name=re.sub(r"[^a-z0-9-]", "-", site_title.lower())),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(OUT / "tsconfig.json").write_text(TSCONFIG_TEMPLATE, encoding="utf-8")
|
||||
(OUT / "src" / "content.config.ts").write_text(CONTENT_CONFIG_TEMPLATE, encoding="utf-8")
|
||||
(OUT / "astro.config.mjs").write_text(
|
||||
astro_config_template(site_title, top_dirs, args.include_meta), encoding="utf-8"
|
||||
)
|
||||
overview_fm, overview_body = ({}, "")
|
||||
overview_path = WIKI / "overview.md"
|
||||
if overview_path.is_file():
|
||||
overview_fm, overview_body = parse_frontmatter(overview_path.read_text(encoding="utf-8"))
|
||||
tagline = overview_fm.get("tldr", "A cascade knowledge base.")
|
||||
(docs_dir / "index.mdx").write_text(
|
||||
SPLASH_TEMPLATE.format(
|
||||
title=yaml_quote(site_title),
|
||||
description=yaml_quote(tagline),
|
||||
tagline=yaml_quote(tagline),
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
else:
|
||||
snippet = (
|
||||
"# Sidebar snippet\n\n"
|
||||
"Paste this into your own `astro.config.mjs`, inside "
|
||||
"`starlight({ ... })`:\n\n"
|
||||
"```js\n"
|
||||
f"sidebar: {build_sidebar_js(top_dirs, args.include_meta)},\n"
|
||||
"```\n\n"
|
||||
"Copy `src/content/docs/**` into your project's equivalent "
|
||||
"directory, and `public/assets/**` into your project's "
|
||||
"`public/assets/`.\n"
|
||||
)
|
||||
(OUT / "SIDEBAR-SNIPPET.md").write_text(snippet, encoding="utf-8")
|
||||
|
||||
# Validate internal links resolve to a generated file
|
||||
broken = []
|
||||
for relpath, _ in pages:
|
||||
if relpath in ("log.md", "error-book.md") and not args.include_meta:
|
||||
continue
|
||||
dest = docs_dir / out_path_for(relpath)
|
||||
text = dest.read_text(encoding="utf-8")
|
||||
for m in re.finditer(r"\]\((/[a-zA-Z0-9_\-./]+/)\)", text):
|
||||
route = m.group(1)
|
||||
candidate_dir = docs_dir / route.strip("/")
|
||||
candidate_file = docs_dir / (route.strip("/") + ".md")
|
||||
candidate_index = candidate_dir / "index.md"
|
||||
candidate_mdx = docs_dir / (route.strip("/") + ".mdx") if route.strip("/") else docs_dir / "index.mdx"
|
||||
if not (candidate_file.is_file() or candidate_index.is_file() or candidate_mdx.is_file()):
|
||||
broken.append(f"{relpath} -> {route}")
|
||||
|
||||
print(f"mode: {args.mode}")
|
||||
print(f"pages exported: {report['pages']}")
|
||||
print(f"assets copied: {len(report['copied_assets'])}")
|
||||
if report["skipped_meta"]:
|
||||
print(f"meta pages skipped (--include-meta not set): {', '.join(report['skipped_meta'])}")
|
||||
if report["unresolved_wikilinks"]:
|
||||
print(f"unresolved [[wikilinks]] (stripped to plain text): {len(report['unresolved_wikilinks'])}")
|
||||
for w in report["unresolved_wikilinks"]:
|
||||
print(f" - {w}")
|
||||
if broken:
|
||||
print(f"WARNING: {len(broken)} internal link(s) do not resolve to a generated page:")
|
||||
for b in broken:
|
||||
print(f" - {b}")
|
||||
print(f"output: {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
177
.agents/skills/project-summary/SKILL.md
Normal file
177
.agents/skills/project-summary/SKILL.md
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
---
|
||||
name: project-summary
|
||||
description: Generate or refresh a one-to-two-page project overview at the repo root, synthesized entirely from the current wiki/ contents — overview paragraph, project state, actions and their status, risks, and assumptions. Use when the user asks for "a project summary", "project overview", "give me the state of the project", "where do things stand", "summarize the project", or wants a quick top-level snapshot without reading the whole wiki.
|
||||
---
|
||||
|
||||
# Project summary skill
|
||||
|
||||
## Purpose
|
||||
|
||||
Produce a short, standalone snapshot of the project at repo root - something
|
||||
a busy stakeholder can read in two minutes without opening `wiki/` at all.
|
||||
Everything in it must trace back to what the wiki actually says; this is a
|
||||
synthesis of existing knowledge, not a place to add new claims or guesses.
|
||||
|
||||
This is a one-way, on-demand export - `wiki/` stays the authoritative
|
||||
source; the generated overview file is always a derived, refreshable
|
||||
snapshot. Re-running the skill regenerates it from whatever the wiki says
|
||||
*now*; it does not append to the previous version.
|
||||
|
||||
## Trigger phrases
|
||||
|
||||
Use this skill when the user says things like:
|
||||
- "give me a project summary" / "project overview"
|
||||
- "where do things stand" / "what's the state of the project"
|
||||
- "summarize the project" / "one-pager" / "two-pager"
|
||||
- "catch me up on the project"
|
||||
|
||||
## How to run this skill
|
||||
|
||||
### Step 1 - Identify the project
|
||||
|
||||
Read `wiki/index.md` and `wiki/entities/index.md` first (small, cheap,
|
||||
gives the lay of the land). Then find every entity page whose frontmatter
|
||||
has `type: project`.
|
||||
|
||||
- **Exactly one** - that's the subject, proceed.
|
||||
- **None found** - tell the user no `type: project` entity exists yet in
|
||||
the wiki, and ask whether to point at a specific entity page instead or
|
||||
run Ingest first. Do not fabricate a project summary from thin air.
|
||||
- **More than one** - list them (title + `tldr`) and ask which one to
|
||||
summarize, or whether they want a separate overview file for each.
|
||||
|
||||
### Step 2 - Gather context via the graph, not just one page
|
||||
|
||||
Read the chosen project entity page in full. Then read
|
||||
`wiki/graph/edges.json` and pull every edge where this entity is the
|
||||
`source` or `target` - these are its direct dependencies. Read each of
|
||||
those pages in full too. If a dependency page itself has further edges
|
||||
that look load-bearing (e.g. another concept it depends on), follow one
|
||||
more hop - but stop there. This is a snapshot, not a full graph traversal;
|
||||
two hops is enough to be accurate without pulling in the entire wiki.
|
||||
|
||||
Also check the single most recent `workload/YYYY-MM-DD_summary.md` (by
|
||||
filename date) for anything very recent that may not have made it into the
|
||||
wiki yet. Treat the wiki as authoritative if the two conflict - workload
|
||||
files are a running log, not a maintained source of truth - but note a
|
||||
real discrepancy in Project State rather than silently picking one.
|
||||
|
||||
### Step 3 - Extract and classify
|
||||
|
||||
Work through everything gathered in Step 2 and sort content into five
|
||||
buckets. Do not pad any section to look complete - a short, honest section
|
||||
is better than filler, and it's fine for Risks or Assumptions to be thin or
|
||||
even say "none currently recorded" if that's genuinely the case.
|
||||
|
||||
1. **Overview** - one short paragraph: what the project is, who's involved
|
||||
(client / delivery org / key roles), and what it's for. This is
|
||||
scene-setting, not a list of facts.
|
||||
|
||||
2. **Project State** - the current concrete status: what phase things are
|
||||
in, what's confirmed vs. pending, key dates. Pull directly from
|
||||
"Current Shape" / "Management Summary" / status-like sections on the
|
||||
entity pages gathered.
|
||||
|
||||
3. **Actions & Status** - every concrete to-do, next step, or open item
|
||||
found across the gathered pages (commonly under headings like "Open
|
||||
Points" or "Open items"). Dedupe overlapping items from different pages
|
||||
into one line. Assign a status to each from this fixed vocabulary based
|
||||
on the page's own wording - don't invent a finer-grained scheme:
|
||||
- **Done** - completed, confirmed, signed, resolved
|
||||
- **In progress** - actively being worked, partially complete
|
||||
- **Blocked** - explicitly waiting on something else before it can move
|
||||
- **Open** - identified but not yet started or scheduled
|
||||
|
||||
4. **Risks** - things that could hurt the project if they go wrong or stay
|
||||
unresolved. Look for language like "risk", "concern", "gap", "blocked
|
||||
on", "unresolved", "dependent on", "may not stick", "left dependent on."
|
||||
For each: what the risk is, why it matters, and - if the source says so
|
||||
- what would mitigate or resolve it.
|
||||
|
||||
5. **Assumptions** - things the current plan depends on being true but that
|
||||
aren't yet confirmed. Look for language like "expected", "should",
|
||||
"likely", "assuming", "TBC", "to be confirmed", "time-bound", "reconfirm
|
||||
before acting." For each: what's assumed, and what would need to happen
|
||||
to confirm or invalidate it.
|
||||
|
||||
An item can legitimately touch both Risks and Assumptions (an unconfirmed
|
||||
assumption is often also a risk if it turns out false) - when that
|
||||
happens, put it wherever it fits better and don't duplicate it in both.
|
||||
|
||||
If there's more candidate material than fits a one-to-two-pager, prioritize
|
||||
by what the source pages themselves flag as higher-confidence, higher-
|
||||
retention, and most recently updated - this is a snapshot of what matters
|
||||
most right now, not an exhaustive appendix.
|
||||
|
||||
### Step 4 - Write the file
|
||||
|
||||
Target length: one to two pages (roughly 500-900 words total, including
|
||||
bullets - err short over long). Write to `PROJECT-OVERVIEW.md` at the repo
|
||||
root, overwriting it completely if it already exists (this is a refreshable
|
||||
snapshot, never hand-append to a previous version).
|
||||
|
||||
Use this structure:
|
||||
|
||||
```markdown
|
||||
# [Project Name] - Project Overview
|
||||
|
||||
*Auto-generated from `wiki/` - do not hand-edit; regenerating this skill overwrites this file. Last generated: YYYY-MM-DD.*
|
||||
|
||||
**At a glance:** [client] · [delivery org] · [one-line phase/status]
|
||||
|
||||
## Overview
|
||||
|
||||
[the synthesis paragraph]
|
||||
|
||||
## Project State
|
||||
|
||||
[status bullets or short paragraph]
|
||||
|
||||
## Actions & Status
|
||||
|
||||
| Action | Status |
|
||||
|---|---|
|
||||
| ... | Done / In progress / Blocked / Open |
|
||||
|
||||
## Risks
|
||||
|
||||
- **[risk]** - [why it matters] [mitigation if known]
|
||||
|
||||
## Assumptions
|
||||
|
||||
- **[assumption]** - [what would confirm/invalidate it]
|
||||
|
||||
---
|
||||
*Sources: [wiki pages used, as markdown links]*
|
||||
```
|
||||
|
||||
Use plain markdown links to the wiki pages actually used (e.g.
|
||||
`[Grant Thornton FDE engagement](wiki/entities/grant-thornton-fde-engagement.md)`)
|
||||
so a reader can jump to the full detail behind any line. Keep the source
|
||||
list to what was actually used, not every page in the wiki.
|
||||
|
||||
If more than one `type: project` entity was chosen for separate summaries
|
||||
(Step 1), name the files `PROJECT-OVERVIEW-<slug>.md` instead of the plain
|
||||
name, using a short kebab-case slug of the project's title.
|
||||
|
||||
### Step 5 - Report
|
||||
|
||||
Tell the user the file was written (path), give a one-line gist of what it
|
||||
found (e.g. "3 open actions, 2 risks, 1 assumption"), and mention it can be
|
||||
regenerated any time by re-running this skill - it will always reflect
|
||||
whatever the wiki says at that moment, not what it said today.
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Empty or near-empty project entity page** - write a short, honest
|
||||
Overview and State, and let Actions/Risks/Assumptions be genuinely short
|
||||
rather than inventing content to fill the template.
|
||||
- **Conflicting information between two source pages** - prefer the page
|
||||
with the more recent `last_updated`; if they're the same date, note the
|
||||
conflict briefly in Project State rather than silently picking one.
|
||||
- **`PROJECT-OVERVIEW.md` already exists** - overwrite it; this file is
|
||||
always meant to reflect the current wiki, not accumulate history. If the
|
||||
user wants history, that's what `wiki/log.md` and `workload/` are for.
|
||||
- **Re-running with no wiki changes since the last run** - should produce
|
||||
essentially the same content each time; don't introduce random variation
|
||||
in section content or ordering between runs.
|
||||
6
.claude/settings.json
Normal file
6
.claude/settings.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
]
|
||||
}
|
||||
}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,2 +1,5 @@
|
|||
libs/
|
||||
tmp/
|
||||
outputs/starlight
|
||||
outputs/okf
|
||||
.env
|
||||
|
|
@ -23,7 +23,9 @@ The root directory contains exactly seven top-level entries. You must maintain t
|
|||
│ # On-demand workflows beyond Ingest/Lint may be defined as Claude Code Skills under
|
||||
│ # `.claude/skills/` — check there before assuming a capability doesn't exist.
|
||||
├── raw/ # WRITTEN BY USER ONLY. Raw files, scratchpad notes, URLs, links.txt.
|
||||
│ └── inbox/ # Drop zone: unprocessed material the agent cleans on ingest.
|
||||
│ ├── inbox/ # Drop zone: unprocessed material the agent cleans on ingest.
|
||||
│ └── archive/ # AGENT MAINTAINED. Ingested raw material, filed by ingestion date.
|
||||
│ └── <YYYY-MM-DD>/ # One folder per ingestion date; holds every raw/inbox file processed that day.
|
||||
├── tmp/ # MANAGED BY AGENT. Temporary files, caches, intermediate processing artifacts (gitignored).
|
||||
├── wiki/ # MANAGED BY AGENT. The local, mutable, structured markdown wiki. Overlays linked/ and libs/.
|
||||
│ ├── index.md # Entry point / routing table with "Use when" triggers. Carries kb_schema_version.
|
||||
|
|
@ -115,7 +117,7 @@ retention: high|medium|low # How aggressively to deprioritize when old
|
|||
|
||||
When the user says "Ingest", "Sync the wiki", or "Update the Wiki" (for syncing this repo's own git history with its remote, see the sync-changes skill under `.claude/skills/` instead):
|
||||
|
||||
1. **Process Inbox:** Scan `raw/inbox/` for new material. Move processed items to `raw/` for archiving. If `raw/inbox/` is empty, scan `raw/` directly.
|
||||
1. **Process Inbox:** Scan `raw/inbox/` for new material. After ingesting, move each processed item to `raw/archive/<YYYY-MM-DD>/`, where the date is today's ingestion date (create the dated folder if it doesn't exist yet). If `raw/inbox/` is empty, scan `raw/` directly (excluding `raw/archive/`, which holds already-processed material).
|
||||
|
||||
2. **Consult Cascade:** Before writing anything, check if the entity already exists (local `wiki/` first, then each `linked/<name>/`, then each `libs/<name>/`). The local `wiki/` always wins. Upstream content is informative but can be overridden locally.
|
||||
|
||||
|
|
|
|||
225
CLAUDE.md
225
CLAUDE.md
|
|
@ -1,225 +0,0 @@
|
|||
# SYSTEM PROMPT: CASCADE KNOWLEDGE BASE ARCHITECT
|
||||
|
||||
## ROLE & PHILOSOPHY
|
||||
You are an autonomous Knowledge Architect Agent for a **Cascade Knowledge Base**. The system is designed as a layered stack: read-only upstream knowledge bases (symlinked in `linked/` and git-managed copies in `libs/`) form the foundation, and the local mutable knowledge base overlays on top. This means knowledge flows downward through the cascade — upstream truths are preserved, while you only ever modify the local layer.
|
||||
|
||||
If an entity exists in both the local wiki and any upstream KB, the local version takes precedence and overrides the upstream one.
|
||||
|
||||
You view directories as storage disks, context windows as RAM, and your processing loops as CPU cycles. Your sole objective is to build, maintain, and dynamically structure a comprehensive knowledge base, respecting the cascade priority rules at all times.
|
||||
|
||||
You possess full autonomy over local directory structure, file naming conventions, and cross-referencing. You must strictly adhere to the operational boundaries and file management rules detailed below.
|
||||
|
||||
---
|
||||
|
||||
## 1. DIRECTORY STRUCTURE
|
||||
The root directory contains exactly seven top-level entries. You must maintain this structure flawlessly:
|
||||
|
||||
```
|
||||
├── libs/ # GIT-MANAGED COPIES ONLY. Read-only external KBs copied into the repo (gitignored — populated by the user via git).
|
||||
│ └── <name>/ # Individual external knowledge base (immutable — never write here).
|
||||
├── linked/ # SYMLINKS ONLY. Each entry is a symbolic link to another KB root (read-only upstream source of truth).
|
||||
│ └── <name>/ # Individual upstream knowledge base (immutable — never write here).
|
||||
├── outputs/ # MANAGED BY AGENT. Generated artifacts, exports, compiled files produced from the wiki.
|
||||
│ # On-demand workflows beyond Ingest/Lint may be defined as Claude Code Skills under
|
||||
│ # `.claude/skills/` — check there before assuming a capability doesn't exist.
|
||||
├── raw/ # WRITTEN BY USER ONLY. Raw files, scratchpad notes, URLs, links.txt.
|
||||
│ └── inbox/ # Drop zone: unprocessed material the agent cleans on ingest.
|
||||
├── tmp/ # MANAGED BY AGENT. Temporary files, caches, intermediate processing artifacts (gitignored).
|
||||
├── wiki/ # MANAGED BY AGENT. The local, mutable, structured markdown wiki. Overlays linked/ and libs/.
|
||||
│ ├── index.md # Entry point / routing table with "Use when" triggers. Carries kb_schema_version.
|
||||
│ ├── overview.md # High-level map of the knowledge base.
|
||||
│ ├── log.md # AGENT LOG. Root rollup tracking wiki-level modifications (see Recursive Index & Log Convention).
|
||||
│ ├── error-book.md # AGENT MAINTAINED. Records compilation errors and derived constraints.
|
||||
│ ├── entities/ # AGENT POPULATED. Typed entity pages (people, projects, concepts, libraries). Has its own index.md.
|
||||
│ └── graph/ # AGENT MAINTAINED. Edge lists and relationship data for the knowledge graph. Has its own index.md.
|
||||
└── workload/ # MANAGED BY AGENT. Summaries of discussions and decisions.
|
||||
└── YYYY-MM-DD_summary.md
|
||||
```
|
||||
|
||||
### Cascade Lookup Priority
|
||||
When searching for any entity, concept, or file, use the following cascade (first match wins):
|
||||
|
||||
1. **Local wiki/** — highest priority; agent-written content overlays everything below.
|
||||
2. **linked/\<name\>/** — read-only upstream KBs mounted as symlinks, searched in alphabetical order.
|
||||
3. **libs/\<name\>/** — read-only external KBs copied via git, searched in alphabetical order.
|
||||
4. If no match is found anywhere, treat the entity as unknown.
|
||||
|
||||
You must **never** create, modify, move, or delete any file or directory inside `linked/` or `libs/`.
|
||||
|
||||
### Index-First Navigation
|
||||
When searching for information, always start by looking for `index.md` files.
|
||||
Read the index to discover what pages and subdirectories are available before
|
||||
drilling into individual files. Scan `index.md` across all layers:
|
||||
|
||||
1. **wiki/** — scan `wiki/index.md`, then recursively check any subdirectory `wiki/<topic>/index.md`.
|
||||
2. **linked/\<name\>/** — for each linked upstream KB, scan its root `index.md` and subdirectory indexes.
|
||||
3. **libs/\<name\>/** — same pattern: root index first, then subdirectory indexes as needed.
|
||||
|
||||
This avoids blind filesystem scans and uses the index as a curated table of contents — exactly as Karpathy's original pattern intended.
|
||||
|
||||
### Recursive Index & Log Convention
|
||||
Index-First Navigation only works if subdirectory indexes actually exist. Maintain them as follows:
|
||||
|
||||
- Every `wiki/` subdirectory that groups multiple pages (`entities/`, `graph/`, and any future topic folder) must contain its own `index.md`. It carries no frontmatter and is a flat bullet list of links, each with a one-line description mirroring the linked page's `tldr` — plus a link to any nested subdirectory.
|
||||
- A subdirectory may also keep its own `log.md` once it has enough independent change history to warrant one (a judgment call — typically once it holds several pages or changes on its own cadence, separate from the rest of the wiki). Entries follow the same reverse-chronological format as Rule B.
|
||||
- The root `wiki/log.md` stays the top-level rollup: it records changes made directly under `wiki/` (`index.md`, `overview.md`, `error-book.md`, directory-creation events) plus one pointer line whenever a subdirectory log absorbs a change, e.g. `- See wiki/entities/log.md for entity-page changes on this date.` Each change gets exactly one home log — never record the same change in both.
|
||||
|
||||
### Lazy-Loading with "Use When" Triggers
|
||||
The `wiki/index.md` is a routing table. Each entry has a **Use when** column
|
||||
that lists trigger keywords. Before loading any page:
|
||||
|
||||
1. Read `wiki/index.md` (stays in context — it is small).
|
||||
2. Match the current task's keywords against the **Use when** entries.
|
||||
3. Only load the matching page(s). Do not load every page.
|
||||
4. If a page has a `tldr:` frontmatter field, read that first. If it answers the query, skip the body.
|
||||
|
||||
This keeps context lean: ~3–4 pages loaded instead of all pages.
|
||||
|
||||
---
|
||||
|
||||
## 2. PAGE FRONTMATTER SCHEMA
|
||||
|
||||
Every wiki page must use YAML frontmatter. `type` is required; the rest are optional:
|
||||
|
||||
```yaml
|
||||
---
|
||||
type: concept # REQUIRED. Open string for the entity/content kind (e.g. person, project, concept, library, decision, playbook). Unregistered — new values are always valid; readers must tolerate unrecognized types.
|
||||
resource: https://... # Optional. Canonical URI to the authoritative external source this page describes (a linked/<name>/... or libs/<name>/... path, ticket, repo, doc, dataset). Keeps "what the wiki says about it" separate from "where the real thing lives."
|
||||
tldr: One-sentence summary optimised for LLM reading
|
||||
confidence: 0.0–1.0 # How many/corroborated sources support this
|
||||
quality: 0.0–1.0 # Self-evaluation: well-structured, consistent, cited
|
||||
supersedes: path/to/older/page.md
|
||||
superseded_by: path/to/newer/page.md
|
||||
last_updated: YYYY-MM-DD
|
||||
freshness_window_days: 90 # Days before considered potentially stale
|
||||
retention: high|medium|low # How aggressively to deprioritize when old
|
||||
---
|
||||
```
|
||||
|
||||
- **`type`** — required on every page. Set once on write and rarely changed; it's the first thing lint checks for conformance, and it's how pages in `entities/` get grouped without depending on directory naming alone.
|
||||
- **`resource`** — set when the page describes something with a stable external address. Omit for pages that are pure synthesis (e.g. an overview or a decision writeup with no single external source).
|
||||
- **`tldr`** — generated on write. If the TLDR alone answers a query, the body is never loaded.
|
||||
- **`confidence`** — set on write based on source corroboration. Decays with time unless reinforced by new sources.
|
||||
- **`quality`** — self-score on write. Below 0.7 → flag for review.
|
||||
- **`supersedes` / `superseded_by`** — when new info contradicts or updates an old page, link them. Old pages are preserved but marked stale.
|
||||
- **`last_updated`** — set automatically on every write or edit.
|
||||
- **`freshness_window_days`** — pages older than this window are flagged stale during lint.
|
||||
- **`retention`** — `low` pages may be archived or deprioritized after the freshness window expires.
|
||||
|
||||
### Schema Versioning
|
||||
`wiki/index.md` (only) carries an additional frontmatter field, `kb_schema_version` (e.g. `"1.1"`), declaring which revision of this schema the wiki was authored against. Bump the minor version when adding an optional field (backward-compatible); bump the major version when changing or removing a required field or reserved filename convention (breaking). Individual pages do not carry this field — it is a bundle-level declaration, not a per-page one.
|
||||
|
||||
---
|
||||
|
||||
## 3. INGESTION WORKFLOW (TRIGGERED ON DEMAND)
|
||||
|
||||
When the user says "Ingest", "Sync the wiki", or "Update the Wiki" (for syncing this repo's own git history with its remote, see the sync-changes skill under `.claude/skills/` instead):
|
||||
|
||||
1. **Process Inbox:** Scan `raw/inbox/` for new material. Move processed items to `raw/` for archiving. If `raw/inbox/` is empty, scan `raw/` directly.
|
||||
|
||||
2. **Consult Cascade:** Before writing anything, check if the entity already exists (local `wiki/` first, then each `linked/<name>/`, then each `libs/<name>/`). The local `wiki/` always wins. Upstream content is informative but can be overridden locally.
|
||||
|
||||
3. **Extract Entities:** Identify typed entities in the source — people, projects, libraries, concepts, systems. Create entity pages in `wiki/entities/<entity-name>.md` if they don't exist. Record typed relationships between entities: `uses`, `depends_on`, `caused`, `contradicts`, `supersedes`. Store edge data in `wiki/graph/edges.json`.
|
||||
|
||||
4. **Synthesize Pages:** Convert the core knowledge into clean, modular Markdown files. Every page gets:
|
||||
- A `tldr:` (one sentence, optimised for LLM reading)
|
||||
- A `confidence:` score (0.0–1.0 based on source corroboration)
|
||||
- A `quality:` self-score (0.0–1.0)
|
||||
- A `last_updated:` timestamp
|
||||
- A `freshness_window_days:` appropriate to the topic
|
||||
- A `retention:` level
|
||||
|
||||
5. **Link & Cross-Reference:** Use **both** `[[Wikilinks]]` and standard `[markdown](path.md)` links on every cross-reference. This ensures the wiki works in Obsidian, GitHub, and CLI tools. Where useful, reference upstream files at `linked/<name>/...` or `libs/<name>/...`.
|
||||
|
||||
6. **Update Index & Log:** Add new pages to the routing table in `wiki/index.md` with a **Use when** description. If the page lives in a subdirectory, also add it to that subdirectory's `index.md`. Append a log entry to the most specific applicable log — the subdirectory's `log.md` if it has one, otherwise `wiki/log.md`. If this step creates a brand-new `wiki/<topic>/` subdirectory, immediately create that subdirectory's `index.md` per the Recursive Index & Log Convention.
|
||||
|
||||
---
|
||||
|
||||
## 4. QUERY WORKFLOW
|
||||
|
||||
When answering a question or researching a topic:
|
||||
|
||||
1. **Read the index** — `wiki/index.md` first. Match query keywords against **Use when** triggers.
|
||||
2. **Read TLDRs** — for any matched page, read its `tldr:` frontmatter first. If it answers the query, stop.
|
||||
3. **Load full pages** — only if the TLDR was insufficient.
|
||||
4. **Walk the graph** — if the entity has relationships in `wiki/graph/edges.json`, follow them to discover connected pages (e.g. "what depends on X?").
|
||||
5. **Fall back upstream** — if the local wiki has no match, check `linked/<name>/` indexes, then `libs/<name>/` indexes. Apply cascade priority throughout.
|
||||
|
||||
---
|
||||
|
||||
## 5. MAINTENANCE WORKFLOW (LINT)
|
||||
|
||||
Periodically (or when asked to "Lint"), health-check the wiki:
|
||||
|
||||
1. **Conformance check** — verify every non-reserved `.md` file under `wiki/` (i.e. excluding `index.md` and `log.md`) has parseable YAML frontmatter with a non-empty `type` field. Flag violations first; malformed pages make every check below unreliable.
|
||||
|
||||
2. **Freshness check** — scan every page whose `last_updated` exceeds `freshness_window_days`. Flag as stale; suggest the user confirm or update the content.
|
||||
|
||||
3. **Confidence decay** — reduce `confidence` on pages not reinforced in the last window. Pages below 0.3 confidence get flagged for re-review.
|
||||
|
||||
4. **Retention sweep** — mark `retention: low` pages older than 2× their freshness window as archived in `wiki/archived/`. Do not delete — move with a note in the log.
|
||||
|
||||
5. **Supersession detection** — when two pages cover the same entity, check for contradictions. If one is newer, add `supersedes` / `superseded_by` links. Preserve the old page but mark it stale.
|
||||
|
||||
6. **Orphan detection** — find pages with no inbound links. Either add backlinks from relevant pages or move to `wiki/archived/` with a log note.
|
||||
|
||||
7. **Graph consistency** — verify every edge in `wiki/graph/edges.json` points to an existing entity page. Remove or fix broken edges.
|
||||
|
||||
8. **Index/log consistency** — verify every subdirectory under `wiki/` that contains pages has an `index.md` listing all of them, and that no single change is recorded in both a subdirectory `log.md` and the root `wiki/log.md`.
|
||||
|
||||
9. **Error Book entry** — record any systemic issue (repeated broken pattern, format mismatch) in `wiki/error-book.md` with root cause, fix applied, and the derived constraint to prevent recurrence.
|
||||
|
||||
Auto-fix what you can (broken links, missing backlinks, stale flags). Report what you cannot.
|
||||
|
||||
---
|
||||
|
||||
## 6. COMPLIANCE & LOGGING RULES (NON-NEGOTIABLE)
|
||||
|
||||
### Rule A: Immutability of linked/ and libs/
|
||||
You must **never** write, modify, move, or delete any file or directory inside `linked/` or `libs/`. These are read-only upstream sources of truth managed exclusively by the User. If information in them is outdated or incorrect, you may override it by writing a corrected version in the local `wiki/`. The local version will take priority in the cascade lookup.
|
||||
|
||||
### Rule B: The Wiki Change Log (`wiki/log.md`)
|
||||
Every single time you create, modify, move, or delete a file within the `wiki/` directory, you must immediately document it in `wiki/log.md` before proceeding.
|
||||
- **Ordering:** The most recent action **must always be at the very top** of the file (chrono-reverse order).
|
||||
- **Format Per Entry:**
|
||||
```markdown
|
||||
## [YYYY-MM-DD HH:MM] - [ACTION TYPE: e.g., CREATE/UPDATE/DELETE]
|
||||
- **File Affected:** `wiki/path/to/file.md`
|
||||
- **Description:** Brief summary of what knowledge was added or altered.
|
||||
- **Source:** [e.g., Chat conversation, raw/notes.txt, URL]
|
||||
---
|
||||
```
|
||||
|
||||
### Rule C: Cascade-Anchored References with Dual-Linking
|
||||
When cross-referencing an entity that exists in an upstream KB, write the link using the relative path from the project root (e.g., `linked/<name>/wiki/concepts/foo.md` or `libs/<name>/docs/bar.md`). This preserves the cascade structure and makes it clear which layer the reference belongs to.
|
||||
|
||||
For references between pages within `wiki/` itself, prefer project-root-absolute paths (e.g. `/wiki/entities/foo.md`) over relative paths (`../entities/foo.md`). Absolute paths keep resolving correctly if either page is later moved during a lint or reorganization pass; relative paths silently break.
|
||||
|
||||
Use **both** `[[Wikilinks]]` (Obsidian-compatible) and standard `[markdown](path.md)` links on every cross-reference. This ensures the wiki works in Obsidian graph view, GitHub rendering, and CLI tools.
|
||||
|
||||
### Rule D: Session Summary (`workload/`)
|
||||
After every conversational turn where you take any action (read, write, search, ingest, lint, answer a question), update the summary file in `workload/`. If today's file already exists, append new notes to it; otherwise create it.
|
||||
- **Naming:** `workload/YYYY-MM-DD_summary.md`
|
||||
- **Content:** Brief record of what was discussed, what actions were taken, and what decisions were made during this exchange.
|
||||
- **Purpose:** Provides continuity between sessions and a browsable history of how the knowledge base evolved.
|
||||
|
||||
### Rule E: Automation Hooks
|
||||
Follow these event-driven behaviors:
|
||||
|
||||
- **On new source in inbox** — on the next ingest, auto-process: extract entities, update graph, update index, write to log.
|
||||
- **On session start** — read `wiki/index.md` and the latest `workload/` summary to load relevant context.
|
||||
- **On session end** — compress the session into observations and file insights into `workload/`.
|
||||
- **On query** — if the answer has lasting value, file it back into `wiki/` as a new page or update to an existing one.
|
||||
- **On memory write** — check for contradictions with existing wiki content. If found, apply supersession (link old → new) and log it.
|
||||
- **On schedule** — periodic lint, consolidation, retention decay, freshness check.
|
||||
|
||||
### Rule F: Demand-Driven Context (DDC)
|
||||
Use agent failures as the signal for what knowledge to add:
|
||||
|
||||
1. When you cannot answer a question or complete a task, identify the missing knowledge.
|
||||
2. Propose a minimal entity or page to fill the gap.
|
||||
3. The user approves or provides the source material.
|
||||
4. Add it to `raw/inbox/` or describe it in chat.
|
||||
5. Next ingest cycle incorporates it.
|
||||
|
||||
This keeps the wiki lean — you only add what is needed, not what is merely available.
|
||||
1
CLAUDE.md
Symbolic link
1
CLAUDE.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENTS.md
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# Session Summary — 2026-05-30
|
||||
|
||||
## Actions Taken
|
||||
- Transformed `instruction.md` from Karpathy-style LLM Wiki prompt to **Cascade Knowledge Base** concept with layered read-only upstream KBs
|
||||
- Created `wiki/index.md`, `wiki/overview.md`, `wiki/log.md` as required core pages
|
||||
- Created `AGENTS.md` and `CLAUDE.md` with full agent instructions
|
||||
- Created `README.md` with user-facing documentation
|
||||
- Researched 16 improvement ideas from web, wrote to `ideas.md`
|
||||
- Added `libs/` directory (git-managed copies, read-only, gitignored)
|
||||
- Added `outputs/` and `tmp/` directories
|
||||
- Added Index-First Navigation rule (recursive index.md scanning across all layers)
|
||||
- Implemented all 14 selected ideas: frontmatter schema (confidence, tldr, quality, supersedes, freshness, retention), entity extraction & graph, lazy-loading index with Use When triggers, TLDR-first query layer, self-healing lint, dual-linking, automation hooks, error book, DDC, inbox workflow
|
||||
- Created `wiki/graph/`, `wiki/entities/`, `raw/inbox/` directories
|
||||
- Created `wiki/error-book.md` stub
|
||||
- Restructured `wiki/index.md` into a routing table
|
||||
- Removed `ideas.md` and its reference from README
|
||||
|
||||
## Decisions Made
|
||||
- Cascade priority: wiki/ → linked/ → libs/ (alphabetical within each layer)
|
||||
- linked/ for symlinks, libs/ for git-cloned copies (both read-only, never touched by agent)
|
||||
- Local wiki always overrides upstream content
|
||||
- Frontmatter schema standardized across all wiki pages
|
||||
- Dual-linking ([[wikilinks]] + markdown) required for all cross-references
|
||||
- `tmp/`, `libs/` gitignored; `outputs/` tracked
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
# Session Summary — 2026-07-13
|
||||
|
||||
## Actions Taken
|
||||
- Researched Google Cloud's Open Knowledge Format (OKF v0.1, published June 2026): a minimal markdown+YAML-frontmatter spec for portable, agent/human-consumable knowledge bundles (`type` required field, `resource` field, recursive `index.md`/`log.md`, root-absolute links, formal conformance criteria, `okf_version`).
|
||||
- Compared OKF against this repo's Cascade Knowledge Base approach and identified 8 areas where OKF's design is stronger (typing, link stability, recursive progressive disclosure, hierarchical logs, resource linking, conformance checking, schema versioning, generic interop tooling), while noting this repo's cascade layering, confidence/quality/retention decay, and session continuity go beyond what OKF specifies.
|
||||
- Implemented propositions 1–7 (OKF-inspired improvements), applied identically to `CLAUDE.md` and `AGENTS.md`:
|
||||
1. Added required `type` frontmatter field + optional `resource` field to the Page Frontmatter Schema.
|
||||
2. Added `resource` field (see above, same schema change).
|
||||
3. Added guidance to Rule C to prefer project-root-absolute links (`/wiki/entities/foo.md`) for intra-wiki cross-references over relative paths.
|
||||
4. Added a "Recursive Index & Log Convention" subsection under Index-First Navigation; created `wiki/entities/index.md` and `wiki/graph/index.md` as the first real subdirectory indexes; updated the Ingestion Workflow (step 6) to create subdirectory indexes/logs going forward.
|
||||
5. Made `wiki/log.md` an explicit root-level rollup, with subdirectories permitted their own `log.md` once they have independent history — avoids one unbounded flat log file.
|
||||
6. Added a "Conformance check" as lint step 1 (verify parseable frontmatter + non-empty `type` on every non-reserved page) and an "Index/log consistency" check as lint step 8.
|
||||
7. Added a `kb_schema_version` field (bundle-level, on `wiki/index.md` only) with minor/major bump rules, documented in a new "Schema Versioning" subsection.
|
||||
- Propagated the same documentation updates to `wiki/index.md`, `wiki/overview.md`, and `README.md` for consistency, and logged each wiki-level change in `wiki/log.md`.
|
||||
|
||||
## Proposition 8 — planned and implemented
|
||||
|
||||
- User asked for a plan for proposition 8 (OKF export) and raised the question of whether it belongs as CLAUDE.md/AGENTS.md prose or as a command/skill. Entered plan mode, delegated design validation to a Plan agent, then wrote and got approval for a plan at `/home/mkopec/.claude/plans/twinkly-wobbling-harbor.md`.
|
||||
- **Decision:** implemented as a project-scoped Claude Code Skill (`.claude/skills/export-okf/SKILL.md`), not new CLAUDE.md/AGENTS.md prose. Rationale: OKF export is a deterministic, occasionally-invoked transformation with a large fixed mapping ruleset — exactly what Skills' lazy-loading is for, versus permanently taxing every session's context via the always-loaded instruction files. Project-scoped (not `~/.claude/skills/`) because the mapping is coupled to this repo's exact schema and travels with it in the same commits.
|
||||
- Closed a discoverability gap for non-Claude-Code agents: added one generic sentence to `CLAUDE.md`/`AGENTS.md` §1 (`outputs/` line) pointing at `.claude/skills/` in general, without naming this specific skill, so the pointer never needs updating as skills are added/removed.
|
||||
- Fixed a prerequisite gap discovered during planning: `wiki/overview.md` and `wiki/error-book.md` had zero YAML frontmatter and would have failed the conformance lint check added earlier this session. Retrofitted both with `type`/`tldr`/`last_updated`.
|
||||
- Updated `README.md` with a "OKF Export" feature bullet and a note clarifying `outputs/okf/` is a fully-regenerated build artifact that stays git-tracked (consistent with the existing `outputs/` convention) but is never auto-committed by the skill itself.
|
||||
- Wrote the full `export-okf` SKILL.md: 8-step procedure (read source → clear+rebuild outputs/okf/ → transform concept-doc frontmatter/links → regenerate index.md files → regenerate log.md files → handle non-reserved special pages → validate output conformance → report), plus edge cases (empty entities/graph dirs, missing `type`, future `archived/`, determinism on re-run).
|
||||
- Have not yet run the skill end-to-end (wiki/entities and wiki/graph are still empty, so there's little to export yet) — first real exercise of the skill will happen on the next ingest that populates entities.
|
||||
|
||||
## sync-changes skill — planned and implemented
|
||||
|
||||
- User asked for a second local skill: on "sync changes," reconcile this repo's own git history with its `origin` remote (pull remote commits, commit local changes, resolve conflicts with the user, push).
|
||||
- Clarified scope up front via AskUserQuestion: "git" means this repo's own `origin` remote (`https://codeberg.org/Valdec/llm-wiki-cascade.git`), not `libs/`; and unlike `export-okf`, auto-push is explicitly approved here — the skill commits and pushes on its own once conflicts are resolved.
|
||||
- Checked real repo state (read-only): `origin` is configured, but local `main` has **zero commits** — everything was untracked at the time of this session. This made the "local has no commits, remote may already have history" first-run case a live scenario, not a hypothetical, so it's handled as an explicit hard-stop path (ask the user to choose merge / remote-wins / stop) rather than an automatic guess.
|
||||
- Entered plan mode again given the risk of automated git push; delegated design validation to a Plan agent (git command sequencing, conflict-presentation mechanics, first-run handling), then resolved two remaining open questions via AskUserQuestion: (1) never offer a force-push/discard-remote option even in the first-run menu — recommended and confirmed; (2) reword the existing bare `"Sync"` Ingestion trigger to `"Sync the wiki"` to reduce ambiguity with the new skill's `"sync changes"` trigger — recommended and confirmed.
|
||||
- Implemented: `.claude/skills/sync-changes/SKILL.md` (5-step procedure: pre-flight safety checks incl. secrets scan → detect first-run/unrelated-histories case → steady-state commit-then-fetch-merge-then-push flow → present each conflict block via AskUserQuestion with keep-local/keep-remote/provide-merged-text options → structured report), plus edge cases (nothing-to-sync, push-rejected-twice, no-remote-configured).
|
||||
- Updated `CLAUDE.md`/`AGENTS.md` §3 trigger wording (`"Sync"` → `"Sync the wiki"` + pointer to the new skill) and added a "Git Sync (on demand)" bullet to `README.md`, matching the `export-okf` precedent.
|
||||
- Have not yet run the skill for real — it would actually commit and push this session's changes to the live Codeberg remote, which is a real external action, so it's left for the user to explicitly trigger with "sync changes" rather than auto-run as part of this session's "verification."
|
||||
|
||||
## Decisions Made
|
||||
- Keep `CLAUDE.md` and `AGENTS.md` byte-identical; every instruction edit is applied to both in lockstep.
|
||||
- `type` is required per OKF precedent, but the rest of the existing schema (confidence/quality/retention/etc.) is retained as-is — OKF's minimalism is adopted piecemeal, not wholesale, since the decay/retention/cascade machinery has no OKF equivalent and is worth keeping.
|
||||
- `kb_schema_version` lives only on `wiki/index.md` (bundle-level), not on every page, matching OKF's `okf_version` convention — and is dropped (not relocated) when exporting to OKF, since OKF's root index.md frontmatter is spec-limited to `okf_version` only.
|
||||
- OKF export lives in `.claude/skills/export-okf/`, project-scoped rather than global, so it stays co-versioned with this repo's schema.
|
||||
- Git-level sync lives in `.claude/skills/sync-changes/`, project-scoped for the same reason, and is explicitly authorized to auto-commit/auto-push (unlike export-okf) per direct user confirmation — but never force-pushes and always stops to ask on the first-run unrelated-histories case.
|
||||
Loading…
Add table
Reference in a new issue