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
8 changes: 8 additions & 0 deletions .github/workflows/conventions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: conventions
on:
push:
branches: [main]
pull_request:
jobs:
conventions:
uses: robertblust/conventions/.github/workflows/check.yml@v1.3.2
2 changes: 1 addition & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- run: python3 verify.py
30 changes: 30 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!-- conventions · v1.3.2 -->
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.

Everything below this block is this repository's own. `sh conventions/conventions-sync check`
says whether the copy matches the release, `sync` brings it to the release the pin names, and
`sh conventions/conventions-check` holds this repository's own Markdown to `WRITING.md`. Edit
a shared file in robertblust/conventions, never here.
<!-- end conventions -->

# robertblust/field-notes — working conventions

Problems that took real work to understand, one file per problem, in the five-part shape the
README gives: symptom, what it was not, root cause, fix, how it was verified. The README is
the manual for writing one; this file is only what an agent needs before touching anything
here.

## Checks

Two jobs, both required by the ruleset on `main`: `verify`, which runs `python3 verify.py` and
resolves every relative link in every note, and `conventions`, called from
robertblust/conventions at the pinned tag and shown by GitHub as `conventions / conventions`.
External URLs are not fetched on purpose; the README says why. Everything about how to write
and how to work with git is in `conventions/`.

4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@AGENTS.md
@conventions/WRITING.md
@conventions/WORKING.md
@conventions/REPOSITORIES.md
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ answer is obvious once you have it and the wrong turn is not.

`python3 verify.py` resolves every relative link in every note. External URLs are
deliberately not fetched — a checker that hits the network fails on someone else's outage,
and a suite that cries wolf is one people stop reading. CI runs the same command.
and a suite that cries wolf is one people stop reading. CI runs the same command. The
`conventions` job, called from robertblust/conventions at the pinned tag, holds the vendored
copy to its release and every Markdown file here to `conventions/WRITING.md`.

## Licence
## License

[CC BY 4.0](LICENSE). Use it, quote it, build on it; credit it. The prose is the artefact
here, which is why this is a content licence rather than the Apache 2.0 the code
[CC BY 4.0](LICENSE). Use it, quote it, build on it; credit it. The prose is the artifact
here, which is why this is a content license rather than the Apache 2.0 the code
repositories carry.
1 change: 1 addition & 0 deletions conventions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "repo": "robertblust/conventions", "tag": "v1.3.2" }
35 changes: 35 additions & 0 deletions conventions/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!-- conventions · v1.3.2 -->
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.

Everything below this block is this repository's own. `sh conventions/conventions-sync check`
says whether the copy matches the release, `sync` brings it to the release the pin names, and
`sh conventions/conventions-check` holds this repository's own Markdown to `WRITING.md`. Edit
a shared file in robertblust/conventions, never here.
<!-- end conventions -->

# robertblust/conventions — working conventions

This repository is the source of the block above. It mirrors the layout it vendors: the
shared files live under `conventions/` here exactly as they do in every member, so the block
reads the same in both. The block is plain words and names no agent vendor; `CLAUDE.md` is the
one vendor adapter, four lines that import the entry file and the three shared files in that
vendor's syntax, and a member carries the same four lines. The one file members receive that
does not sit under `conventions/` in the source is this `AGENTS.md`, which the script fetches
from the root and vendors as `conventions/AGENTS.md` so that `check` can compare a member's
block against the release without a network.

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 both scripts against temporary members with this
checkout as the source, and `sh conventions/conventions-check` over this checkout itself, with
`docs/superpowers/` excluded because a spec or plan quotes the very list it scans for.
`.superpowers/` is excluded too, as tooling scratch that is not prose.
49 changes: 49 additions & 0 deletions conventions/REPOSITORIES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Repositories

Three organizations, one family. `robertblust` holds the person and the shared machinery,
`guestgraph` the guest identity graph, `companygraph` the meta-model for operating a company.
Every repository below vendors this repository's `conventions/` at a pinned release and opens
its `AGENTS.md` with the same block; `CLAUDE.md` is the same four-line vendor adapter
everywhere.

| Repository | Purpose | Default branch | Local path |
|---|---|---|---|
| robertblust/conventions | how the family writes and works, vendored by every member | main | ~/git/robertblust/conventions |
| robertblust/design | the design system shared by the three sites: tokens, chrome, page checks | main | ~/git/robertblust/design |
| robertblust/robertblust.github.io | blust.ch, the profile page and two talks | main | ~/git/robertblust/robertblust.github.io |
| robertblust/mental-model | Robert Blust described in CompanyGraph, the reference instance | main | ~/git/robertblust/mental-model |
| robertblust/field-notes | problems that took real work to understand, one file each | main | ~/git/robertblust/field-notes |
| guestgraph/guestgraph.github.io | guestgraph.io, the landing page and the intro talk | main | ~/git/guestgraph/guestgraph.github.io |
| guestgraph/engine | identity resolution, guest graph and REST API, the open core | main | ~/git/guestgraph/engine |
| guestgraph/.github | the organization profile GitHub shows, and nothing else | main | ~/git/guestgraph/.github |
| companygraph/companygraph.github.io | companygraph.io, the landing page, the model and example pages, the intro talk | main | ~/git/companygraph/companygraph.github.io |
| companygraph/meta-model | the meta-model: core vocabulary, packs and the conventions that make a graph of Markdown checkable | main | ~/git/companygraph/meta-model |
| companygraph/.github | the organization profile GitHub shows, and nothing else | main | ~/git/companygraph/.github |

