agentleFS
tboome33tboome33/obsidian-mcp-router/skills/conventions/SKILL.md

conventions

Skill1 starsChanged 7 months agoApache-2.0View on GitHub

Manage CLAUDE.md conventions across Obsidian vaults — install, remove, check status, migrate, or propagate conventions like source-type / languages / heading-hierarchy / auto-enrichment. Triggers (EN) `install source-type convention on smile`, `list conventions on this vault`, `what conventions are installed`, `sync source-type to all vaults`, `remove bilingual convention from vault X`, `set this vault's languages to fr, en`, `migrate bilingual to languages on X`. Triggers (FR) `installe la convention source-type sur smile`, `liste les conventions disponibles`, `quelles conventions sont actives sur ce vault`, `propage la convention source-type à tous les vaults`, `retire la convention bilingue du vault X`, `les langues de ce vault sont fr, en`, `migre bilingual vers languages sur X`.

17 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use review and release.

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.
  • SentrySkill

    review

    Fixture review skill.

    Also in 3 projects that use this

  • MicrosoftSkill

    release

    Use ONLY when the user explicitly asks to cut/publish a Huabu desktop release or tag a version. Performs an outward action — bumps the version in apps/desktop/package.json, commits + annotated-tags vX.Y.Z, and pushes the branch + tag. That tag push auto-builds the macOS/Windows installers and publishes a GitHub Release. Provides `status`, `bump`, `tag-push`, and a one-command `run` (bump → tag-push).

    Also in 3 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

