Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/conventions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ on:
pull_request:
jobs:
conventions:
uses: robertblust/conventions/.github/workflows/check.yml@v1.34.0
uses: robertblust/conventions/.github/workflows/check.yml@v1.35.0
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<!-- conventions · v1.34.0 -->
<!-- conventions · v1.35.0 -->
Shared conventions of the robertblust, guestgraph and companygraph organizations live in `conventions/`, vendored from robertblust/conventions at the release `conventions.json` names. Read them before writing or committing anything here.

- `conventions/WRITING.md` — how we write: one voice, three registers, English and German.
- `conventions/WORKING.md` — how we work with git and GitHub.
- `conventions/REPOSITORIES.md` — the family: what each repository is and what pins what.
- `conventions/PINS.md` — what a member pins and how the family resync moves it.
- `conventions/WRITER.md`, `conventions/TRANSLATOR.md`, `conventions/EDITOR.md`,
`conventions/BACKREADER.md`, `conventions/GLOSSARY.md`, `conventions/GERMAN.md` — the four roles
that make a text, the terms they keep and the German they write.
Expand Down
2 changes: 1 addition & 1 deletion conventions.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "repo": "robertblust/conventions", "tag": "v1.34.0" }
{ "repo": "robertblust/conventions", "tag": "v1.35.0" }
5 changes: 3 additions & 2 deletions conventions/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<!-- conventions · v1.34.0 -->
<!-- conventions · v1.35.0 -->
Shared conventions of the robertblust, guestgraph and companygraph organizations live in `conventions/`, vendored from robertblust/conventions at the release `conventions.json` names. Read them before writing or committing anything here.

- `conventions/WRITING.md` — how we write: one voice, three registers, English and German.
- `conventions/WORKING.md` — how we work with git and GitHub.
- `conventions/REPOSITORIES.md` — the family: what each repository is and what pins what.
- `conventions/PINS.md` — what a member pins and how the family resync moves it.
- `conventions/WRITER.md`, `conventions/TRANSLATOR.md`, `conventions/EDITOR.md`,
`conventions/BACKREADER.md`, `conventions/GLOSSARY.md`, `conventions/GERMAN.md` — the four roles
that make a text, the terms they keep and the German they write.
Expand All @@ -17,4 +18,4 @@ This repository is the source of the block above. It mirrors the layout it vendo

Releasing is a tag and a GitHub Release with notes. Before tagging, set the version in the first line of this file to the new tag: the script rewrites it to the pin on sync, so a stale number here misleads only a reader of the source, but that reader is the one deciding whether to release.

The tests are `sh test/run.sh`, which runs the scripts against temporary members with this checkout as the source, and `sh conventions/conventions-check` and `sh conventions/conventions-format` over this checkout itself, with `docs/superpowers/` excluded from the prose check because a spec or plan quotes the very list it scans for, and held to the form like everything else. `.superpowers/` is excluded from both, as tooling scratch that is not prose.
The tests are `sh test/run.sh`, which runs the scripts against temporary members with this checkout as the source, and `sh conventions/conventions-check` and `sh conventions/conventions-format` over this checkout itself, with `docs/superpowers/` excluded from the prose check because a spec or plan quotes the very list it scans for, and held to the form like everything else. It also runs the tests of `family/`, the family report and resync, on Node's own runner. `.superpowers/` is excluded from both, as tooling scratch that is not prose.
36 changes: 36 additions & 0 deletions conventions/PINS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Pins

What a member takes from another member of the family is a pin, and `WORKING.md` says why a pin is a tag or a commit. This file says how a member declares its pins so that the family resync in robertblust/conventions can read and move them: a report that says which pins are behind, and a run that moves the ones the owner chooses through to merged pull requests and releases.

## The kinds

| Kind | The line | How it moves |
| --- | --- | --- |
| `conventions` | `tag` in `conventions.json` | set the tag, `sh conventions/conventions-sync sync`, and every `robertblust/conventions/.github/workflows/check.yml@<tag>` line under `.github/workflows/` |
| `service-conventions` | `tag` in `service-conventions.json` | set the tag, `sh service-conventions/service-conventions-sync sync` |
| `npm-tag` | `github:owner/repo#tag` in a `package.json` | set the tag, `npm install` |
| `source-commit` | an object with `repo` and `commit` in a JSON file, at the top or under a key | set the commit of the object whose `repo` matches |
| `contract-commit` | a string `owner/repo@commit:path` in a JSON file | set the commit of every string for that repository |
| `core-release` | `core.version` in `.companygraph/manifest.json` | the entry's own `move` command |

## `pins.json`

A member declares its pins in `pins.json` at its root. The report reads every pin a member's files hold, declared or not, and shows a pin the file does not declare as unmanaged and never moves it. The file is a member's consent to be moved, and the commands in it are how the member rebuilds itself after a move.

