Zdůvodnění
Verze 1 tohoto pravidla říkala, jak má commit zpráva vypadat, a končila větou „Toto pravidlo nerozhoduje žádný příkaz; rozhoduje recenzent.“ Ta věta je přesně selhání, kterému má zbytek správy tohoto repozitáře zabránit: uvedená invariance bez verdiktu za sebou. Také, měřitelně, nefungovala. Ze 1161 nemerge commitů je medián předmětu 73 znaků, 48 přesahuje i šířku, kterou tento projekt deklaruje pro řádek čehokoli, a 38 nese slovo typu, které neobsahoval žádný slovník — nikdo si ničeho nevšiml, protože se nic nedívalo.
Parser tu už byl. release::commits čte konvenční commity z logu, aby sestavil changelog, od té doby, co changelog existuje. Chyběla gramatika; chyběl soudce a okamžik, kdy promluví.
Druhá polovina problému byla zcela mimo repozitář. Postup psaní commitu — odvoď rozsah z cesty, propoj práci, nevymýšlej číslo úkolu, oprava potřebuje test — žil v próze, kterou AI klient četl a prováděl ručně, jeden soubor na klienta, s tabulkou cesta-rozsah udržovanou člověkem. Workflow, jehož sémantika žije v promptu, je nezverzované, nevynutitelné, netestovatelné a pravdivé pro právě jeden nástroj. Táž tabulka je odvoditelná: každý commit v historii je ukázkovým příkladem, do jakého rozsahu množina cest patří, rozhodnutým tím, kdo změnu udělal, a udržovaným tím, kdo ji zrevidoval.
Požadované chování
Jedna gramatika. commit::header parsuje předmět; changelog i soudce čtou tentýž parse. Changelog nese to, co neumí klasifikovat, místo aby to zahodil; soudce to hlásí. Dva verdikty, jeden parse — v hooku, kontrole ani skriptu není druhý regulární výraz.
Jedna politika, deklarovaná jako data. commit: v .ai/repo/policy.yaml nese šířku předmětu a tři úrovně; schéma majordomus.policy/v1 vlastní její tvar a share/allow/policy.txt se z tohoto schématu generuje. Slovník typů v politice není: je to ChangeKind, z něhož už vykresluje changelog, a druhá kopie těchto slov je duplikace, kterou toto pravidlo existuje, aby odstranilo.
Posuzuje se tam, kde jsou důkazy. Hook commit-msg se ptá commit.validate s připravenými cestami, což je jediný okamžik, kdy existuje zpráva i soubory — takže fix bez testu mezi soubory lze zachytit, dokud je jeho doplnění ještě levné. Hook žádnou logiku nenese; jen se ptá.
Slovník se pozoruje, nedeklaruje. commit.scopes se z nejvýše 1500 commitů vlastního logu učí, které scopes tento repozitář používá, jak často a o kterých adresářích. Scope mimo něj je varování, nikdy odmítnutí: odvozování nesmí odmítnout první commit subsystému, který ještě nikdy neviděl.
Nic se nevymýšlí. Id záznamu ve zprávě — I1305, M000 — se vyřeší proti tomu, co vrstva drží, a id, které se nevyřeší na nic, je chyba. Je to opak rozlišení, které changelog už dělá, a obojí čte jeden průchod, takže se nemohou rozejít v tom, jak id vypadá.
Vlastní subjekty gitu jsou vyňaty, ne chybné. Merge …, Revert "…", fixup! a squash! skládá git; validátor, který by je odmítl, by odmítal git. Subjekt merge:, který napsal člověk, není vyňat — tento repozitář je píše záměrně, takže merge je deklarované slovo typu a tyto commity podléhají téže šířce a týmž referencím jako všechny ostatní.
Existující dluh se eviduje a nesmí růst. 54 commitů v této historii nesplňuje politiku. Všechny jsou publikované, takže jejich přepisování nepřipadá v úvahu; počet žije v .ai/repo/commit-policy-baseline.txt a kontrola selže, když stoupne.
Chování při selhání
Hook commit-msg (.githooks/commit-msg, registrovaný pod enforcement v .ai/repo/policy.yaml jako commit-policy-on-message a sladěný pomocí majordomus doctor) končí s 10 a git commit přeruší, přičemž vypíše každý nález s jeho kódem.
Kontrola commit-policy (scripts/ci/commit-policy) selže s kódem 10, když commit, který tato branch přidává nad trunk, nesplňuje politiku — rovnou, bez základního stavu, protože hook tu zprávu odmítl při psaní — nebo když vzroste zaznamenaný dluh historie. Checkout bez trunku k porovnání hlásí, že nic neměřil, a nehlásí úspěch.
test/cases/274_commit_policy.sh je behaviorální polovina, v jednorázových repozitářích: že dobrá zpráva projde a špatná je odmítnuta s exit 10, že merge subject je vyňat, že vymyšlené id záznamu je odmítnuto a skutečné ne, že oprava bez testu je nahlášena a neodmítnuta, že slovník pochází z historie a následuje ji, když se historie změní, že plán je odmítnut, jakmile se pod ním pohne HEAD nebo index, že dvě worktrees jednoho repozitáře nesdílejí plán a že samotná kontrola může selhat.
Ověření
scripts/ci/commit-policy # the gate
bash test/run.sh 274_commit_policy # the behavioural case
majordomus commit validate <<'EOF' # one message
feat(commit): a subject
EOF
majordomus commit history origin/master..HEAD # this branch's commits
majordomus commit scopes # the vocabulary, and what it was learned from
majordomus commit plan # what the working tree would commit, and why
apps/majordomus-cli/src/commit/ nese unit polovinu: že gramatika projde round-trip, že neparsovaná hlavička je jeden nález, ne pět, že remíza ve slovníku je nahlášena, ne rozhodnuta, že seskupení je funkcí stromu, ne pořadí, v jakém git hlásil, a že otisk pojmenuje, co se pohnulo.