An Obsidian plugin that checks a CompanyGraph instance as it is edited and completes what its schemas declare. An instance is a folder of Markdown files with YAML frontmatter, which is also what an Obsidian vault is, so an instance opens as a vault with no change to any file. What a vault lacks is what the instance's CI supplies after the edit: an unresolvable reference is an error, and so is a field the schema does not declare. The plugin says so while the edit is made.
It implements no rule of its own. The parser and the checks are those of companygraph/meta-model, bundled at the release package.json pins, and the schemas are read from the core the vault vendored, so an instance is held to the release it adopted and to no other. The writing rules in each schema are checked by nothing mechanical, here or anywhere: they are a judgment, and this plugin reports compliance with the meta-model and claims nothing about them.
A vault with a .companygraph/manifest.json is an instance; in any other vault the plugin stays idle. In an instance it checks the whole model on every change, because a reference crosses files, and shows the result in a pane that lists the failures by file, as a mark on the line of each failure in the open file, and as a count in the status bar. The pane's text can be selected and copied, and Copy report puts the whole of it on the clipboard as plain text, for an agent or an issue. The pane is called Meta-model compliance, which is what it reports: the instance held to the schemas and the rules of the core it vendored, and nothing else. A click on the status bar opens it, and so does the command CompanyGraph: Open the compliance pane. CompanyGraph: Check compliance now runs the checks without waiting for a change, which is also how a vault that has only just become an instance is first read.
The status bar says pin differs when tooling in the manifest names another release of the checker than the one this build bundles. The checks still run, because the instance's CI is the gate for that pin: move the pin and the workflow line together, or take the plugin release that bundles the release the manifest names. A vendored core newer than the bundled checker is refused instead, and the pane names both releases.
CompanyGraph: Make this vault an instance does in a vault what the meta-model's init does in a folder, with the same code: it asks the company's name and which of core's folders to write, all of them unless some are switched off, and writes the core of the release this build bundles under meta/core/, the manifest, the model's folders and its first three entities, a CI workflow pinned to that release, and Claude's AGENTS.md, CLAUDE.md and the skills the release carries. It writes over nothing: a vault that is an instance already is refused, and so is one holding a file it would write, named. CompanyGraph: Move this vault's core is the meta-model's upgrade: it shows what it would write, from which core to which, and on a press moves the vendored core, the skills the tooling installed, the manifest and the workflow's tag together, and leaves the model alone except where the new core reads it differently: a vault that still keeps its seats as roles has them moved from model/roles/ to model/seats/, a profile's roles renamed seats and an experience's role renamed capacity, each page keeping its id, and the emptied model/roles/ folder goes to the trash, where it is found if it is looked for; the preview names what it moves and rewrites before anything is written. A role cell in a Type column is renamed seat. Files of the vault's own that still say roles, and pages that link into roles/, are the owner's to edit, since nothing here rewrites them; the preview names each one, as the move would leave the vault, and the command line's upgrade lists the same files when it makes this move. A vendored file edited in the vault stops it and is named, and the command line's upgrade --force is what overwrites one. The checks run again when either is done.
CompanyGraph: Open the command line opens the meta-model's command line itself, in a tab at the vault's root, at the release this build bundles, and leaves a shell open when its menu is quit. It is offered where the Terminal plugin is switched on, on a computer: Terminal draws the shell, and the command hands it the integrated shell that Terminal's settings name as the default, or the first one they hold for this platform, with the command line to run first.
While typing it offers what the file's schema declares: the frontmatter fields the file lacks, the permitted values of an enum, the canonical names of the type a reference declares, the same by column in a table section, and the sections the file lacks. A name is inserted plain, as the conventions write a reference; the plugin resolves it and Obsidian's own graph view does not see it. An empty line in the frontmatter lists every field the file may still take, which is how they are found at all. An empty entry of a list and an empty cell show everything they may hold as well; there Enter and Tab stay the editor's while nothing is typed, so they end the list, add a row or move to the next cell as they always did. Moving in the list with an arrow key is choosing, and from then on Enter and Tab accept; a click accepts at any time. The command CompanyGraph: Complete here opens the popup again after it was closed, and takes a hotkey in Obsidian's settings. Nothing is offered while text follows the cursor on its line, or in its cell, because accepting would leave that text standing behind the inserted name.
In Live Preview the frontmatter is Obsidian's Properties widget, and its own Add property lists every property name used anywhere in the vault: a long list that knows no schema, in which a field no file uses yet does not appear at all. In a note that is an entity, pressing Add property opens a picker instead, with exactly the fields this file's schema declares and the file lacks, the required ones first; it adds the chosen one and puts the cursor into its value. Its last entry hands over to Obsidian's own list, for a property the schema does not declare, which the checks will then report. Obsidian's own command Add file property opens the same picker in an entity, and so does typing --- at the top of an empty one, which runs that command; the command CompanyGraph: Add a field opens it too.
Frontmatter completes as text in Source mode, and with properties shown as source. In Live Preview the Properties widget completes too, for the fields the schema declares: a reference field offers the canonical names of its type, every one the model holds less those the list already has, an enum its permitted values, and an image field the pictures beside the note; a field the schema declares as plain text keeps Obsidian's own suggestions, which are the values the vault's files hold. Obsidian's list is what a declared field showed before, and it lost a name the moment the last file dropped it, which is the one moment it is wanted back. A table completes in both modes: in Live Preview, where Obsidian draws a table as its own widget and edits one cell at a time, a cell offers what its column declares, named by the same header the checks read, and a failing row is tinted with its message as the tooltip. An empty cell that is clicked into shows its list at once; one reached with Tab or an arrow key waits for a letter, or for CompanyGraph: Complete here, so that a list is not in the way of moving through a table. A table whose separator row carries alignment colons is not a table to the checks and gets no completion in either mode, and a cell holding a line break gets none in Live Preview.
A name a schema declares as a reference is styled as a link, in Source mode, in the Properties widget and in a table in Live Preview, and one that names nothing as an unresolved link. Cmd+click, or Ctrl+click, opens the entity it names; a plain click still edits, since a name is text and not a link. It resolves as the checks do, by its declared type, and for an owned type within the owner the file is in. The model's edges are also added to what Obsidian draws its graph view, local graph and backlink count from, so an entity's neighbors there are the ones the model names. The backlinks pane's list of linked mentions is drawn from real links only and does not show them.
A question, a type of core since 0.40.0, names the entities its answer rests on in a ## Rests on table whose Entity column takes its type from the row: the Type cell names the type, and where that type is owned, the Owner cell names the owner. The plugin reads the row as the checks do, and never by the page the row is written on. The Type cell offers the types the vault's core declares, the Owner cell the names of the type that owns the row's type, and the Entity cell the names of the row's type within the row's owner, so an experience is offered once its profile is named. The name in the Entity cell is styled as a link and opens on Cmd+click, and one the row's owner does not hold is marked as naming nothing. The references pane lists the question under each entity it rests on and under the owner its row names. Rename entity rewrites an Entity cell that names the renamed entity and every Owner cell that names a renamed owner, and Delete entity lists the rows it would leave naming nothing.
A KPI, a type of core since 0.41.0, is written like any other entity: New entity offers it and creates it in kpis/ with its required sections, direction completes to lower, higher and target and marks anything else, owner, measures, serves and read-with complete to the vault's seats, processes, objectives and KPIs, and the references pane lists a KPI under each entity it names.
A brand, a type of core since 0.42.0, is the one file that says what the company looks and sounds like, and the plugin treats it as it treats the identity and the vision: New instance writes model/brand.md beside them from the package's own starting entities, Add a section offers whichever of its five sections the page lacks and writes a table section with its header row, and each table's cells are held to the columns the schema declares.
A decision, a type of core since 0.43.0, is written like any other entity: New entity offers it, asks for decided and leads the filename with its year, as it does for an experience, and creates it in decisions/ with its required sections; kind, status, by, upholds, serves and supersedes complete to the vault's decision kinds, statuses, seats, values, objectives and decisions; and the references pane lists a decision under each entity it names.
A question kind, a type of core since 0.45.0, is written like any other entity: New entity offers it and creates it in question-kinds/ with its required section, and a question's kind, required from the same release, completes to the vault's question kinds; the references pane lists each question under the kind it names.
An entity whose schema declares an image field, core's profile since 0.38.0, carries a picture: a JPEG or PNG beside the note, named in the frontmatter. The plugin hands the checks the file's own bytes, so a picture that is not square, is outside 256 to 1024 pixels on a side, is over 300 KB or is not the format its name says fails by name in the pane, and a click on that failure opens the picture. In the editor the picture is drawn round at the start of the note's H1, in Live Preview and in Source mode alike; a value the checks would fail draws nothing. Reading view draws none of the plugin's editor marks, the picture among them, so it shows no picture at all.
So the plugin brings its own. CompanyGraph: Open the references pane opens a pane that follows the note in front and says what names this entity, grouped by the file each name is written in and opening that line on a click, and what it names, grouped by the entity each name points at and opening that entity. Each entry says where the name stands, a field, a section and its column, or the section a grouped heading heads. A name that resolves to the note it is written in, as a phase whose If not met leads back to itself, is listed once, under what it names, as this page, and opens the line it stands on. CompanyGraph: Toggle references in document, and the item of the same name in the editor's more-options menu, hangs the same two lists under the note itself. While the vault is an instance, Obsidian's own backlink, outgoing-link, properties and tag panes are switched off, since the first two say nothing of a name a schema declares, the third lists every property name in the vault, which no entity is held to, and the last lists a vocabulary an instance has none of: what classifies an entity here is another entity, with a file, a schema and a source, and a frontmatter field a schema does not declare is a failure. The note's own Properties widget stays, and a setting keeps all four where they were.
Obsidian's table editor rewrites a whole table the moment one cell is edited, every column padded to its widest cell, and undo gives back the cell but not the padding, so a note that was only opened carries a diff. In a vault that vendors the family's conventions, any vault and not only an instance, the plugin writes a note back into that Markdown form when the note is left, when it is saved with Cmd+S or Ctrl+S, and when Obsidian quits: the same rules and the same markdownlint that conventions-format runs in CI, so the note comes out byte for byte as the CI's fix would write it, and an edit that was undone leaves no diff at all. In a vault outside the family that is an instance, or a repository companygraph adopt gave the form, there is no rule file of the vault's own, and the plugin writes the note into the form meta-model holds every repository to, the same rules, skipping the paths under the manifest's exclude, as companygraph form does in its CI. Not on the saves Obsidian makes by itself while the note is edited, because the table editor would pad the table again at the next key. A note still open in another tab is changed in its editor and saved as any edit is. The folders conventions.json leaves out of the form, its format-exclude or else its exclude, are left as they are, and CompanyGraph: Write this note in the Markdown form does it at once and says when there is nothing to do.
npx --yes 'github:companygraph/meta-model#semver:*'The plugin is not in Obsidian's community directory, so the meta-model's tooling installs it: its menu's Obsidian plugin entry, or obsidian <vault> without the menu, puts this plugin's newest release into .obsidian/plugins/companygraph/ in the vault and switches it on in .obsidian/community-plugins.json, keeping the plugins already on there. Run again, it updates the plugin and leaves its settings as they are. The first time Obsidian opens the vault it asks whether its author is trusted. A vault once browsed in restricted mode lists no community plugin until Settings → Community plugins → Turn on community plugins, a switch Obsidian keeps outside the vault, where no command reaches it; the tooling says so after every install. Copying main.js, manifest.json and styles.css from a release by hand does the same. Completion is written for Source mode, which the command Toggle Live Preview/Source mode reaches. An instance that is a git repository keeps .obsidian/ in its .gitignore.
Node 24 or newer, because the tests run TypeScript through Node's own type stripping. npm install, then npm test, which first fetches its fixtures over the network, the meta-model at the pinned tag and the reference instance at one commit, then npm run typecheck and npm run build. npx --yes 'github:companygraph/meta-model#semver:*' obsidian <vault> --from . puts a build of one's own into a vault. npm run e2e starts Obsidian on a copy of the pinned reference instance, with the build installed through that command's code, and works the plugin with real clicks and keys; it needs Obsidian installed and is run by hand. The design, with every decision and its reason, is in docs/superpowers/specs/.