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.46.0
uses: robertblust/conventions/.github/workflows/check.yml@v1.52.0
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- conventions · v1.46.0 -->
<!-- conventions · v1.52.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.
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.46.0" }
{ "repo": "robertblust/conventions", "tag": "v1.52.0" }
2 changes: 1 addition & 1 deletion conventions/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- conventions · v1.46.0 -->
<!-- conventions · v1.52.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.
Expand Down
2 changes: 1 addition & 1 deletion conventions/GERMAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ selbstständig → selbständig
Offener Kern → Open Core
Open Source → quelloffen
Takeaway → Fazit
Requirements → Anforderungen
```

A bare count of the years, «fünfundzwanzig Jahre» without «über», is refused by a rule of its own in the check, because a phrase list cannot say "unless preceded by".
Expand All @@ -60,6 +59,7 @@ Each row is a sentence the editor flagged and the owner settled, kept where it g
| «eine einzige Quelle der Wahrheit» | «eine einzige verbindliche Quelle» | The English picture read as a calque. |
| «Ein Konzept, erklärt» | «Ein Konzept im Überblick» | The label copied the English. |
| «Der Haken» | «Der Aufhänger» | The hook of a talk; «Haken» is a catch. |
| «das Anforderungs-Pack» | «das Requirements-Pack» | A pack keeps its English name, as «das Landscape-Pack» and «das Software-Pack» do; the generic noun stays «Anforderungen», and the type is `Anforderung`, the glossary's row. The refused form that turned every Requirements into Anforderungen was wrong in this sentence, so it left the fence. |
| «wie Arbeit fliesst» | «wie die Arbeit abläuft» | The English picture of flow. |
| «Menschen richten sich schneller aus» | «Menschen finden schneller eine gemeinsame Linie» | *Align* word for word. |
| «Zusammenführen ist einfach. Falsch liegen ist teuer.» | «Treffer finden ist einfach. Danebenliegen ist teuer.» | *Matching* is finding a match, not merging. |
Expand Down
8 changes: 8 additions & 0 deletions conventions/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ The German cells are inline code because the prose check reads no language and a
| MCP server | MCP server | `MCP-Server` | The server that answers over MCP; `companygraph/mcp-server` is the package. Masculine, `der MCP-Server`. |
| MCP Registry | MCP Registry | `MCP Registry` | The public index a server is published to. A proper name, English in both languages, and feminine: `in der MCP Registry`, never `im`. No page carried German for it before, which is why the gender is recorded here rather than left to the next sentence. |
| AI agent | AI agent | `KI-Agent` | The reader a model serves beside a person. Masculine, `der KI-Agent`, plural `die KI-Agenten`, the form blust.ch's ideas page already uses. |
| AI Act | AI Act | `AI Act` | The EU's regulation on artificial intelligence, whose Article 50 asks a chat to say that an AI answers. A name, English in both languages, as `MCP` and `core` are, so a reader matches the German page to the English one; masculine, `der AI Act`. Not `KI-Verordnung`, the German press's term, set aside on 2026-10-11 so both pages carry the one name. |
| read-only | read-only | `nur lesend` | Of a server that answers and never writes. Not `schreibgeschützt`, which says write-protected and describes what may be done to a file rather than what a server offers. |
| shipped | shipped | `ausgeliefert` | Of a thing released and running, as against designed or built. Not `gebaut`, which says only built, and not `fertig`, which says finished. |
| entity | entity | `Entität` | One file in the model, of whatever type. The form blust.ch's model page uses. |
Expand Down Expand Up @@ -83,6 +84,13 @@ The German cells are inline code because the prose check reads no language and a
| actor claim | actor claim | `Actor-Claim` | A claim in a JWT that names who acts. Masculine, `der Actor-Claim`. |
| steward | steward | `verantwortliche Person` | Who reviews a proposed match; plural `die Mitarbeitenden, die prüfen`. A hotel reader does not know Steward. |
| API (surface) | surface | `API` | An API as what a caller reaches, `die API`. Not `Schnittstelle`, and not `Surface`, which is a page the model is published on. |
| interface (between systems) | interface | `Schnittstelle` | What a connection between two systems goes through, as a landscape's `As` column names it: an API, a file transfer, a queue. Feminine, `die Schnittstelle`. An API a caller reaches stays `API`, the row above. |
| service (of a system) | service | `Service` | What a system exposes to other systems, the landscape pack's type. Masculine, `der Service`, as Swiss IT writes it; not `Dienst`, which beside `System` and `Plattform` reads as a Windows service or a public one. |
| landscape (the picture) | landscape | `Landschaft` | The picture of one system among those it runs on, takes its data from and gives data to, and the chat's caption for it: «Zeig mir die Landschaft von Chat server». Feminine, `die Landschaft`. Not `Systemlandschaft`, the commoner word in Swiss IT, because the caption and the pack say landscape alone; the pack itself keeps its English name, `das Landscape-Pack`. |
| organization chart | organization chart | `Organigramm` | The picture of a company's units, with who leads each and who belongs to it: «die Einheiten als Organigramm». Neuter, `das Organigramm`. The caption over the picture stays `Organisation`, and `Diagramm` is any drawn figure. |
| requirements pack | requirements pack | `Requirements-Pack` | The pack for a company that says what its products must do before it builds them, with the types use case and requirement. Neuter, `das Requirements-Pack`, the English name kept as every pack's is. |
| use case | use case | `Anwendungsfall` | The requirements pack's type: one goal for one actor and every way of reaching it, in Cockburn's form. Masculine, `der Anwendungsfall`, Cockburn's German; its scope is `der Geltungsbereich`, its trigger `der Auslöser`, an extension `die Erweiterung`, its primary actor `der Hauptakteur`. |
| requirement (the type) | requirement | `Anforderung` | The requirements pack's type: one statement a product is held to, with its kind, `die Art`, its modality, `muss`, `sollte` or `darf`, and its fit criterion, `das Erfüllungskriterium`. Feminine, `die Anforderung`; a constraint is `die Randbedingung`. |
| credit meter | credit meter | `Abrechnung nach Guthaben` | Billing against a prepaid balance. |
| backfill | backfill | `Historischer Import` | The first import of the history before live data. |
| walk-in | walk-in | `Walk-in` | A guest without a reservation, as Swiss hotels say it; plural `Walk-ins`. |
Expand Down
52 changes: 45 additions & 7 deletions conventions/conventions-check
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/sh
# conventions-check — hold a member's own Markdown to WRITING.md: American English and the
# spaced em-dash. Fenced code and inline code are not prose and are dropped before the scan;
# spaced em-dash. Fenced code and inline code are not prose and are dropped before the scan; the
# stem scan also skips frontmatter, the first H1 of a page and quoted text (see below);
# folders under "exclude" in conventions.json are someone else's prose and are not read, nor is
# anything git ignores, which the shared job never sees either.
#
Expand Down Expand Up @@ -31,6 +32,12 @@ PIN=$ROOT/conventions.json
# way but on the letter that follows, since British and American diverge right there. Case is
# folded by lowering the line before matching, not by the pattern, since IGNORECASE is a GNU
# extension awk on macOS does not have.
# What the stem scan does not read: text in backticks, text in guillemets «…», the page's H1 line
# and the YAML frontmatter. A name is the company's and is quoted, not spelled, so a German name
# that holds a stem, or a product's own feature name, is not a spelling. Only the stem scan skips
# these spans; the closed-dash check below still reads them. A quote is cut within its line; a
# pair spanning two lines is read. Curly double quotes “…” are cut like guillemets, since an
# English name is quoted the en-US way.
STEMS='organis(e|a|i)|colour|behaviour|centre|licence|recognis|modelling|catalogue|grey([^a-z]|$)|judgement|artefact|whilst|amongst|favour|honour|analys(e|ed|ing)([^a-z]|$)|optimis(e|a|i)|normalis|serialis|initialis(e|a|i)|visualis|minimis(e|a|i)|prioritis|customis|summaris|categoris|parameteris|rasteris|unrecognis|cancelled|travelled|labelled|aluminium|instalment|neighbour|totalled|defence|offence|programmes?([^a-z]|$)|fulfil([^a-z]|$)|enrol([^a-z]|$)|skilful|ageing|cosy([^a-z]|$)|focuss|cheque([^a-z]|$)|tyre([^a-z]|$)|kerb([^a-z]|$)|storey|mould|plough|draught|sceptic|generalis(e|ed|es|ing|ation|ations)|specialis(e|ed|es|ing|ation)|realis(e|ed|es|ing|ation)|materialis(e|ed|es|ing|ation)|penalis(e|ed|es|ing|ation)|singularis(e|ed|es|ing|ation)|authoris(e|ed|es|ing|ation)|emphasis(e|ed|es|ing)|criticis(e|ed|es|ing)|capitalis(e|ed|es|ing|ation)|formalis(e|ed|es|ing|ation)|finalis(e|ed|es|ing|ation)|stabilis(e|ed|es|ing|ation)|utilis(e|ed|es|ing|ation)|harmonis(e|ed|es|ing|ation)|standardis(e|ed|es|ing|ation)|synchronis(e|ed|es|ing|ation)|mobilis(e|ed|es|ing|ation)|memoris(e|ed|es|ing|ation)|apologis(e|ed|es|ing)|synthesis(e|ed|es|ing)|hypothesis(e|ed|es|ing)|paralys(e|ed|es|ing)|catalys(e|ed|es|ing)|modelled|modeller|flavour|harbour|labour|rumour|humour|vigour|rigour|armour|endeavour|savour|odour|valour|metre([^a-z]|$)|metres|litre|fibre|theatre|calibre|travelling|signalling|cancelling|fuelled|levelled|channelled|counsellor|marvellous|jewellery'
DASH='[^ ]—|—[^ ]'

Expand Down Expand Up @@ -76,8 +83,12 @@ if [ -z "$files" ]; then
fi

hits=$(printf '%s\n' "$files" | while IFS= read -r f; do
# Drop fenced code and inline code, then scan what is left of each line for a stem or a
# closed dash; the match is against the line's own content, never the path awk read it from.
# Drop fenced code, inline code and URLs, then scan what is left of each line for a stem or a
# closed dash. The stem scan alone also skips frontmatter, the page's first H1 and quoted
# text; the dash check reads those as it did before, with backticks and URLs stripped. The match is against the line's own content, never
# the path awk read it from.
# An address is fixed by the site it points to, as a name is, so it ends at a space, a closing
# parenthesis or angle bracket, or a table pipe, and a Markdown link's text is still read.
# A fence left open at end of file is a hit of its own, not a silent skip of the rest.
awk -v stems="$STEMS" -v dash="$DASH" -v root="$ROOT" '
function relpath( p, prefix) {
Expand All @@ -86,19 +97,46 @@ hits=$(printf '%s\n' "$files" | while IFS= read -r f; do
if (index(p, prefix) == 1) p = substr(p, length(prefix) + 1)
return p
}
/^```/ { fence = !fence; next }
fence { next }
# Cut the text between o and c from s. The glyph lengths are taken with length(), so the
# cut is right in a byte awk and in a character awk alike.
function cut(s, o, c, a, b, lo, lc) {
lo = length(o); lc = length(c)
while ((a = index(s, o)) > 0 && (b = index(substr(s, a + lo), c)) > 0)
s = substr(s, 1, a - 1) substr(s, a + lo + b - 1 + lc)
return s
}
# A byte order mark is dropped by its bytes in a string, which every awk reads alike.
FNR == 1 && index($0, "\357\273\277") == 1 { $0 = substr($0, length("\357\273\277") + 1) }
FNR == 1 && /^---[ \t\r]*$/ { fm = 1; next }
fm && /^---[ \t\r]*$/ { fm = 0; next }
# A frontmatter that is never closed ends at the first line that is none of these: blank,
# indented, a "- item" at column 0, a "#" comment or a key line. A stray opener cannot hide
# the whole page, though a malformed page whose --- is never closed may still hide a line
# that looks like a key.
fm && !/^[ \t\r]*$/ && !/^[ \t]/ && !/^- / && !/^#/ && !/^["\047]?[$@A-Za-z0-9_][A-Za-z0-9_ .-]*["\047]?:( |$)/ { fm = 0 }
!fm && /^```/ { fence = !fence; next }
fence { next }
{
line = $0
sub(/\r$/, "", line)
raw = line
# Only the stem scan skips frontmatter, the first H1 of the page and quoted text; the
# dash check below reads the line as it did before.
skip = fm
if (!fm && !h1 && line ~ /^# /) { h1 = 1; skip = 1 }
gsub(/`[^`]*`/, "", line)
line = cut(line, "«", "»")
line = cut(line, "“", "”")
gsub(/https?:\/\/[^ )>|]*/, "", line)
low = tolower(line)
if (match(low, stems)) {
if (!skip && match(low, stems)) {
w = substr(low, RSTART, RLENGTH)
sub(/[^a-z]$/, "", w)
printf "✗ %s:%d: %s\n", relpath(), FNR, w
}
if (match(line, dash)) printf "✗ %s:%d: closed em-dash\n", relpath(), FNR
gsub(/`[^`]*`/, "", raw)
gsub(/https?:\/\/[^ )>|]*/, "", raw)
if (match(raw, dash)) printf "✗ %s:%d: closed em-dash\n", relpath(), FNR
}
END { if (fence) printf "✗ %s:%d: unclosed code fence\n", relpath(), FNR }
' "$f"
Expand Down
10 changes: 5 additions & 5 deletions conventions/manifest.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"repo": "robertblust/conventions",
"tag": "v1.46.0",
"tag": "v1.52.0",
"files": {
"conventions/WRITING.md": "sha256:51d9a902ec3038392160df07f86aba98d34db1d4dbfaa5f4a647590eaccfb390",
"conventions/WORKING.md": "sha256:3a7a6b4ab1bd23f2407fc6ada2dd0512710a49d233fa7c3342951f6fc526ab23",
"conventions/REPOSITORIES.md": "sha256:461654ef06ee307d2e10daba201e97dbcad093cf6b3b7c35b6bad3627f9f4fec",
"conventions/PINS.md": "sha256:68df67ae820a8673d3130582aa829d4283176d3c8ec0a69adda3132a1a755b93",
"conventions/WRITER.md": "sha256:bc3b92683130b75993f824f0e34073d4344846c91be2009fd90c39099fd4fd48",
"conventions/TRANSLATOR.md": "sha256:3a3d2d0916d7230cbc760bced1be249b11cc516956fb41ad010b3216f23024ba",
"conventions/GLOSSARY.md": "sha256:429733de608d68142cc261dc48aa4fdf0dde3dd002902f0af413a63e6467f9b9",
"conventions/GERMAN.md": "sha256:e8b311ed4b48cc2ded964bbaa0cfcf70178cdbd8072cd467b586c2bfa0cb949b",
"conventions/GLOSSARY.md": "sha256:f29b2a31e171f9666e70e3c0ab3290876fcd90846f99c86a12a6ac56c16acdf1",
"conventions/GERMAN.md": "sha256:6a0ab79fd52f93d1361aceb83a1771ce6448124d5ea707af3d71baf9efe35e11",
"conventions/EDITOR.md": "sha256:e65d0b2893ac841faefede9b873f7e33c1fd7b02bedf97d37d5362c0e83c08ea",
"conventions/BACKREADER.md": "sha256:36b12d4bee8e4fe190469e2f550d50d8a7f6dcab6bec573da17eb1c165c417cd",
"conventions/AGENTS.md": "sha256:861c1ec80624afb6e87a993922a18ea101cc718ace94e4237c7da89bf6367f2b",
"conventions/AGENTS.md": "sha256:45567ff4468265436d3eacd590b750235906ccda8b03a9d75105e8e3907d8584",
"conventions/conventions-sync": "sha256:a190ff79c2d8d3f6f6d9a597e9f261654f2419d30e2ac43abf6c64ebe2c47ed9",
"conventions/conventions-check": "sha256:fb55ca4b4cfc5caa0d346ad6c705d4402125155f95db6c974099a8112e275c69",
"conventions/conventions-check": "sha256:4a633cc53579f4577939702350c439bbb58ebed4ccbf6736d94c01e5fc160147",
"conventions/conventions-format": "sha256:2911a0d30dda1bc0f621522a9538b3e39704eee03458e65ba320eccbf39b5c7f",
"conventions/markdown-rules.cjs": "sha256:5cbf6120bf16e358b3a4e362015d054f0cd6e47ddfb0e5fe5ac17143b2bdef26",
"conventions/vscode-settings.json": "sha256:86d3c951f7c06d86ee3bc828ad6e49fac3531be98117a7a590cc75da7ba3600a",
Expand Down
Loading