add shared brain vault skill
This commit is contained in:
@@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
name: shared-brain-vault
|
||||||
|
description: "Build and maintain a Git-synced Obsidian vault as shared working memory between user and AI agents. Use when: (1) Writing session summaries or project updates, (2) Recording decisions or learnings, (3) Adding people/profiles to the vault, (4) Populating or restructuring the vault, (5) Setting up multi-agent vault access, (6) Answering 'what did we decide' or 'where do we stand on X'. Triggers: 'shared brain', 'm3ta-brain', 'session summary', 'vault update', 'decision note', 'project status', 'what did we decide'."
|
||||||
|
compatibility: opencode
|
||||||
|
---
|
||||||
|
|
||||||
|
# Shared Brain Vault
|
||||||
|
|
||||||
|
Manage the Git-synced Obsidian vault that serves as shared working memory between Sascha and his AI agents.
|
||||||
|
|
||||||
|
## Vault Location
|
||||||
|
|
||||||
|
- **Repo**: `ssh://gitea@code.m3ta.dev/m3tam3re/m3ta-brain.git`
|
||||||
|
- **Standard clone path**: `~/m3ta-brain` (on m3-hermes this resolves to `/var/lib/hermes/m3ta-brain`)
|
||||||
|
- **Local clone** (Sascha's machines): opened as Obsidian vault
|
||||||
|
|
||||||
|
## Scheduled Dream Pass / Cron
|
||||||
|
|
||||||
|
When configuring or troubleshooting the nightly m3ta-brain Dream Pass cron, use this installed skill name: `shared-brain-vault`. Do **not** attach a non-existent `m3ta-brain` skill unless it has actually been deployed in the active Hermes profile. The cron prompt should explicitly say to load `shared-brain-vault` and then follow the canonical vault files `~/m3ta-brain/AGENTS.md` and `~/m3ta-brain/SCHEMA.md`; if those disagree with the skill, the vault files win.
|
||||||
|
|
||||||
|
Pitfall: a cron run may do useful work and even commit/push, but still be marked `error` if the scheduled job references a missing skill. Fix the job's `skills` field first, then remember that `last_status` will remain `error` until the next successful run.
|
||||||
|
|
||||||
|
Cross-agent note: the skill being installed in `~/.hermes/skills/` does not mean it exists in the AGENTS/agent-hub repo. If Sascha asks whether it is in the AGENTS repo, check/ensure `.agents/skills/shared-brain-vault/SKILL.md` separately.
|
||||||
|
|
||||||
|
## Read Order (Session Start)
|
||||||
|
|
||||||
|
1. `AGENTS.md` — the rules (canonical source for all agents)
|
||||||
|
2. `SCHEMA.md` — frontmatter types, tag taxonomy, naming conventions
|
||||||
|
3. `INDEX.md` — dashboard, what's new, what needs attention
|
||||||
|
4. `20-Projects/` — scan for current project context
|
||||||
|
5. `10-Preferences/do-dont.md` — hard rules
|
||||||
|
|
||||||
|
Do NOT read the entire vault. Read by need.
|
||||||
|
|
||||||
|
## Write Policy
|
||||||
|
|
||||||
|
### Agent MAY write directly (no review):
|
||||||
|
- `60-Learning/sessions/` — session summaries after substantial work
|
||||||
|
- `60-Learning/patterns/` — recurring observations
|
||||||
|
- `60-Learning/corrections/` — things that were wrong and got fixed
|
||||||
|
- `60-Learning/inbox/` — candidates pending user review
|
||||||
|
- `20-Projects/*.md` — status updates after concrete work
|
||||||
|
- `30-Decisions/*.md` — when a clear decision was made
|
||||||
|
|
||||||
|
### Agent SHOULD write as DRAFT (`status: draft`):
|
||||||
|
- `00-Telos/*.md` — identity, goals, constraints
|
||||||
|
- `10-Preferences/*.md` — personal preferences, working style
|
||||||
|
- `40-People/*.md` — person profiles (especially colleagues)
|
||||||
|
- `70-Collaboration/*.md` — collaboration agreements
|
||||||
|
|
||||||
|
### Agent MUST NOT write:
|
||||||
|
- Passwords, API keys, tokens, secrets
|
||||||
|
- Intimate/private details without explicit consent
|
||||||
|
- Speculative assumptions stated as facts
|
||||||
|
- Full chat transcripts (use summaries, not raw dumps)
|
||||||
|
|
||||||
|
## Session-End Workflow
|
||||||
|
|
||||||
|
After substantial work in a session:
|
||||||
|
|
||||||
|
1. Write session summary to `60-Learning/sessions/YYYY-MM-DD-{slug}.md`
|
||||||
|
2. If a decision was made → create/update `30-Decisions/` note
|
||||||
|
3. If project state changed → update `20-Projects/{project}.md`
|
||||||
|
4. If something new was learned → candidate in `60-Learning/inbox/`
|
||||||
|
5. Update `INDEX.md` if new notes were added
|
||||||
|
6. `git add -A && git commit -m "update: {description}" && git push`
|
||||||
|
|
||||||
|
Not every session needs a summary. Only sessions that produced durable value.
|
||||||
|
|
||||||
|
## Language & Link Conventions
|
||||||
|
|
||||||
|
- **Language**: German primary, English technical terms (match how Sascha communicates)
|
||||||
|
- **Links**: Standard Markdown relative links (`[Text](relative/path.md)`) — works in Gitea AND Obsidian. Do not write Obsidian-only double-bracket links.
|
||||||
|
- **Tags**: YAML frontmatter `tags:` list, not inline `#tag`
|
||||||
|
- **Dates**: `YYYY-MM-DD` everywhere
|
||||||
|
- **File naming**: `kebab-case.md`
|
||||||
|
|
||||||
|
> **CRITICAL**: Current `AGENTS.md` and `SCHEMA.md` in the vault are canonical. If this skill disagrees with them, follow the vault files and patch the skill.
|
||||||
|
|
||||||
|
## Vault Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
00-Telos/ Who Sascha is, goals, constraints
|
||||||
|
10-Preferences/ Communication, tech-stack, working-style, do-don't
|
||||||
|
20-Projects/ Active project cards (state, next actions, decisions)
|
||||||
|
30-Decisions/ ADR-style decision records
|
||||||
|
40-People/ Colleagues, external contacts, family
|
||||||
|
50-Knowledge/ Reference knowledge (systems, concepts, companies)
|
||||||
|
60-Learning/ Session summaries, patterns, corrections
|
||||||
|
70-Collaboration/ How agents and human work together
|
||||||
|
80-Templates/ Note templates (project, decision, person, etc.)
|
||||||
|
90-Archive/ Completed/deprecated
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi-Agent Compatibility
|
||||||
|
|
||||||
|
This vault works with ANY agent (Hermes, Pi, OpenCode, Claude Code, Codex):
|
||||||
|
- `AGENTS.md` at root = universal instructions (no agent-specific assumptions)
|
||||||
|
- Agent identity stays in commit messages, not in note bodies
|
||||||
|
- Multiple agents may read/write — last-writer-wins via Git
|
||||||
|
- Sascha uses Pi + OpenCode on work laptop (with Basecamp CLI access)
|
||||||
|
|
||||||
|
### Agent-Hub skill deployment
|
||||||
|
|
||||||
|
When Sascha asks whether this skill is in the AGENTS/agent-hub repo, verify `.agents/skills/shared-brain-vault/SKILL.md`; the Hermes-installed copy under `~/.hermes/skills/` is not enough for cross-agent use. If deploying it, copy the whole skill directory including `references/` and `scripts/`, stage only `.agents/skills/shared-brain-vault`, run the link audit and Agent-Hub checks, then commit without mixing pre-existing dirty Agent-Hub files. See `references/agent-hub-skill-deployment.md` for the command recipe and verification checklist.
|
||||||
|
|
||||||
|
## Initial Population Technique
|
||||||
|
|
||||||
|
When populating the vault from scratch or doing a major migration:
|
||||||
|
|
||||||
|
1. **Extract context** from all available sources:
|
||||||
|
- Hermes internal memory (`~/.hermes/memories/MEMORY.md`, `USER.md`)
|
||||||
|
- Honcho peer card and user profile
|
||||||
|
- Session search for key topics (5-10 queries covering all major projects)
|
||||||
|
- Existing wikis (tech wiki, nemoti wiki)
|
||||||
|
- Git repos for infrastructure details
|
||||||
|
2. **Compile into a single source file** at `/tmp/m3ta-brain-source/context.md`
|
||||||
|
3. **Dispatch parallel subagents** (up to 3), each handling distinct sections:
|
||||||
|
- Agent A: Telos + Preferences + Collaboration
|
||||||
|
- Agent B: Projects + Decisions + Knowledge
|
||||||
|
- Agent C: People + Learning
|
||||||
|
4. Each subagent reads AGENTS.md + SCHEMA.md + context file, writes its files
|
||||||
|
5. **Merge, update INDEX.md, commit, push**
|
||||||
|
|
||||||
|
See `references/km-landscape-research.md` for the research that informed this design (Karpathy LLM Wiki, Miessler PAI, qmd, Obsidian MCP integrations).
|
||||||
|
|
||||||
|
See the vault's current link-audit command (documented in the `m3ta-brain` skill's `references/vault-map.md` when available, or in vault maintenance notes) after any vault population or restructuring.
|
||||||
|
|
||||||
|
## Relationship to Other Knowledge Stores
|
||||||
|
|
||||||
|
| Store | Path | Purpose | Links |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **m3ta-brain** (this) | `~/m3ta-brain` | Shared working memory | Standard Markdown relative links |
|
||||||
|
| Tech Wiki | `/var/lib/hermes/wiki` | Research, concepts (EN) | Standard Markdown relative links |
|
||||||
|
| Nemoti Wiki | `/var/lib/hermes/wiki-nemoti` | Lore, art strategy (DE) | Standard Markdown relative links |
|
||||||
|
| Hermes Memory | `~/.hermes/memories/` | Hot cache, high-priority facts | N/A |
|
||||||
|
| Session DB | Hermes internal | Raw transcripts | `session_search()` |
|
||||||
|
|
||||||
|
**Rule**: Don't duplicate content across stores. m3ta-brain holds context and cross-references; research wikis hold detailed knowledge; Hermes memory holds hot facts for fast injection.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- **Use standard Markdown links in m3ta-brain** — Gitea compatibility matters. This differs from older Obsidian-only guidance; current vault rules in `AGENTS.md` and `SCHEMA.md` are canonical.
|
||||||
|
- **Don't dump raw chat transcripts** — write concise summaries with decisions, learnings, next actions.
|
||||||
|
- **Don't create notes without cross-links** — minimum 2 internal Markdown links per note. Orphan notes break the graph.
|
||||||
|
- **Don't write to 00-Telos or 10-Preferences without `status: draft`** — these are personal and need user review.
|
||||||
|
- **Don't forget to `git push`** — the vault is useless if changes stay local.
|
||||||
|
- **Don't confuse vault purposes** — m3ta-brain is working memory (state, decisions, learnings), not a research encyclopedia. For research, use the `research-intelligence` skill.
|
||||||
|
- **People profiles are sensitive** — use `sensitivity: confidential` and `status: draft` for all person notes. Add colleagues only as information is learned through actual work.
|
||||||
|
- **Subagents produce broken links** — after ANY parallel subagent population, run a Markdown-link audit and fix broken relative targets. Common errors: paths relative to vault root instead of the current file, placeholder example links, directory-only links with no target file, and ambiguous note names.
|
||||||
|
- **`.gitkeep` is invisible in Obsidian** — use `README.md` instead for placeholder/directory-explanation content. Obsidian only shows `.md` files in the file explorer; hidden files are excluded.
|
||||||
|
- **`execute_code` may be blocked in cron profiles** — run the link audit via `terminal`/`python3`, not via `execute_code`, if cron execution rejects Python tool calls.
|
||||||
|
- **Dream Pass cron skill names can drift** — if a scheduled vault job reports `Skill(s) not found` for a vault-specific alias such as `m3ta-brain`, first list installed skills and update the cron to use the available class-level skill (`shared-brain-vault`) while keeping `~/m3ta-brain/AGENTS.md` and `~/m3ta-brain/SCHEMA.md` as canonical runtime instructions. Update both the cron `skills` field and any prompt text that tells the agent to load the missing skill; otherwise the next run may still be marked failed even if the task succeeds via fallback.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Agent-Hub Skill Deployment Notes
|
||||||
|
|
||||||
|
Use when making the Shared Brain Vault workflow available to cross-agent tools through the AGENTS/agent-hub repository.
|
||||||
|
|
||||||
|
## Target layout
|
||||||
|
|
||||||
|
Copy the complete skill directory, not only `SKILL.md`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/hermes/workspace/agent-hub/.agents/skills/shared-brain-vault/
|
||||||
|
SKILL.md
|
||||||
|
references/
|
||||||
|
scripts/
|
||||||
|
```
|
||||||
|
|
||||||
|
Source of the active Hermes-installed skill is usually:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/var/lib/hermes/.hermes/skills/knowledge-management/shared-brain-vault/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safe deployment sequence
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /var/lib/hermes/workspace/agent-hub
|
||||||
|
mkdir -p .agents/skills/shared-brain-vault
|
||||||
|
cp -a /var/lib/hermes/.hermes/skills/knowledge-management/shared-brain-vault/. \
|
||||||
|
.agents/skills/shared-brain-vault/
|
||||||
|
chmod +x .agents/skills/shared-brain-vault/scripts/*.py 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
Then stage only the shared-brain skill files so unrelated dirty Agent-Hub work is not mixed into the commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .agents/skills/shared-brain-vault
|
||||||
|
git diff --cached --stat
|
||||||
|
git diff --cached --name-status
|
||||||
|
git commit -m "add shared brain vault skill"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Run the vault link audit against the actual vault path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 .agents/skills/shared-brain-vault/scripts/audit-wikilinks.py /var/lib/hermes/m3ta-brain
|
||||||
|
```
|
||||||
|
|
||||||
|
If Agent-Hub has its repo check script, run it too:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/check-agent-hub.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Before claiming success, confirm the committed files and current status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status --short
|
||||||
|
git log -1 --oneline
|
||||||
|
```
|
||||||
|
|
||||||
|
If `git remote -v` prints nothing, do not claim a push happened; report that the local commit exists and that no remote is configured.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- Do not assume a skill installed in `~/.hermes/skills/` is present in Agent-Hub. Check `.agents/skills/<name>/` separately.
|
||||||
|
- Do not copy only `SKILL.md`; supporting `references/` and `scripts/` carry reusable detail and verification helpers.
|
||||||
|
- Do not mix pre-existing Agent-Hub changes into the skill commit. Stage the skill path explicitly.
|
||||||
|
- Keep the active Hermes-installed copy and Agent-Hub copy in sync when patching durable workflow guidance.
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# Knowledge Management Landscape Research (2026-06-29)
|
||||||
|
|
||||||
|
Research conducted when designing the m3ta-brain vault. Condensed for future reference.
|
||||||
|
|
||||||
|
## 1. Karpathy LLM Wiki Pattern (April 2026)
|
||||||
|
|
||||||
|
Andrej Karpathy published a "pattern gist" for LLM-maintained personal knowledge bases.
|
||||||
|
|
||||||
|
**Core idea**: Instead of RAG (re-derive from raw docs every query), the LLM builds and maintains a **persistent, compounding wiki** — cross-references already there, contradictions already flagged, synthesis already done.
|
||||||
|
|
||||||
|
**Three layers**:
|
||||||
|
- Raw sources (`raw/`) — original articles, papers, transcripts
|
||||||
|
- LLM Wiki (`wiki/`) — entity pages, concepts, comparisons, synthesis (LLM writes ALL of this)
|
||||||
|
- Schema (`CLAUDE.md` / `AGENTS.md`) — tells the LLM how the wiki is structured
|
||||||
|
|
||||||
|
**Key quote**: *"Obsidian is the IDE, the LLM is the programmer, the wiki is the codebase."*
|
||||||
|
|
||||||
|
**What we adopted**: AGENTS.md as schema, structured directories, LLM-maintained notes, Obsidian as viewer.
|
||||||
|
**What we extended**: Added project state, decisions, people, learnings — not just research concepts.
|
||||||
|
|
||||||
|
Source: https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f
|
||||||
|
|
||||||
|
## 2. Daniel Miessler PAI / Personal AI Infrastructure (v5.0, May 2026)
|
||||||
|
|
||||||
|
PAI ("Life Operating System") is the most comprehensive personal AI system design.
|
||||||
|
|
||||||
|
**7 Architecture Components**: Intelligence, Context (Memory), Personality, Tools, Security, Orchestration, Interface.
|
||||||
|
|
||||||
|
**Key insight — Scaffolding > Model**: "When Kai gives me a result I don't want, it's almost never because Claude is dumb. It's because my scaffolding didn't provide the right context."
|
||||||
|
|
||||||
|
**Memory System v7.6 — three tiers by PURPOSE, not chronology**:
|
||||||
|
- `MEMORY/WORK/` — active task tracking, ISAs (Ideal State Artifacts)
|
||||||
|
- `MEMORY/KNOWLEDGE/` — typed graph (People, Companies, Ideas, Research)
|
||||||
|
- `MEMORY/LEARNING/` — meta-patterns, signals, failures, corrections
|
||||||
|
- `MEMORY/RELATIONSHIP/` — DA-Principal relationship notes
|
||||||
|
- `MEMORY/OBSERVABILITY/` — every tool call, hook firing, satisfaction signal
|
||||||
|
|
||||||
|
**Signal capture**: Every interaction generates signals — explicit ratings, implicit sentiment, failure captures (ratings 1-3 trigger full-context saves).
|
||||||
|
|
||||||
|
**Principles adopted**: "Text over opaque storage" (if you can't `cat` it, don't store it), filesystem as context (no RAG), memory structured by purpose.
|
||||||
|
|
||||||
|
**What we adopted**: Purpose-based directory structure (Telos/Preferences/Projects/Decisions/People/Learning), write policy with review gates, learning patterns and corrections as first-class citizens.
|
||||||
|
**What we skipped**: ISA/ISC verification system, Pulse dashboard, hooks, voice — too Claude-Code-specific.
|
||||||
|
|
||||||
|
Source: https://danielmiessler.com/blog/personal-ai-infrastructure, github.com/danielmiessler/PAI
|
||||||
|
|
||||||
|
## 3. qmd — Query Markup Documents (Tobi Lütke, 26K stars)
|
||||||
|
|
||||||
|
Fully local CLI search engine for markdown. BM25 + Vector + LLM Reranking, all on-device.
|
||||||
|
|
||||||
|
**Three search modes**:
|
||||||
|
- `qmd search` — BM25 keyword (fast, no LLM)
|
||||||
|
- `qmd vsearch` — Vector semantic (embeddings)
|
||||||
|
- `qmd query` — Hybrid: query expansion + parallel retrieval + RRF fusion + LLM reranking
|
||||||
|
|
||||||
|
**Key features**: Collections (named indexed directories), Context (human descriptions attached to paths), MCP server mode, multiple output formats (`--json`, `--md`, `--files`).
|
||||||
|
|
||||||
|
**Local models** (~2GB total): embeddinggemma-300M, qwen3-reranker-0.6b, qmd-query-expansion-1.7B.
|
||||||
|
|
||||||
|
**Our plan**: Install as retrieval layer over m3ta-brain + tech wiki + nemoti wiki. Collections: `brain`, `tech-wiki`, `nemoti-wiki`. Not yet installed (requires Node.js/Bun on server).
|
||||||
|
|
||||||
|
Source: github.com/tobi/qmd, npm: `@tobilu/qmd`
|
||||||
|
|
||||||
|
## 4. Obsidian MCP Integration Landscape
|
||||||
|
|
||||||
|
Multiple MCP servers connect AI agents to Obsidian vaults:
|
||||||
|
|
||||||
|
| Server | Transport | Key Feature |
|
||||||
|
|---|---|---|
|
||||||
|
| Local REST API plugin | HTTP (localhost:27124) | Built-in MCP at `/mcp/`, the foundation |
|
||||||
|
| yanxue06/obsidian-mcp | stdio/HTTP | Graph-aware: backlinks, multi-hop, safe rename |
|
||||||
|
| lstpsche/obsidian-mcp (Rust) | stdio/HTTP | Single binary, filesystem-native, works without Obsidian running |
|
||||||
|
| Vasallo94/obsidian-mcp-server | stdio | Explicit Hermes support documented |
|
||||||
|
| cyanheads/obsidian-mcp-server | stdio/HTTP | 14 tools, surgical section editing, read/write path scoping |
|
||||||
|
|
||||||
|
**Key limitation for us**: MCP over localhost requires Obsidian on same machine as agent. Hermes runs server-side, Obsidian runs on Sascha's desktop. So Git-sync is Phase 1, MCP is Phase 2 (optional).
|
||||||
|
|
||||||
|
## 5. Shared Memory Platforms
|
||||||
|
|
||||||
|
| Platform | Architecture | Key Differentiator |
|
||||||
|
|---|---|---|
|
||||||
|
| Open Second Brain | Obsidian-native, Hermes-first | Nightly "dream" passes, preferences with confidence bands |
|
||||||
|
| Agentkeep | Git-backed markdown vault | Content-hash compare-and-swap, safe two-driver editing |
|
||||||
|
| Noosphere | PostgreSQL + Redis | REST API, scoped access, Obsidian export/import |
|
||||||
|
|
||||||
|
**What we adopted from these**: Git as the sync/merge mechanism (Agentkeep), draft-then-review pattern for sensitive notes (Open Second Brain), plain markdown as the substrate (all three).
|
||||||
|
|
||||||
|
## Design Decisions Summary
|
||||||
|
|
||||||
|
| Decision | Chosen | Rejected | Rationale |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Sync mechanism | Git (Gitea) | Syncthing | Versioned, auditable, conflict diffs, Gitea already exists |
|
||||||
|
| Link format | Standard Markdown links (`[text](relative/path.md)`) | Obsidian-only `[[wikilinks]]` | Works in both Gitea and Obsidian; current vault rules supersede the initial design note |
|
||||||
|
| Language | German + EN tech | English only | Match how Sascha communicates |
|
||||||
|
| Agent instructions | `AGENTS.md` | Agent-specific config | Multi-agent compatible (Pi, OpenCode, Claude Code, Hermes) |
|
||||||
|
| Memory model | Purpose-based dirs | Chronological | Inspired by PAI v7.6 (WORK/KNOWLEDGE/LEARNING pattern) |
|
||||||
|
| Retrieval | qmd (planned) | RAG/black-box | Local, hybrid search, no cloud dependency |
|
||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Audit [[wikilinks]] in an Obsidian vault.
|
||||||
|
|
||||||
|
Scans all .md files for [[wikilink]] references and reports:
|
||||||
|
- Broken links (target file doesn't exist)
|
||||||
|
- Ambiguous links (multiple files match the basename)
|
||||||
|
- Code-example false positives ([[wikilinks]] inside backticks)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 audit-wikilinks.py [VAULT_PATH]
|
||||||
|
|
||||||
|
If VAULT_PATH is omitted, defaults to ~/m3ta-brain.
|
||||||
|
|
||||||
|
Exit codes:
|
||||||
|
0 = no broken links (ambiguous/code-examples are warnings only)
|
||||||
|
1 = broken links found
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
VAULT = sys.argv[1] if len(sys.argv) > 1 else os.path.expanduser("~/m3ta-brain")
|
||||||
|
|
||||||
|
# Collect all .md files
|
||||||
|
md_files = []
|
||||||
|
for root, dirs, files in os.walk(VAULT):
|
||||||
|
if '.git' in root:
|
||||||
|
continue
|
||||||
|
for f in files:
|
||||||
|
if f.endswith('.md'):
|
||||||
|
full_path = os.path.join(root, f)
|
||||||
|
rel_path = os.path.relpath(full_path, VAULT)
|
||||||
|
md_files.append((rel_path, full_path))
|
||||||
|
|
||||||
|
# Build indices
|
||||||
|
basename_index = defaultdict(list)
|
||||||
|
fullpath_set = set()
|
||||||
|
for rel_path, full_path in md_files:
|
||||||
|
basename = os.path.basename(rel_path).replace('.md', '')
|
||||||
|
basename_index[basename].append(rel_path)
|
||||||
|
fullpath_set.add(rel_path)
|
||||||
|
|
||||||
|
print(f"Total .md files: {len(md_files)}")
|
||||||
|
|
||||||
|
# Extract and validate wikilinks
|
||||||
|
# Pattern matches [[target]], [[target|alias]], [[target#heading]], [[target#heading|alias]]
|
||||||
|
wikilink_pattern = re.compile(r'\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|[^\]]*)?\]\]')
|
||||||
|
# Pattern to detect if a wikilink is inside backtick code
|
||||||
|
backtick_pattern = re.compile(r'`[^`]*\[\[[^\]]*\]\][^`]*`')
|
||||||
|
|
||||||
|
broken_links = []
|
||||||
|
ambiguous_links = []
|
||||||
|
code_example_links = []
|
||||||
|
valid_links = 0
|
||||||
|
total_links = 0
|
||||||
|
|
||||||
|
for rel_path, full_path in md_files:
|
||||||
|
with open(full_path, 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
for match in wikilink_pattern.finditer(line):
|
||||||
|
target = match.group(1).strip()
|
||||||
|
if target.startswith('./'):
|
||||||
|
target = target[2:]
|
||||||
|
total_links += 1
|
||||||
|
|
||||||
|
# Check if this link is inside backtick code on the same line
|
||||||
|
# (simplified: checks if backticks surround the match on this line)
|
||||||
|
start = match.start()
|
||||||
|
prefix = line[:start]
|
||||||
|
backtick_count_before = prefix.count('`')
|
||||||
|
in_code = backtick_count_before % 2 == 1
|
||||||
|
|
||||||
|
if '/' in target:
|
||||||
|
target_path = target + '.md' if not target.endswith('.md') else target
|
||||||
|
if target_path in fullpath_set:
|
||||||
|
valid_links += 1
|
||||||
|
elif in_code:
|
||||||
|
code_example_links.append((rel_path, i, match.group(0), target))
|
||||||
|
else:
|
||||||
|
broken_links.append((rel_path, i, match.group(0), target))
|
||||||
|
else:
|
||||||
|
matches = basename_index.get(target, [])
|
||||||
|
if len(matches) == 0:
|
||||||
|
if in_code:
|
||||||
|
code_example_links.append((rel_path, i, match.group(0), target))
|
||||||
|
else:
|
||||||
|
broken_links.append((rel_path, i, match.group(0), target))
|
||||||
|
elif len(matches) > 1:
|
||||||
|
ambiguous_links.append((rel_path, i, match.group(0), target, matches))
|
||||||
|
else:
|
||||||
|
valid_links += 1
|
||||||
|
|
||||||
|
print(f"Total wikilinks: {total_links}")
|
||||||
|
print(f"Valid: {valid_links}")
|
||||||
|
print(f"Broken: {len(broken_links)}")
|
||||||
|
print(f"Ambiguous: {len(ambiguous_links)}")
|
||||||
|
print(f"Code-example (in backticks, OK): {len(code_example_links)}")
|
||||||
|
|
||||||
|
if broken_links:
|
||||||
|
print("\n=== BROKEN LINKS ===")
|
||||||
|
by_target = defaultdict(list)
|
||||||
|
for src, line, raw, target in broken_links:
|
||||||
|
by_target[target].append((src, line))
|
||||||
|
for target in sorted(by_target.keys()):
|
||||||
|
sources = by_target[target]
|
||||||
|
print(f"\n '{target}' — {len(sources)} ref(s):")
|
||||||
|
for src, line in sources[:5]:
|
||||||
|
print(f" {src}:{line}")
|
||||||
|
|
||||||
|
if ambiguous_links:
|
||||||
|
print("\n=== AMBIGUOUS LINKS ===")
|
||||||
|
for src, line, raw, target, matches in ambiguous_links:
|
||||||
|
print(f" '{target}' in {src}:{line} → {matches}")
|
||||||
|
|
||||||
|
# Exit code
|
||||||
|
if broken_links:
|
||||||
|
print("\n⚠️ Broken links found — fix before committing.")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print("\n✅ No broken links.")
|
||||||
|
sys.exit(0)
|
||||||
Reference in New Issue
Block a user