```json
{
"pins": [
{ "kind": "conventions", "file": "conventions.json", "repo": "robertblust/conventions" },
{ "kind": "npm-tag", "file": "package.json", "repo": "robertblust/design", "after": ["npm run design"] },
{ "kind": "source-commit", "file": "source.json", "repo": "robertblust/mental-model", "after": ["npm run model", "npm run pages"] }
],
"verify": ["npm test"],
"release": ["npm version {version} --no-git-tag-version"]
}
```

`pins` lists the pins. Each names its `kind`, the `file` that holds the line and the `repo` it points at. `after` lists the commands that run in the member once that pin has moved, in order. `move`, a command with `{version}` in it, replaces the kind's own move, and a `core-release` pin must give one. `watch` lists paths in the upstream for a commit pin, which is then behind only when a newer commit touches one of them; a `contract-commit` pin watches the paths its strings name without being told.

`verify` lists the commands that run once every pin of the member has moved, and a failing one blocks the member. `release` lists the commands that run when the resync releases the member, with `{version}` standing for the new version without its `v`; they leave the bump uncommitted, and the run commits it. A member that nothing takes by tag has no `release`.

The report names every entry that does not match a line in the file it names, so a `pins.json` that has fallen behind its member is seen the first time the report runs.
51 changes: 50 additions & 1 deletion conventions/REPOSITORIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,56 @@ What is listed here is the family; what is not listed is outside it. An agent wo

## What pins what

The three sites pin `robertblust/design` by tag in `package.json`, and `npm run design` writes the fenced copies. blust.ch pins `robertblust/mental-model`, companygraph.io pins `companygraph/meta-model` and `companygraph/mental-model`, and guestgraph.io pins `guestgraph/mental-model`, each by commit in `source.json`, and each builds its model pages from that commit. blust.ch and guestgraph.io also depend on `companygraph/meta-model` by tag for the instance parser. The three instances, `robertblust/mental-model`, `companygraph/mental-model` and `guestgraph/mental-model`, each vendor meta-model's `core/` at a release named in their own manifest. The MCP server depends on `companygraph/meta-model` by tag for the same parser; each of its three deployments pins an instance by commit in `source.json`, mcp.blust.ch `robertblust/mental-model`, mcp.companygraph.io `companygraph/mental-model` and mcp.guestgraph.io `guestgraph/mental-model`, and the server by tag in `package.json`, and builds and deploys with the parts the server ships under `deploy/`. The chat server pins nothing of the family: it is a client of whichever MCP host a deployment names, and each of the same three deployments pins it by tag in `chat/package.json` and deploys it beside the host from the parts it ships under `deploy/`, so a re-pin of the host is a change the chat sees without one of its own. The Obsidian plugin depends on `companygraph/meta-model` by tag for the parser and the checks, and bundles them into what it releases. Every member pins this repository by tag in `conventions.json`.
The three sites pin `robertblust/design` by tag in `package.json`, and `npm run design` writes the fenced copies. blust.ch pins `robertblust/mental-model`, companygraph.io pins `companygraph/meta-model` and `companygraph/mental-model`, and guestgraph.io pins `guestgraph/mental-model`, each by commit in `source.json`, and each builds its model pages from that commit. All three sites also depend on `companygraph/meta-model` by tag in `package.json` for the instance parser, and take `companygraph/mcp-server` by tag there too. guestgraph.io pins `guestgraph/engine` and `guestgraph/connector-apaleo` by commit in `api-sources.json`, and builds its API pages from the OpenAPI specification each commit holds. The three instances, `robertblust/mental-model`, `companygraph/mental-model` and `guestgraph/mental-model`, each vendor meta-model's `core/` at the release that `core.version` names in their own `.companygraph/manifest.json`. The engine and the connector each vendor `guestgraph/service-conventions` at the tag in `service-conventions.json`, and the connector pins the contracts it implements, which live under the engine's `specs/`, by commit in `src/main/resources/api/sources.json`. The MCP server depends on `companygraph/meta-model` by tag in `package.json` for the same parser; each of its three deployments pins an instance by commit in `source.json`, mcp.blust.ch `robertblust/mental-model`, mcp.companygraph.io `companygraph/mental-model` and mcp.guestgraph.io `guestgraph/mental-model`, and the server and `robertblust/design` by tag in `package.json`, and builds and deploys with the parts the server ships under `deploy/`. The chat server takes `companygraph/mcp-server` by tag in `package.json` as a development dependency and is a client of whichever MCP host a deployment names, and each of the same three deployments pins it and `robertblust/design` by tag in `chat/package.json` and deploys it beside the host from the parts it ships under `deploy/`, so a re-pin of the host is a change the chat sees without one of its own. The Obsidian plugin depends on `companygraph/meta-model` by tag in `package.json` for the parser and the checks, and bundles them into what it releases. Every member pins this repository by tag in `conventions.json`.