## The list is the scope

What is listed here is the family; what is not listed is outside it. An agent working in a
member reads, links and reasons within this list, and does not reach for a repository, a
directory or a file outside it on its own — not for context, not for an example, not because
it sits beside a member on the same disk. When a task needs something outside the list, the
task says so, names it, and names the one purpose it serves; that reference belongs to that
task and does not bring the thing into the family.

## 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` and companygraph.io pins
`companygraph/meta-model` by commit in `source.json`, and each builds its model pages from
that commit. blust.ch also depends on `companygraph/meta-model` by tag for the instance
parser. mental-model vendors meta-model's `core/` at a release named in its own manifest.
Every member pins this repository by tag in `conventions.json`.

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.

## Re-syncing after a release

In this order, one pull request each: design, then the three sites, then mental-model and
meta-model, then the engine, then field-notes, then the two `.github` repositories. Design
first because a site's suite runs design's checks; the models before the engine because the
sites' model pages are built from them. Nothing here opens those pull requests for you.
99 changes: 99 additions & 0 deletions conventions/WORKING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Working

How the family acts with git and GitHub. Each rule carries its reason, because a rule
without its reason is the first thing a fresh clone drops.

## Branches and commits

One branch per change, named for what it does, branched from the default branch. Nothing is
committed on the default branch directly; it is protected in every repository that has a
suite, and a ruleset that forbids a push is the only kind that survives a hurried afternoon.

A branch is deleted once its pull request is merged. The merge commit is its record; a branch
left standing is a question every reader of the branch list has to answer again.

An agent commits when the owner asks, and not on its own initiative. It proposes the message
in the git register of `WRITING.md`. The author of the commit is the person. A tool that
co-authored the change is named in a `Co-Authored-By` trailer, whichever tool it was, so the
history says who and what wrote it. “Commit and open the pull request” is a request to do
exactly that; it is not approval to merge.

## Pull requests

Every change reaches the default branch through a pull request with one green status check.
The description is the commit body reread for a reviewer who has not seen the diff.

**A pull request is merged with a merge commit, `gh pr merge --merge`, never squashed.**
GitHub re-authors a squash commit to the account that pressed the button, so a commit made
locally under the wrong identity would land on the default branch looking correct. A merge
commit preserves the author it was given, which is the point: a wrong identity surfaces
instead of being laundered.

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.

## Identity

A commit is authored by the person who made it, under the address they mean to be known by,
and the merge commit carries that address to the default branch unchanged. That is the whole
rule, and it holds for a contributor from outside exactly as it holds for the owner; nothing
here asks a contributor to be anyone but themselves.

Nothing on GitHub enforces an address, and nothing should: the ruleset rule that could, an
author-email pattern, would shut out every outside contributor. So each person's identity is
their own `git config` to keep. The owner's is `robert.blust@flatland.ch` in all three
organizations, keyed by `includeIf` blocks in `~/.gitconfig` to the directories
`~/git/robertblust/`, `~/git/guestgraph/` and `~/git/companygraph/`; a clone made anywhere
else takes the global default and gives no warning. Before the first commit in a fresh clone,
whoever you are, run `git config user.email` and read the answer.

## Releases and pins

This holds for every repository in the family that another one takes from, whatever it
provides: a design system, a model, a parser, a set of shared files.

A release is a tag and a GitHub Release with notes in the prose register: what changed for the
consumer, what breaks, how to take it. There is no publish step anywhere in the family; the tag
is the release.

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.

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

Verification is running the suite, not reading the diff. Nothing is called done, fixed or
passing until the command that proves it has run and its output has been read; a pipe into
`tail` hides an exit code, so the exit code is checked on its own.

A branch ruleset requires a status check by its job id, not by the workflow's name. Renaming
the job leaves the ruleset requiring a name that will never report again: the branch looks
protected and is not. Each repository names its required job id in its own agent file; rename
one only together with its ruleset.

Every member's ruleset requires the `conventions` job beside the job that runs its own
suite; a repository without a suite requires it alone. That job holds the vendored copy against
its release and the repository's own Markdown against `WRITING.md`, and it is the same job
everywhere because it is called from one place.

CI never writes what the repository commits. Rendered cards, exported PDFs and generated
pages are built locally and committed; CI checks that the committed copy matches what would
be built.

## Reviews

A review finding is an input to the person who merges, never a verdict. One finding per
comment, with a severity and the line it sits on. Silence is a valid answer to a finding.

## What is never written

Closed-source predecessor projects are not mentioned — in code, documentation, commits,
pull requests, issues or release notes. Secrets are never printed, not to check them and not
in a debug line; a value that reaches a transcript has to be rotated.
Loading