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
- hanyang-tech/claude-skillstheir own version, first seen Nov 15, 2026
- MohammadShehadeh/agent-skillstheir own version, first seen Nov 1, 2026
- piomin/claude-ai-spring-boottheir own version, first seen Oct 25, 2026
- scottlivingston/claude-skillstheir own version, first seen Oct 25, 2026
- wuyining0130/c-roomtheir own version, first seen Oct 15, 2026
- philtzjp/skillstheir own version, first seen Oct 13, 2026
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.
SentrySkillreview
Fixture review skill.
Also in 3 projects that use this
MicrosoftSkillrelease
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
Other versions17 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 17
- meteor/meteor★ 45ksame name
- piomin/claude-ai-spring-boot★ 1.4ksame name
- psenger/ai-agent-skills★ 10same name
- yyyyolo7a79-sketch/claude-conventions-skill★ 3same name
- MohammadShehadeh/agent-skills★ 2same name
- philtzjp/skills★ 2same name
- psenger/catholic-ai-skills★ 2same name
- stijnvanhulle/agents★ 2same name
- yacb2/aidex★ 2same name
- kamenkurtev/ui-consistency★ 1same name
7 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
- 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-lintSkill
- 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
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
- Very specific: line 65
Call audit_vault_conventions({ vault }) (read-only, works on remote vaults) — or, for every local vault at once, node …
- Very specific: line 143
If you can't find the snippets dir, fall back to reading <router-clone>/templates/wiki/CLAUDE.md and extracting H2 sections from there.
- Distinctive: line 148
If user said *"on vault X"* → just that vault
- Very specific: line 149
If user said *"on all vaults"* → call list_vaults, filter to online: true
- Very specific: line 150
Default (no vault specified) → the current default vault from list_vaults
- Distinctive: line 177
Default → the current default vault
- Distinctive: line 183
Read the result — every field is a sentence you owe the user:
- Very specific: line 220
Preview before write: after locating the section to remove, show the user the EXACT content that will be deleted (between code fences) …
- Very specific: line 223
Backups are NOT auto-cleaned: Leave the .bak-* files in place — they're the user's safety net.
- Very specific: line 225
Failure mode policy: if any guard fails (preview can't be rendered, backup write fails, confirm:true missing on --all), STOP for that vault.
- Very specific: line 262
Create <plugin-root>/skills/conventions/snippets/<new-id>.md with the H2 heading as first line and the convention content below.
- Distinctive: line 263
Update this skill's "Mapping" table above (optional documentation).
- Distinctive: line 298
Always end with a brief next-step suggestion:
- Very specific: line 299
After install: *"convention takes effect on the next Claude session opened on this vault.
- Distinctive: line 300
After remove: *"convention removed from N vault(s).
- Distinctive: line 301
After list: no special suggestion, just the table.
“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.
Files
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
- Target vault(s) are online — call
list_vaultsfirst. - 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
wikiskill (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:
- Fence-aware. A
##line inside a fenced code block is an example, not a heading. Thelanguagesandpath-disambiguationsnippets (and the retiredbilingual) all DISPLAY##lines inside a```markdownblock. Acontent.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. - Exact identity, never resemblance.
## Bilingual convention (FR + EN, FR primary) — mes ajoutsis the USER's section, not the convention. A prefix or substring match calls it installed, andremovethen deletes their writing. Matching is exact after trimming and stripping ATX closing hashes (## Foo ##isFoo), 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:
CLAUDE.md— the standard Claude Code locationwiki-meta/CLAUDE.mdDocumentation/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:
| finding | what it means | repair — only after the user says yes |
ambiguous-conventions-file | two or more candidate files; the router reads none | when 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-recommended | recommended conventions absent from the file in force | install <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-backups | CLAUDE.md.bak-* files with the same NAME and the same BYTES as one of the reference vault's own backups | nothing 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-file | no candidate at all | the picker (pick); the first install creates the root file. |
reference-unknown | the reference vault could not be read | "template copy" and "inherited" are UNKNOWN, not false — say so. |
bilingual-to-migrate | the file still carries the RETIRED bilingual convention | the 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-unreadable | languages is installed but its value line is missing, doubled, or not ISO 639-1 codes | show 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 file | Convention id | Identifying H2 heading |
auto-enrichment.md | auto-enrichment | ## Auto-enrichment (4 modes — \ClaudeAsk` / `Hybrid` / `FullAuto` / `off`)` |
claim-citations.md | claim-citations | ## Claim-level citations — line-range markers (v0.15.0+, complements \source-type`)` |
default-vault-health-check.md | default-vault-health-check | ## Default vault health check at session start |
description-frontmatter.md | description-frontmatter | ## One-line summary — \description` frontmatter (mandatory for every page)` |
heading-hierarchy.md | heading-hierarchy | ## Note structure — headings hierarchy (mandatory) |
languages.md | languages | ## Languages convention (declared per vault) — carries a VALUE, see below |
log-discipline.md | log-discipline | ## Log discipline — index mince + détail dans wiki-meta/Sessions/ |
path-disambiguation.md | path-disambiguation | ## Workspace-bound path disambiguation — NEVER mix cwd path with vault subpath |
prompt-status.md | prompt-status | ## Prompt lifecycle — \status` frontmatter on `type: prompt` pages (mandatory, since 2026-09-10)` |
roadmap-discipline.md | roadmap-discipline | ## Roadmap discipline — création + maintenance dans le vault courant |
source-type.md | source-type | ## Source provenance — \source_type` frontmatter (mandatory for substantive pages, since 2026-05-18 v0.8.8)` |
temporal-validity.md | temporal-validity | ## Temporal validity — \valid_from` / `valid_through` frontmatter (optional, any page type, since 2026-09-11)` |
tribu-routing.md | tribu-routing | ## Family-member auto-routing — identify the speaker, route saves to wiki/People/<member>/ |
wiki-query-first.md | wiki-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), theninstall_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 --allandsync-all-vaults languagesare 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 thevaultLanguagesfield everyinstall_conventionsresult carries; itsproblem(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_conventionsdoes 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.
- Measure:
audit_vault_conventions({ vault }). If it reportsambiguous-conventions-file, STOP for this vault — which file is in force comes first. Thenget_filethe file in force and keep itscontentSha256. Note the two facts the next steps branch on: islanguagesalready installed (readVaultLanguages(content).installed), and isbilingualpresent? - Ask the value (
fr,fr, en, …) — only iflanguagesis not installed yet, and unless the owner has already given it for this vault in THIS conversation. Iflanguagesis installed with a readable value, keep it: the migration is then only the removal ofbilingual. If it is installed with an unreadable value, stop and repair that first (languages-value-unreadable). - Show the plan: the
languagessection that will be added (rendered, with the value) — or "already in place:<value>" — and, ifbilingualis present, the EXACT text that will be removed, verbatim, not abbreviated. Wait for the owner's yes for this vault. - Back up the whole file beside it:
write_fileto<file>.bak-languages-<YYYY-MM-DD-HHmmss>withifNew: true. If the backup fails, STOP for this vault. - Compute the new content with the helpers:
removeConvention(content, bilingualHeading)whenbilingualis present, checked withverifyRemoval(nothing else may move); then appendrenderLanguagesSection(...).textonly whenlanguageswas not installed — appending it to a file that already carries it creates a second section and makes the value unreadable (duplicate-section). Write withwrite_fileandifMatchset to thecontentSha256read at step 1. - Re-read and re-audit: exactly one
languagessection with the right value,bilingualabsent, 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
- Resolve the target vault(s):
- If user said "on vault X" → just that vault
- If user said "on all vaults" → call
list_vaults, filter toonline: true - Default (no vault specified) → the current default vault from
list_vaults
- For each vault:
install_conventions({ vault, ids: [], dryRun: true }). It returns the library (catalogue; entries flaggedretired: trueare not offered), the resolved conventions file (path,fileExisted),detectionandvaultLanguages. Mark ✅ or ❌ frominstalled, flagduplicate, and report thelanguagesvalue. 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. - (Offline fallback only, when the router is unreachable:
Globthe snippets directory and rundetectConventions(content, catalogue)from the helper yourself.) - The ids come from
catalogue— never hardcode the library. - 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.
- Resolve target vault(s):
--all→ all online vaults fromlist_vaults— except forlanguages, which is refused with--all(its value is asked per vault, see above)on <vault>→ that specific vault- Default → the current default vault
- For each target vault, ONE call:
Withinstall_conventions({ vault, ids: ["source-type", "heading-hierarchy", …] })languagesamong 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. - 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 (dryRunbelow).retired— ids the library no longer offers (bilingual): say it is retired and name what replaced it (languages, viamigrate-bilingual).verified—trueonly when every requested convention is present exactly once in what the vault now holds (and, forlanguages, that the value read back is the one written).false→ showproblemsand 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
languageswithout a value, or with a value that is not ISO 639-1 codes. - Link the file with the returned
clickToOpenUrl.
- Report a summary:
N installed, M already in place, K unknown, R retired, verified yes/noper 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
- Same snippet resolution as install — falling back to
retired/<convention-id>.mdfor a retired convention (remove bilingualis the migration's second half). The heading is also whatinstall_conventions({ ids: [], dryRun: true }).cataloguereports for that id. - Same vault resolution.
- For each target vault:
- Resolve its conventions file and read it via
get_file. findConventionSection(content, heading)— iffoundis 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 itsreason. 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 toldexpectAbsent: 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:
Its primary question is strict: is the result exactlyconst { ok, problems } = verifyRemoval({ before, after, heading, catalogue });beforewith 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.) Ifokis false, ABORT and showproblems. 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_filewith the verified content.
- Resolve its conventions file and read it via
- 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):
- 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. - Sidecar backup: write a copy of the current
CLAUDE.mdtoCLAUDE.md.bak-<convention-id>-<YYYY-MM-DD-HHmmss>(in the same vault directory) BEFORE thewrite_filethat strips the section. Usewrite_filewith 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). - Explicit
confirm: trueon--all: when the operation targets multiple vaults (--allor any multi-vault resolution), require an explicitconfirm: trueargument in the slash command invocation. Without it, refuse and tell the user to addconfirm: trueafter they've reviewed the preview. Single-vaultremovecan proceed after preview (one vault, low blast radius). - 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:
install_conventions({ vault, ids: [], dryRun: true })— it resolves the conventions file, reads it, and returnscatalogue+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.- Build the options from the OFFERED entries of
catalogue— the ones withoutretired: 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 yourcataloguefor step 4). A retired conventiondetectionfinds installed is not an option: it is abilingual-to-migrateto propose after the picker. - 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.
- Sort the answer into four buckets from
detection— the same ruleplanConventionPicker({ content, catalogue, selected })implements, if you can run the helper:
| bucket | meaning | action |
install | checked, absent | ONE install_conventions call with every id of this bucket (with languages: [...] when languages is in it — ask the value first) |
keep | checked, present | nothing — report "already in place" |
remove | unchecked, present | ask (see below) |
skip | unchecked, absent | nothing, silently |
- 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 conventiondetectionmarksduplicate. - Install the whole
installbucket with ONEinstall_conventions({ vault, ids: [...] })call and show its verified result (installed,alreadyPresent,verified, the file'sclickToOpenUrl). 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
- Create
<plugin-root>/skills/conventions/snippets/<new-id>.mdwith the H2 heading as first line and the convention content below. - Update this skill's "Mapping" table above (optional documentation).
- The router picks it up on the next call:
install_conventionsreads the directory each time.
Anti-patterns
- Don't hardcode the list of conventions — read
cataloguefrominstall_conventions(orGlobthe 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.
installis 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, andwiki-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. Useclaude-md-conventions.mjsfor 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
languageswith 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 listto 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.
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.