16 rules. 0 are common, 7 are distinctive, and 9 are very specific to this project.

  • 0

    Common

    Most of the 14 edited versions we read keep these.

  • 7

    Distinctive

    This author's own choices. Worth a look.

  • 9

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Call audit_vault_conventions({ vault }) (read-only, works on remote vaults) — or, for every local vault at once, node …

    line 65
  2. Very specific:

    If you can't find the snippets dir, fall back to reading <router-clone>/templates/wiki/CLAUDE.md and extracting H2 sections from there.

    line 143
  3. Distinctive:

    If user said *"on vault X"* → just that vault

    line 148
  4. Very specific:

    If user said *"on all vaults"* → call list_vaults, filter to online: true

    line 149
  5. Very specific:

    Default (no vault specified) → the current default vault from list_vaults

    line 150
  6. Distinctive:

    Default → the current default vault

    line 177
  7. Distinctive:

    Read the result — every field is a sentence you owe the user:

    line 183
  8. Very specific:

    Preview before write: after locating the section to remove, show the user the EXACT content that will be deleted (between code fences) …

    line 220
  9. Very specific:

    Backups are NOT auto-cleaned: Leave the .bak-* files in place — they're the user's safety net.

    line 223
  10. Very specific:

    Failure mode policy: if any guard fails (preview can't be rendered, backup write fails, confirm:true missing on --all), STOP for that vault.

    line 225
  11. Very specific:

    Create <plugin-root>/skills/conventions/snippets/<new-id>.md with the H2 heading as first line and the convention content below.

    line 262
  12. Distinctive:

    Update this skill's "Mapping" table above (optional documentation).

    line 263
  13. Distinctive:

    Always end with a brief next-step suggestion:

    line 298
  14. Very specific:

    After install: *"convention takes effect on the next Claude session opened on this vault.

    line 299
  15. Distinctive:

    After remove: *"convention removed from N vault(s).

    line 300
  16. Distinctive:

    After list: no special suggestion, just the table.

    line 301

“Common” is measured: kept by at least 6 in 10 of the 14 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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

conventions

Manage the named conventions that ship in vault-root CLAUDE.md files — install, remove, check status, or propagate to many vaults at once. A "convention" is a self-contained ## H2 section of CLAUDE.md (e.g. ## Source provenance — \source_type` frontmatter`) that tells Claude how to behave for that vault. The router ships a library of these as markdown snippets; this skill is the installer / detector / synchronizer.

Pre-conditions

  1. Target vault(s) are online — call list_vaults first.
  2. The plugin install ships the convention snippets at <plugin-root>/skills/conventions/snippets/*.md. The router reads them itself (install_conventions); you only need to read them for the offline fallback.

When to use

  • "install source-type on this vault" / "installe la convention source-type ici"
  • "list conventions" / "quelles conventions sont disponibles"
  • "what's installed on vault X" / "quelles conventions sont actives sur X"
  • "sync source-type to all vaults" / "propage source-type partout"
  • "remove bilingual from vault Y" / "retire bilingual de Y"

When NOT to use

  • The user wants to write a CUSTOM convention not in the snippet library → file it as a regular snippet first (see "Add a new convention" below).
  • The user wants to overwrite the entire CLAUDE.md → use wiki skill (re-scaffold) instead.
  • The user wants to remove a section they hand-wrote (not from the library) → it's their content, don't touch via this skill.

How a convention is identified

Each convention snippet starts with a unique ## H2 heading. That heading is the convention's stable identity — used both to detect "is this convention installed in the target CLAUDE.md?" and to remove it cleanly. The snippet filename (source-type.md) is the convention id used in slash command arguments.

Never answer either question by hand. Both live in <plugin-root>/src/helpers/claude-md-conventions.mjs, and both were wrong before v0.94.3 — measurably so:

import {
  resolveClaudeMd, detectConventions, findConventionSection,
  isConventionInstalled, removeConvention, verifyRemoval, planConventionPicker,
} from '<plugin-root>/src/helpers/claude-md-conventions.mjs';

Two rules the helper enforces and a hand-rolled check does not:

  1. Fence-aware. A ## line inside a fenced code block is an example, not a heading. The languages and path-disambiguation snippets (and the retired bilingual) all DISPLAY ## lines inside a ```markdown block. A content.includes("## <heading>") test reports a convention that is merely quoted as installed; a cut that stops at "the next ## " stops inside the example, leaves two thirds of the section behind, and severs the fence — which swallows the rest of the document at render time. That happened on a real vault on 2026-09-11.
  2. Exact identity, never resemblance. ## Bilingual convention (FR + EN, FR primary) — mes ajouts is the USER's section, not the convention. A prefix or substring match calls it installed, and remove then deletes their writing. Matching is exact after trimming and stripping ATX closing hashes (## Foo ## is Foo), at column 0, and at the identity's own level: a # H1 spelled like a convention is a document title, and treating it as the convention makes its "section" run to the end of the file.

A convention's section runs from its heading to the next heading of the same level or higher — its own ### subsections belong to it, and an # H1 below ends it. Identity is narrow, the boundary is wide: a heading the user indented, or wrote setext-style (underlined with ===), still stops the cut. A missed boundary deletes more than the convention.

The scanner has one documented blind spot: fences are tracked at column 0 only, so a heading-looking line inside an INDENTED fenced block is read as a heading. Every attempt to widen that broke a witness from the v0.92.0 lot (a scanner that opens a fence it cannot close hides the rest of the file). A repository test scans the whole snippet library and fails if such a line ever appears there. If you meet one in a user's CLAUDE.md, say so and stop — do not cut.

Where the vault's conventions file lives

There is no single path. The fleet audit that produced CLAUDE_MD_CANDIDATES found three, and they are searched in this order:

  1. CLAUDE.md — the standard Claude Code location
  2. wiki-meta/CLAUDE.md
  3. Documentation/CLAUDE.md — what the reference template ships

This matters more than it looks. A vault provisioned from the template has Documentation/CLAUDE.md and NO root file. A naive get_file("CLAUDE.md") 404s, the skill concludes "not installed", and install appends a SECOND conventions file at the root — two sets of rules, one of which nobody reads.

So: probe the candidates (one list_files on the vault root, plus wiki-meta/ and Documentation/ if present), pass what exists to resolveClaudeMd, and use its answer for install, remove AND list. When two candidates exist it returns ambiguous: true and path: null — there is nothing to act on by design; name the files in present to the user and let them choose. If nothing exists, create at createAt (the vault root) and say where you put it. install_conventions applies exactly this resolution server-side: two files present → it refuses and names them.

audit — which conventions a vault is REALLY under, and what to repair

Call audit_vault_conventions({ vault }) (read-only, works on remote vaults) — or, for every local vault at once, node <router-clone>/scripts/conventions-audit.mjs (read-only; remote vaults come back skipped, never "clean"). Each vault gets ok / attention / broken and findings, each with the repair to PROPOSE:

findingwhat it meansrepair — only after the user says yes
ambiguous-conventions-filetwo or more candidate files; the router reads nonewhen exactly one is a verbatim copy of the template's file: rename THAT copy — hand the move_file step the audit gives (it carries ifMatch; the new name ends .from-template-<date>, so it is reversible) to /obsidian-router:manage-move, which owns moves and their partial-failure report. This skill does not move files itself. Otherwise ask which file holds the vault's rules.
missing-recommendedrecommended conventions absent from the file in forceinstall <id> from the CURRENT snippet (preview + backup as usual). Never restore a section from a .bak file — it may be stale. Absent may be the owner's choice: offer, do not push.
inherited-backupsCLAUDE.md.bak-* files with the same NAME and the same BYTES as one of the reference vault's own backupsnothing to repair. They are almost certainly the TEMPLATE's history, copied in by a sync, so they say nothing about this vault's past: a convention present in them and absent from the current file is not evidence of a loss. A backup that holds the template's text under a name of its own is the vault's OWN history (it edited a template-born file) — the audit does not call it inherited.
no-conventions-fileno candidate at allthe picker (pick); the first install creates the root file.
reference-unknownthe reference vault could not be read"template copy" and "inherited" are UNKNOWN, not false — say so.
bilingual-to-migratethe file still carries the RETIRED bilingual conventionthe migration below (migrate-bilingual): ask the owner the value, install languages, remove bilingual. Never assume fr, en — on 2026-09-26 the owner chose fr for ten of the thirteen vaults that carried it.
languages-value-unreadablelanguages is installed but its value line is missing, doubled, or not ISO 639-1 codesshow the section, ask the owner, correct the one **Languages of this vault: …** line (backup first).

The audit also returns languages — the declared list (["fr","en"]) or null. Report it in every status: it is the one convention whose value differs per vault.

Never read a backup beside a conventions file as that vault's history without the audit. On 2026-09-26 a session did exactly that on Kiviri-OS and reported four conventions "lost on 2026-09-11": the backups were the reference vault's, copied in with its Documentation/ folder, and the vault had simply been born with the template's four-convention file (decision conventions-livrees-par-le-modele). The same sync (2026-09-22) gave 13 local vaults a SECOND conventions file beside their own; since this release a template sync no longer copies a conventions file into a vault that has one, nor any backup anywhere.

Repairs are one vault at a time, each shown and approved. No --all, no silent pass: the fleet report lists, the human decides per vault.

Mapping (the library as of this page — 14 snippets; the identity is the WHOLE first line, parenthesised part included, since matching is exact):

Snippet fileConvention idIdentifying H2 heading
auto-enrichment.mdauto-enrichment## Auto-enrichment (4 modes — \ClaudeAsk` / `Hybrid` / `FullAuto` / `off`)`
claim-citations.mdclaim-citations## Claim-level citations — line-range markers (v0.15.0+, complements \source-type`)`
default-vault-health-check.mddefault-vault-health-check## Default vault health check at session start
description-frontmatter.mddescription-frontmatter## One-line summary — \description` frontmatter (mandatory for every page)`
heading-hierarchy.mdheading-hierarchy## Note structure — headings hierarchy (mandatory)
languages.mdlanguages## Languages convention (declared per vault) — carries a VALUE, see below
log-discipline.mdlog-discipline## Log discipline — index mince + détail dans wiki-meta/Sessions/
path-disambiguation.mdpath-disambiguation## Workspace-bound path disambiguation — NEVER mix cwd path with vault subpath
prompt-status.mdprompt-status## Prompt lifecycle — \status` frontmatter on `type: prompt` pages (mandatory, since 2026-09-10)`
roadmap-discipline.mdroadmap-discipline## Roadmap discipline — création + maintenance dans le vault courant
source-type.mdsource-type## Source provenance — \source_type` frontmatter (mandatory for substantive pages, since 2026-05-18 v0.8.8)`
temporal-validity.mdtemporal-validity## Temporal validity — \valid_from` / `valid_through` frontmatter (optional, any page type, since 2026-09-11)`
tribu-routing.mdtribu-routing## Family-member auto-routing — identify the speaker, route saves to wiki/People/<member>/
wiki-query-first.mdwiki-query-first## Wiki-query-first reflex — check the vault BEFORE answering

(Do not rely on this table for the live list: install_conventions with dryRun: true and ids: [] returns the library as the server ships it — catalogue, retired conventions flagged retired: true — and its detection against the vault's file.)

Retired conventions — recognised, never offered

<plugin-root>/skills/conventions/retired/ holds conventions the library no longer offers. Today: bilingual.md (## Bilingual convention (FR + EN, FR primary)), replaced by languages on 2026-09-26 (decision convention-languages-remplace-bilingual). Never install a retired convention, never show it in the picker. install_conventions enforces the first half: a retired id is reported in retired and never written; its detection still finds the heading in a file, so a vault to migrate stays visible. Keep using its heading to DETECT it and to REMOVE it: remove bilingual reads retired/bilingual.md for the heading, exactly like any other remove. The audit reports a vault that still carries it as bilingual-to-migrate.

languages — the one convention with a value

The languages section carries one line that belongs to the vault: **Languages of this vault: fr, en** — an ordered list of ISO 639-1 codes, the first being the primary language. The snippet carries <languages> there. Everything that touches it goes through <plugin-root>/src/helpers/convention-languages.mjs:

import {
  parseLanguagesValue, renderLanguagesSection, readVaultLanguages,
} from '<plugin-root>/src/helpers/convention-languages.mjs';
  • To install, ask the owner the value first (default fr), then install_conventions({ vault, ids: ["languages"], languages: ["fr"] }) — the router renders the value into the section server-side and refuses the call without one, or with a value that is not ISO 639-1 codes (relay its message and ask again). Installing the raw snippet would install a convention that declares no language (the drift check reports that state as a one-line drift); the tool never writes the placeholder.
  • Never on several vaults at once with one answer. install languages --all and sync-all-vaults languages are refused: the value is a property of each vault, so it is asked per vault, and each vault is installed on its own answer. One answer copied into every vault is exactly the "propagate a value that speaks of one vault" mistake the drift masking exists to avoid.
  • To read a vault's value, readVaultLanguages(content) — or the vaultLanguages field every install_conventions result carries; its problem (missing-value, ambiguous-value, invalid-value, duplicate-section) says which repair to propose.
  • To change a value, show the section, back the file up, and replace that one line — nothing else in the section. install_conventions does not change a value: a vault that already has the section is "already in place", whatever value you pass.
  • Different values in different vaults are not drift. The drift detector masks a valid value before comparing; never "reconcile" one vault's value to another's, and never overwrite it with the placeholder.

migrate-bilingual [on <vault>] — written one vault at a time

Each vault is measured, shown and written on its own. The owner may approve several vaults in one answer, but only after each one's plan has been shown; the writes still happen one vault at a time, each with its own backup and ifMatch.

  1. Measure: audit_vault_conventions({ vault }). If it reports ambiguous-conventions-file, STOP for this vault — which file is in force comes first. Then get_file the file in force and keep its contentSha256. Note the two facts the next steps branch on: is languages already installed (readVaultLanguages(content).installed), and is bilingual present?
  2. Ask the value (fr, fr, en, …) — only if languages is not installed yet, and unless the owner has already given it for this vault in THIS conversation. If languages is installed with a readable value, keep it: the migration is then only the removal of bilingual. If it is installed with an unreadable value, stop and repair that first (languages-value-unreadable).
  3. Show the plan: the languages section that will be added (rendered, with the value) — or "already in place: <value>" — and, if bilingual is present, the EXACT text that will be removed, verbatim, not abbreviated. Wait for the owner's yes for this vault.
  4. Back up the whole file beside it: write_file to <file>.bak-languages-<YYYY-MM-DD-HHmmss> with ifNew: true. If the backup fails, STOP for this vault.
  5. Compute the new content with the helpers: removeConvention(content, bilingualHeading) when bilingual is present, checked with verifyRemoval (nothing else may move); then append renderLanguagesSection(...).text only when languages was not installed — appending it to a file that already carries it creates a second section and makes the value unreadable (duplicate-section). Write with write_file and ifMatch set to the contentSha256 read at step 1.
  6. Re-read and re-audit: exactly one languages section with the right value, bilingual absent, every other convention byte-identical.

The vault must be open: the router goes through the REST API, never the disk. A closed vault is postponed, not bypassed. Backups stay in place.

Steps

Resolving the snippets directory (offline fallback only)

The router reads the snippets itself. When the router is unreachable and you must read them: they live in the plugin install at <plugin-root>/skills/conventions/snippets/. Find the plugin root via ${CLAUDE_PLUGIN_ROOT} env var if available, otherwise look in ~/.claude/plugins/ for a folder containing skills/conventions/snippets/. Once found, Glob it for *.md.

If you can't find the snippets dir, fall back to reading <router-clone>/templates/wiki/CLAUDE.md and extracting H2 sections from there.

list — show available conventions + status

  1. Resolve the target vault(s):
    • If user said "on vault X" → just that vault
    • If user said "on all vaults" → call list_vaults, filter to online: true
    • Default (no vault specified) → the current default vault from list_vaults
  2. For each vault: install_conventions({ vault, ids: [], dryRun: true }). It returns the library (catalogue; entries flagged retired: true are not offered), the resolved conventions file (path, fileExisted), detection and vaultLanguages. Mark ✅ or ❌ from installed, flag duplicate, and report the languages value. A retired convention found installed is not a status line, it is a migration to propose (bilingual-to-migrate). A vault with no conventions file at all is "nothing installed", not an error; two conventions files is a refusal that names them — report it for that vault.
  3. (Offline fallback only, when the router is unreachable: Glob the snippets directory and run detectConventions(content, catalogue) from the helper yourself.)
  4. The ids come from catalogue — never hardcode the library.
  5. Render as a markdown table, and name the file each column was read from — on a fleet where the path differs per vault, a status table that hides which file it read is a status table nobody can check.

Example output:

## Conventions status

Vault: smile (Documentation/CLAUDE.md) — languages: fr
- ✅ source-type          (installed)
- ❌ languages            (not installed)
- ✅ heading-hierarchy    (installed)
- ❌ auto-enrichment      (not installed)

For multi-vault status, render one row per vault with checkmark columns.

install <convention-id>[,<id>…] [on <vault>] [--all] — add one or more conventions

One tool call per vault, whatever the number of conventions: install_conventions. Do not read the snippets and do not paste their text anywhere — the router reads them from its own package. Do not use append_to_file for this: it carries no precondition, so the shared-vault gate refuses it on every vault list_vaults reports with writesRequireIfMatch, and copying ~31 KB of rules through tool calls is how a rule gets altered on the way.

  1. Resolve target vault(s):
    • --all → all online vaults from list_vaults — except for languages, which is refused with --all (its value is asked per vault, see above)
    • on <vault> → that specific vault
    • Default → the current default vault
  2. For each target vault, ONE call:
    install_conventions({ vault, ids: ["source-type", "heading-hierarchy", …] })
    With languages among the ids, add the value the owner gave for THIS vault: languages: ["fr"]. The tool resolves the conventions file itself (the three candidates above; two present → it refuses and names them, so put the question to the user), skips what is already present, appends every missing snippet in ONE compare-and-swap write, then reads the file back.
  3. Read the result — every field is a sentence you owe the user:
    • installed — added by this call. alreadyPresent — "already in place", never "installed". unknown — ids the library does not ship: say so, and show the library (dryRun below). retired — ids the library no longer offers (bilingual): say it is retired and name what replaced it (languages, via migrate-bilingual).
    • verified — true only when every requested convention is present exactly once in what the vault now holds (and, for languages, that the value read back is the one written). false → show problems and say the install is NOT confirmed; do not paper over it.
    • created: true — no conventions file existed; say where it was created (path).
    • A conflict error (409) means the file changed between the read and the write and nothing was written: run the same call again — it re-reads and never overwrites.
    • A malformed id (anything but lowercase letters, digits, hyphens) is refused before any I/O; so is languages without a value, or with a value that is not ISO 639-1 codes.
    • Link the file with the returned clickToOpenUrl.
  4. Report a summary: N installed, M already in place, K unknown, R retired, verified yes/no per vault.

State without writing: install_conventions({ vault, ids: [], dryRun: true }) returns catalogue (the library, id + heading, retired ones flagged), detection (installed / duplicate per convention, retired ones included, against the resolved file), vaultLanguages and path. That is what list and pick read — no local Glob, no local import.

Say "already in place", never "installed", for a skip. Reported per file, "already installed" reads as a benign detail; reported as a total, it reads as "your configuration was applied". A run that installed nothing must say so in the first line — a user who picked six conventions and got six no-ops believes they configured a vault that was already configured for them.

remove <convention-id> [on <vault>] [--all] — strip a convention

  1. Same snippet resolution as install — falling back to retired/<convention-id>.md for a retired convention (remove bilingual is the migration's second half). The heading is also what install_conventions({ ids: [], dryRun: true }).catalogue reports for that id.
  2. Same vault resolution.
  3. For each target vault:
    • Resolve its conventions file and read it via get_file.
    • findConventionSection(content, heading) — if found is false, SKIP and report "not installed".
    • The section it returns IS the boundary: heading line through the line before the next heading of the same level or higher (or EOF). Do NOT re-derive it by scanning for ## yourself; that rule is what destroyed a file on 2026-09-11.
    • removeConvention(content, heading) — and read its reason. It refuses rather than guessing: not-installed, duplicate-identity (the heading appears more than once), no-such-occurrence. On a duplicate, show the user every occurrence with its line number (lines) and ask which is theirs; cutting the first copy blind would report success on a file where the convention is still installed. Their answer goes straight back in — removeConvention(content, heading, { occurrence: 2 }) — and the verification below is then told expectAbsent: false, because a copy is meant to remain. A refusal you cannot answer would be a dead end.
    • MANDATORY backup before write (IMP-4 from /review+ 2026-05-21) — see "Safety guards" below.
    • Verify the proposed content BEFORE writing it, with the helper, not by eye:
      const { ok, problems } = verifyRemoval({ before, after, heading, catalogue });
      Its primary question is strict: is the result exactly before with the located byte range removed? Anything else — a trimmed seam, an edited preamble, a swallowed personal section — fails, including damage to text no catalogue knows about. It then asks a second, independent question: is every convention that was installed before still installed with byte-identical text? (That one catches a range located wrongly, since a severed fence hides everything below it.) If ok is false, ABORT and show problems. Do not invent your own check — counting fence lines for parity, the obvious invention, is not a balance test (a four-backtick block may legitimately contain a literal triple-backtick line).
    • Then write_file with the verified content.
  4. Report summary, INCLUDING the path of every sidecar backup created (so the user can rollback by hand if needed).

Safety guards on remove (mandatory, IMP-4)

Why these guards exist — the H2-heading match strips the section between ## <heading> and the next ## line. If the user has hand-edited the convention's section in their vault's CLAUDE.md (extended it with personal rules, inline examples, etc.), remove would wipe all their customisations along with the convention. The guards make destructive intent explicit and rollback trivial.

MANDATORY for every remove call (single-vault or --all):

  1. Preview before write: after locating the section to remove, show the user the EXACT content that will be deleted (between code fences) BEFORE calling write_file. Do NOT abbreviate; do NOT just say "the source-type convention section". The user must see verbatim what disappears.
  2. Sidecar backup: write a copy of the current CLAUDE.md to CLAUDE.md.bak-<convention-id>-<YYYY-MM-DD-HHmmss> (in the same vault directory) BEFORE the write_file that strips the section. Use write_file with the original full content; the timestamped name guarantees no clobber. If the backup write fails, ABORT the remove (don't continue to the destructive step).
  3. Explicit confirm: true on --all: when the operation targets multiple vaults (--all or any multi-vault resolution), require an explicit confirm: true argument in the slash command invocation. Without it, refuse and tell the user to add confirm: true after they've reviewed the preview. Single-vault remove can proceed after preview (one vault, low blast radius).
  4. Backups are NOT auto-cleaned. Leave the .bak-* files in place — they're the user's safety net. Mention their paths in the final summary so the user can rm them manually after verifying the convention removal is what they wanted.

Failure mode policy: if any guard fails (preview can't be rendered, backup write fails, confirm:true missing on --all), STOP for that vault. Continue with other vaults in a multi-vault operation only if their guards pass independently. Report each vault's outcome separately.

sync-all-vaults <convention-id> — bulk install with smart skip

Convenience alias for install <convention-id> --all. Same logic, with a clearer report grouping vaults by status (online + installed, online + just-installed, offline + skipped, online + failed). Refused for languages (one value per vault, asked per vault).

pick — the state-aware picker (what meta-attach-vault delegates to)

A picker that has not read the target is a picker that lies. Before showing anything:

  1. install_conventions({ vault, ids: [], dryRun: true }) — it resolves the conventions file, reads it, and returns catalogue + detection + vaultLanguages (the real state; a vault with no file is "nothing installed"). If it refuses because two conventions files exist, stop and ask which one is the user's.
  2. Build the options from the OFFERED entries of catalogue — the ones without retired: true — one per convention (AskUserQuestion, multiSelect: true; when the library is larger than one question can hold, split it over several questions and keep the displayed set as your catalogue for step 4). A retired convention detection finds installed is not an option: it is a bilingual-to-migrate to propose after the picker.
  3. Show every option with the installed ones already checked and labelled "déjà en place" / "already in place", plus the caller's recommended ones checked (the wizards name theirs). That single change removes the whole first defect: the user stops "choosing" things that are already there.
  4. Sort the answer into four buckets from detection — the same rule planConventionPicker({ content, catalogue, selected }) implements, if you can run the helper:
bucketmeaningaction
installchecked, absentONE install_conventions call with every id of this bucket (with languages: [...] when languages is in it — ask the value first)
keepchecked, presentnothing — report "already in place"
removeunchecked, presentask (see below)
skipunchecked, absentnothing, silently
  1. Print the plan line before acting — it counts intentions, and says so (planned: N to install, M already in place, R unchecked but present (confirm before removing), S untouched). Report any id the answer named that the library does not ship, and any convention detection marks duplicate.
  2. Install the whole install bucket with ONE install_conventions({ vault, ids: [...] }) call and show its verified result (installed, alreadyPresent, verified, the file's clickToOpenUrl). The CLOSING summary of the run is built from what actually happened (installed / already in place / removed / declined / failed), never from the plan: a user who declined both removals must not be told "2 to remove".

The catalogue you plan with must be exactly the options you displayed. If the library ships a ninth convention and the picker only showed eight, the ninth lands in remove — and the confirmation then says "you did not check these" about a checkbox the user never saw. One collection, displayed and planned.

The remove bucket is the whole point, and it is an INTENTION, not an action. Unchecking is ambiguous — it can mean "do not install this" as easily as "delete what is there" — so resolve it by asking, never by guessing in either direction:

Ces conventions sont déjà présentes dans le fichier de conventions du vault (Documentation/CLAUDE.md) et tu ne les as pas cochées : languages, auto-enrichment. Je retire leurs sections de ce fichier ? (elles restent en place si tu dis non)

On a yes, go through remove in full — verbatim preview, sidecar backup and verifyRemoval included. On a no, say that their sections stay in this file. What must never happen again is the third possibility: saying nothing, and leaving a rule the user believes they turned off governing the vault. auto-enrichment governs automatic saves.

Say what you are actually doing: removing a section from THIS file. Several of these conventions also exist in the user's global ~/.claude/CLAUDE.md; deleting the vault's local copy does not switch the global rule off, and a session already open will not see the change either (conventions are read at session start). Nothing here observes whether a rule is in force — only what a file contains. Promising "désactivée" when you delivered "retirée de ce fichier" is the same class of lie as reporting a skip as an install.

Add a new convention to the library

  1. Create <plugin-root>/skills/conventions/snippets/<new-id>.md with the H2 heading as first line and the convention content below.
  2. Update this skill's "Mapping" table above (optional documentation).
  3. The router picks it up on the next call: install_conventions reads the directory each time.

Anti-patterns

  • Don't hardcode the list of conventions — read catalogue from install_conventions (or Glob the snippets dir offline) every time so newly-added conventions appear automatically.
  • Don't rely on full-file equality to detect "already installed" — users may have edited the convention content in their vault's CLAUDE.md. Match on the H2 heading only.
  • Don't read "already installed" as "up to date". They are different questions, and the gap between them is measurable: on 2026-09-11, five of the eight conventions installed in the reference vault had drifted from their snippets, two of them losing a whole rule. install is right to skip a present section — silently rewriting a user's edits would be worse — but say "present; not compared" rather than letting a skip read as agreement. The comparison is a separate, read-only check (src/helpers/convention-drift.mjs, and wiki-lint's Check Q); it reports which lines differ and never decides which side is right, because drift runs in both directions — one snippet is deliberately anonymised for distribution, and one vault-side copy is deliberately newer than its snippet.
  • Don't detect with includes() and don't cut at "the next ## " — both ignore fenced blocks, and the snippets contain fenced ## examples. Use claude-md-conventions.mjs for both.
  • Don't assume the conventions file is at the vault root — resolve it. The template ships it under Documentation/, and guessing wrong creates a second one.
  • Don't report a skip as an install, and don't let an unchecked-but-present convention pass in silence — those are the two halves of the same lie, and the second one leaves a rule running that the user thinks they turned off.
  • Don't install languages with a value nobody chose — the tool refuses a call without one; do not answer it yourself.
  • Don't auto-restart Claude or Obsidian — the user does it. Tell them the convention takes effect at the next Claude session start (since CLAUDE.md is read at session start).
  • Don't propagate to offline vaults — they'll fail with ECONNREFUSED. List them explicitly in the report so the user knows to come back later.
  • Don't strip whitespace at the section boundary on remove — the snippet starts with \n## H2, the previous section probably ends with \n\n. Leaving the trailing newlines is fine; Obsidian renders the same.

Examples

User: "installe source-type sur smile" → install source-type on=smile → install_conventions({ vault: "smile", ids: ["source-type"] }) → installed: ["source-type"], verified: true → report "✅ installed on smile (verified)" with the file's link

User: "les langues de ce vault sont fr, en" → install languages on=<current default> → install_conventions({ vault, ids: ["languages"], languages: ["fr", "en"] }) → vaultLanguages.languages: ["fr", "en"], verified: true

User: "liste les conventions disponibles" → list (no vault specified) → install_conventions({ ids: [], dryRun: true }) → list every offered entry of catalogue with its identifying H2 and its detection status on the default vault, plus the languages value

User: "sync source-type partout" → sync-all-vaults source-type → list_vaults online → loop install on each → group result by status

User: "quelles conventions sont actives sur ce vault" → list on=<current default> → check each convention's H2 presence in the CLAUDE.md → table

Output format

Always end with a brief next-step suggestion:

  • After install: "convention takes effect on the next Claude session opened on this vault. Run /obsidian-router:conventions list to verify."
  • After remove: "convention removed from N vault(s). Next Claude session on these vaults will skip the convention."
  • After list: no special suggestion, just the table.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.