The same pins, drawn: an arrow runs from the member that pins to the repository it pins, and its label says how. Each node is named as its repository is, under its organization.

```mermaid
flowchart TB
subgraph robertblust
design[design]
blust[robertblust.github.io]
rbmm[mental-model]
mcpblust[mcp-blust-ch]
end
subgraph companygraph
cgio[companygraph.github.io]
meta[meta-model]
cgmm[mental-model]
mcpsrv[mcp-server]
chat[chat-server]
mcpcg[mcp-companygraph-io]
obsidian[obsidian-plugin]
end
subgraph guestgraph
ggio[guestgraph.github.io]
engine[engine]
connector[connector-apaleo]
svc[service-conventions]
ggmm[mental-model]
mcpgg[mcp-guestgraph-io]
end
blust & cgio & ggio -->|tag| design
blust & ggio -->|tag| meta
cgio -->|tag, commit| meta
blust -->|commit| rbmm
cgio -->|commit| cgmm
ggio -->|commit| ggmm
ggio -->|commit| engine & connector
rbmm & cgmm & ggmm -->|release of core/| meta
mcpsrv & obsidian -->|tag| meta
blust & cgio & ggio -->|tag| mcpsrv
chat -->|tag| mcpsrv
mcpblust & mcpcg & mcpgg -->|tag| design
engine & connector -->|tag| svc
connector -->|commit| engine
mcpblust & mcpcg & mcpgg -->|tag| mcpsrv & chat
mcpblust -->|commit| rbmm
mcpcg -->|commit| cgmm
mcpgg -->|commit| ggmm
```

Every member also pins `robertblust/conventions`, and drawn that would be an arrow from every node to one more; it is left out, and with it `robertblust/field-notes`, `guestgraph/.github` and `companygraph/.github`, which pin nothing else. A test holds the drawing to the table: each repository listed above is a node here or is named in this paragraph, so a member added to the table and not to the drawing turns the suite red. It does not check the arrows; the paragraph above is what they draw, and an edit to one is an edit to both.

A pin is an editorial line, moved on purpose. Which release each member is on is read from the pin, never from this file, so this file does not repeat versions.

Expand Down
4 changes: 4 additions & 0 deletions conventions/WORKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Every change reaches the default branch through a pull request with one green st

Merging is a decision the owner makes. An agent opens the pull request, reports the check, and stops; it merges when told to, and the word for that is the owner's, not inferred from an earlier one.

A family resync is the one run that merges on its own. When the owner chooses chains from the family report, that choice is their word for every merge, release and deploy the run needs to carry those pins through, and the run asks nothing more. It stops wherever a person's judgment is needed by blocking the member — a failed step or suite, a check that does not pass, a clone without an address, or work on the default branch that no release describes — and holds everything downstream of it. It never releases work whose notes a person has not written, and it never releases conventions.

Pull requests that depend on one another are linked as a stack, GitHub's own, with `gh stack link` from the bottom up. Each then shows only its own layer, the default branch's rules and checks hold for every layer and not only the lowest, and the one above re-targets to the default branch by itself when the one below merges; re-targeted by hand, a wrong order closes the pull request. A stack is merged from the bottom with `gh stack merge --merge`, because GitHub refuses the ordinary command for a pull request in a stack, and the merge commit rule holds there as everywhere.

When a layer merges, GitHub rebases the branches above it onto the merge and replaces them on its side. That rewrites a pushed branch without the owner's word, and it is the one rewrite that passes: the content is byte for byte what it was, the author, the committer and the message are kept, and only the commit's name changes, so nothing a reviewer read is different and no identity is laundered. Whoever drives the branch resets their worktree to the remote before adding to it. The cascade a person starts, `gh stack sync` or `gh stack rebase`, is not that. It is a rewrite like any other, and it waits for the owner's word.
Expand All @@ -40,6 +42,8 @@ A release is a tag and a GitHub Release with notes in the prose register: what c

Everything one repository takes from another is pinned by a visible line in the taking repository, in whatever form its tooling gives it — a tag in a package file, a commit in a source file, a release in a vendoring manifest. Pins are editorial. They move when the owner decides they move, in a commit that says why, and no bot proposes them; a pin that is behind is intent until the owner says it is drift.

What is taken decides the form of the pin. Code or rules the taking repository runs or is checked against are pinned by tag, because the release is what carries the notes and the minor or major an update depends on: a parser, a design system, a schema vendored as `core/`, these files. Content it draws or serves is pinned by commit, because content changes with every edit and promises nothing a release could state, and the pin only chooses which state is shown: a mental model on a site or behind an MCP host, an OpenAPI file on a page. One repository can be taken both ways, and meta-model is: companygraph.io runs its parser at a tag and draws its `example/` and `core/` at a commit. One pin breaks the rule. The Apaleo connector implements contracts that live in the engine, and the engine has no releases, so the connector pins them by commit; once the engine releases, that pin becomes a tag.

