Extract durable knowledge from the current source — a project, the conversation, a research session — and place it in the user's wiki. Merge before creating, respect the wiki's conventions, and keep every file inside its size cap.
The wiki model
A wiki is a folder whose index.md carries a ## Spaces heading. ## Spaces is the navigation contract: every space directly inside is listed there as - [label](path/index.md) — description, and tools traverse only what it lists. Spaces nest recursively; each space is itself a wiki one level down. Plain folders (no index.md) just group files. The markdown dialect is Obsidian — wikilinks, frontmatter, callouts, embeds; the companion obsidian-markdown and obsidian-bases skills cover the syntax.
Resolving the wiki
Resolution order: an explicit path from the user → the nearest CWD-ancestor folder that is a wiki, or carries a .wiki-spaces/ folder that is one (a folder's own space) → the wiki key in ~/.config/wiki-spaces/config. The user's words override the mechanics: "my wiki" means the configured one even when CWD sits inside another wiki (a company repo, say). When a CWD wiki and a different configured wiki both exist, announce which root you resolved; ask once if intent is ambiguous. Never silently operate on the wrong wiki.
The bundled script
scripts/ws.py sits next to this SKILL.md — stdlib python3 (3.9+), zero dependencies, read-only. It parses the contract, never the content: structure — traversal, scope, caps, drift — is the script's side; reading and judging meaning is yours. Invoke it by absolute path (your working directory is usually elsewhere):
python3 <skill-dir>/scripts/ws.py list --wiki <root>— spaces reachable via the## Spacescontract, each with its entry description (--externalto cross mounts).… files --wiki <root>— markdown files reachable via the contract.… grep <pattern> [-i] [-F] --wiki <root>— regex line search over those files,-Ffor a literal string (a name carrying metacharacters sweeps exact); printsrel:line: text, exits 1 on no match. The sweep primitive: a link worklist, a tag inventory, an escaping-reference check are each a pattern plus your judgment on the hits.… check-size <target> [--stdin] --wiki <root>— cap verdict for a file; pipe planned content with--stdinto check before writing.… audit --wiki <root>— contract drift, entries crossing a space boundary, over-cap or unreadable files, unhealthy mounts. Findings name their repair where one is safe to name (amissing entryprints the exact line to add); apply repairs as ordinary edits and re-run the audit to verify — the script never writes.
Trust the script's output over re-deriving structure by hand. Stdout is data; stderr carries the resolved root (audit prints it as its stdout header instead) and note: advisories naming whatever a walk skipped, the enclosing wiki when the root is nested inside one, and the configured wiki when the root is another — relay them when they could change the answer.
Trust scope and size discipline
Owned vs external is relative to the resolved root: anything under a folder named shared/ (at any depth), a git submodule, or a symlink into either is external; a symlink to a folder outside the tree answers to where it sits, like a clone. Reads cross owned spaces by default and enter external ones only when the user explicitly asks. Writes stay inside the targeted space; any other space — owned or external — is written only on explicit instruction.
Caps are UTF-8 bytes including frontmatter, keyed by basename: index.md 5000, log.md and hot.md 100000, any other *.md 15000.
- Caps belong to the user: a wiki that wants different ones sets them in
_meta/limits.md, andcheck-sizereads them. - Check caps with
check-size: pipe the planned content via--stdinbefore every write, new page or edit — a write that shrinks an over-cap file reportsok, progress, so repairs converge; a file checked on disk answers to the cap alone. - An overflow is a signal about shape, not just size: distill the page, reshape the space, or promote a page that has grown into one — never truncate, never raise the cap.
- The exception is
log.md: an over-cap log rolls — move it whole to_archives/log-<YYYYMMDD>.mdand start a fresh one — so the history archives, never shrinks.
Conventions are opt-in per wiki. Read the markers present at the scope root (the space the user targeted, else the resolved root) — log.md, _meta/taxonomy.md, _meta/limits.md, _meta/ignore.md, frontmatter on pages, _template.md, hot.md, .git — and degrade gracefully when one is absent. If log.md exists, append one line per operation:
printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '<OP> <details>' >> <scope-root>/log.md
Initialization
When nothing resolves — no explicit path, no CWD-ancestor wiki, no valid config — set one up inline: follow references/init.md, then resume the request. A broken configured path (missing on disk, no index.md, not absolute) is a hard stop, not a setup trigger — surface it and let the user decide. A folder's own space — asked for ("set up a wiki for this project"), or offered once when a sync targets a folder that has none — follows references/own-space.md, whether or not a wiki already resolves.
Procedure
-
Resolve the wiki (core block) and announce the root when it came from CWD or could be ambiguous.
-
Detect conventions at the scope root (core block). Presence markers (
log.md, taxonomy,hot.md) never inherit from a parent;_meta/limits.md,_meta/ignore.md, and_template.mdfollow their nearest-ancestor rule. Skip every step whose marker is absent. -
Detect the mode from the user's intent — CWD only disambiguates which project, never the mode:
Mode Trigger Input Direct add "add this to the wiki", "save this recipe" — a concrete artifact in hand the handed content Project sync "sync this project", explicit project naming project files, git log Conversation capture "save this", "capture this" — the session itself, no single artifact the current conversation Research capture "store this research" findings from the session -
Extract knowledge. Direct add: take the content as handed — your job is placement and dedup, not re-synthesis. Project sync: decisions and rationale, patterns, tool/API wiring, trade-offs, results — from the files and recent git history. Conversation: durable conclusions and decisions, written as facts ("X works by…"), never chat summary. Research: what took effort and would be expensive to re-derive.
-
Filter and deduplicate. Knowledge-capture wikis get the noise filter: answerable from the code or a quick search? skip; needed in 3 months? keep. Content-store wikis (recipes, journals, runbooks) skip the filter — every entry is intentional. Enumerate existing pages via
filesand rank by filename/path/frontmatter overlap: when a near-match exists, merge into it instead of creating a sibling. -
Place.
listprints each space with its entry description — the placement hints; check top-level plain folders too. Match semantically: knowledge about a source belongs with that source's space when one exists — its own.wiki-spaces/, or its space in the wiki; knowledge reusable beyond it belongs with its topic. Standing in a folder's own space, that topic's home is the configured wiki the script notes — a second root: name the page and its place there, and on a yes take that root through steps 2 and 5 before writing. Several equally plausible candidates, or none → ask. Flat wiki → write at the root. Names follow the wiki's own pattern — match what existing pages do; where no pattern exists, default to lowercase, hyphenated, ≤50 chars. -
Check the cap on every write. Pipe planned content before writing:
python3 <skill-dir>/scripts/ws.py check-size <target> --stdin --wiki <root> <<'EOF' <planned content> EOFAn edit pipes its planned content the same way (a shrink of an over-cap page reports
ok); the audit backstops what slips through. Onover— never truncate, never ignore, never raise the cap. Re-read the page and its space's index, diagnose why it overflowed, then act:- Bloat — padding, near-duplicates, prose pasted in raw, entries you wouldn't re-create today → distill: merge, tighten, drop. The cap exists to force this judgment.
- Growth — genuinely distinct topics sharing one file → reshape the space to what it would look like had it been designed for today's content: self-standing sibling pages under a hub, a promotion, or a regrouped layout (Space operations links both procedures).
Both can apply — distill first, then reshape what remains. Update every index you touch. Filenames like
topic-2.mdortopic-more.mdmean a file was split without rethinking the space — never ship them. An over-capindex.mdis navigation debt: push detail into child pages, one line per entry.
-
Write. New pages: nearest ancestor
_template.mdif present, else the wiki's page shape (frontmatter only where the wiki already uses it). Mark non-source claims%%inferred%%, conflicting sources%%ambiguous%%. Add up to 2 wikilinks on first natural mentions. Updates: merge, preserve manual content, bumpupdated:if frontmatter is in use, never overwrite unrelated sections. On a project sync, record the repo path (~-contracted) on the project's hub page —repo:/sources:frontmatter where in use, else one plain line — so a later session maps the checkout to its page. More than ~10 pages changing → show the plan and ask first. -
Keep the contract. Created a new space, removed one, or moved pages? Update
## Spacesper Space operations below.## Itemssections are optional human curation — maintain one where the index already has it. Then check the shape you're leaving: entry descriptions still true, no name that only makes sense historically — small fixes now; bigger reshapes become the close-out'sws-tendsuggestion. -
Close out. Run
audit. Apply the safe structural repairs yourself, re-auditing between rounds until gone:- Add the exact entry line a
missing entryfinding prints, then trail it with a— description— the placement hint every later operation reads. - Insert the
## Spacesheading a registered bare child lacks — only where the finding's hint asks for the insert; a near-miss hint asks for a rename, which stays reported as author intent. - Remove an entry the audit says crosses another space's boundary — that space owns the deeper listing; register it there instead when it is missing.
An undeclared bare index is a promotion decision — surface it, act only on the user's call. Other findings are reported, not auto-repaired; outside this sync's write scope, suggest a
ws-tendpass — suggestion only, never run it yourself. Log anUPDATEline per the core block and confirm in one block:Updated wiki (<root>)with created paths, updated paths, mode, and audit outcome. - Add the exact entry line a
Space operations
Create a space (a folder needing first-class navigation):
mkdir -p <root>/<path> && printf '# <Title>\n\n## Spaces\n' > <root>/<path>/index.md
Register it in the nearest ancestor space's ## Spaces — - [<name>/](<name>/index.md) — description, href percent-encoded where the name demands it; the description is the placement hint later operations read. The audit's missing entry finding prints the exact line if in doubt.
Remove a page: sweep for what points at it first — grep -F '<stem>' --wiki <root> (every link form carries the stem; judge each hit, code examples aside) — rewrite or drop the real links, prefer a move to _archives/ over deletion, then audit.
Remove a space: confirm with the user (prefer _archives/ over deletion), delete the folder, remove its entry from the parent's ## Spaces, then audit to verify nothing dangles.
Mount someone else's space: references/mount.md. Share a space of yours: references/share.md.
Promote a page into a space — routine once a page's shape asks for it; inventory its links, snapshot, move, rewrite, verify: references/promote.md. Rename, move, merge, or demote a space on the same skeleton: references/restructure.md.