From b7c549a2cd1b345e7333de08ac2a07b13f69f37b Mon Sep 17 00:00:00 2001 From: Implementer Date: Tue, 6 Oct 2026 11:23:58 +0200 Subject: [PATCH] Takes conventions v1.44.0 The family resync moves robertblust/conventions in `conventions.json` from v1.43.0 to v1.44.0. The upstream notes are at https://github.com/robertblust/conventions/releases/tag/v1.44.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/PINS.md | 6 ++ conventions/conventions-check | 97 ++++++++++++++++++++++++++++++- conventions/manifest.json | 8 +-- 7 files changed, 110 insertions(+), 9 deletions(-) diff --git a/.github/workflows/conventions.yml b/.github/workflows/conventions.yml index 7748e90..c0a440d 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.43.0 + uses: robertblust/conventions/.github/workflows/check.yml@v1.44.0 diff --git a/AGENTS.md b/AGENTS.md index 8a597f5..2dae686 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 102410c..f982d37 100644 --- a/conventions.json +++ b/conventions.json @@ -1 +1 @@ -{ "repo": "robertblust/conventions", "tag": "v1.43.0" } +{ "repo": "robertblust/conventions", "tag": "v1.44.0" } diff --git a/conventions/AGENTS.md b/conventions/AGENTS.md index 9a91c96..9926069 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/PINS.md b/conventions/PINS.md index 12e2f5b..b1d8840 100644 --- a/conventions/PINS.md +++ b/conventions/PINS.md @@ -34,3 +34,9 @@ A member declares its pins in `pins.json` at its root. The report reads every pi `verify` lists the commands that run once every pin of the member has moved, and a failing one blocks the member. A check in `verify`, `npm run :check`, fails as soon as a move changes what its writer `npm run ` writes, so the report names a check whose writer no pin's `after` or `move` runs. `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. + +## Dependabot + +A pin moves when the owner decides, in the resync, together with everything its move rewrites, so no bot proposes one. A bot that does moves the line alone: a conventions workflow past `conventions.json` and its vendored copy, a package past the files its `after` writes. Where a member keeps `.github/dependabot.yml`, each of its blocks ignores every family repository it would otherwise watch, and Dependabot keeps every other dependency in view. What a block watches is read from the member's own files in the block's directory: the `uses:` lines under `.github/workflows` for `github-actions`, a `github:owner/repo#tag` value in a `package.json` for `npm`, and a `git::` module source in a `.tf` file for `terraform`. A `dependency-name` is matched as a glob, so `"*robertblust/conventions*"` covers every workflow of the repository it names. A tool whose version a conventions release pins counts the same wherever a `package.json` declares it: `markdownlint-cli2`, which `conventions-format` runs as the family's Markdown form, moves with the release and not with a bot. + +`conventions-check` holds this and names each family pin a block watches with the line to add to its ignore. A member whose `dependabot.yml` is itself vendored, as a service's is from guestgraph/service-conventions, takes the entry from where the file comes from. diff --git a/conventions/conventions-check b/conventions/conventions-check index eb4c495..a7f53fe 100755 --- a/conventions/conventions-check +++ b/conventions/conventions-check @@ -151,9 +151,104 @@ else fi fi -if [ -n "$hits" ] || [ -n "$title_hit" ]; then +# --- Dependabot leaves the family's pins alone ------------------------------------------------- +# A pin is editorial and the family resync moves it, with everything its move rewrites: a bot +# that proposes one moves a line alone, a conventions workflow past conventions.json, a package +# past what its `after` writes. So where a member keeps .github/dependabot.yml, every family +# repository a block of it would watch is ignored in that block. What a block watches is read +# from the member's own files in the block's directory: `uses:` lines under .github/workflows for +# github-actions, `github:owner/repo#` values in a package.json for npm, and `git::` module +# sources in a .tf file for terraform. A dependency-name is a glob, so "*companygraph/mcp-server*" +# covers every path of that repository. No file, no list, or no family pin is a quiet pass. +# The tools a conventions release pins count as family pins too, wherever a package.json declares +# them: their version is the family's, set in the vendored scripts, so a bump of one copy alone +# leaves the others behind. TOOLS is that list, and test/run.sh holds it to conventions-format. +BOT=$ROOT/.github/dependabot.yml +TOOLS="markdownlint-cli2" +bot_hits="" +if [ -f "$BOT" ] && [ -f "$LIST" ]; then + tab=$(printf '\t') + family=$(awk -F'|' '/^\|/ { n = $2; gsub(/^[ \t]+|[ \t]+$/, "", n); if (n ~ /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/) printf "%s ", n }' "$LIST") + # Every block as "ecosystemdirectorypattern" lines, one per ignored name, and one + # with no pattern, so a block that ignores nothing is still seen as watching its directory. + blocks=$(awk ' + function val(s) { sub(/^[^:]*:[ \t]*/, "", s); sub(/[ \t]+#.*$/, "", s); gsub(/["\047]/, "", s); sub(/[ \t]+$/, "", s); return s } + function emit( i, j, d) { + if (eco == "") return + for (i = 1; i <= nd; i++) { + d = dirs[i]; sub(/\/+$/, "", d); if (d == "") d = "/" + print eco "\t" d "\t" + for (j = 1; j <= np; j++) print eco "\t" d "\t" pats[j] + } + } + /^[ \t]*-[ \t]*package-ecosystem:/ { emit(); eco = val($0); nd = 0; np = 0; inlist = 0; next } + /^[ \t]*directory:/ { dirs[++nd] = val($0); inlist = 0; next } + /^[ \t]*directories:/ { inlist = 1; next } + inlist && /^[ \t]*-[ \t]*["\047]?\// { s = $0; sub(/^[ \t]*-[ \t]*/, "", s); gsub(/["\047]/, "", s); sub(/[ \t]+$/, "", s); dirs[++nd] = s; next } + /^[ \t]*[a-z-]+:/ { inlist = 0 } + /dependency-name:/ { pats[++np] = val($0) } + END { emit() }' "$BOT") + # Every family pin the member's files hold, as "ecosystemdirectorynamewheresuggestion". + dirof() { d=${1%/*}; d=${d#"$ROOT"}; [ -n "$d" ] || d=/; printf '%s' "$d"; } + pins=$( + for f in "$ROOT"/.github/workflows/*.yml "$ROOT"/.github/workflows/*.yaml; do + [ -f "$f" ] || continue + awk -v fam="$family" -v file="${f#"$ROOT"/}" ' + BEGIN { n = split(fam, F, " "); for (i = 1; i <= n; i++) R[F[i]] = 1 } + /^[ \t]*#/ { next } + /uses:[ \t]*[^ \t]+@/ { + s = $0; sub(/.*uses:[ \t]*/, "", s); gsub(/["\047]/, "", s); sub(/@.*/, "", s) + split(s, p, "/"); r = p[1] "/" p[2] + if (r in R) print "github-actions\t/\t" s "\t" file ":" NR "\t*" r "*" + }' "$f" + done + find "$ROOT" -name node_modules -prune -o -name .git -prune -o -type f -name package.json -print | while IFS= read -r f; do + awk -v fam="$family" -v tools="$TOOLS" -v file="${f#"$ROOT"/}" -v dir="$(dirof "$f")" ' + BEGIN { n = split(fam, F, " "); for (i = 1; i <= n; i++) R[F[i]] = 1; m = split(tools, T, " ") } + { + for (i = 1; i <= m; i++) if (index($0, "\"" T[i] "\"") && match($0, "\"" T[i] "\"[ \t]*:")) print "npm\t" dir "\t" T[i] "\t" file ":" NR "\t" T[i] + # Every "name": "github:owner/repo#tag" pair on the line, wherever it stands. + line = $0 + while (match(line, /"[^"]+"[ \t]*:[ \t]*"github:[^"#]+#/)) { + pair = substr(line, RSTART, RLENGTH); line = substr(line, RSTART + RLENGTH) + name = pair; sub(/^"/, "", name); sub(/".*/, "", name) + s = pair; sub(/.*"github:/, "", s); sub(/#$/, "", s) + if (s in R) print "npm\t" dir "\t" name "\t" file ":" NR "\t" name + } + }' "$f" + done + find "$ROOT" -name .terraform -prune -o -name .git -prune -o -type f -name '*.tf' -print | while IFS= read -r f; do + awk -v fam="$family" -v file="${f#"$ROOT"/}" -v dir="$(dirof "$f")" ' + BEGIN { n = split(fam, F, " "); for (i = 1; i <= n; i++) R[F[i]] = 1 } + /source[ \t]*=[ \t]*"git::https:\/\/github\.com\// { + s = $0; sub(/.*"git::/, "", s); sub(/[?"].*/, "", s) + r = s; sub(/^https:\/\/github\.com\//, "", r); split(r, p, "/"); r = p[1] "/" p[2]; sub(/\.git$/, "", r) + if (r in R) print "terraform\t" dir "\t" s "\t" file ":" NR "\t*" r "*" + }' "$f" + done + ) + # A pin a block watches and no pattern of that block covers is a hit. Globbing is off while the + # patterns are split, so "*owner/repo*" stays a pattern and is not expanded against the tree. + set -f + bot_hits=$(printf '%s\n' "$pins" | while IFS="$tab" read -r eco dir name where suggest; do + [ -n "$eco" ] || continue + printf '%s\n' "$blocks" | grep -q "^$eco$tab$dir$tab" || continue + covered=no + for p in $(printf '%s\n' "$blocks" | awk -F'\t' -v e="$eco" -v d="$dir" '$1 == e && $2 == d && $3 != "" { print $3 }'); do + # shellcheck disable=SC2254 # the pattern is meant to match as a glob + case "$name" in ($p) covered=yes; break ;; esac + done + if [ "$covered" = no ]; then + echo "✗ .github/dependabot.yml: the $eco block for $dir watches $name ($where), a family pin the resync moves; add - dependency-name: \"$suggest\" to its ignore" + fi + done) + set +f +fi + +if [ -n "$hits" ] || [ -n "$title_hit" ] || [ -n "$bot_hits" ]; then if [ -n "$hits" ]; then echo "$hits"; fi if [ -n "$title_hit" ]; then echo "$title_hit"; fi + if [ -n "$bot_hits" ]; then echo "$bot_hits"; fi exit 1 fi echo "✓ every Markdown file follows WRITING.md" diff --git a/conventions/manifest.json b/conventions/manifest.json index 30087c8..bc4557d 100644 --- a/conventions/manifest.json +++ b/conventions/manifest.json @@ -1,20 +1,20 @@ { "repo": "robertblust/conventions", - "tag": "v1.43.0", + "tag": "v1.44.0", "files": { "conventions/WRITING.md": "sha256:51d9a902ec3038392160df07f86aba98d34db1d4dbfaa5f4a647590eaccfb390", "conventions/WORKING.md": "sha256:3a7a6b4ab1bd23f2407fc6ada2dd0512710a49d233fa7c3342951f6fc526ab23", "conventions/REPOSITORIES.md": "sha256:461654ef06ee307d2e10daba201e97dbcad093cf6b3b7c35b6bad3627f9f4fec", - "conventions/PINS.md": "sha256:754a2f3d3f442e58a3ca05d6832c1062b92df80b21eb4d36e94c4b16fd302e10", + "conventions/PINS.md": "sha256:68df67ae820a8673d3130582aa829d4283176d3c8ec0a69adda3132a1a755b93", "conventions/WRITER.md": "sha256:bc3b92683130b75993f824f0e34073d4344846c91be2009fd90c39099fd4fd48", "conventions/TRANSLATOR.md": "sha256:3a3d2d0916d7230cbc760bced1be249b11cc516956fb41ad010b3216f23024ba", "conventions/GLOSSARY.md": "sha256:1d0fbdb8415f8a65ccee75b4a26617d7aa1a4b146988cc2a2a8155fe67c02623", "conventions/GERMAN.md": "sha256:2f4651dd74e2970f0751621bf9264441b43ee619b8a6e1373d5f3c7809581f77", "conventions/EDITOR.md": "sha256:e65d0b2893ac841faefede9b873f7e33c1fd7b02bedf97d37d5362c0e83c08ea", "conventions/BACKREADER.md": "sha256:36b12d4bee8e4fe190469e2f550d50d8a7f6dcab6bec573da17eb1c165c417cd", - "conventions/AGENTS.md": "sha256:9a6774f72436107eb94128f59a55b746fa9be987f8f6ac02d542283f32c0d03c", + "conventions/AGENTS.md": "sha256:2c71f0d0d0f6c2adfa2e343dc7b3565e84d8a8497bdbe52a6077d64a6e2bac00", "conventions/conventions-sync": "sha256:a190ff79c2d8d3f6f6d9a597e9f261654f2419d30e2ac43abf6c64ebe2c47ed9", - "conventions/conventions-check": "sha256:30b2528e84381f04fd6b06b26ef103cd2360d877639706eb0b53f362a10fc433", + "conventions/conventions-check": "sha256:fb55ca4b4cfc5caa0d346ad6c705d4402125155f95db6c974099a8112e275c69", "conventions/conventions-format": "sha256:2911a0d30dda1bc0f621522a9538b3e39704eee03458e65ba320eccbf39b5c7f", "conventions/markdown-rules.cjs": "sha256:5cbf6120bf16e358b3a4e362015d054f0cd6e47ddfb0e5fe5ac17143b2bdef26", "conventions/vscode-settings.json": "sha256:86d3c951f7c06d86ee3bc828ad6e49fac3531be98117a7a590cc75da7ba3600a",