A change to anything another repository vendors or builds from is at least a minor release, because it makes every copy stale. A change that asks the taking repository to do anything beyond re-syncing or re-pinning is a major. The notes say which.

## Checks
Expand Down
2 changes: 1 addition & 1 deletion conventions/conventions-sync
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ case $cmd in sync|check) ;; *) usage ;; esac

PIN=conventions.json
DIR=conventions
FILES="WRITING.md WORKING.md REPOSITORIES.md WRITER.md TRANSLATOR.md GLOSSARY.md GERMAN.md EDITOR.md BACKREADER.md AGENTS.md conventions-sync conventions-check conventions-format markdown-rules.cjs vscode-settings.json vscode-extensions.json"
FILES="WRITING.md WORKING.md REPOSITORIES.md PINS.md WRITER.md TRANSLATOR.md GLOSSARY.md GERMAN.md EDITOR.md BACKREADER.md AGENTS.md conventions-sync conventions-check conventions-format markdown-rules.cjs vscode-settings.json vscode-extensions.json"

# What a member receives at its own root rather than under conventions/, because something other
# than this family reads it there. markdownlint-cli2 and the editor plugins built on it discover
Expand Down
11 changes: 6 additions & 5 deletions conventions/manifest.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
{
"repo": "robertblust/conventions",
"tag": "v1.34.0",
"tag": "v1.35.0",
"files": {
"conventions/WRITING.md": "sha256:c789a79512ba7599fde6eeb08ae7832bf2812c9c193872742219daad308eb6dc",
"conventions/WORKING.md": "sha256:00f7bd64fa2a0837025030752723e80f5a9059885b76b86671cd9ded844f4643",
"conventions/REPOSITORIES.md": "sha256:377341e38a31b2d765ac2c3b5d46ffbbfbe0131f85669dda11dfb947d1dfca91",
"conventions/WORKING.md": "sha256:4e1ad8b8c22355303868b795f6c100c5b5d28aa9a3de334fedfa3e4632a01de1",
"conventions/REPOSITORIES.md": "sha256:c37d322b74344a7677ab5248841b230612c42107e6721123971458abde03dddd",
"conventions/PINS.md": "sha256:87acf40bafc38b522fc790f41eaebf90eef482cda1354515d142577a39248940",
"conventions/WRITER.md": "sha256:bc3b92683130b75993f824f0e34073d4344846c91be2009fd90c39099fd4fd48",
"conventions/TRANSLATOR.md": "sha256:026c4767347ea907e6ef285f7ccf8192e0b3dec12b40207acd32ad3a2f9b5140",
"conventions/GLOSSARY.md": "sha256:ffad6b58a3c3b5c92109ec15b1aa0669a2915e9641b7d240c3126a65a16a3cbf",
"conventions/GERMAN.md": "sha256:2f4651dd74e2970f0751621bf9264441b43ee619b8a6e1373d5f3c7809581f77",
"conventions/EDITOR.md": "sha256:e65d0b2893ac841faefede9b873f7e33c1fd7b02bedf97d37d5362c0e83c08ea",
"conventions/BACKREADER.md": "sha256:36b12d4bee8e4fe190469e2f550d50d8a7f6dcab6bec573da17eb1c165c417cd",
"conventions/AGENTS.md": "sha256:0825a75474112172a1ef15a62d55de9b06895e0f01f2dfb61741586ea9b553fc",
"conventions/conventions-sync": "sha256:cecda4403c6acd7c7f495808196d94f436c15f726cbe4c1ba19b9580aa6b48c5",
"conventions/AGENTS.md": "sha256:7e3d5d6940ef1888f28e4e8e4116ddbf44c2415dd06680c3e9f1ee07594e8b99",
"conventions/conventions-sync": "sha256:bc68607ea24c5cea4d449c1bc2f3a1bf681f55254aa581bee7f8750667a824f7",
"conventions/conventions-check": "sha256:30b2528e84381f04fd6b06b26ef103cd2360d877639706eb0b53f362a10fc433",
"conventions/conventions-format": "sha256:2911a0d30dda1bc0f621522a9538b3e39704eee03458e65ba320eccbf39b5c7f",
"conventions/markdown-rules.cjs": "sha256:5cbf6120bf16e358b3a4e362015d054f0cd6e47ddfb0e5fe5ac17143b2bdef26",
Expand Down
6 changes: 6 additions & 0 deletions pins.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"pins": [
{ "kind": "conventions", "file": "conventions.json", "repo": "robertblust/conventions" }
],
"verify": ["sh conventions/conventions-check", "sh conventions/conventions-format"]
}
Loading