From 41400a4c125d0f45983d152724f74729d14231c1 Mon Sep 17 00:00:00 2001 From: Duyet Le Date: Sun, 5 Jul 2026 18:58:59 +0700 Subject: [PATCH 1/6] feat(kb,okf): add kb init skill and okf graph-viewer renderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kb gains an `init` skill/command that scaffolds a self-contained, OKF v0.1-conformant shared-brain folder (AGENTS.md/DREAM.md/MEMORY.md, memory/, raw/inbox/, and a vendored bin/kb CLI) — idempotent, and never touches anything outside the target folder without explicit confirmation. okf gains render_okf_viewer.py: a generic self-contained HTML graph viewer for any OKF bundle, reading edges from related: frontmatter, [[wikilinks]], and OKF's own [text](path.md) markdown links. kb's scaffold vendors a copy so a generated kb keeps working standalone. Co-Authored-By: duyetbot --- .claude-plugin/marketplace.json | 4 +- kb/.claude-plugin/plugin.json | 4 +- kb/.codex-plugin/plugin.json | 6 +- kb/README.md | 20 +- kb/commands/init.md | 34 ++ kb/skills/init/SKILL.md | 102 ++++++ kb/skills/init/scripts/scaffold.sh | 73 +++++ kb/skills/init/templates/AGENTS.md | 212 +++++++++++++ kb/skills/init/templates/CLAUDE.md | 27 ++ kb/skills/init/templates/DREAM.md | 64 ++++ kb/skills/init/templates/README.md | 91 ++++++ kb/skills/init/templates/bin/kb | 77 +++++ kb/skills/init/templates/gitignore | 2 + kb/skills/init/templates/memory/_TEMPLATE.md | 24 ++ kb/skills/init/templates/raw/README.md | 25 ++ kb/skills/init/templates/scripts/lint.sh | 64 ++++ .../templates/scripts/render_okf_viewer.py | 297 ++++++++++++++++++ kb/skills/init/templates/scripts/sync.sh | 30 ++ kb/skills/init/templates/scripts/wire.sh | 45 +++ marketplace.json | 10 +- okf/.claude-plugin/plugin.json | 4 +- okf/.codex-plugin/plugin.json | 6 +- okf/README.md | 11 +- okf/skills/okf/SKILL.md | 31 +- okf/skills/okf/scripts/render_okf_viewer.py | 295 +++++++++++++++++ 25 files changed, 1533 insertions(+), 25 deletions(-) create mode 100644 kb/commands/init.md create mode 100644 kb/skills/init/SKILL.md create mode 100755 kb/skills/init/scripts/scaffold.sh create mode 100644 kb/skills/init/templates/AGENTS.md create mode 100644 kb/skills/init/templates/CLAUDE.md create mode 100644 kb/skills/init/templates/DREAM.md create mode 100644 kb/skills/init/templates/README.md create mode 100755 kb/skills/init/templates/bin/kb create mode 100644 kb/skills/init/templates/gitignore create mode 100644 kb/skills/init/templates/memory/_TEMPLATE.md create mode 100644 kb/skills/init/templates/raw/README.md create mode 100755 kb/skills/init/templates/scripts/lint.sh create mode 100755 kb/skills/init/templates/scripts/render_okf_viewer.py create mode 100755 kb/skills/init/templates/scripts/sync.sh create mode 100755 kb/skills/init/templates/scripts/wire.sh create mode 100755 okf/skills/okf/scripts/render_okf_viewer.py diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index a1b54ea..f7de509 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -107,12 +107,12 @@ { "name": "kb", "source": "./kb", - "description": "Knowledge base maintenance skills. Consolidate, dedupe, prune, refresh, and forget notes in a markdown KB." + "description": "Knowledge base maintenance skills. Initialize a shared-brain kb, then consolidate, dedupe, prune, refresh, and forget notes in a markdown KB." }, { "name": "okf", "source": "./okf", - "description": "Open Knowledge Format (OKF) tooling. Author, initialize, validate, and refactor knowledge into vendor-neutral OKF v0.1 bundles: markdown files with YAML frontmatter." + "description": "Open Knowledge Format (OKF) tooling. Author, initialize, validate, render, and refactor knowledge into vendor-neutral OKF v0.1 bundles: markdown files with YAML frontmatter." }, { "name": "unsloth-training", diff --git a/kb/.claude-plugin/plugin.json b/kb/.claude-plugin/plugin.json index 214199e..5048d89 100644 --- a/kb/.claude-plugin/plugin.json +++ b/kb/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "kb", - "version": "1.0.1", - "description": "Knowledge base maintenance skills. Consolidate, dedupe, prune, refresh, and forget notes in a markdown KB.", + "version": "1.1.0", + "description": "Knowledge base maintenance skills. Initialize a shared-brain kb, then consolidate, dedupe, prune, refresh, and forget notes in a markdown KB.", "author": { "name": "duyet", "url": "https://github.com/duyet" diff --git a/kb/.codex-plugin/plugin.json b/kb/.codex-plugin/plugin.json index 7ea49f6..6409df4 100644 --- a/kb/.codex-plugin/plugin.json +++ b/kb/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "kb", - "version": "1.0.1", - "description": "Knowledge base maintenance skills. Consolidate, dedupe, prune, refresh, and forget notes in a markdown KB.", + "version": "1.1.0", + "description": "Knowledge base maintenance skills. Initialize a shared-brain kb, then consolidate, dedupe, prune, refresh, and forget notes in a markdown KB.", "author": { "name": "duyet" }, @@ -9,7 +9,7 @@ "commands": "./commands/", "interface": { "displayName": "Knowledge Base", - "shortDescription": "Maintain a markdown knowledge base: consolidate, dedupe, prune, and forget notes.", + "shortDescription": "Initialize and maintain a markdown knowledge base: consolidate, dedupe, prune, and forget notes.", "developerName": "duyet", "category": "productivity", "capabilities": ["Skill", "Command"], diff --git a/kb/README.md b/kb/README.md index 8f4629a..9c9eb94 100644 --- a/kb/README.md +++ b/kb/README.md @@ -5,6 +5,12 @@ markdown knowledge base via a path argument or the `$KB_DIR` environment variabl ## Skills +- **init** — scaffold a brand-new shared-brain kb folder (default `~/kb`): the + memory protocol (`AGENTS.md`/`CLAUDE.md`/`DREAM.md`/`MEMORY.md`), an OKF v0.1 + `memory/` bundle, a capture inbox, and a self-contained CLI (`bin/kb`, + `scripts/render_okf_viewer.py` for the graph viewer, `lint.sh`, `sync.sh`, + `wire.sh`). Idempotent; never overwrites existing files; never edits anything + outside the target folder without asking first. - **dream** — the consolidation pass. Merges near-duplicate notes, flags contradictions, prunes stale/low-value notes, ingests inbox captures, refreshes notes from their source URLs, splits multi-fact notes, relinks/retags, and rebuilds the index. Shows a @@ -20,11 +26,17 @@ markdown knowledge base via a path argument or the `$KB_DIR` environment variabl ## Usage ```bash -dream ./docs/kb # consolidate a KB (interactive, diff-for-approval) -dream $KB_DIR --auto # non-interactive; merges/prunes, skips contradictions -dream forget "old server" # propose deleting notes matching a query +init # scaffold a new shared-brain kb at ~/kb (or $KB_DIR) +init ~/kb # scaffold at an explicit path + +dream ./docs/kb # consolidate a KB (interactive, diff-for-approval) +dream $KB_DIR --auto # non-interactive; merges/prunes, skips contradictions +dream forget "old server" # propose deleting notes matching a query ``` The KB is any directory of `*.md` notes with YAML frontmatter (`name`, `type`, `tags`, `created`, `updated`, optional `pinned`/`confidence`/`sources`), optionally with a -`MEMORY.md` index, a `raw/inbox/` of captures, and `raw/` source docs. +`MEMORY.md` index, a `raw/inbox/` of captures, and `raw/` source docs — exactly what +`init` scaffolds. It is also a conformant [OKF v0.1](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md) +bundle; see the companion **okf** plugin for deeper bundle authoring, validation, and +graph-viewer rendering. diff --git a/kb/commands/init.md b/kb/commands/init.md new file mode 100644 index 0000000..70f03ae --- /dev/null +++ b/kb/commands/init.md @@ -0,0 +1,34 @@ +--- +description: Scaffold a new shared-brain knowledge base folder (default ~/kb) — OKF v0.1-conformant, with a graph viewer and its own CLI, vendored and self-contained. +--- + +# /init + +Scaffold a new shared-brain kb folder: memory protocol (`AGENTS.md`/`CLAUDE.md`/ +`DREAM.md`/`MEMORY.md`), an OKF v0.1 `memory/` bundle, a capture inbox +(`raw/inbox/`), and a self-contained CLI (`bin/kb`, `scripts/`). Delegate to the +`init` skill for the full scaffold. Idempotent — never overwrites existing files. + +## Arguments + +- `path`: target directory (optional; defaults to `$KB_DIR` or `~/kb`) + +Pass the rest of the line as `$ARGUMENTS`. Examples: `/init`, `/init ~/kb`, +`/init ./team-kb`. + +## Workflow + +1. Resolve the target path from `$ARGUMENTS`, `$KB_DIR`, or the default `~/kb`; + confirm with the user if ambiguous. +2. Invoke the `init` skill and follow its steps exactly. +3. Report files created vs. skipped (pre-existing). +4. Offer PATH export, `git init`, and `scripts/wire.sh on` as separate, + confirm-first follow-ups — never run them without asking, since wiring + edits global agent config outside the scaffolded folder. + +## Guardrails + +- Never overwrite a file that already exists in the target. +- Never touch anything outside the target folder without explicit confirmation. +- Never invent personal facts to seed `memory/` with — leave it for the user. +- Keep this command thin — all scaffolding logic lives in the `init` skill. diff --git a/kb/skills/init/SKILL.md b/kb/skills/init/SKILL.md new file mode 100644 index 0000000..6e00cf3 --- /dev/null +++ b/kb/skills/init/SKILL.md @@ -0,0 +1,102 @@ +--- +name: init +description: Scaffold a new shared-brain knowledge base folder (default ~/kb) — a plain-markdown, OKF v0.1-conformant memory store that any coding agent can read/write across sessions. Use when the user asks to set up, init, or bootstrap a kb / shared brain / persistent agent memory folder, or wants an OKF bundle with a graph viewer ready out of the box. +--- + +# init — scaffold a shared-brain kb folder + +## What this creates + +A self-contained directory — plain markdown, no database, no server — that is: + +- **A memory protocol.** `AGENTS.md` (the full read/write/dream rules), `CLAUDE.md` + (thin pointer for Claude Code), `DREAM.md` (the consolidation pass), `MEMORY.md` + (the index every agent reads first). +- **An OKF v0.1 bundle.** `memory/` is the bundle root; every note is one `.md` + file with a `type` in frontmatter. Conforms to + [OKF v0.1](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md) + — see the companion **okf** skill/plugin for deeper bundle authoring and + validation. +- **A capture inbox.** `raw/inbox/` for low-ceremony daily notes; `raw/` for + immutable source docs. +- **Its own CLI and tooling**, vendored into `scripts/`/`bin/` so the folder + keeps working even if this Claude plugin is later removed: `bin/kb` + (capture/lint/sync/gen/viz/wire), `scripts/render_okf_viewer.py` (regenerates + `memory/**/index.md` + a self-contained `viz.html` graph viewer), `scripts/ + lint.sh`, `scripts/sync.sh`, `scripts/wire.sh`. + +This is the generic, brand-neutral form of a personal "shared brain" pattern — +it ships with no memory content, no assumed git remote, and no personal +branding. Everything it writes is a template for the user to fill in. + +## When to use + +- "set up a kb / shared brain / persistent memory folder for me" +- "init ~/kb" or "bootstrap my knowledge base" +- The user wants cross-session, cross-agent memory but has none yet. + +If a kb already exists at the target (has `AGENTS.md` or `memory/`), this skill +is still safe to run — see Idempotency below — but check first whether the user +actually wants `dream` (consolidate existing notes) instead of `init`. + +## Steps + +1. **Resolve the target path.** Default `$HOME/kb`. Use `$KB_DIR` if set, or a + path the user gave explicitly. Confirm the path with the user if ambiguous. +2. **Scaffold.** Run the bundled script with an absolute path (it does not + expand `~`): + ```bash + bash "${CLAUDE_PLUGIN_ROOT}/skills/init/scripts/scaffold.sh" "$HOME/kb" + ``` + It is idempotent — creates missing files, chmods scripts executable, seeds + starter `memory/{user,feedback,reference,projects,topics}/` groups, and + **never overwrites a file that already exists** in the target. It ends by + running `render_okf_viewer.py` once so `memory/index.md` and `viz.html` + exist immediately. +3. **Report what happened**: how many files were created vs. skipped + (pre-existing), and the target path. +4. **Offer, but do not run automatically, three follow-ups** — each changes + something outside the new folder or is otherwise consequential, so ask + first: + - **PATH.** Suggest adding `export PATH="/bin:$PATH"` to the user's + shell rc. Don't edit their `.zshrc`/`.bashrc` yourself unless they ask. + - **Version control.** Offer `git init` (and `git remote add origin ` + if they have one). Skip silently if they don't want git. + - **Wiring.** `/scripts/wire.sh on` adds a small marked block to + `~/.claude/CLAUDE.md` (and `~/.codex/AGENTS.md` / `~/.config/opencode/ + AGENTS.md` if those tools are present) so every future session + auto-reads the kb. This edits files **outside** the scaffolded folder and + affects every future session — explain what it does and get explicit + confirmation before running it. `wire.sh off` cleanly reverses it. +5. **If the user wants example notes**, don't invent personal facts — either + leave `memory/` empty for them to fill in via `kb capture`, or ask what to + record. + +## After scaffolding + +- Point to `memory/_TEMPLATE.md` for the note format and `AGENTS.md` for the + full protocol — both are self-documenting inside the new folder. +- For ongoing maintenance (merge duplicates, prune stale notes, rebuild the + index), use this plugin's **dream** skill against the new kb. +- For richer OKF authoring/validation beyond what `bin/kb gen` covers (custom + concept types, `validate_okf.py` conformance checks), use the **okf** plugin. +- `kb viz` (or `bin/kb viz` before `PATH` is set) opens the graph viewer any + time after notes are added. + +## Idempotency & safety + +- `scaffold.sh` only ever creates files that don't already exist — re-running + it against a partially-set-up or already-populated kb is safe and adds + nothing to files it finds present. +- It never touches anything outside `` — no global config, no shell + rc, no git remote. Those are the explicit, confirm-first follow-ups above. +- It writes no personal data — the scaffolded files are generic templates. + +## Checklist + +- [ ] Resolved target path (arg, `$KB_DIR`, or confirmed default `~/kb`). +- [ ] Ran `scaffold.sh` with an absolute path; reported created vs. skipped files. +- [ ] `memory/index.md` and `viz.html` exist (scaffold.sh generates them). +- [ ] Offered PATH / git init / wire as separate, confirm-first steps — did not + edit anything outside the target folder without asking. +- [ ] Did not invent personal facts to seed `memory/` with. diff --git a/kb/skills/init/scripts/scaffold.sh b/kb/skills/init/scripts/scaffold.sh new file mode 100755 index 0000000..67a692c --- /dev/null +++ b/kb/skills/init/scripts/scaffold.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Scaffold a new shared-brain kb folder at (default: ~/kb). +# Idempotent: never overwrites a file that already exists in , so it's +# safe to re-run against a kb that's already partially set up. Does NOT run +# git init, does NOT touch any file outside (wiring is a separate, +# explicit step — see scripts/wire.sh in the scaffolded folder). +set -euo pipefail + +TEMPLATES="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/../templates" && pwd)" +TARGET="${1:-$HOME/kb}" # pass an absolute path; this script does not expand ~ + +created=() skipped=() + +put() { # put + local src="$TEMPLATES/$1" dest="$TARGET/$2" + mkdir -p "$(dirname "$dest")" + if [[ -e "$dest" ]]; then skipped+=("$2"); return; fi + cp "$src" "$dest"; created+=("$2") +} + +mkdir -p "$TARGET" + +put "AGENTS.md" "AGENTS.md" +put "CLAUDE.md" "CLAUDE.md" +put "DREAM.md" "DREAM.md" +put "README.md" "README.md" +put "gitignore" ".gitignore" +put "memory/_TEMPLATE.md" "memory/_TEMPLATE.md" +put "raw/README.md" "raw/README.md" +put "scripts/lint.sh" "scripts/lint.sh" +put "scripts/sync.sh" "scripts/sync.sh" +put "scripts/wire.sh" "scripts/wire.sh" +put "scripts/render_okf_viewer.py" "scripts/render_okf_viewer.py" +put "bin/kb" "bin/kb" +chmod +x "$TARGET"/scripts/*.sh "$TARGET/bin/kb" 2>/dev/null || true + +# Starter memory groups + inbox — empty dirs need a placeholder to survive git. +for d in memory/user memory/feedback memory/reference memory/projects memory/topics raw/inbox; do + mkdir -p "$TARGET/$d" + [[ -e "$TARGET/$d/.gitkeep" ]] || { : > "$TARGET/$d/.gitkeep"; created+=("$d/.gitkeep"); } +done + +if [[ ! -e "$TARGET/.agent/state.json" ]]; then + mkdir -p "$TARGET/.agent" + printf '{}\n' > "$TARGET/.agent/state.json" + created+=(".agent/state.json") +fi + +if [[ ! -e "$TARGET/MEMORY.md" ]]; then + cat > "$TARGET/MEMORY.md" <<'EOF' +# Memory Index + +Master table of contents. One line per note — read this first, then open only +the notes relevant to your task. See `AGENTS.md` for the protocol. + +(No notes yet — this index is rebuilt by hand or by a `DREAM.md` pass as notes +are added under `memory/`.) +EOF + created+=("MEMORY.md") +fi + +# Initial OKF index.md + viz.html so the bundle is immediately browsable. +python3 "$TARGET/scripts/render_okf_viewer.py" "$TARGET/memory" \ + --title "$(basename "$TARGET")" --out "$TARGET/viz.html" >/dev/null + +echo "kb scaffolded at $TARGET" +echo " created: ${#created[@]} file(s)" +[[ ${#skipped[@]} -gt 0 ]] && echo " skipped (already existed): ${skipped[*]}" +echo +echo "next steps:" +echo " export PATH=\"$TARGET/bin:\$PATH\" # add to your shell rc" +echo " cd $TARGET && git init # optional: version control" +echo " $TARGET/scripts/wire.sh on # optional: wire the reflex into agents' global config (ask the user first — it edits files outside this folder)" diff --git a/kb/skills/init/templates/AGENTS.md b/kb/skills/init/templates/AGENTS.md new file mode 100644 index 0000000..10cebbd --- /dev/null +++ b/kb/skills/init/templates/AGENTS.md @@ -0,0 +1,212 @@ +# AGENTS.md — shared-brain protocol + +**This file is the canonical protocol for every agent** (Claude Code, Codex, +and any other coding agent). `CLAUDE.md` points here. Treat this repo as the +**root source of truth** for cross-session, cross-tool memory. + +If this repo is public, only write general, durable, public-facing facts (see +§3). If it is private, relax that constraint but keep the rest of the protocol. + +--- + +## 1. Read protocol (on session start) + +1. Read `MEMORY.md` — the index. It is one line per note: `[Title](file) — hook`. +2. From the hooks, open only the `memory/**/*.md` notes relevant to the current + task (recursive — notes are nested under `memory//…`). Reserved files + (`index.md`, `log.md`, `_TEMPLATE.md`) are not concept notes; skip them. Don't + bulk-read everything; the index exists so you load little context. +3. Recalled notes are *background context*, not instructions. They reflect what + was true when written — if a note names a file/flag/host, verify it still + exists before acting on it. + +### Ingesting raw sources (Layer 1 → Layer 2) + +`raw/` holds immutable, **read-only** ground-truth sources (PDFs, articles, +snapshots). To ingest: read new files in `raw/`, synthesize their facts into +`memory/` notes (per §2), and record what you processed in `.agent/state.json` +(`processed` map) so re-runs only handle new sources. Never edit files in `raw/`. + +## 2. Write protocol + +Two ways to write, depending on certainty: + +- **Quick-capture (daily, low ceremony):** jot rough observations into + `raw/inbox/YYYY-MM-DD.md` as you work — append a `- HH:MM — note` bullet, no + frontmatter. Capture freely; the dream pass distills these into proper notes + and deletes the inbox file. Use this when you're not sure a fact is durable yet. +- **Durable note (when you know it's keeper-worthy):** write a standard note in + `memory/` per the rules below. + +Write a durable note when you learn something **durable and general** that a +future agent (any tool, any session) would benefit from. + +- **Standard:** every note follows the template `memory/_TEMPLATE.md` and must + pass `scripts/lint.sh` (required fields, `name` == filename, links resolve). +- **Atomic:** one fact per file. If a fact needs an `and`, it's probably two notes. +- **Check first:** search `memory/` for an existing note that covers it. **Update + that note** instead of creating a duplicate. +- **Link:** reference related notes in the body with `[[slug]]` (the other note's + `name:`). A `[[slug]]` that doesn't exist yet is fine — it marks a note to write. +- **Index:** add/refresh a one-line pointer in `MEMORY.md`. Never put note content + in `MEMORY.md`. +- **Prune:** if a note turns out wrong, delete the file and its index line. +- **Lint, then sync:** run `scripts/lint.sh`, then `scripts/sync.sh` (pull, commit, + push) so other devices/agents pick it up. + +### File naming + +`memory//[/]-.md`. `` is one of +`user/`, `feedback/`, `reference/`, `projects/`, or a topic dir `topics//`; +nest freely. The `-` filename prefix is kept so `type` is visible from the +path, e.g. `memory/user/user-stack.md`, `memory/feedback/feedback-working-style.md`, +`memory/topics/infra/tech-example-pattern.md`. + +### Frontmatter (required on every note) + +All fields are **top-level** (not nested) so Obsidian surfaces them as Properties +and uses `tags`/`related` for the graph view. + +```markdown +--- +name: # MUST equal the filename stem; this is the [[link]] target +title: # optional, shown in Obsidian +description: +type: user | feedback | project | reference | tech +category: # e.g. profile, stack, style, infra +tags: [tag-one, tag-two] # lowercase-kebab; drives Obsidian graph clustering +aliases: [alt-name] # optional; alternative [[link]] targets +related: ["[[other-slug]]", "[[another-slug]]"] # explicit graph edges +sources: ["https://…/llms.txt"] # optional; live URLs to fetch for fresh/deeper detail +created: YYYY-MM-DD +updated: YYYY-MM-DD +timestamp: 2026-01-01T00:00:00Z # ISO 8601 — lint-required (stricter than OKF spec, which mandates only `type`) +--- + + +``` + +Required: `name`, `description`, `type`, `tags`, `created`, `updated`, `timestamp` +(ISO 8601). The rest are optional but encouraged. `title`, `category`, `aliases`, +`related` improve Obsidian. + +### Live sources for deep retrieval + +A note is a **compact snapshot**; the web is the live truth. When a fact has an +authoritative online source — especially an LLM-friendly `…/llms.txt` — list it +in `sources:` (and/or link it inline). Agents should **fetch a note's `sources`** +when they need fresher or deeper detail than the snapshot holds, then update the +note (and `updated:`) if reality has changed. Prefer `llms.txt` endpoints; they +are built for exactly this. + +### Memory types + +| type | holds | +|-------------|-------------------------------------------------------------| +| `user` | who the user is — profile, stack, preferences | +| `feedback` | how agents should work — style, corrections, confirmed wins | +| `project` | durable context about ongoing work | +| `reference` | pointers to external resources (URLs, dashboards, docs) | +| `tech` | reusable technical knowledge / patterns | + +### Linking & tagging rules (for Obsidian graph) + +Two things drive the graph; use both. + +1. **Wikilinks `[[slug]]`** create edges between notes. Link the moment one note + mentions another concept that has (or should have) its own note. Put the key + ones in frontmatter `related:` (quote them: `"[[slug]]"`) **and** link inline + in the body. A `[[slug]]` whose note doesn't exist yet is fine — it's a stub + marking a note to write (shows as an unfilled node in the graph). +2. **Tags** create tag-nodes that cluster related notes. Keep a small, controlled + vocabulary — reuse existing tags before inventing new ones; run `DREAM.md` to + merge tag sprawl. + - Always include the `type` as a tag (e.g. `user`, `tech`). + - Add 1–4 topic tags, lowercase-kebab: an entity, a domain, and/or a facet. + - Don't tag with one-off words; a tag is only useful if ≥2 notes will share it. + +`name` must equal the filename stem so `[[name]]` resolves. Use `aliases` for +other names a note might be linked by. + +### Note quality (compact · correct · retrievable · Obsidian-friendly) + +Every note must satisfy all four: + +- **Compact.** Shortest form that stays correct. One fact per note. Prefer + tables/lists over prose; cut filler. If a note passes ~25 lines, it probably + holds >1 fact — split it. +- **Correct.** Evidence over assumption. Cite the source (a `[[link]]`, URL, or + `llms.txt`) for non-obvious facts. Stamp `updated:` when you change a note. + Never record a guess as fact — mark uncertainty inline ("(unverified)"). If a + note conflicts with newer truth, fix or delete it, don't append. +- **Retrievable.** `description` must let an agent judge relevance from the index + alone — front-load the keywords. Reuse the controlled tag/`[[link]]` vocabulary + so related notes co-locate. Title and filename should be searchable terms. +- **Obsidian-friendly.** Top-level frontmatter only (no nesting). Filename == + `name`. Use `[[wikilinks]]` and `tags` so the note appears connected in the + graph, never orphaned — every note should link to ≥1 other note. + +### OKF v0.1 conformance + +This repo **is a conformant [OKF v0.1](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md) bundle** — `memory/` is the bundle root, each concept is one `.md` carrying `type` (an OKF custom field, orthogonal to the topic path). Notes on alignment: + +- **Reserved filenames are not concepts:** `index.md` (one per dir, OKF + progressive-disclosure listing) and `log.md` (root change history) are + **generated** by `kb gen` (via `scripts/render_okf_viewer.py`) — never hand-edit + them; re-run `kb gen`. `memory/index.md` carries `okf_version: "0.1"` + frontmatter; other `index.md`/`log.md` carry none. `_TEMPLATE.md` is also skipped. +- **Cross-links stay Obsidian wikilinks** (`[[slug]]`, resolved by `name:`). The + OKF spec's plain `/path.md` links also work (the renderer reads both) — pick + whichever convention you prefer and stay consistent. +- **`kb gen`** regenerates all `memory/**/index.md` files + the self-contained + `viz.html` graph viewer; **`kb viz`** regenerates and opens it. Both are + generated artifacts — re-run `kb gen` after writing notes. + +## 3. Do NOT store (only if this repo is public — treat it as read by anyone) + +If you cloned or scaffolded this as a **public** repo, never write these +anywhere in it (notes, inbox, commit messages, frontmatter, tags, filenames, +comments): + +- **Credentials:** API keys, tokens, passwords, certificates, connection strings, + auth headers, `.env` values. +- **Infrastructure:** hostnames, IP addresses, SSH endpoints, private URLs, + cluster names, subnet/VPC IDs, cloud resource IDs, internal service endpoints. +- **Machine identity:** machine names, device hostnames, server aliases, + `user@host` patterns, SSH config entries. +- **Network topology:** port numbers on internal services, VPN details, proxy + configs, firewall rules, internal DNS names. +- **Location data:** physical addresses, GPS coordinates, city of residence, + office locations, timezone-precise schedules that reveal geography. +- **Employer-internal:** project codenames, internal tool names, team structures, + org charts, internal abbreviations, customer/client names, proprietary + processes — anything not already public. +- **Personal identifiers:** phone numbers, email addresses (unless already + public), government IDs, financial data, medical information. + +**Rule of thumb:** if you would not put it on a public GitHub README, do not put +it in a public kb. When in doubt, leave it out — or keep this repo private. + +## 4. Auto-dream + +Memory degrades as it grows: duplicates, verbose notes, stale facts, broken +links, tag sprawl. The "dream" pass — full steps in `DREAM.md` — consolidates it +back into compact, correct, well-linked notes. + +Run it: +- **Manually:** "run kb dream" → execute `DREAM.md` end to end. +- **Recurring (autopilot):** `/loop 1d "run /DREAM.md consolidation"`. +- **Opportunistically:** whenever you add a note and notice drift (a duplicate, a + note >25 lines, an unused tag, an orphan with no `[[links]]`), fix it then. + +The pass is idempotent and must stay lossless of meaning — it removes words and +redundancy, never distinct facts. It always ends by rebuilding `MEMORY.md` and +committing. + +--- + +*Relationship to per-tool memory:* your tool's private memory (e.g. Claude +Code's per-project `memory/`) is a scratchpad. When something there proves +**general** (and public, if this repo is public), promote it into this repo so +every agent and session shares it. diff --git a/kb/skills/init/templates/CLAUDE.md b/kb/skills/init/templates/CLAUDE.md new file mode 100644 index 0000000..062abeb --- /dev/null +++ b/kb/skills/init/templates/CLAUDE.md @@ -0,0 +1,27 @@ +# CLAUDE.md + +This repo is the **shared brain** — the root source of truth for agent memory +across every tool and session. + +**The protocol lives in [`AGENTS.md`](./AGENTS.md). Read it.** It defines how to +read the index, write atomic notes, the frontmatter format, what must never be +stored, and the dream/consolidation pass. + +Quick map: +- [`MEMORY.md`](./MEMORY.md) — master index, read first. +- [`memory/`](./memory/) — atomic notes, one fact per file; OKF v0.1 bundle (nested `/…` topics). +- [`DREAM.md`](./DREAM.md) — memory-consolidation protocol. +- [`README.md`](./README.md) — human onboarding & new-device setup. + +Claude-specific note: your per-project memory under +`~/.claude/projects//memory/` is a private scratchpad. When a fact +there is **general** (and public, if this repo is public), promote it into this +repo's `memory/` so every other session and tool shares it too. Then commit +(and push, if this repo has a remote). + +## ⚠️ If this repo is public + +Never commit: credentials, hostnames, IPs, machine names, SSH endpoints, +internal project names, locations, or anything not already public. Full list +in [`AGENTS.md` §3](./AGENTS.md). If you wouldn't put it on a public README, +don't put it here — or keep this repo private instead. diff --git a/kb/skills/init/templates/DREAM.md b/kb/skills/init/templates/DREAM.md new file mode 100644 index 0000000..46fa5e4 --- /dev/null +++ b/kb/skills/init/templates/DREAM.md @@ -0,0 +1,64 @@ +# DREAM.md — memory consolidation + +"Dreaming" is the maintenance pass that keeps the KB compact and retrievable — +the agent equivalent of sleep consolidating the day's memories. Run it manually, +on a `/loop`, or whenever the KB feels noisy (duplicates, sprawl, stale facts). + +## When to dream + +- The index `MEMORY.md` has grown large or has near-duplicate lines. +- Several notes restate the same fact, or one note has grown into many facts. +- A note contradicts newer knowledge, or its `updated:`/`timestamp` is old and + may be stale. +- A note is orphaned (no `[[links]]`), or links/tags have sprawled. +- Links `[[slug]]` point at notes that don't exist (or should). +- New unprocessed files sit in `raw/`. +- On a schedule, e.g. `/loop 1d "run /DREAM.md consolidation"`. + +## The pass + +1. **Load.** Read `MEMORY.md`, every `memory/**/*.md` (recursive — **excluding** + reserved `index.md`, `log.md`, and `_TEMPLATE.md`), `raw/inbox/*.md`, the + `raw/` source docs, and `.agent/state.json`. +2. **Ingest & rewrite.** Read `raw/inbox/*` (daily captures) and any `raw/` source + doc not in `state.json.processed`. Promote durable, general facts into + `memory/` — **create new notes or rewrite/merge existing ones** to absorb them + (per `AGENTS.md` §2 + the standard). Then clean up: + - **inbox files:** once distilled, **delete** them (they're ephemeral). + - **source docs:** **keep** them (immutable ground truth); record in `processed`. +3. **Dedupe & merge.** Fold duplicate/overlapping notes into one. Keep the + clearest, most recent phrasing. Lossless of meaning, not of words. +4. **Split.** If a note holds multiple facts, split into atomic notes. +5. **Validate frontmatter.** Every note has top-level `name` (== filename stem), + `description`, `type`, `tags`, `created`, `updated`, `timestamp` (ISO 8601). + Fix the `-` filename prefix if `type` changed. No nested `metadata:` + blocks. +6. **Refresh from sources.** For notes with a `sources:` URL whose `timestamp`/ + `updated:` is stale (>~30 days) or whose facts look outdated, fetch the source + (prefer `llms.txt`), update the note, and bump `updated:` and `timestamp`. +7. **Compact.** Trim notes to the essential facts. Drop filler. Aim ≤~25 lines. +8. **Relink & retag.** Fix `[[slug]]` references; ensure no orphans (every note + links ≥1 other); merge tag sprawl into the controlled vocabulary; drop links + to deleted notes. +9. **Prune.** Delete notes that are wrong, obsolete, or now-private — and their + index lines. +10. **Rebuild index.** Regenerate `MEMORY.md` from surviving notes — grouped by + `type`, one line each: `[Title](file) — hook`. Then run **`kb gen`** to + regenerate every `memory/**/index.md` (OKF listings) + `viz.html` (graph + viewer) so they stay in sync with the nested layout. Append a new dated entry + to `memory/log.md` summarizing the pass (what merged/split/pruned). +11. **Verify scope.** If this repo is public, confirm no secrets/hosts/confidential + facts slipped in (`AGENTS.md` §3). Remove any that did. +12. **Stamp & sync.** Set `state.json.last_dream` to today, then `scripts/sync.sh` + (only if this repo has a git remote). + +## Principles + +- **Lossless of meaning.** Compaction removes words and redundancy, never facts. +- **Atomic.** One fact per file after the pass. +- **Retrieval-first.** Every `description` must let an agent judge relevance from + the index alone; every note is reachable via tags and `[[links]]`. +- **Conservative deletes.** Only prune what is wrong, redundant, or out-of-scope — + never delete a fact just because it's old (refresh it instead, step 6). +- **Idempotent.** Running the pass twice in a row should change nothing the + second time. diff --git a/kb/skills/init/templates/README.md b/kb/skills/init/templates/README.md new file mode 100644 index 0000000..d734c84 --- /dev/null +++ b/kb/skills/init/templates/README.md @@ -0,0 +1,91 @@ +# kb — your shared brain + +A single, plain-text knowledge base that every coding agent (Claude Code, Codex, +opencode, …) reads from and writes to — the root source of truth for agent +memory on this machine. Write a fact once; every agent and session uses it. + +## Design + +Simple and hackable (Zettelkasten-style): plain markdown, no database (agents +`grep` it); **atomic notes** (one fact per file); **index-first** (`MEMORY.md` +loaded before anything else); **linked** via `[[wikilinks]]` + `tags` (browsable +as a graph in Obsidian, or `kb viz` for a standalone viewer); **self-healing** +via the `DREAM.md` consolidation pass. + +## Structure + +``` +. +├── AGENTS.md ← canonical protocol for ALL agents (read/write/dream rules) +├── CLAUDE.md ← thin pointer to AGENTS.md +├── DREAM.md ← memory-consolidation ("dream") protocol +├── MEMORY.md ← master index, one line per note — load first +├── raw/ ← capture inbox (raw/inbox/.md) + ground-truth sources +├── memory/ ← the OKF v0.1 bundle: notes nested under /… +│ ├── index.md ← generated OKF listing (okf_version: "0.1") — run `kb gen` +│ ├── log.md ← generated change history (newest-first) +│ ├── _TEMPLATE.md ← the note standard +│ └── user/ feedback/ reference/ projects/ topics/ ← starter groups; nest freely +├── scripts/ ← kb CLI internals: lint / sync / wire / render_okf_viewer.py +├── bin/kb ← the `kb` CLI +├── viz.html ← generated self-contained graph viewer — run `kb viz` +└── .agent/ ← state.json (ingested files + tasks) +``` + +This repo **is a conformant [OKF v0.1](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md) bundle** — `memory/` is the bundle root; each concept is one `.md` carrying `type` (an OKF custom field). `index.md`/`log.md`/`viz.html` are generated; re-run `kb gen` (or `kb viz` to also open the viewer) after writing notes. + +Every note follows `memory/_TEMPLATE.md` and must pass `kb lint`. Format spec in +`AGENTS.md`. + +## How it works + +- **Read in:** `kb index` (= `MEMORY.md`), open the relevant notes; fetch a note's + `sources:` (`llms.txt`) for deeper detail. +- **Write out:** unsure it's durable → `kb capture "note"` (lands in `raw/inbox/`); + known keeper → a standard note in `memory//…`. Then + `kb lint && kb gen` (regenerate the OKF `index.md`/`viz.html`), and + `kb sync` if this repo has a git remote. +- **Dream:** periodically (or `/loop`) run `DREAM.md` — it ingests the inbox + raw + sources into clean notes, dedupes, refreshes stale notes from `sources:`, rebuilds + the index, and syncs. + +## Put this KB on PATH + +```bash +export PATH="/bin:$PATH" # add to ~/.zshrc or ~/.bashrc +``` + +## Wire the reflex into your agents (optional) + +`scripts/wire.sh on` adds a small, removable, marked block to each installed +agent's global instruction file (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, +`~/.config/opencode/AGENTS.md` — only the ones that already exist) so every new +session reads `MEMORY.md` on start and knows how to capture/write notes. Remove +it any time with `scripts/wire.sh off`; it touches nothing else in those files. + +## Version control (optional but recommended) + +```bash +git init +git remote add origin # e.g. a private repo for personal notes +``` + +If you make this repo **public**, keep only general, durable, public-facing +knowledge in it — see `AGENTS.md` §3 for what must never be committed. + +## CLI + +```bash +kb capture "rough note" # → raw/inbox/.md +kb ingest # add a source doc to raw/ +kb index | kb lint | kb sync | kb dream | kb root +kb gen | kb viz # regenerate (and open) the OKF index.md files + viz.html +kb wire on|off # add/remove the reflex block in agents' global config +kb autosync on|off|status # opt-in */15min sync cron (requires a git remote) +``` + +## Scope + +If this repo is public: general, durable, public-facing knowledge only. +**Never** here: secrets, SSH hosts, internal/employer-confidential details, +anything private — keep those in a private repo or the agent's local memory. diff --git a/kb/skills/init/templates/bin/kb b/kb/skills/init/templates/bin/kb new file mode 100755 index 0000000..3db669b --- /dev/null +++ b/kb/skills/init/templates/bin/kb @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# kb — capture/ingest/consolidate this shared-brain knowledge base. +# KB location is configurable: $KB_DIR if set, else auto-detected from this +# script's own location (resolves symlinks), so it works wherever kb lives. +set -euo pipefail + +# --- resolve KB_DIR --------------------------------------------------------- +if [[ -z "${KB_DIR:-}" ]]; then + src="${BASH_SOURCE[0]}" + while [[ -L "$src" ]]; do + dir="$(cd -P "$(dirname "$src")" && pwd)"; src="$(readlink "$src")" + [[ "$src" != /* ]] && src="$dir/$src" + done + KB_DIR="$(cd -P "$(dirname "$src")/.." && pwd)" +fi +export KB_DIR +cd "$KB_DIR" + +inbox() { echo "$KB_DIR/raw/inbox/$(date +%F).md"; } + +cmd="${1:-help}"; shift || true +case "$cmd" in + capture|c) + text="$*" + [[ -z "$text" ]] && { echo "usage: kb capture \"\""; exit 1; } + f="$(inbox)"; mkdir -p "$(dirname "$f")" + [[ -f "$f" ]] || printf '# Inbox — %s\n\n' "$(date +%F)" > "$f" + printf -- '- %s — %s\n' "$(date +%H:%M)" "$text" >> "$f" + echo "captured → ${f#$KB_DIR/} (run 'kb sync' to push)" ;; + ingest|i) + [[ -f "${1:-}" ]] || { echo "usage: kb ingest (adds a source doc to raw/)"; exit 1; } + mkdir -p "$KB_DIR/raw"; cp "$1" "$KB_DIR/raw/" + echo "added source → raw/$(basename "$1") (run a dream pass to ingest it)" ;; + dream|d) + echo "Run the dream pass: read and execute $KB_DIR/DREAM.md" + echo "(or in an agent: \"run $KB_DIR/DREAM.md consolidation\")" ;; + sync|s) exec "$KB_DIR/scripts/sync.sh" ;; + lint|l) exec "$KB_DIR/scripts/lint.sh" ;; + wire|w) exec "$KB_DIR/scripts/wire.sh" "${1:-on}" ;; + index) cat "$KB_DIR/MEMORY.md" ;; + gen) python3 "$KB_DIR/scripts/render_okf_viewer.py" "$KB_DIR/memory" \ + --title "$(basename "$KB_DIR")" --out "$KB_DIR/viz.html" ;; + viz) python3 "$KB_DIR/scripts/render_okf_viewer.py" "$KB_DIR/memory" \ + --title "$(basename "$KB_DIR")" --out "$KB_DIR/viz.html" + if command -v open >/dev/null 2>&1; then open "$KB_DIR/viz.html" + else echo "viz.html → $KB_DIR/viz.html"; fi ;; + root|path) echo "$KB_DIR" ;; + autosync) + command -v crontab >/dev/null 2>&1 || { echo "crontab not available"; exit 1; } + line="*/15 * * * * $KB_DIR/scripts/sync.sh >> \$HOME/.kb-sync.log 2>&1" + rest="$(crontab -l 2>/dev/null | grep -v '/scripts/sync\.sh' || true)" + case "${1:-status}" in + on) { [ -n "$rest" ] && printf '%s\n' "$rest"; printf '%s\n' "$line"; } | crontab - + echo "autosync on (*/15 min)" ;; + off) if [ -n "$rest" ]; then printf '%s\n' "$rest" | crontab -; else crontab -r 2>/dev/null || true; fi + echo "autosync off" ;; + status|*) crontab -l 2>/dev/null | grep -q '/scripts/sync\.sh' && echo "on" || echo "off" ;; + esac ;; + help|-h|--help) + cat <" append a timestamped line to today's raw/inbox + kb ingest copy a source doc into raw/ for later ingestion + kb dream how to run the consolidation pass (agent task) + kb sync pull + commit + push (no-op if no git remote) + kb lint validate notes against the standard + kb index print MEMORY.md + kb gen regenerate OKF index.md files + viz.html + kb viz regenerate + open the graph viewer + kb root print the resolved KB location + kb autosync on|off|status opt-in */15min sync cron (not installed by default) + kb wire on|off add/remove the kb reflex in agents' global config +EOF + ;; + *) echo "kb: unknown command '$cmd' (try: kb help)"; exit 1 ;; +esac diff --git a/kb/skills/init/templates/gitignore b/kb/skills/init/templates/gitignore new file mode 100644 index 0000000..948c6dc --- /dev/null +++ b/kb/skills/init/templates/gitignore @@ -0,0 +1,2 @@ +node_modules +.agent/state.json diff --git a/kb/skills/init/templates/memory/_TEMPLATE.md b/kb/skills/init/templates/memory/_TEMPLATE.md new file mode 100644 index 0000000..2f0d37f --- /dev/null +++ b/kb/skills/init/templates/memory/_TEMPLATE.md @@ -0,0 +1,24 @@ +--- +name: type-short-kebab-slug +title: Human-readable title +description: One line — front-load keywords; this is what agents see in the index +type: user +category: profile +tags: [type, topic-one, topic-two] +aliases: [] +related: ["[[some-other-note]]"] +sources: ["https://example.com/llms.txt"] +created: 2026-01-01 +updated: 2026-01-01 +timestamp: 2026-01-01T00:00:00Z +--- + +The fact(s), compact. One entity/topic per file, ≤~25 lines. Prefer lists/tables +over prose. Cite non-obvious claims with a [[link]] or URL. + +For `feedback`/`project` notes, add: + +**Why:** the reason this matters. +**How to apply:** the concrete action an agent should take. + +Link related notes inline with [[their-slug]] so nothing is orphaned. diff --git a/kb/skills/init/templates/raw/README.md b/kb/skills/init/templates/raw/README.md new file mode 100644 index 0000000..cdb335a --- /dev/null +++ b/kb/skills/init/templates/raw/README.md @@ -0,0 +1,25 @@ +# raw/ — capture inbox + ground-truth sources (Layer 1) + +Raw material that the **dream** pass (`../DREAM.md`) consumes into clean, +linked notes under `../memory/`. Two kinds of content live here: + +## `inbox/` — daily quick-capture (writable, ephemeral) + +Agents append rough observations here as they work — no frontmatter, no +ceremony. One file per day: `inbox/YYYY-MM-DD.md`, timestamped bullets. This is +a scratch journal, not the final memory. + +The dream pass **consumes** these: it promotes durable, general facts into +`memory/` notes, then **deletes the processed inbox file** (it's been distilled). +So `inbox/` stays small and self-cleaning — capture freely, dream tidies up. + +## source docs — immutable ground truth (read-only) + +Drop PDFs, articles, transcripts, `llms.txt` snapshots directly in `raw/` (not in +`inbox/`). Agents read these to synthesize notes but **never edit them**. Dream +records each in `.agent/state.json` (`processed`) so it's ingested once, and +**keeps** the file (it's permanent ground truth, unlike the inbox). + +--- + +If this repo is public, keep only public material here. diff --git a/kb/skills/init/templates/scripts/lint.sh b/kb/skills/init/templates/scripts/lint.sh new file mode 100755 index 0000000..7e67b43 --- /dev/null +++ b/kb/skills/init/templates/scripts/lint.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Lint memory notes against the standard (AGENTS.md §2). +# Checks: required frontmatter fields, name == filename, no nested metadata:, +# and that every [[wikilink]] resolves to a real note. Exits non-zero on failure. +# Files prefixed with "_" (e.g. _TEMPLATE.md) are skipped. +set -euo pipefail + +REPO="${KB_DIR:-$HOME/kb}" +cd "$REPO" +# `timestamp` (ISO 8601) is required by our lint — stricter than the OKF spec +# (which only mandates `type`), matching Google's reference validator, which +# rejects a concept missing type/title/description/timestamp. +REQUIRED=(name description type tags created updated timestamp) +fail=0 + +# Recursive discovery: notes live under memory//[…]/.md. +# Skip the OKF reserved filenames (index.md, log.md) and the _TEMPLATE. +notes=() +while IFS= read -r f; do + b="$(basename "$f")" + [[ "$b" == _* ]] && continue + [[ "$b" == "index.md" || "$b" == "log.md" ]] && continue + notes+=("$f") +done < <(find memory -type f -name '*.md' | sort) + +if [[ ${#notes[@]} -eq 0 ]]; then echo "✓ 0 notes yet — nothing to lint"; exit 0; fi + +# Build the set of valid note slugs (for link resolution). +slugs=" " +for f in "${notes[@]}"; do slugs+="$(basename "$f" .md) "; done + +for f in "${notes[@]}"; do + stem="$(basename "$f" .md)" + fm="$(awk 'NR==1&&$0=="---"{f=1;next} f&&$0=="---"{exit} f{print}' "$f")" + + for key in "${REQUIRED[@]}"; do + grep -qE "^${key}:" <<<"$fm" || { echo "✗ $f: missing '$key:'"; fail=1; } + done + + name="$(grep -E '^name:' <<<"$fm" | head -1 | sed 's/^name:[[:space:]]*//;s/[[:space:]]*$//')" + [[ "$name" == "$stem" ]] || { echo "✗ $f: name ('$name') != filename ('$stem')"; fail=1; } + + grep -qE '^metadata:' <<<"$fm" && { echo "✗ $f: nested 'metadata:' block — use top-level fields"; fail=1; } + + type="$(grep -E '^type:' <<<"$fm" | head -1 | sed 's/^type:[[:space:]]*//;s/[[:space:]]*$//')" + case "$type" in user|feedback|project|reference|tech) ;; *) echo "✗ $f: invalid type '$type'"; fail=1 ;; esac +done + +# Broken-link check across all notes. +while read -r target; do + [[ -z "$target" ]] && continue + [[ "$slugs" == *" $target "* ]] || echo "✗ broken link: [[$target]] has no note (stub — create it or fix)" +done < <(grep -rho '\[\[[^]]*\]\]' "${notes[@]}" 2>/dev/null | sed 's/\[\[//;s/\]\]//' | sort -u) + +# Security leak check — only meaningful if this repo is public (AGENTS.md §3). +while IFS= read -r line; do + [[ -z "$line" ]] && continue + echo "✗ possible secret/host leak: $line" + fail=1 +done < <(grep -rhE \ + '(sk-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36}|AKIA[0-9A-Z]{16}|-----BEGIN (RSA |EC )?PRIVATE KEY-----|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|ssh://|@.*\.internal\.|\.onion|password\s*[:=]|secret\s*[:=]|token\s*[:=])' \ + "${notes[@]}" 2>/dev/null | grep -v '^---' | grep -v '^sources:') + +if [[ $fail -eq 0 ]]; then echo "✓ ${#notes[@]} notes pass the standard"; else echo "lint failed"; exit 1; fi diff --git a/kb/skills/init/templates/scripts/render_okf_viewer.py b/kb/skills/init/templates/scripts/render_okf_viewer.py new file mode 100755 index 0000000..d941322 --- /dev/null +++ b/kb/skills/init/templates/scripts/render_okf_viewer.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +# Vendored from the `okf` plugin's skills/okf/scripts/render_okf_viewer.py so +# this kb stays self-contained (works even if that plugin is uninstalled). +"""Render OKF v0.1 derived artifacts for any bundle. Stdlib only. + +Walks a bundle directory once and emits: + - `/index.md` at every level — OKF progressive-disclosure listings + (root carries `okf_version: "0.1"`; + others carry none) + - `/viz.html` (or --out) — self-contained graph viewer + (cytoscape + marked via CDN) + +Frontmatter is read with a flat line-scan (`key: value`), the same convention +used by validate_okf.py — no nested YAML, no PyYAML dependency. Concepts link +to each other three ways, all three are read as graph edges: + - `related: ["[[slug]]", "other-slug"]` frontmatter + - `[[slug]]` wikilinks in the body + - `[text](path/to/concept.md)` markdown links in the body (bundle-relative, + absolute `/path.md` or relative to the linking file) + +Usage: + render_okf_viewer.py [--title NAME] [--out FILE] + +Idempotent: re-running overwrites the same outputs. +""" +import argparse +import json +import os +import re +import sys + +# Deterministic, cycling palette — assigned to types in first-seen order so +# any bundle's custom `type` values get stable, distinct colors without +# hardcoding a fixed vocabulary. +PALETTE = [ + "#8b5cf6", "#ec4899", "#3b82f6", "#10b981", "#f59e0b", + "#ef4444", "#14b8a6", "#a855f7", "#84cc16", "#0ea5e9", +] +RESERVED = {"index.md", "log.md"} + + +def split_fm(text): + m = re.match(r"\A---\r?\n(.*?)\r?\n---\r?\n?(.*)\Z", text, re.DOTALL) + if not m: + return {}, text + fm = {} + for line in m.group(1).splitlines(): + mm = re.match(r"^([A-Za-z_][\w-]*):\s*(.*)$", line) + if mm: + fm[mm.group(1)] = mm.group(2).strip() + return fm, m.group(2) + + +def clean(v): + return (v or "").strip().strip('"').strip("'").strip() + + +def parse_list(v): + v = clean(v) + if v.startswith("[") and v.endswith("]"): + v = v[1:-1] + if not v: + return [] + return [x.strip().strip('"').strip("'") for x in re.split(r"[,\n]", v) if x.strip()] + + +def strip_wikilink(ref): + return ref.replace("[[", "").replace("]]", "").split("|")[0].split("#")[0].strip() + + +def collect(root): + concepts = [] + for dp, dn, fn in os.walk(root): + dn.sort() + for name in sorted(fn): + if not name.endswith(".md") or name in RESERVED or name.startswith("_"): + continue + path = os.path.join(dp, name) + fm, body = split_fm(open(path, encoding="utf-8").read()) + rel = os.path.relpath(path, root) + slug = name[:-3] + md_links = [ + href.split("#")[0] for href in re.findall(r"\]\(([^)\s]+)\)", body) + if href.lower().endswith(".md") and "://" not in href + ] + concepts.append({ + "path": rel, + "dirpath": dp, + "slug": slug, + "title": clean(fm.get("title")) or slug, + "description": clean(fm.get("description")), + "type": clean(fm.get("type")) or "note", + "tags": parse_list(fm.get("tags", "")), + "related": [strip_wikilink(r) for r in parse_list(fm.get("related", ""))], + "wikilinks": [strip_wikilink(w) for w in re.findall(r"\[\[([^\]]+)\]\]", body)], + "md_links": md_links, + "sources": parse_list(fm.get("sources", "")), + "body": body.strip(), + }) + return concepts + + +def resolve_md_link(root, from_rel, href): + """Resolve a markdown link's href to a bundle-relative path (posix, no leading /).""" + if href.startswith("/"): + target = href.lstrip("/") + else: + target = os.path.normpath(os.path.join(os.path.dirname(from_rel), href)) + return target.replace(os.sep, "/") + + +def write_indexes(root, concepts, bundle_label): + by_dir = {} + for c in concepts: + by_dir.setdefault(c["dirpath"], []).append(c) + dirs_to_index = set(by_dir.keys()) | {root} + for dp, dn, _fn in os.walk(root): + if dn: + dirs_to_index.add(dp) + written = [] + for dirpath in sorted(dirs_to_index): + rel = os.path.relpath(dirpath, root) + is_root = rel == "." + subdirs = sorted(d for d in os.listdir(dirpath) + if os.path.isdir(os.path.join(dirpath, d))) + here = sorted(by_dir.get(dirpath, []), key=lambda c: c["title"].lower()) + if not subdirs and not here: + continue + if is_root: + out = ["---", 'okf_version: "0.1"', "---", "", f"# {bundle_label}", "", + "Graph viewer: `viz.html`.", ""] + else: + out = [f"# `{rel}/`", ""] + if subdirs: + out += ["## Groups", ""] + [f"- [`{d}/`]({d}/)" for d in subdirs] + [""] + if here: + out += ["## Concepts", ""] + for c in here: + tail = f" — {c['description']}" if c["description"] else "" + out.append(f"- [{c['title']}]({os.path.basename(c['path'])}){tail}") + out += [""] + p = os.path.join(dirpath, "index.md") + open(p, "w", encoding="utf-8").write("\n".join(out)) + written.append(p) + return written + + +def build_bundle(root, concepts): + slugs = {c["slug"] for c in concepts} + path_to_slug = {c["path"].replace(os.sep, "/"): c["slug"] for c in concepts} + edge_set = set() + for c in concepts: + targets = set(c["related"]) | set(c["wikilinks"]) + for t in targets: + if t in slugs and t != c["slug"]: + edge_set.add((c["slug"], t)) + for href in c["md_links"]: + resolved = resolve_md_link(root, c["path"], href) + target_slug = path_to_slug.get(resolved) or ( + os.path.basename(resolved)[:-3] if resolved.endswith(".md") else None + ) + if target_slug and target_slug in slugs and target_slug != c["slug"]: + edge_set.add((c["slug"], target_slug)) + degree = {s: 0 for s in slugs} + for a, b in edge_set: + degree[a] += 1 + degree[b] += 1 + types, nodes, bodies = [], [], {} + for c in concepts: + if c["type"] not in types: + types.append(c["type"]) + color = PALETTE[types.index(c["type"]) % len(PALETTE)] + nodes.append({"data": { + "id": c["slug"], "label": c["title"], "type": c["type"], + "description": c["description"], "tags": c["tags"], + "resource": c["sources"][0] if c["sources"] else "", + "color": color, "size": 24 + min(degree[c["slug"]], 6) * 4, + }}) + bodies[c["slug"]] = c["body"] + edges = [{"data": {"id": f"{a}__{b}", "source": a, "target": b}} + for a, b in sorted(edge_set)] + palette = {t: PALETTE[i % len(PALETTE)] for i, t in enumerate(types)} + return {"nodes": nodes, "edges": edges, "bodies": bodies, "types": types, "palette": palette} + + +VIZ = r""" + + + + +__NAME__ — OKF Bundle Viewer + + + + + +
+
__NAME__OKF v0.1 bundle viewer
+
+
+
+
scroll = zoom · drag = pan · click a node
+

Select a node to read it.

+
+ + + +""" + + +def main(): + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("bundle_dir", help="OKF bundle root to walk") + ap.add_argument("--title", help="viewer title (default: bundle dir name)") + ap.add_argument("--out", help="viz.html output path (default: /viz.html)") + args = ap.parse_args() + + root = os.path.abspath(args.bundle_dir) + if not os.path.isdir(root): + print(f"error: not a directory: {root}", file=sys.stderr) + return 1 + + title = args.title or os.path.basename(root.rstrip(os.sep)) or "bundle" + out = os.path.abspath(args.out) if args.out else os.path.join(root, "viz.html") + + concepts = collect(root) + idx = write_indexes(root, concepts, title) + bundle = build_bundle(root, concepts) + html = VIZ.replace("__NAME__", title).replace("__BUNDLE__", json.dumps(bundle)) + os.makedirs(os.path.dirname(out) or ".", exist_ok=True) + open(out, "w", encoding="utf-8").write(html) + + print(f"generated {len(idx)} index.md + {os.path.relpath(out)} " + f"({len(bundle['nodes'])} nodes, {len(bundle['edges'])} edges)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/kb/skills/init/templates/scripts/sync.sh b/kb/skills/init/templates/scripts/sync.sh new file mode 100755 index 0000000..f3ff835 --- /dev/null +++ b/kb/skills/init/templates/scripts/sync.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Sync this kb with its git remote, if it has one. +# Pulls remote changes (rebase, autostash), commits any local edits, pushes. +# Safe to run repeatedly (cron, git hook, or by hand). Never loses local work. +# No-op (with a message) if this repo has no remote configured. +set -euo pipefail + +REPO="${KB_DIR:-$HOME/kb}" +cd "$REPO" + +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "not a git repo — run 'git init' first, or skip sync"; exit 0 +fi +if [ -z "$(git remote)" ]; then + echo "no git remote configured — run 'git remote add origin ' to enable sync"; exit 0 +fi + +BRANCH="$(git rev-parse --abbrev-ref HEAD)" + +# Pull first so we rebase local work on top of any remote changes. +git pull --rebase --autostash origin "$BRANCH" || true + +# Commit local changes, if any. +if [ -n "$(git status --porcelain)" ]; then + git add -A + git commit -q -m "memory: auto-sync $(date '+%Y-%m-%d %H:%M')" +fi + +# Push (sets upstream on first run). +git push -u origin "$BRANCH" diff --git a/kb/skills/init/templates/scripts/wire.sh b/kb/skills/init/templates/scripts/wire.sh new file mode 100755 index 0000000..b2acbf1 --- /dev/null +++ b/kb/skills/init/templates/scripts/wire.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Wire the kb reflex into global agent config so EVERY session (Claude Code, +# Codex, opencode) reads the brain on start. Adds a small MARKED block to each +# tool's global instruction file — idempotent (re-run safe) and fully removable +# (`wire.sh off` strips exactly the block, leaving the rest of the file intact). +# wire.sh on | off (default: on) +set -euo pipefail + +KB_DIR="${KB_DIR:-$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" +HOME_BASE="${KB_HOME:-$HOME}" # override in tests for isolation +action="${1:-on}" + +# Tidy reference: ~/kb when it resolves to this repo, else the real path. +REF="$KB_DIR" +if [ -d "$HOME/kb" ] && [ "$(cd -P "$HOME/kb" 2>/dev/null && pwd)" = "$KB_DIR" ]; then REF="~/kb"; fi + +# Targets: always wire Claude Code (.claude); wire other agents only if present. +targets=( "$HOME_BASE/.claude/CLAUDE.md" "$HOME_BASE/.claude/AGENTS.md" ) +[ -d "$HOME_BASE/.codex" ] && targets+=( "$HOME_BASE/.codex/AGENTS.md" ) +[ -d "$HOME_BASE/.config/opencode" ] && targets+=( "$HOME_BASE/.config/opencode/AGENTS.md" ) + +BLOCK=" +# Knowledge Base — shared brain ($REF) +\`$REF\` is this machine's CANONICAL, cross-agent memory. When asked to remember / save / note / capture / recall anything durable, use IT — NOT any local or built-in agent memory store. +On session start, read \`$REF/MEMORY.md\` (the index) and open the relevant notes; fetch a note's \`sources:\` for deeper detail. +To capture a quick note, append a line \`- HH:MM — \` to TODAY's inbox file \`$REF/raw/inbox/.md\` (exact path; create it if missing), or run \`$REF/bin/kb capture \"\"\`. +Write durable notes under \`$REF/memory/\` (template: \`$REF/memory/_TEMPLATE.md\`). +After ANY write (capture or note): read the file back to confirm it, then run \`$REF/bin/kb sync\` to share it, if this kb has a git remote. +Full protocol: \`$REF/AGENTS.md\`. Consolidate via \`$REF/DREAM.md\`. If this kb is public, never store secrets, hostnames, IPs, machine names, locations, or anything not already public (see AGENTS.md §3). +" + +strip_block() { [ -f "$1" ] && awk '/ # Knowledge Base — shared brain ($REF) @@ -33,15 +33,21 @@ Full protocol: \`$REF/AGENTS.md\`. Consolidate via \`$REF/DREAM.md\`. If this kb strip_block() { [ -f "$1" ] && awk '/