Zpráva commitu je v tomto repozitáři hodnota s gramatikou, politikou a verdiktem, ne konvence, o které se lidem připomíná. Tato stránka říká, co to znamená, co to stojí a kde žije každá polovina.
Problém, pro který byl napsán
Dvě poloviny tohoto předmětu existovaly a nevěděly o sobě.
Repozitář mohl commit přečíst: release::commits od existence changelogu parsuje konvenční subjekty z logu, aby changelog sestavil, řeší I1305 a M000 proti tomu, co vrstva drží, a vypouští id, které se nevyřeší na nic.
Repozitář žádný nedokázal posoudit. project.conventional-commits uvedlo konvenci a jeho chování při selhání četlo „Žádný příkaz o tomto pravidle nerozhoduje; rozhoduje reviewer.“ Měření z doby psaní: z 1161 commitů bez merge přesahuje 48 předmětů šířku, kterou tento projekt deklaruje pro řádek čehokoli, a 38 nese typové slovo, které neobsahuje žádný slovník. Nikdo si nevšiml, protože nikdo nehledal.
Třetí polovina byla úplně mimo repozitář. Postup psaní commitu — odvodit scope z cesty, propojit práci, nevymýšlet číslo úkolu, oprava potřebuje test — žil v próze, kterou klient AI četl a prováděl ručně, jeden soubor na klienta, s tabulkou cesta-scope udržovanou člověkem. Workflow, jehož sémantika žije v promptu, je nezverzované, nevynutitelné, netestovatelné a pravdivé pro právě jeden nástroj.
Co existuje nyní
flowchart TD
policy["<b>.ai/repo/policy.yaml</b><br/><code>commit:</code><br/>the width, three levels"]
log["<b>git log</b><br/>at most 1500 commits"]
status["<b>git status</b><br/>this working tree"]
subgraph domain["apps/majordomus-cli/src/commit/"]
direction TB
header["<b>header</b> · the one grammar<br/><b>message</b> · header, body, trailers"]
pol["<b>policy</b> · what a commit is held to"]
scopes["<b>scopes</b> · the vocabulary, learned"]
plan["<b>plan</b> · the tree, grouped, fingerprinted"]
verdict["<b>verdict</b> · the judge"]
end
policy --> pol
log --> scopes
status --> plan
header --> verdict
pol --> verdict
scopes --> verdict
plan --> verdict
hook["<b>commit-msg hook</b><br/>one message, with its paths"]
gate["<b>scripts/ci/commit-policy</b><br/>the history, ratcheted"]
cli["<b>majordomus commit</b><br/>a person"]
mcp["<b>MCP tools + resources</b><br/>an agent"]
api["<b>GET /api/v1/commit/…</b><br/>a program"]
verdict --> hook
verdict --> gate
domain --> cli
domain --> mcp
domain --> api
classDef source fill:#eef2ff,stroke:#4f46e5,color:#1e1b4b
classDef surface fill:#ecfdf5,stroke:#059669,color:#064e3b
class policy,log,status source
class hook,gate,cli,mcp,api surface
Čtyři schopnosti, deklarované jednou v apps/majordomus-cli/src/capability/builtin/commit.rs, každá promítnutá na každý povrch, který politika expozice připouští:
| schopnost | na co odpovídá | CLI | MCP | HTTP |
|---|---|---|---|---|
commit.scopes | které rozsahy tato historie používá, jak často, o kterých adresářích | majordomus commit scopes | majordomus_commit_scopes, majordomus://commit/scopes | GET /api/v1/commit/scopes |
commit.plan | co by pracovní strom commitnul, rozdělené, s otiskem | majordomus commit plan | majordomus_commit_plan, majordomus://commit/plan | GET /api/v1/commit/plan |
commit.validate | verdikt k jedné zprávě | majordomus commit validate | majordomus_commit_validate | GET /api/v1/commit/validate |
commit.history | každý commit v rozsahu, v jednom průchodu | majordomus commit history | majordomus_commit_history | GET /api/v1/commit/history |
Nic v té tabulce se neregistruje nikde dvakrát. Název nástroje MCP, trasa a příkazová řádka jsou pole deklarace schopnosti; operace OpenAPI, payload Cockpitu, shellové doplňování a docs/generated/capabilities.md se z ní odvozují a majordomus capabilities projections --unmet shodí build, když deklarace tvrdí příkazovou řádku, kterou spustitelný soubor nemá.
Samotné provedení commitu tu záměrně není. git commit je mutace repozitáře a politika expozice (command_graph::policy) zastavuje každý strojový povrch u lokální mutace. Agent se smí ptát, čím by commit byl a zda zpráva projde; commituje člověk. Podsystém je tak tvarován: plánovač i soudce jsou čisté funkce stromu a zprávy.
Gramatika
Jeden parser, commit::CommitHeader::parse, totální a nikdy neselhávající:
feat(commit)!: the header is parsed once
^^^^ ^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^
word scope breaking subject
Changelog a soudce čtou stejný parse a liší se jen v tom, co dělat s hlavičkou, která není konvenční. Pro changelog to není selhání — commit, který nikdo nenapsal konvenčně, se přesto stal, a changelog, který by jej vynechal, by lhal o tom, co release obsahuje. Pro soudce je to jeden nález, commit.not_conventional, a jeho důsledky se záměrně nehlásí jako samostatné nálezy: ten, kdo opravuje update stuff, nepotřebuje navíc slyšet, že jeho rozsah není znám.
Slova typů jsou slova ChangeKind, z nichž už changelog vykresluje nadpisy. V politice není klíč types:, protože druhá kopie těch slov je přesně duplikace, kterou tento subsystém odstraňuje. merge je mezi nimi: tento repozitář píše merge: bring origin/master into feature/x ručně pro integrace, takže tyto předměty se drží stejné šířky a stejných odkazů jako všechny ostatní, místo aby byly vyňaty z kontroly úplně.
Politika
commit: v .ai/repo/policy.yaml, jehož tvar vlastní schéma majordomus.policy/v1 a z něhož se generuje share/allow/policy.txt:
commit:
subject_max_chars: 100
scope_unknown: warning
fix_requires_test: warning
reference_unresolved: error
breaking_unexplained: error
subject_max_chars je změřeno, ne zvoleno. Konvence gitu je 72; přes 1161 ne-merge commitů tohoto repozitáře je medián předmětu 73 znaků a 95. percentil 98. 100 je to, co tento projekt deklaruje pro řádek čehokoli, a šířka, kterou tato historie splní. Mez, kterou porušuje polovina historie, není mez — je to šum, který lidi učí kontrolu ignorovat.
Tři úrovně jsou soudy, o nichž se repozitáře mohou rozumně lišit. off je hodnota, ne absence: repozitář, který rozhodl, že se na něj kontrola nevztahuje, učinil rozhodnutí a rozhodnutí stojí za to přečíst zpět.
Nálezy
| kód | závažnost | co to znamená |
|---|---|---|
commit.not_conventional | chyba | hlavička není type(scope): subject |
commit.subject_empty | chyba | nic za dvojtečkou |
commit.subject_too_long | chyba | širší než subject_max_chars, měřeno celé |
commit.subject_trailing_period | chyba | subjekt je titulek, ne věta |
commit.unknown_scope | policy | rozsah, který historie nepoužila |
commit.breaking_unexplained | policy | ! bez těla a bez BREAKING CHANGE: |
commit.unresolved_reference | policy | id záznamu, které vrstva nedrží |
commit.fix_without_test | policy | fix bez testu mezi svými soubory |
Každé je Diagnostic — stejná závažnost, kód a zpráva, kterými hlásí zbytek tohoto spustitelného souboru, takže zjištění dorazí do terminálu, ke konzumentovi JSON i ke klientovi MCP stejně.
Slovník rozsahů, naučený, ne deklarovaný
Obvyklý způsob, jak odpovědět na „do jakého rozsahu tato změna patří?“, je tabulka vzorů cest. V den napsání je správná; pak se přidá nebo přejmenuje adresář, nebo se rozdělí podsystém, a tabulka jmenuje rozsah, který historie přestala používat před měsíci, a nic si toho nevšimne — tabulka nemá jak se mýlit.
commit.scopes místo toho čte odpověď z historie. Každý commit je vzorový příklad toho, do jakého rozsahu patří množina cest, rozhodnutý tím, kdo změnu provedl, a udržený tím, kdo ji zrevidoval. Čte se nejvýše 1500 commitů; rozsahy se počítají a spolu s nimi i asociace každého rozsahu s předponami adresářů hlubokými čtyři segmenty.
$ majordomus commit scopes
learned from 1161 commit(s), at most 1500
derive 211 site, site/data, site/data/registry
site 134 site, site/data, scripts
ci 60 docs, scripts, site
plan 44 site, site/data, site/data/generated
…
Rozsah mimo slovník je varování, nikdy zamítnutí: inference nesmí zamítnout první commit subsystému, který nikdy neviděla. Slovník nepotřebuje údržbu — subsystém commitnutý dnes je v něm dnes a ten, kterého se rok nikdo nedotkl, klesne sám.
Změřeny jsou i čtyři segmenty. Zdroj tohoto repozitáře žije na apps/majordomus-cli/src/<subsystem>/; tři segmenty končí o úroveň nad podsystémem, kde má commity tucet rozsahů a žádnou cestu nelze od jiné odlišit.
Plán
$ majordomus commit plan
branch feature/the-commit-is-an-intent · no upstream
tree 16 staged, 0 unstaged, 0 untracked
fingerprint 11a032984c6f @ 8998e11fd
commit 1 <type>(release): <subject>
why 4 file(s) under apps/majordomus-cli/src/release, which 8 prior commit(s) scoped `release`
…
O tom výstupu stojí za to říct tři věci.
Předmět je záměrně prázdný. Co změna udělala, je jediná věc, kterou žádný důkaz ve stromu nedokáže vyslovit, a plánovač, který by o tom napsal sebejistou větu, by do historie psal fikci. Rozsah a druh jsou odvozeny tam, kde je důkazy rozhodují; věta je vaše.
Seskupení je doporučení s připojeným zdůvodněním, nikdy automatické rozdělení. Cesty, které historie označuje stejným rozsahem, jsou jeden commit a zdůvodnění říká, kolik dřívějších commitů to říká. Cesty v adresářích, na které má právo více rozsahů stejně, jsou jedna skupina a zdůvodnění to říká, což je jiná situace než adresář, který historie nikdy nezařadila, a vede k jinému dalšímu kroku. Při nízké jistotě se nic nedělí.
Otisk je část pro více agentů. Plán se odvozuje ze stromu v daném okamžiku. V repozitáři, na kterém pracuje jeden člověk, je to detail; v tomto, kde několik workerů sdílí checkout a další sdílejí repozitář, plán spočítaný při jednom HEAD a provedený při jiném commituje soubory, které někdo jiný stageoval, pod zprávou o práci, která už není tím, co se změnilo. Otisk tvoří repozitář (společný adresář gitu), worktree (cesta tohoto checkoutu), HEAD a hash přes každou změnu s jejím stagem a stavem. Cokoli jedná podle plánu, jej nejprve porovná se stromem před sebou, a co ohlásí, je věta:
HEAD moved from a1b2c3d4e to e4f5a6b70 since the plan was made
the plan is of another worktree (/repo-wt/feature/x rather than /repo)
Není to zámek a nezabrání to závodu. Odmítá být tím, kdo v něm potichu prohraje.
Kde se vynucuje
| okamžik | co běží | naměřeno |
|---|---|---|
commit-msg | .githooks/commit-msg → commit.validate s připravenými cestami | 806 ms |
| kontrola | scripts/ci/commit-policy → commit.history, dvakrát: větev a historie | 1720 ms |
| CI | kontrola commit-policy v jobu structure | jako výše |
Jsou to čísla z debug sestavení na vytíženém stroji, což je nejhorší případ, s jakým se člověk setká. Tři příkazy čtoucí historii stojí asi sekundu každý — commit scopes 957 ms, commit plan 926 ms, commit history 992 ms nad 1788 commity — a nic na horké cestě z toho neplatí nic: env enter má 38 ms proti rozpočtu 750 ms, completion query 11 ms, --help 10 ms, a žádný se logu nedotýká. Historie se čte jen tam, kde už člověk čeká na git.
Hook běží v jediném okamžiku, kdy existují zpráva i soubory, proto se tam lze zeptat fix_requires_test a nikde jinde: oprava bez testu mezi soubory stojí za povšimnutí, dokud je přidání jednoho levné. Kontrola čtoucí historii žádné cesty nezná, a proto tento nález nečiní — soud, jehož důkaz chybí, se neprovede, místo aby se hádal.
Hook je registrován pod enforcement: v .ai/repo/policy.yaml jako commit-policy-on-message, což způsobí, že jej majordomus doctor sesouhlasí: cesta se musí vyřešit, být spustitelná a být volána .githooks/commit-msg, aniž by byl její exit kód spolknut.
Baseline
54 commitů této historie nesplňuje politiku. Každý je publikován, takže jejich přepsání nepřichází v úvahu. Počet žije v .ai/repo/commit-policy-baseline.txt; kontrola selže, když vzroste, a ohlásí, když klesne. Commity, které větev přidá, podléhají politice bez výhrad a bez baseline, protože hook tyto zprávy odmítl už při psaní — selhání tam znamená, že hook byl obejit nebo se politika posunula.
$ scripts/ci/commit-policy
commit-policy: branch 8998e11fd..HEAD, 0 failing
commit-policy: history 54 failing, baseline 54
Checkout bez trunku, se kterým by se porovnával, ohlásí, že nic neměřil, místo aby hlásil úspěch.
Použití
$ majordomus commit plan # what this tree would commit, and why
$ majordomus commit scopes # the vocabulary, and what it was learned from
$ majordomus commit validate <<'EOF' # one message, before writing it
feat(commit): a subject
EOF
$ majordomus commit validate --rev HEAD # the message of a commit already made
$ majordomus commit history origin/master..HEAD # this branch's commits
$ majordomus commit plan --format json # the same answer, for a program
Vše výše funguje offline. Nic v tomto subsystému nesahá do sítě a nic, co se týká vstupu do repozitáře, doplňování slova nebo odpovědi na --help, nečte historii — slovník se učí ve chvíli, kdy se commit plánuje nebo posuzuje, což je chvíle, která už čekání na git zahrnuje.
Důkaz
| co | kde |
|---|---|
| pravidlo | .ai/repo/rules/project/conventional-commits.v2.md (blokující) |
| jednotka | apps/majordomus-cli/src/commit/ — 46 testů vedle kódu, který posuzují |
| behaviorální | test/cases/274_commit_policy.sh — skutečné repozitáře, skutečné worktree, skutečný hook |
| kontrola | scripts/ci/commit-policy, deklarováno v .ai/repo/ci/gates.yaml |
| deklarace | apps/majordomus-cli/src/capability/builtin/commit.rs |
bash test/run.sh 274_commit_policy
scripts/ci/commit-policy
cargo test --manifest-path apps/majordomus-cli/Cargo.toml --lib commit::Co záměrně nedělá
Neposuzuje, zda je předmět dobrá věta, zda je změna atomická ani zda stála práce za udělání. To rozhoduje recenzent a validátor, který by předstíral opak, je validátor, kterému nikdo nevěří.
Nevytváří úkoly, nepushuje ani neotevírá pull requesty. To jsou vzdálené mutace s vlastními způsoby selhání a celá hodnota tohoto podsystému je v tom, že je čistou funkcí stromu a zprávy: funguje offline, nemůže uspět napůl a každou jeho odpověď lze přepočítat a ověřit.