wiki-lint
Skill1 starsChanged 7 months agoApache-2.0View on GitHub
Health-check a wiki vault. Finds orphan pages (no inbound links), dead wikilinks (point to non-existent pages), missing frontmatter fields, stale claims, empty sections, and pages absent from catalog.md. Produces a structured report with severity tiers and proposes concrete fixes — but does not auto-apply them unless the user confirms. Use when the user says "lint the wiki", "health check", "audit my wiki", "find orphans", "what's broken in the wiki", "/wiki-lint", or after a long ingestion session to catch drift.
43 other projects use a version of this.
Adopted by
Most recently taken up by
- Ar9av/obsidian-wikitheir own version, first seen Nov 8, 2026
- ggoosen/Digital-Workforcetheir own version, first seen Nov 8, 2026
- barakgut/llm-wikitheir own version, first seen Nov 1, 2026
- chobizzy/llm-wikitheir own version, first seen Nov 1, 2026
- wareagledusty/wiki-federation-starter-kittheir own version, first seen Nov 1, 2026
- leiverkus/research-superpowerstheir own version, first seen Nov 1, 2026
Used with
Projects with it also use wiki-capture, wiki-status and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
jehrhardtSkillwiki-capture
Quickly capture raw thoughts, links, images, or notes to today's Obsidian daily note from anywhere in Pi. Use when the user wants to jot something down for later wiki processing.
Also in 6 projects that use this
nexuschainsSkillwiki-status
Reports the current state of the wiki vault — page counts, category breakdown, hub pages, orphans, stale entries, and a delta showing what has changed since the last ingest. Use this skill when the operator asks "what's the status", "wiki dashboard", "how many pages", "what's stale", "what's been ingested", "show me the delta", or "wiki health overview". Also surfaces structural insights (top hubs, fragmented clusters, bridge pages) when the operator asks "wiki insights", "what's central", or "show me the hubs". Read-only: this skill reports and recommends; it does not modify any files.
Also in 5 projects that use this
nexuschainsSkillcross-linker
Scans the wiki vault and adds missing [[wikilinks]] between pages that are related but not yet connected. Use this skill when the operator says "link my pages", "find missing links", "cross-reference the wiki", "connect my wiki", "add wikilinks", or after any large ingest to weave new pages into the existing knowledge graph. Also trigger when orphaned pages need connecting or when wiki-lint reports fragmented tag clusters. This is a write-heavy skill — it modifies page files to insert links, unlike wiki-lint which only reports.
Also in 4 projects that use this
Ivy-Chen1999Skillwiki-synthesize
Systematically discover synthesis opportunities across the Obsidian wiki — pairs or clusters of concepts that co-occur frequently across pages but have no synthesis page connecting them. Creates new synthesis/ pages that draw explicit cross-cutting conclusions. Use when the user says "synthesize my wiki", "find connections", "what concepts keep coming up together", "/wiki-synthesize", or after a large ingest when the vault has grown significantly.
Also in 4 projects that use this
Ivy-Chen1999Skillwiki-setup
Initialize a new Obsidian wiki vault with the correct structure, special files, and configuration. Use this skill when the user wants to set up a new wiki from scratch, initialize the vault structure, create the .env file, or says things like "set up my wiki", "initialize obsidian", "create a new vault", "get started with the wiki". Also use when the user needs to reconfigure their existing vault or fix a broken setup.
Also in 5 projects that use this
bozhouDevSkillwiki-init
Use when bootstrapping a new personal wiki for any knowledge domain — research, codebase documentation, reading notes, competitive analysis, or any long-term knowledge accumulation project.
Also in 7 projects that use this
- +1 more from nexuschains
- +4 more from Ivy-Chen1999
Other versions43 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 43
- AgriciDaniel/claude-obsidian★ 16ksame name
- Ar9av/obsidian-wiki★ 3.7ksame name
- kfchou/wiki-skills★ 189same name
- IssacW228/student-llm-wiki★ 173same name
- vanillaflava/llm-wiki-skills★ 65same name
- cookyman74/llmwiki-harness★ 34same name
- Ivy-Chen1999/llm-wiki-kit★ 21same name
- schub-tech/llm-ideation-wiki★ 11same name
- CarbeneAI/Forge★ 9same name
- CodePhobiia/claude-roblox-game-studio★ 9same name
33 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository49 other files it gives its agents.
- AGENTS.mdAGENTS.md
- auto-modeSkill
- autoresearchSkill
- bind-workspaceSkill
- build-search-indexSkill
- canvasSkill
- conventionsSkill
- decision-consolidateSkill
- defuddleSkill
- discover-list-filesSkill
- discover-list-vaultsSkill
- force-new-port-startSkill
- hot-compactSkill
- lockSkill
- manage-deleteSkill
- manage-moveSkill
- meta-attach-vaultSkill
- meta-audit-bridge-readinessSkill
- meta-setupSkill
- meta-statusSkill
- meta-sync-templateSkill
- obsidian-basesSkill
- obsidian-markdownSkill
- read-frontmatterSkill
- read-getSkill
- read-searchSkill
- read-search-smartSkill
- saveSkill
- sync-from-githubSkill
- template-executeSkill
- unlockSkill
- who-is-speakingSkill
- wiki-boundarySkill
- wiki-exportSkill
- wiki-foldSkill
- wiki-graphSkill
- wiki-ingestSkill
- wiki-neighborsSkill
- wiki-pathSkill
- wiki-querySkill
- wiki-refresh-digestsSkill
- wikiSkill
- wiki-tourSkill
- write-appendSkill
- write-bundleSkill
- write-create-or-replaceSkill
- write-frontmatter-mergeSkill
- write-frontmatter-setSkill
- write-patchSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
15 rules. 0 are common, 5 are distinctive, and 10 are very specific to this project.
0
Common
Most of the 34 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
Read-only diagnostic. Surfaces problems and suggests fixes.
- Very specific: line 51
Call mcp__obsidian-router__audit_sources once.
- Very specific: line 59
If ledgerPresent: false, say so plainly and without alarm: the ledger is filled forward by ingestion, so an absent one means *"nothing has …
- Very specific: line 63
Never write to the ledger from a lint run.
- Very specific: line 67
Call mcp__obsidian-router__find_boundary_pages({ vault, limit: 10 }) once.
- Very specific: line 91
Every wiki page should have type: set.
- Very specific: line 102
If hot.md ## Last Updated is more than 7 days old, flag it.
- Distinctive: line 119
Performance note: : Check H reads each cited source file once to get its line count.
- Very specific: line 129
If the page referenced by digest.for no longer exists (page deleted), surface that as ERROR orphaned-digest and suggest removing the digest …
- Very specific: line 205
Read page A's content, parse [[...]] wikilinks.
- Very specific: line 207
If NEITHER page references the other → WARNING missing-wikilink : "pages X and Y share concepts [list] but don't reference each other .
- Distinctive: line 409
Don't auto-fix without permission.
- Distinctive: line 410
Don't fabricate "stale claims" — claim staleness needs a domain signal you can't have.
- Very specific: line 411
Don't read every page sequentially.
- Distinctive: line 412
Don't recurse infinitely on broken wikilink suggestions.
“Common” is measured: kept by at least 6 in 10 of the 34 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
wiki-lint
Read-only diagnostic. Surfaces problems and suggests fixes; never mutates the wiki without explicit confirmation.
Modes
The skill has three modes :
- Default (structural) — runs Checks A through H, plus N (decision-layer coherence), O (two folders named "Sessions"), P (prompt lifecycle), Q (conventions drift) and R (temporal validity). Cheap, scans page metadata + wikilinks + citations + two directory listings + the vault
CLAUDE.mdonly. The right mode for routine health checks. --deep(v0.15.0+, roadmap item #7') — also runs Checks I through L (plus Check J-bis, C11, which needs no digest — it reads the Smart Connections vector store and reports itself unavailable where there is none), which read the digest sidecars (wiki-meta/digests/<full-vault-path>— NESTED layout mirroringwiki/, review+ pass 3+ hardening) in bulk to detect cross-page redundancies, contradictions, and missing wikilinks. More expensive (reads N digests + N² comparisons in the worst case). Use after a long ingestion session or when you suspect the wiki has drifted. Enumeration MUST recurse —list_files({directory:'wiki-meta/digests'})returns immediate children only ; walk the tree to get every.mdunderneath.--okf <path>(v0.33.0+) — runs Check M ONLY : validates an OKF knowledge bundle (Google's Open Knowledge Format v0.1) against the spec's three conformance rules. The path is either a bundle exported bywiki-export --target okf(wiki-meta/exports/okf/<name>/inside a vault) or any local directory / cloned repo containing a third-party bundle. This mode doesn't lint the wiki itself.
Trigger phrases :
- "lint the wiki" / "health check" / "audit my wiki" → default mode
- "deep lint" / "find redundant concepts" / "detect contradictions" / "wiki-lint --deep" → deep mode
- "lint the wiki and fix what you can" → default mode with auto-fix offered for ERRORs
- "validate this OKF bundle" / "is this bundle conformant" / "check OKF conformance" / "wiki-lint --okf
<path>" → OKF mode
A related skill, wiki-refresh-digests, regenerates stale digests detected by Check I (see skills/wiki-refresh-digests/SKILL.md).
Pre-conditions
- Target vault has
wiki/scaffolding. - Vault is online.
- For
--deepmode only :wiki-meta/digests/directory exists with at least one digest. If empty / missing, gracefully report "no digests to deep-lint; runwiki-ingestto generate digests, or/wiki-refresh-digeststo backfill existing pages".
Steps
1. Inventory the wiki
mcp__obsidian-router__list_files({ vault, directory: "wiki" })Build a flat set of every page path under wiki/. Read wiki-meta/catalog.md and parse the catalog into a separate set.
2. Run checks in parallel
For each check, accumulate findings. Don't bail on the first issue — surface the whole set.
Check A: orphan pages
A page is orphan if NO other page wikilinks to it (excluding self-references and the page being its own index entry). Build the inbound-link set by reading every page and parsing [[wikilinks]]. Pages with type: source or type: answer in frontmatter are exempt — those are reachable via the index.
Check A-bis: source ledger — stale, unreviewed, single-origin (v0.64.0+, borrowing C6)
Call mcp__obsidian-router__audit_sources once. It is read-only and needs no arguments beyond the vault.
Report, in the info tier unless noted:
stale[]— sources past their refresh horizon, worst overdue first. Each is a "go re-check this", not an error. Surface the count and the top few with theiroverdueDays.unreviewed[]/disputed[]— review gaps.disputedis worth a warning tier: the vault is resting on contested material.invalid[]— malformed entries. Warning: these are silently excluded from every other count, so an unreportedinvalidmeans the audit you just showed is narrower than it looks.origins.countvstotal— how many genuinely independent publishers the whole ledger represents. A vault with 40 sources and 3 origins is worth mentioning out loud.
If ledgerPresent: false, say so plainly and without alarm: the ledger is filled forward by ingestion, so an absent one means "nothing has been recorded yet", not "this vault has no sources". Never present it as a defect of the pages.
Per-page verdicts (opt-in, on request): for a specific page, audit_sources({ page }) returns whether it is corroborated by at least 2 distinct origins, with excluded listing what did NOT count and why (synthetic output, retired entries, unvouched local files). Do not run this across every page by default — it is a reviewer's question, not a lint sweep, and a low origin count is frequently legitimate.
Never write to the ledger from a lint run. Lint reports; record_source (ingestion) is the only writer.
Check A-ter: frontier pages — heavily linked, thin inside (v0.69.0+, borrowing C10)
Call mcp__obsidian-router__find_boundary_pages({ vault, limit: 10 }) once. Read-only, one graph read, nothing written.
Report in the info tier and never above it. A crossroads that is thin is not a defect: nothing is broken, nothing needs fixing, and a lint report that raised it to a warning would be telling the user to repair something that may be exactly right. Show the top few with their inbound / substanceWords / ageDays, and always state the count in exempted — a ranking that silently dropped 31 pages reads as "I looked at everything" when it did not.
Skip the check without alarm when:
- the tool reports no graph → "no knowledge graph yet; run
/wiki-graphto enable frontier detection". This is not a wiki defect. - the tool refuses for want of substance measurements → the graph predates the feature; same offer, same absence of alarm.
graphAnalyzedAtis old → still report, but say how old the snapshot is. A stale graph ranks pages that may have been rewritten or deleted since.
Two notions of "inbound link" coexist in this report, deliberately. Check A above re-parses every page, so it counts wikilinks written in frontmatter (related:, superseded_by:); find_boundary_pages uses the knowledge graph, which parses page bodies only. Measured on the router's own vault (140 articles): they agree on 117 pages, and on the 23 that differ the graph always counts fewer. The graph's set is a strict subset of Check A's, so the two can never contradict — a page credited with inbound links by the frontier check can never be reported as an orphan by Check A. Don't "reconcile" the numbers if a reader notices the gap; explain it.
Check B: dead wikilinks
A wikilink is dead if [[Target]] points to a page that doesn't exist. Resolve aliases ([[Target|Alias]]) and folder-prefixed forms ([[concepts/Foo]]). If a link looks dead, double-check by trying both with and without the .md extension and against alias frontmatter.
Check C: catalog drift (map-of-maps semantics since v0.59.4)
wiki-meta/catalog.md is a map of maps: one entry per directory, each linking to that directory's generated index.md. It is deliberately not a page-by-page list, so do not report a page as "missing in index" merely because it has no row — page-level exhaustiveness is the generated indexes' job, and their freshness is Check L's. Reporting per-page gaps here would push the catalog straight back to the 70 KB / 115-row monolith the map-of-maps convention exists to undo.
What to check instead:
- A directory exists under
wiki/but no catalog entry links to itsindex.md, and no parent-area entry covers it → "area missing from the catalog" (warning). Sub-directories of an area covered by that area's entry are fine — the parent index lists them. - A catalog link points at an
index.mdthat doesn't exist → "stale area entry" (error): a directory was renamed or removed. - A catalog wikilink points at a page that doesn't exist → "stale curated entry" (error).
- An index is referenced with a wikilink rather than a markdown path link → "ambiguous index link" (error). Every directory index shares the
indexbasename; a wikilink resolves by basename and Obsidian will retarget it silently. This is the exact failure theindex→catalogrename was performed to prevent.
Check D: frontmatter gaps
Every wiki page should have type: set. Sources should have url: (or path:) and ingested_at:. Answers should have question: and answered_at:. Missing fields are warnings, not errors.
description: is checked here too (v0.59.2+) — every page under wiki/ must carry a one-sentence description (see "One-line summary" in the vault CLAUDE.md). It matters more than the other gaps because it is published: the OKF directory indexes render * [Title](file.md) - description, so a page without one appears in the vault's own navigation as a bare filename. Report each offender with its path so it can be fixed at the source — and do NOT offer to auto-fill it from the body, even in --fix mode. The at-rest projections deliberately refuse to synthesize descriptions; a lint that quietly does it instead would reintroduce exactly the machine-written sentences that refusal exists to keep out of the vault. refresh_okf_projections reports the same set in missingDescription, so the two agree.
Check E: empty sections
Pages with section headings followed by no body until the next heading. Surface them — they're usually placeholders that were forgotten.
Check F: log consistency
wiki-meta/journal.md should be append-only, monotonically increasing timestamps. Out-of-order or duplicate timestamps are a smell (manual edit?). Surface them as info-level.
Check G: hot.md staleness
If hot.md ## Last Updated is more than 7 days old, flag it. Real-world: hot caches go stale fast and become misleading.
Check H: claim-range-validity (v0.15.0+, roadmap item #1)
Scan every wiki page body for line-range citation markers of the form ^[<filename>:<start>-<end>], ^[<filename>#L<start>-L<end>], ^[<filename>:<line>], or the paragraph-level fallback ^[<filename>]. For each marker with a range :
- Resolve the cited file — the canonical location is
wiki/sources/<slug>.md(seewiki-ingeststep 4). Try in order : (a)wiki/sources/<filename>, (b) path relative to the citing page's folder (for sibling-references), (c)<filename>at vault root (legacy fallback). If none resolves → WARNINGcited-source-not-found. Reject any cited path containing..segments or absolute roots (/,C:\,\\server\) — emitcited-source-unsafe-pathWARNING instead and do NOT attempt the fetch (review+ pass 2 hardening : a malicious source citation must not become a vault-escape vector). - Parse the range — accept colon-style
:42-58and GitHub-style#L42-L58(semantically equivalent). Reject malformed ranges (non-numeric, missing parts). - Validate the range :
start > 0andend > 0— both must be positive integers (line 0 doesn't exist) → WARNINGclaim-range-zero-or-negativeend >= start—8-3is invalid → WARNINGclaim-range-invertedend <= sourceLineCount— range can't extend past the source's actual length → WARNINGclaim-range-overflowwith detail "source has N lines"
All Check H findings are WARNING-level, not ERROR. Source files can legitimately shorten over time (refactor, edit, summarisation), and we don't want lint to fail loudly on routine maintenance. The user reads the warnings and decides whether to refresh the citing page, refresh the source, or accept the drift.
Single-line citations ^[file.md:42] and paragraph-level fallbacks ^[file.md] are also validated — single-line is just the special case where start == end; paragraph-level needs only the cited-source-not-found check (no range to validate).
Performance note : Check H reads each cited source file once to get its line count. Cache the line counts per source within a single lint run to avoid re-reading the same source multiple times when several pages cite it.
2b. Deep checks (v0.15.0+, --deep mode only)
The following 4 checks are gated behind the --deep flag because they involve reading every digest in wiki-meta/digests/ and doing pairwise comparisons. Cheap individually, but N² in page count — typical vault (100 pages) → 5000 comparisons, fine ; large vault (1000 pages) → 500k comparisons, may take a few seconds.
Check I (deep): digest staleness
For each digest file (enumerate wiki-meta/digests/ recursively — see Modes section), parse it with parseDigest from src/helpers/digest-generator.mjs. Compare the stored page_hash against a fresh computePageHash(currentPageContent) of the page the digest is for (digest.for field). On mismatch → WARNING digest-stale with detail "page edited since digest was generated ; run /wiki-refresh-digests to update".
If the page referenced by digest.for no longer exists (page deleted), surface that as ERROR orphaned-digest and suggest removing the digest file too.
Check J (deep): redundant concepts across pages
Load all digests. For each pair of digests (A, B), compute conceptOverlap(A, B) via src/helpers/digest-generator.mjs (Jaccard similarity over the concepts arrays). Thresholds :
- Overlap ≥ 0.7 → ERROR
concept-overlap-strong: "pages X and Y share concepts [list] — read both, they may be covering one subject twice" - Overlap 0.4..0.7 → WARNING
concept-overlap-moderate: "pages X and Y share concepts [list] — worth a look at how the two divide the subject"
The severities above are unchanged (concept-overlap-strong stays ERROR — see the severity list in step 3). Only the WORDING is: a lint finding reports what it measured and hands the judgement back. What to do about two overlapping pages — leave them, cross-link them, rewrite one, combine them — is a decision about MEANING that no overlap coefficient can make, and phrasing it as an instruction was inviting the reader to act on a number. Same posture as Check J-bis below; the two sit two paragraphs apart and must not contradict each other.
Report only pairs where the OVERLAP is above the WARNING threshold, not all 5000+ pairs.
Check J-bis (deep): quasi-twin pages by cosine (v0.72.0+, C11)
Check J compares CONCEPT LISTS (Jaccard over the digests' concepts arrays). Check J-bis is the same question asked of the embedding space instead: two pages can be the same page written twice and share almost no concept vocabulary, because two sessions name things differently. Where the vault has Smart Connections vectors, cosine sees that; Jaccard cannot.
mcp__obsidian-router__find_twin_pages({ vault, limit: 10 })Read-only, one pass over the vector store, nothing written. Works on a remote vault too since v0.82.0 — the store is read through the bridge's GET /smart-env/sources (requires obsidian-mcp-router-bridge 0.9.0+ on the machine running that vault). Measured on the router's own vault, the disk and remote runs return the same pairs, the same threshold and the same exclusion counts; the remote run costs ~6× the wall clock, because the whole store crosses the wire.
Report in the info tier and never above it. Two pages that resemble each other are not a broken state: a templated series, a decision and its record, a page deliberately split from its "gotchas" companion all look alike and are all correct. This is a deliberate divergence from Check J, which raises concept-overlap-strong to ERROR — do not inherit that severity here, and never phrase a finding as a merge instruction. Show, per pair, the similarity and the four evidence columns (sameFolder, sameBasename, sharedLinks, linked); they are what lets a reader dismiss a false positive in one glance.
Always state the derived threshold and the corpus it came from. The cut is computed from this vault's own distribution — threshold.similarity, threshold.medianSimilarity, threshold.sensitivity — and it does not transfer to another vault. A report that shows pairs without the threshold that produced them is unauditable. Raise or lower sensitivity (higher = stricter) until the list is a length the user will actually read.
A scoped run answers a scoped question. Passing folders narrows the corpus and the distribution the cut comes from, so the same pair can be reported by a whole-vault run and absent from a folder-scoped one (measured on the router's vault: whole wiki → cut 0.9326, 4 pairs ; folders: ['wiki/obsidian-mcp-router/Features'] → cut 0.9613, 0 pairs). That is correct — "unusual for this section" and "unusual for this vault" are different questions — but say which one you asked, and quote the cut. restrictTo behaves differently on purpose: it filters pairs after the cut is derived, so it never changes a pair's verdict, only whether it is shown.
Always state excluded. The tool holds out indexed paths whose page is gone from disk (notOnDisk — routinely a third of the store), generated projections, and pages typed redirect/source/answer. A list of pairs that silently ignored a third of the corpus reads as "I compared everything" when it did not.
Always state the COVERAGE, in the numbers the tool gives you. available: true does NOT mean the whole vault was analysed — only the pages that carried a vector were compared, and on a vault indexed a while ago that can be far fewer. Quote coverage.statement verbatim, or render coverage.comparedPages / coverage.eligiblePages in the "N of M" form: "112 of 187 eligible pages carried a vector and were compared". Never present an exhaustive comparison of the 4 vectorised pages as coverage of the 10 pages that exist.
Always state the FRESHNESS. freshness.caveat says it: these similarities come from an index snapshot, not from the pages as they are now. A page edited since the last indexing pass still carries its previous vector, and this answer does not check which pages those are (freshness.perPageStaleness: "unknown") — it compares vectors, not timestamps. Say so in the report; an unqualified similarity reads as a statement about the pages today. (search_smart does check, per hit, and returns a freshness block naming the pages edited since indexing — reach for it when the freshness of a specific page matters.)
available: false IS NOT "no twins", and available is THE discriminator. Branch on it. Ten reasons arrive as a response with available: false, a reason, and no pairs key at all:
reason | what it means |
no-embeddings | no Smart Connections index (or no indexed page survives the exclusions) |
no-wiki | nothing under wiki/ |
corpus-too-small | fewer than 30 comparable pairs (≈ 9 pages) — a median+MAD would describe nothing |
no-spread | at least half the pairs share one similarity; no outlier cut can be derived |
bridge-route-absent | remote vault, and nothing served the store route (404) — usually a bridge older than 0.9.0, tell the user to upgrade it, though a proxy masking auth or not routing the path looks identical |
store-truncated | the bridge sent only a prefix of the store (it hit its own budget) |
store-inconsistent | the store response's own header contradicts its body — counts that do not balance, or fewer records/bytes than claimed |
store-unreachable | the store could not be fetched at all — network, auth, timeout |
wiki-enumeration-incomplete | the vault's file list did not come back whole, so no exclusion count can be trusted |
wiki-read-incomplete | pages were lost between the walk and the read — a pair needs BOTH halves, so a ranking from what arrived would hide twins rather than report none |
An eleventh way to decline is a thrown refusal, too-many-pages (err.kind: "validation", err.reason: "too-many-pages"), when the corpus is past maxPages — there is no response body at all. Scope with folders, or raise maxPages knowingly.
Report any of these as "this check is unavailable on this vault, because …" — never as a clean bill of health. result.pairs?.length ?? 0 would read every one of them as "no twins", which is exactly why the key is absent rather than empty; the absence is defence in depth, the field to read is available.
The last six arise on a vault reached over the network (one exception: wiki-read-incomplete can also fire on a local vault, when every candidate page was deleted between the walk and the read — the detail then says the store is fine). The first of them is the one worth acting on: bridge-route-absent names a plugin the operator can upgrade, not a fact about topology. Do not report it as "this vault has no index" — that is a different reason with a different fix. The honest fallback line: "Check J (concept overlap) still runs wherever digests exist; cosine needs embeddings, and this vault has none." Only available: true with found: 0 means the vault was examined and nothing stood out.
Known false-positive mode, worth saying out loud in the report: the vectors are whole-page and the model's window is 512 tokens, so pages that share a template score very high on their common head. Measured on a real vault, two course sheets scored cosine 0.9914 with a 5-word-shingle overlap of 0.064. When sameBasename is true across sibling folders, say so — it is usually a series, not a duplication.
Check K (deep): contradiction signals
Conservative heuristic only. LLMs detect contradictions poorly when given prose ; the deterministic heuristic here flags only HIGH-confidence cases to avoid false positives that would erode trust in the linter.
For each pair of digests with conceptOverlap ≥ 0.5 (i.e. they're about overlapping topics), scan their claims arrays for direct negation patterns :
- One page asserts
"X is Y"and another asserts"X is not Y"(or"X is never Y"/"X cannot be Y") - One asserts
"always do X"and another asserts"never do X"/"avoid X" - One asserts
"X is the only way to Y"and another asserts"X is one of several ways to Y"
The heuristic is regex-based — match (\\b\\w+\\b) is (\\w+) in page A against \\1 is (?:not|never)? \\2 in page B (and symmetric variants). Surface ONLY exact matches, never fuzzy paraphrases — false positives are worse than false negatives here.
Severity : WARNING contradiction-suspected. Always document the limitation in the report : "naive heuristic, likely misses many contradictions ; treat as a starting point not a guarantee".
Check L (deep): missing wikilinks
For each pair of digests with conceptOverlap ≥ 0.4 (i.e. they share at least some topics), check whether either page wikilinks to the other. Specifically :
- Read page A's content, parse
[[...]]wikilinks. If page B's basename is in the wikilinks → OK. - Same in reverse for page B → page A.
- If NEITHER page references the other → WARNING
missing-wikilink: "pages X and Y share concepts [list] but don't reference each other ; consider adding[[X]]or[[Y]]to the other page".
This check often surfaces genuine knowledge-graph gaps that humans miss when adding new pages incrementally.
2b-bis. Check L: OKF projections drift (v0.59.0+)
Call mcp__obsidian-router__refresh_okf_projections({ vault, check: true }).
upToDate: true→ ✅ nothing to report.written[]/deleted[]non-empty → drift (INFO severity): the generatedwiki/index.md/ per-directory indexes /wiki/log.mdno longer match the tree. Fix = re-run withoutcheck(safe: pure regeneration).conflicts[]non-empty → WARN: a hand-written file squats a reserved projection path (index.md/log.mdunderwiki/). Never auto-fix — surface it and let the user rename the page (the basenames are reserved by OKF; see the 2026-07-30 catalog/journal decision).skipped: not-initialized→ the vault predates volet ② — offer to initialise (one tool call).
Projection files are excluded from Checks A/B (orphans/dead-links): they are generated navigation, not content — recognisable by their > Generated by obsidian-mcp-router marker line.
2b-ter. Check O: two folders named "Sessions" (v0.92.0+)
Purely structural, one list_files away, and it catches a drift no other check sees: the same content type living in two directories at once.
wiki-meta/Sessions/ is where session notes go — one file per Claude Code session, written by the session-auto-journal hook, and their home since v0.12.8 moved them out of wiki/. Anything under wiki/Sessions/ (any case) is a second, competing home.
Preferred path (v0.92.0+): the refresh_okf_projections({ vault, check: true }) call Check L already makes returns a sessions block — findings[] (each with rule, severity, and then either wikiFiles/metaFiles/detail for a folder rule, or file/line/heading for the catalogue rule) or skipped. Read it from there; the marker rule below is applied by the router from the same bytes the projection planner used. Fall back to the manual walk only when that tool is unavailable:
Reading skipped — two values, two very different meanings. enumeration-failed / enumeration-truncated: the vault could not be listed in full, findings is empty, and the honest report is "Check O could not run" rather than "no collision". catalog-read-failed: only the catalogue could not be read, so the heading question below is open — but any findings listed alongside it were genuinely established and must still be reported. Never drop a finding because skipped is set.
list_files({ vault, directory: 'wiki' })andlist_files({ vault, directory: 'wiki-meta' }); find directories whose name isSessionscase-insensitively (sessionsis the pre-v0.12.8 spelling and still on disk in several vaults).- Enumerate the
.mdfiles under each, recursively. For a file namedindex.mdorlog.md, read it and ignore it only if its body carries the> Generated by obsidian-mcp-routermarker — a generated projection outlives the content that caused it, so counting one flags exactly the vaults whose misfiled note was just moved away. The reserved basename is not enough: this repo treats an UNMARKED file at a reserved path as user-owned (that is whyrefresh_okf_projectionsreports it as aconflictinstead of overwriting it), and a hand-writtenwiki/Sessions/index.mdis content that must be reported. - Content under
wiki/Sessions/and underwiki-meta/Sessions/→ ERRORsession-folder-collision. Content underwiki/Sessions/only → WARNINGsession-folder-stray(a pre-v0.12.8 vault whose migration never ran, or a misfile in a vault the hook has not written to yet). - The heading, not only the folder — WARNING
catalog-sessions-heading: the vault'swiki-meta/catalog.md(or the pre-0.58.0index.md) still carries a## SessionsAREA heading. This is the cause rather than the symptom — the catalogue answering "where does a session note go?" with a folder that must not hold them — and it fires in vaults where nothing has been misfiled yet, which is why the folder rules alone called 16 fleet catalogues clean on 2026-09-07. The tool reports thefile, the 1-basedlineand the rawheadingtext; a heading is only counted where markdown makes it one, so an example inside a fenced code block is not a finding.
Report; never propose a move as the fix. The wiki/ files are frequently curated pages — an incident write-up, a project recap — not raw logs. Folding those into wiki-meta/Sessions/ would bury them: that folder is in DEFAULT_EXCLUDE_FOLDERS and drops out of the search corpus. The right destination is a real content area (wiki/Incidents/, wiki/Projects/, wiki/Daily/), and only the user can say which. When rule 4 above also fired on the same vault, pair the two in your report: the heading is what sent the file there, and removing it is the durable half of the repair — the one mechanical fix this check does offer.
The same rules exist deterministically in src/helpers/session-folder-collision.mjs, which the repo's offline fleet CLI (okf-projections, --all-vaults) uses to sweep every vault at once with Obsidian closed. That is the operator's tool, not this skill's — wiki-lint stays read-only over the router tools. Apply the rules above as written; never write a second implementation of them.
2c. Check M (--okf mode only): OKF bundle conformance (v0.33.0+)
Validates a bundle against the Open Knowledge Format v0.1 conformance rules (SPEC.md §9). Google ships no standalone validator — this check is one of the ecosystem's first.
- Collect the bundle files. For a bundle inside a vault (
wiki-meta/exports/okf/<name>/) : recursivelist_files+ parallelget_file. For a local directory outside a vault : read from disk. Build[{ path, content }]with bundle-relative posix paths (strip the bundle root prefix). - Run the checker :
import { checkOkfConformance } from 'src/helpers/okf-conformance-checker.mjs';
const result = checkOkfConformance(files);
// → { conformant, errors, warnings, info, stats }- Severity mapping (calibrated to OKF's permissive-consumption philosophy — deliberate, don't tighten it) :
- ERRORS = violations of the three conformance rules only :
frontmatter-missing(rule 1),type-missing(rule 2),index-frontmatter-forbidden/index-frontmatter-extra-keys/log-date-not-iso(rule 3). - WARNINGS = deviations the spec shows by example but never marks MUST :
index-heading-level,index-bullet-marker,index-bullet-form,index-unexpected-content,log-not-newest-first,log-frontmatter-unexpected; plus compat signals :filename-charset(Google's reference tooling rejects spaces/accents),wikilink-syntax(Obsidian-only links in bodies). - INFO =
reference-impl-keys(Google's reference implementation wantstype+title+description+timestamp),okf-version-missing,root-index-missing,readme-without-frontmatter.
- ERRORS = violations of the three conformance rules only :
- Verdict line :
✅ conformant OKF v0.1when zero errors,❌ NOT conformantotherwise — then the standard severity tables. A bundle with warnings is still conformant ; say so explicitly (consumers MUST tolerate those deviations).
This check is read-only like everything else in the skill. Do NOT offer to auto-fix a third-party bundle (it's someone else's artifact) ; for bundles produced by our own wiki-export --target okf, an error means an exporter bug — report it as such.
2d. Check N: decision-layer coherence (v0.49.0+)
Runs on every lint (no flag needed) whenever the vault has pages typed decision / adr / decision-input. Validates the frontmatter contract those pages must satisfy — see "Decision pages — frontmatter contract" in the vault CLAUDE.md.
- Collect the pages. You already have the inventory from step 1 ; you need each page's frontmatter. Pass
[{ path, content }], or[{ path, frontmatter }]if you already read the frontmatter. Include NON-decision pages too —affects:legitimately points at specs, user stories and plain notes, and a page missing from the input reads as a dead target. - Run the checker :
import { lintDecisions } from 'src/helpers/decision-lint.mjs';
const result = lintDecisions(pages, { today: '<YYYY-MM-DD>' });
// → { ok, errors, warnings, info, stats: { pages, decisions, byStatus } }- Severity mapping :
- ERRORS = the decision layer actively misleads :
status-missing,status-invalid(carries asuggestionwhen the value is a known legacy one —active/decided→accepted,captured/awaiting-validation→proposed),supersedes-self,supersedes-target-missing,supersedes-target-not-decision,supersedes-target-not-superseded(two decisions read as live at once),supersedes-cycle. - WARNINGS = degraded but usable :
superseded-without-successor(nothing claims it AND it has nosuperseded_by:),superseded-by-not-reciprocated(the named in-vault successor doesn't point back),affects-target-missing,scope-missing,review-after-invalid,review-after-expired,alternatives-missing/alternatives-empty(v0.50.0+ — the "what we ruled out" section, EN or FR heading; only checked when you passedcontent, never for frontmatter-only input). - INFO =
evidence-missing.
- ERRORS = the decision layer actively misleads :
- Corpus scope caveat. Every cross-page rule resolves only against the pages you passed in. If you lint a subfolder, say so in the report —
supersedes-target-missingmay just mean the target lives outside the slice. That asymmetry is also whysuperseded-without-successoris a warning, not an error.
Auto-fix posture (step 4): status-invalid with a suggestion is the one decision finding worth offering to fix (a mechanical set_frontmatter). Never auto-fix supersedes-target-not-superseded silently — flipping the target's status is a semantic act the human should confirm, since it retires a decision.
2d-bis. Check P: prompt lifecycle
Runs on every lint whenever the vault has pages typed prompt. A prompt page is a work order — a brief written to be pasted into a fresh session and executed once — so its status describes the run, not the writing of the document. The vocabulary an author reads is the prompt-status convention snippet, installed in the vault CLAUDE.md.
- Collect the pages — the same inventory as Check N; pass
[{ path, frontmatter }]. Non-prompt pages may be included and are skipped. - Run the checker :
import { lintPrompts } from 'src/helpers/prompt-lint.mjs';
const findings = lintPrompts(pages);
// → [{ rule, path, severity: 'warning', detail, suggestion? }]- The five states, in lifecycle order :
draft(still being written) →ready(pasteable as-is) →in-progress(a session is executing it) →executed(delivered; the page is an ARCHIVE, do not re-run) ·abandoned(will not be run). - Severity mapping — both rules are WARNINGS, and neither affects
ok:prompt-status-missing— the field is absent,null, or blank (empty or whitespace-only): all three mean "nothing is recorded here". Never suggest a value: absence is not evidence of any state, and guessing "it was probably finished" invents an execution history.prompt-status-invalid— a value outside the five. Carries asuggestiononly for a spelling whose meaning was established (doneandshippedboth meantexecuted). A word likewipgets a diagnostic and no suggestion: it plausibly means "a session is executing this", the opposite of thedrafta naive reading would assume.
- Why a warning and not an error. Nothing consumes
statusto decide anything automatically — it is read by humans. But do not report it as cosmetic either: a delivered brief left atreadyinvites a second session to redo the work, which is the expensive mistake this check exists to prevent.
Scope: type: prompt only, matched case-insensitively; a page with no type is skipped rather than guessed at from its filename. Pages inside a frozen copy of the vault (.okf-rename-backup/, .trash/) are excluded — a finding nobody can act on would return on every run.
Auto-fix posture (step 4): prompt-status-invalid with a suggestion may be offered as a mechanical set_frontmatter. Everything else here is a question for the author — and when you rewrite a status, check the page BODY too: one prompt in this family documents its own tracking protocol in prose, and migrating the frontmatter alone would leave the page contradicting itself.
2d-ter. Check Q: conventions drift
Does this vault's CLAUDE.md still say what the convention library says? A convention lives at two addresses — the snippet under skills/conventions/snippets/ and the section of the same name in the vault's CLAUDE.md, which is what an agent actually reads at session start — and nothing compared them until 2026-09-11. The measurement that day, on the reference vault every other vault is cloned from: five of its eight installed conventions had drifted, including the one forbidding a filesystem fallback when a router call fails, and the whole frontmatter contract for decision pages.
- Locate the conventions file —
CLAUDE.md, thenwiki-meta/CLAUDE.md, thenDocumentation/CLAUDE.md. If the vault has two, read both and say so: only one of them is being read at session start, and theconventionsskill refuses to act while both exist. Eleven of the fleet's twenty-eight vaults are in that state. - Run the checker :
import { auditConventionDrift } from 'src/helpers/convention-drift.mjs';
const { findings } = auditConventionDrift({
snippets, // [{ id, heading, text }] from the snippet library
targets: [{ file, content, governed: false }], // a vault is OBSERVED, never governed
});The same helper backs a repo-side checker that covers every vault in the router config in one read-only pass; a maintainer working in the router clone can reach for that instead of linting vault by vault.
- Four states, and
absentis not a weakdrift:in-step·drift(with the number of linesdiffwould mark) ·not-installed(never installed — a choice, not rot) ·duplicate-identity(the same convention twice in one file; there is no single section to compare). Conflating the first two turns every uninstalled convention into a repair task and buries the real ones. - Severity — WARNING, and it never affects
ok. A vault'sCLAUDE.mdbelongs to its owner, who may have extended a section deliberately. The report says what diverges; it never says who is right. The measurement proves why: one convention differs because the snippet was anonymised for public distribution, another because the consigne is newer than the snippet. - Never offer
--all --fix, and never propose a rewrite as the mechanical repair. Applying a convention is a per-vault decision, taken through theconventionsskill with its preview-and-sidecar-backup guards. The one thing to offer is the reading: which lines differ, so the user can judge which side is right.
2d-ter-bis. Check S: which conventions is this vault under (2026-09-26+)
Check Q compares a section that is THERE with its snippet. It cannot see a vault with two conventions files (the router then reads none), a convention that was never installed, or a backup that is somebody else's history. Call audit_vault_conventions({ vault }) — read-only, over REST, remote vaults included — and fold its findings into the report:
ambiguous-conventions-file→ ERROR (the vault's rules are in force for nobody). Quote the audit's repair: when one file is a verbatim template copy, themove_filethat renames it (with itsifMatch) — to PROPOSE, never to run inside the lint.missing-recommended→ WARNING, with theconventions install <id>lines. A template-born vault lacks four by design since 2026-09-11: offer them, and never report them as "lost" — nothing the audit reads can prove a vault once carried them.inherited-backups→ INFO. Say plainly that these backups are the reference vault's, so no conclusion about this vault may be drawn from them — the misreading that produced this check.reference-unknown→ say "unknown", never "no".bilingual-to-migrate→ WARNING. The vault still carries the retiredbilingualconvention (replaced bylanguageson 2026-09-26). Propose/obsidian-router:conventions migrate-bilingual on <vault>; never assume the value — the owner chooses it.languages-value-unreadable→ WARNING.languagesis installed but its value line cannot be read: nothing checks the language of this vault's pages.- Report the audit's
languagesvalue in the summary (languages: fr, enornot declared). A value that differs between two vaults is never drift: Check Q masks a valid value before comparing, and this lint must not suggest aligning one vault's languages on another's.
With a declared value, also flag in the pages you already scan (WARNING, same rules the router applies at write time): a language: code outside the vault's list, and — in a vault with several languages — a page of 500 words or more without one ## section per language (recognised by its flag or its name, e.g. ## 🇫🇷 Version française), or with them out of the declared order. Navigation files (catalog, hot, journal, overview, indexes) are exempt.
For the whole local fleet in one pass, the router clone ships scripts/conventions-audit.mjs for a maintainer (read-only; --json for machines) — not something this skill runs. Repairs stay per vault, each shown and approved.
2d-quater. Check R: temporal validity
Does what a page says still apply? A page may declare a window with valid_from / valid_through (the temporal-validity convention snippet is what an author reads). This check never asks for one — nothing here can know that a page is regulatory — so a page without a window produces no finding at all. It exists for a window that WAS declared and cannot be trusted, or that no longer covers today.
- Collect the pages — the same inventory as Check N and Check P; pass
[{ path, frontmatter }]. Every type is checked, not just decisions: the feature was built forconcept,factandreferencepages as much as for rulings. This check needsfrontmatter, and it must come from a real YAML parse — callget_frontmatterfor it. Do not hand it the{ path, content }form Check N accepts: that path runs the repo's line-oriented parser, which FLATTENS a nested block, and both ways it then goes wrong are invisible afterwards. A key nested under a parent surfaces at the top level, so a page that declared nothing is reported as not-yet-in-force — a finding about a page nobody dated. And a bound holding a block collapses to an empty value, so a malformed window passes unreported. Neither is something the checker can detect once it has the flattened object; by then the shape is gone. This is a real difference from Checks N and P, whose fields are scalars that survive flattening. - Run the checker :
import { lintTemporalValidity } from './src/helpers/temporal-validity-lint.mjs';
const findings = lintTemporalValidity(pages, { today: '<YYYY-MM-DD>' });
// → [{ rule, path, severity, detail }]today is the reference day, resolved once per pass and defaulting to the UTC day — the same clock review_after has always used, so one lint pass reads with one vocabulary. Omitting it, or passing null, asks for the current day; any other unreadable value (hier, 01/01/2026, an empty string, a number) is refused, and refused before the first page is touched.
- Four rules, and the severities are not uniform :
valid-window-inverted— ERROR.valid_fromis aftervalid_through: both dates read fine and the pair cannot be true, so the page misleads whichever bound the reader believes.valid-window-unreadable— WARNING. A bound that is not a calendar date (01/01/2026,2026-02-30, a number, a list). The detail names every bad bound; a report that mentions one and hides the other sends the author back twice.valid-window-not-yet— INFO. Declared, readable, starts later.valid-window-expired— INFO. Declared, readable, already ended.
- Why the last two are information and not defects. A repealed rule is a legitimate page: it explains the past, and it explains the rule that replaced it. Reporting it as a problem pushes an author to delete knowledge in order to silence a linter. Say the state, never ask for a fix.
- Nothing here is auto-fixable. A wrong date is corrected by reading the source; a linter that guesses one invents history — the same refusal Check P makes about an absent status. Neither INFO rule affects
ok; the ERROR does.
Scope: any page type, pages inside a frozen copy (.okf-rename-backup/, .trash/, .obsidian-backup/) excluded, and an entry whose path is missing or blank is skipped rather than reported as a finding pointing at no file. Fed a proper YAML parse as step 1 requires, a bound holding a block is an object, which is not a date, so it is reported as valid-window-unreadable — the case only disappears when the input came from the wrong parser.
3. Render the report
Group findings by severity:
- Errors (broken state): dead wikilinks, stale index entries pointing to nonexistent files, Check J
concept-overlap-strong(deep), Check Iorphaned-digest(deep), Check N decision errors (status-missing,status-invalid,supersedes-*), Check Osession-folder-collision, Check Rvalid-window-inverted(a window no day can satisfy) - Warnings (degraded state): orphans, missing index entries, frontmatter gaps, empty sections, Check H claim-range issues (cited-source-not-found, claim-range-zero-or-negative, claim-range-inverted, claim-range-overflow), Check I
digest-stale(deep), Check Jconcept-overlap-moderate(deep), Check Kcontradiction-suspected(deep, conservative heuristic), Check Lmissing-wikilink(deep), Check Nsuperseded-without-successor/affects-target-missing/scope-missing/review-after-*, Check Osession-folder-strayandcatalog-sessions-heading, Check Pprompt-status-missing/prompt-status-invalid, Check Qconvention-drift(a drifted or duplicated convention section — never above warning, because which side is right is the reader's call), Check Rvalid-window-unreadable(a bound that is not a calendar date) - Info (informational): log out-of-order entries, hot.md staleness, Check N
evidence-missing, Check A-ter frontier pages (never above info — a thin crossroads is not a defect), Check J-bis quasi-twin pairs (never above info — resemblance is not a defect, and the check proposes a reading, never a merge), Check Rvalid-window-not-yet/valid-window-expired(never above info — a page describing a period now past is still knowledge)
For each finding:
- The path or wikilink involved
- 1-line description
- A proposed fix (concrete, applyable)
Render as markdown tables grouped by severity. Total counts at the top.
4. Offer to fix
For ERROR-level findings only, offer to auto-fix:
- Dead wikilinks → suggest the closest existing page (Levenshtein), let the user pick or skip
- Stale index entries → offer to remove the row
Exception — Check O's session-folder-collision is an ERROR with no offered fix. Its "proposed fix" column reads: the user names the content area these pages belong in; nothing is moved until then. The only mechanical repair you may offer is removing the ## Sessions area heading a catalog-sessions-heading finding names (it carries the file and the exact line) — that heading is what sends the next agent to the wrong folder, and it is a WARNING you may fix even though the collision beside it is an ERROR you may not. An agent reading this skill cold flagged the tension between "propose a fix for every ERROR" and "never propose a move"; this paragraph is the resolution, and it is deliberate: moving a curated page into wiki-meta/Sessions/ buries it (the folder is excluded from search), so a wrong guess costs more than an open question.
For WARNING-level findings, do NOT offer auto-fix. The orphan might be intentional; the missing index entry might be a genuine omission you don't want to cement.
The user must explicitly say "fix the errors" or "yes fix dead links" before any mutation.
5. Append to journal.md (only when mutations happened)
This skill is read-only by default. A pure dry-run does NOT touch journal.md — that would be a hidden mutation contradicting the read-only contract.
Append a log entry only if the user accepted at least one ERROR-level auto-fix in step 4:
- YYYY-MM-DD HH:MM — lint — accepted N fix(es) — <comma-separated list of fixed paths>For dry-runs, surface the report in your reply and stop. The user can re-run later to capture the fix history if they want.
Anti-patterns
- Don't auto-fix without permission. The user might have intentional orphans (drafts, archives).
- Don't fabricate "stale claims" — claim staleness needs a domain signal you can't have. Limit yourself to structural checks.
- Don't read every page sequentially. Use
Promise.allSettled-style parallelget_filecalls when feasible (the router supports concurrent requests). - Don't recurse infinitely on broken wikilink suggestions. If the closest match is below a similarity threshold (~0.6), say "no good candidate" rather than suggesting noise.
Output format
🔍 Wiki lint — vault `<name>` — N pages scanned
ERRORS (X)
| Type | Where | Detail | Fix |
|---|---|---|---|
| dead wikilink | wiki/concepts/Bayes.md | `[[Frequentism]]` → not found | Did you mean `[[Frequentist Inference]]`? |
| ... | ... | ... | ... |
WARNINGS (Y)
| Type | Where | Detail |
|---|---|---|
| orphan | wiki/notes/old-thing.md | no inbound links | (review and delete or link, your call) |
| ... | ... | ... |
INFO (Z)
| Type | Where | Detail |
|---|---|---|
| hot stale | wiki-meta/hot.md | Last Updated 12 days ago | run wiki-fold or refresh hot manually |
Run `/obsidian-router:wiki-lint --fix-errors` to apply the X error fixes (interactive).Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

