From a151b5cabfb023d3fa118f3debe7e26efe528a9b Mon Sep 17 00:00:00 2001 From: Implementer Date: Wed, 14 Oct 2026 16:28:23 +0200 Subject: [PATCH] Takes conventions v1.52.0 The family resync moves robertblust/conventions in `conventions.json` from v1.46.0 to v1.52.0. The upstream notes are at https://github.com/robertblust/conventions/releases/tag/v1.52.0. Verified: `sh conventions/conventions-sync sync` and `python3 verify.py` passed. Process: Delivery Phase: Implement Track: Code --- .github/workflows/conventions.yml | 2 +- AGENTS.md | 2 +- conventions.json | 2 +- conventions/AGENTS.md | 2 +- conventions/GERMAN.md | 2 +- conventions/GLOSSARY.md | 8 +++++ conventions/conventions-check | 52 ++++++++++++++++++++++++++----- conventions/manifest.json | 10 +++--- 8 files changed, 63 insertions(+), 17 deletions(-) diff --git a/.github/workflows/conventions.yml b/.github/workflows/conventions.yml index ef7855b..fb92e3e 100644 --- a/.github/workflows/conventions.yml +++ b/.github/workflows/conventions.yml @@ -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 diff --git a/AGENTS.md b/AGENTS.md index 9eb68f1..13e1430 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ - + 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. diff --git a/conventions.json b/conventions.json index a72f316..55c263d 100644 --- a/conventions.json +++ b/conventions.json @@ -1 +1 @@ -{ "repo": "robertblust/conventions", "tag": "v1.46.0" } +{ "repo": "robertblust/conventions", "tag": "v1.52.0" } diff --git a/conventions/AGENTS.md b/conventions/AGENTS.md index 2568465..3e85f05 100644 --- a/conventions/AGENTS.md +++ b/conventions/AGENTS.md @@ -1,4 +1,4 @@ - + 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. diff --git a/conventions/GERMAN.md b/conventions/GERMAN.md index a919913..363988f 100644 --- a/conventions/GERMAN.md +++ b/conventions/GERMAN.md @@ -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". @@ -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. | diff --git a/conventions/GLOSSARY.md b/conventions/GLOSSARY.md index ad2b657..df1c48b 100644 --- a/conventions/GLOSSARY.md +++ b/conventions/GLOSSARY.md @@ -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. | @@ -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`. | diff --git a/conventions/conventions-check b/conventions/conventions-check index a7f53fe..b00a070 100755 --- a/conventions/conventions-check +++ b/conventions/conventions-check @@ -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. # @@ -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='[^ ]—|—[^ ]' @@ -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) { @@ -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" diff --git a/conventions/manifest.json b/conventions/manifest.json index e36c1e3..56eeb18 100644 --- a/conventions/manifest.json +++ b/conventions/manifest.json @@ -1,6 +1,6 @@ { "repo": "robertblust/conventions", - "tag": "v1.46.0", + "tag": "v1.52.0", "files": { "conventions/WRITING.md": "sha256:51d9a902ec3038392160df07f86aba98d34db1d4dbfaa5f4a647590eaccfb390", "conventions/WORKING.md": "sha256:3a7a6b4ab1bd23f2407fc6ada2dd0512710a49d233fa7c3342951f6fc526ab23", @@ -8,13 +8,13 @@ "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",