Kam patří každý linked git worktree tohoto repozitáře, jak se to rozhoduje a co nástroj dělá s tím, který je jinde. Chování tak, jak je implementováno a testováno; kde se tento dokument a spustitelný soubor rozcházejí, je chybný dokument a mění se ve stejném commitu. Rozhodnutí je ADR 21; pravidlo je project.worktree-topology.
~/dev/prismatic-majordomus the primary checkout: hosts the trunk, never moves
~/dev/prismatic-majordomus-wt/ the container: the checkout's sibling, name + "-wt"
~/dev/prismatic-majordomus-wt/feature/x the one worktree of branch feature/x
~/dev/prismatic-majordomus-wt/fix/a/b the one worktree of branch fix/a/b — hierarchy kept
Název větve je relativní cesta. Nic se neplošťuje (feature-x), nehashuje (feature-x-a93f) ani nečísluje (worktree-1) a nic se nikde neregistruje: pro daný repozitář a větev existuje právě jedna cesta a každý povrch ji odvozuje stejně.
Proč
Na jednom repozitáři pracuje současně několik relací — lidí i agentů — a každá potřebuje vlastní checkout. Mechanismem je linked worktree gitu a git bere cíl jako argument, takže o jeho umístění rozhoduje každý volající. Třiapadesát z nich se zde rozhodlo jinak: ploché sourozenecké adresáře pojmenované po ničem, scratch adresáře relací, jeden uvnitř primárního checkoutu, jeden zabírající vlastní cestu kontejneru. Cenou nebyla neuklizenost. Bylo to, že „kde je větev X“ a „kam patří nový“ nemělo odpověď, která by nezahrnovala hledání, hádání nebo vymýšlení, a relace, která musí cestu vymyslet, nechá svou práci tam, kde ji další relace hledat nebude.
Topologie dává odpověď jednou: izolace funkcí bez rozhodování, paralelní agenti, kteří nemohou kolidovat na cestě, předvídatelné mapování relace → větev → worktree, které předávka umí pojmenovat a obnovená relace se podle něj umí vrátit, úklid, který je odvozený seznam, ne archeologie, a topologie, kterou může ukázat každý povrch, protože ji každý počítá stejně.
Jak se cesta odvozuje
git rev-parse --git-common-dir which repository this is, from any directory of any worktree
git worktree list --porcelain its worktrees; the first record is the primary checkout
parent(primary) / name(primary) + "-wt" the container
container / branch name the worktree, one directory per component
Druhý řádek je ten, který se snadno pokazí. git rev-parse --show-toplevel odpovídá na aktuální worktree, takže odvození kontejneru z něj uvnitř linked worktree by vytvořilo …-wt/feature/x-wt, kontejner pro každý worktree, zanořující se donekonečna. Primární checkout se proto čte ze sdílených metadat gitu, takže odpověď je stejná z primárního checkoutu, z jeho podadresáře, z linked worktree i ze čtyř adresářů hluboko uvnitř něj.
Název větve je vstup odvozený ze souborového systému a zachází se s ním tak: název se validuje podle vlastních pravidel gitu pro reference (žádné .., žádná komponenta začínající ., žádná končící .lock, žádné řídicí znaky, žádný z ~ ^ : ? * [ \, žádné @{, žádné počáteční -), každá komponenta platného názvu je běžný název adresáře a odvozená cesta se při každém volání dokazuje jako striktně pod kontejnerem. Komponenty v Unicode jsou běžné názvy adresářů. Dva názvy lišící se jen velikostí písmen se ohlásí (worktree.case_collision), protože na souborovém systému nerozlišujícím velikost je obě obsahuje jeden adresář.
Trunk se objevuje, nikdy není napevno zadán: HEAD vzdáleného repozitáře (refs/remotes/origin/HEAD), pak init.defaultBranch, když ta větev existuje, pak kterákoli z main a master existuje sama, pak vlastní větev primárního checkoutu. Hlásí se, která rozhodla.
-wt se píše jednou, jako CONTAINER_SUFFIX v apps/majordomus-cli/src/worktree/path.rs. Žádná konfigurace neexistuje: kontejner, který lze konfigurovat, je kontejner, o kterém by se dva checkouty jednoho repozitáře mohly rozejít.
Architektura
flowchart TD git["git<br>common dir · worktree list · for-each-ref · status"] identity["RepositoryIdentity<br>primary, current, container, trunk —<br>from anywhere inside"] path["path::expected_path()<br>container / branch, proved to stay<br>inside the container"] service["WorktreeService<br>topology · status · guard · inspect<br>create · migrate · repair · remove"] cli["CLI"] registry["capability registry"] hook["git hook"] mcp["MCP tools + majordomus://worktrees<br>/api/v1/worktrees* · OpenAPI · Swagger UI"] cockpit["Cockpit /cockpit/worktrees · docs/generated"] git --> identity identity --> path path --> service service --> cli service --> registry service --> hook registry --- mcp registry --- cockpit
Business logika existuje jednou, v apps/majordomus-cli/src/worktree/. Každý povrch je adaptér, který vykresluje nebo promítá, co služba odpověděla; žádný neodvozuje cestu, nerozhoduje o postavení ani neposuzuje, zda je tah bezpečný. Pět otázek jen pro čtení jsou schopnosti registru (worktree.topology, worktree.status, worktree.inspect, worktree.migration_plan, worktree.uncommitted), což je staví na MCP, HTTP, OpenAPI, Swagger UI, do Cockpitu a vygenerované reference, aniž by kterýkoli z nich nesl vlastní trasu nebo schéma. Vytváření, migrace, oprava a odebírání jsou operace téže služby na příkazové řádce. Schopnost, která zapisuje do repozitáře, musí deklarovat efekt repository_mutation, být příkazem a být dosažena přes POST — unit test jejich množinu připíná, takže přidání další je viditelné rozhodnutí — a přesouvání worktrees na disku není něco, co sdílený server MCP přebírá; Cockpit jmenuje přesný příkaz pro každý z nich.
Postavení a diagnostika
Každý registrovaný worktree má jeden stav:
| postavení | význam |
|---|---|
primary | hlavní worktree; vyňato z pravidla cest, vázáno pravidlem trunku |
canonical | linked worktree přesně na cestě své větve |
misplaced | linked worktree jinde; migrace jej přivede domů |
detached | žádná větev, tedy žádná kanonická cesta; nikdy se nepřesouvá |
ephemeral | pracovní checkout relace pod deklarovaným pracovním kořenem — dočasným adresářem, <primary>/.claude/worktrees/ pro Claude Code, adresářem vláken bb pod jeho datovým adresářem — jak je deklaruje share/providers.yaml; hlásí se, odmítá se pro commity, přesouvá se jen na požádání |
missing | registrace, jejíž adresář zmizel; repair ji odstraní |
a každá podmínka má stabilní kód, na každém povrchu stejný, každá s nápravou:
| kód | závažnost | co to znamená |
|---|---|---|
worktree.path_mismatch | chyba | linked worktree není na kanonické cestě své větve |
worktree.container_occupied | chyba | worktree zabírá samotnou cestu kontejneru |
worktree.nested | varování | leží uvnitř primárního checkoutu nebo jiného worktree |
worktree.destination_conflict | chyba | kanonická cesta je obsazena něčím jiným — registrovaným worktree jiné větve, cizím checkoutem, adresářem, souborem, symbolickým odkazem |
worktree.missing / worktree.stale_registration | varování | adresář je pryč |
worktree.branch_already_checked_out | chyba | větev je checkoutnutá jinde než na své kanonické cestě |
worktree.detached | info | žádná větev |
worktree.ephemeral | varování | odkládací checkout relace držící větev |
worktree.primary_on_non_trunk | chyba | primární checkout drží větev, která není trunk |
worktree.trunk_in_linked_worktree | varování | trunk je checkoutnutý v propojeném worktree |
worktree.path_escape / worktree.invalid_branch_name | chyba | z názvu nelze odvodit cestu |
worktree.locked | varování | git ho nepřesune, dokud se neodemkne |
worktree.migration_verification_failed | chyba | otisk po přesunu se liší od otisku před ním |
worktree.trunk_unknown | varování | nic neřeklo, která větev je trunk |
worktree.case_collision | varování | dva názvy větví odvozují jeden adresář na souborovém systému nerozlišujícím velikost písmen |
worktree.cross_device | info | přesun byl proveden kopií přes souborové systémy |
Topologie je platná, pokud nestojí žádná diagnostika na úrovni chyby. Odpojené a efemérní worktree ji neznehodnocují; guard přesto odmítne commit z efemérního.
Které adresáře činí checkout efemérním, jsou data, ne seznam ve spustitelném souboru: share/providers.yaml deklaruje vlastní scratch kořeny nástroje (dočasný adresář) a kořeny každého providera — <primary>/.claude/worktrees pro Claude Code a pro bb, orchestrátor, který drží worktree na vlákno, ${BB_DATA_DIR:-~/.bb}/plugins/environment-git-worktree/host-data/worktrees. Kořen se rozbalí vůči primárnímu checkoutu a prostředí; kořen, pod nímž žije sám primární checkout, se přeskočí, takže fixture testu v dočasném adresáři má běžné sourozence. Diagnostika jmenuje providera, jehož kořen odpovídal. Cizí checkout se nikdy nemigruje bez vyžádání a nástroj jej nikdy neuklízí: náprava, kterou guard nabízí, je pokračovat v kanonickém worktree (ADR 0024).
Příkazy
majordomus worktree # where am I, and is that where I belong (exit 10 if not)
majordomus worktree create feature/improve-cli # start: the path is derived, never given
cd "$(majordomus worktree path feature/improve-cli)"
majordomus worktree list # every worktree with its standing
majordomus worktree topology --format json # the whole document the API and the Cockpit render
majordomus worktree doctor # every diagnostic with its code and remedy
majordomus worktree migrate --plan # what would move; changes nothing
majordomus worktree migrate # move, verify, report
majordomus worktree cleanup # what is merged and clean; deletes nothing
majordomus worktree uncommitted # uncommitted work in linked worktrees, and what holds it
majordomus worktree cleanup --remove # removes those worktrees, refusing what it cannot prove| command | co dělá | exit 10, když |
|---|---|---|
worktree / worktree status | tento worktree: větev, postavení, kanonická cesta, nezacommitovaná práce, upstream, úkol | tento worktree je na špatném místě |
worktree list | každý worktree, jeden řádek na worktree | topologie má chybu |
worktree topology | repozitář, kontejner, trunk, worktrees, větve bez worktree, diagnostika, součty | topologie má chybu |
worktree root | kontejner, jedna cesta | |
worktree path <branch> | kanonická cesta větve, jedna cesta; větev nemusí existovat | název je neplatný |
worktree inspect <branch> | kanonická cesta, zda větev existuje, co cestu zabírá, co brání | |
worktree create <branch> [--base REF] [--issue ID] | kanonický worktree; větev, kterou má vzdálený repozitář trunku, se převezme a sleduje ji, nová začíná na právě stáhnutém trunku vzdáleného repozitáře a řádek jmenuje přesný commit | obsazeno, checkoutnuto jinde, neplatné |
worktree ensure <branch> | totéž, ale místo odmítnutí vrátí existující kanonický worktree | checkoutnuto jinde |
worktree migrate [--plan] [--only B]… [--allow-copy] [--include-ephemeral] | přivést zbloudilé worktrees domů, ověřeno otiskem | krok byl zablokován nebo selhal |
worktree validate / worktree doctor | chyby, nebo každá diagnostika | chyba trvá |
worktree guard [--quiet] | smí odsud commit pokračovat | ne |
worktree repair [--dry-run] | zahodí zastaralé registrace, opraví odkazy gitu; nemaže žádný adresář | |
worktree remove <branch|path> [--force] | odstranit jeden linked worktree; nikdy primární, nikdy větev, nikdy špinavou práci bez vynucení | odmítnuto |
worktree cleanup | větve sloučené do trunku nebo do sledované vzdálené větve, kterou trunk následuje, jejichž worktree je čisté nebo chybí, s příkazy, které by je odstranily. Primární checkout, který nikdo nepullnul, neskrývá to, co už přistálo. | |
worktree cleanup --remove | odstraní ty worktrees, každý předtím znovu změří; odmítne větev předbíhající svůj remote, worktree, ve kterém se pracuje, necommitnutou práci nebo měření, které nešlo provést | |
worktree uncommitted [--older-than D] | každý linked worktree s necommitnutou prací: cesty, stáří nejstarší změny, co je drží, abandoned či ne, a příkaz, který by je dostal na vzdálenou ref; nic nezapisuje | |
worktree trunk | jak daleko jsou tento checkout a lokální trunk od vzdáleného trunku, podle posledního fetche, posuzováno vůči checkout.trunk_distance, s příkazy, které jej zaktualizují; nic nestahuje | checkout je zastaralý |
worktree branches [--without-worktree] | každá lokální větev, jedna na řádek |
wt je alias pro worktree. --format json je dostupný všude a je to tatáž typovaná odpověď, kterou vykresluje lidská forma. Selektor pro remove je přesný — název větve nebo cesta — a nic se neshoduje podle prefixu ani podobnosti. Neexistuje worktree cd: podřízený proces nemůže změnit adresář nadřazeného shellu, takže path jeden vypíše a zbytek udělá shell.
worktree create --issue I0042 jmenuje větev feature/I0042-<slug> z vlastního záznamu úkolu a topologie čte úkol zpět z jakékoli větve, která nese id úkolu jako složku cesty (feature/I0042-live-page → I0042). Nic se neodvozuje z podobnosti: feature/I00420-x nejmenuje žádný úkol.
Shellové doplňování argumentů větví čte živou množinu, ne seznam:
# zsh
_mj_wt_branches() { compadd -- ${(f)"$(majordomus worktree branches 2>/dev/null)"} }
compdef '_arguments "1:sub:(status list topology root path inspect create ensure migrate validate doctor guard repair remove cleanup uncommitted trunk branches)" "2:branch:_mj_wt_branches"' majordomus-worktreeŽivotní cyklus
flowchart TD issue["issue / task<br>majordomus plan next"] branch["branch<br>feature/<ID>-<slug>, or any name git accepts"] worktree["canonical worktree<br>majordomus worktree create <branch><br>→ <repo>-wt/<branch>"] session["session / agent<br>majordomus context, start, checkpoint, handover<br>— in that worktree"] commit["commit / PR<br>the pre-commit hook asks the guard;<br>push and open the PR from there"] cleanup["cleanup<br>majordomus worktree cleanup → worktree remove,<br>git branch -d, by a person"] issue --> branch branch --> worktree worktree --> session session --> commit commit --> cleanup
Odkazováno z tohoto diagramu (2)
Předávka zaznamenává větev a worktree; relace obnovená jinde odvodí worktree z větve, místo aby věřila zaznamenané cestě, protože cesta je pomíjivá a větev ne.
Kde nová větev začíná
Primární checkout hostí trunk a nic jej neposouvá: 2026-10-16 byl jeho lokální master o 779 commitů za origin/master. Větev z něj odříznutá nenesla nic z té práce, report říkal (new, from master), jako by to bylo aktuální, a každý pracovník resetoval nový worktree na origin/master ručně. Větev, která existovala jen na remote, byla horší: začala se znovu z toho zastaralého master místo aby se převzala.
Proto create a ensure, pro větev, která zde neexistuje, se ptají vzdáleného úložiště trunku — vzdáleného, jehož HEAD pojmenovává trunk, jinak toho, které pojmenovává branch.<trunk>.remote — dvěma tichými, omezenými fetchi (--no-tags, bez promptu, zabité po 60 sekundách) trunku a větve do jejich remote-tracking refů. Lokální trunk se nikdy nepřesouvá.
| remote | větev začíná | report |
|---|---|---|
| má větev | kde stojí větev vzdáleného repozitáře, sledující ji | branch fix/x (from origin/fix/x at <sha>, tracking it) |
| odpovídá, takovou větev nemá | na trunku vzdáleného serveru tak, jak byl právě stažen, bez sledování čehokoli do prvního pushe | branch feature/x (new, from origin/master at <sha>) |
| nelze přečíst | na lokálním trunku, nebo na větvi remote, jak byla naposledy stažena, pokud je známa | stejný řádek z master a řádek warning jmenující remote, důvod gitu, o kolik commitů je lokální trunk za trunkem remote podle posledního fetche a reset, který spustit, až odpoví |
| žádný není | na lokálním trunku; nic se nestahuje a nic se neříká | branch feature/x (new, from master at <sha>) |
--base REF se bere tak, jak je zadáno, a nikdy se nestahuje. JSON nese rozhodnutí jako start (from: remote_trunk, remote_branch, local_trunk, given, head, se vzdáleným repozitářem, důvodem a vzdáleností) a commit přečtený zpět z nového worktree jako start_commit. Je to jediné místo, kde příkaz worktree sahá do sítě; každé jiné čtení a rozhodnutí funguje offline.
.envrc nové cesty
direnv schvaluje .envrc podle cesty a obsahu, takže vlastní .envrc repozitáře na cestě, která před chvílí neexistovala, je pro direnv soubor, který nikdy neviděl, a první cd do nového worktree byl zodpovězen direnv: error .envrc is blocked. Naměřeno 2026-09-09: z 37 worktree, které nesly .envrc, jich bylo 36 zablokováno a jediný schválený byl primární checkout. create, ensure a každý přesunutý krok migrate proto přenášejí schválení primárního checkoutu na novou cestu — direnv allow na .envrc worktree v okamžiku, kdy cesta existuje — a hlásí, co se stalo, na řádku envrc a v poli envrc JSON.
Přenášeno, ne uděleno: .envrc worktree musí být bajt po bajtu shodné s primárním checkoutem a primární checkout musí být sám schválen, jinak není schváleno nic a report říká které (differs, not_approved_in_primary). Větev s vlastním .envrc je soubor, který člověk nečetl, a schvalovat ho za něj je jediná věc, které má direnv allow zabránit. Bez direnv v PATH je výsledek direnv_absent a nic není blokováno, protože by soubor nic nenačetlo.
Strojově lokální .envrc.local primárního checkoutu se sdílí ve stejném okamžiku. .envrc repozitáře načítá .envrc.local z adresáře, do kterého se vstoupí, a pravidlo adaptéru drží tento soubor bez programu, který by vyhledání primárního checkoutu potřebovalo. Nový worktree proto dostane symlink na .envrc.local primárního checkoutu a vlastní exporty člověka, včetně tajemství uložených v keychainu, jako je OPENAI_API_KEY, se tam načtou také, z jednoho souboru. Odkaz se vytvoří jen tam, kde git název ignoruje (jinak not_ignored), a worktree s vlastním .envrc.local si ho ponechá (own_kept).
Hooky jeho vlastního stromu
Repozitář, který sleduje své háčky (.githooks/, jmenované v core.hooksPath), chce, aby se každý commit posuzoval háčky stromu, ve kterém vzniká. core.hooksPath žije v konfiguraci, kterou sdílí každý worktree, takže jakmile obsahuje absolutní cestu — <primary>/.githooks, jako tady —, každý propojený worktree spouští háčky primárního checkoutu. Dne 2026-10-14 byl primární checkout o tři dny pozadu a doctor, které jeho háčky spouštěly, odmítlo každý commit aktuálního masteru v každém worktree (FAIL wiring ssh-permissions-on-commit … not invoked by <primary>/.githooks/pre-commit); I2718.
Každý propojený worktree má proto vlastní cestu k hookům. create, ensure, každý přesunutý krok migrate a repair čtou politiku repozitáře z efektivního core.hooksPath primárního checkoutu, najdou adresář, který uvádí, uvnitř worktree, a když by git spustil hooky jiného checkoutu, svážou worktree s jeho vlastními přes konfiguraci gitu pro jednotlivé worktree:
git config core.repositoryformatversion 1 # only when it was 0
git config extensions.worktreeConfig true # only when it was off
git -C <worktree> config --worktree core.hooksPath <worktree>/.githooks
Hodnota je absolutní, takže přesunuté worktree se znovu naváže na své nové umístění. Zpráva říká, co se stalo, na řádku hooks a v poli hooks JSON (set, own, not_configured, shared, untracked, failed). Nic se nevymýšlí: repozitář bez core.hooksPath ponechává vlastní adresář hooků gitu, adresář hooků mimo primární checkout je sdílen záměrně a ponechán beze změny a strom, který adresář nenese, si ponechá primární místo žádného. Relativní .githooks git už řeší proti vrcholu každého worktree a hlásí se jako own. Primární checkout se nikdy nemění.
doctor, spuštěné v linked worktree, které git posílá do hooků jiného checkoutu, selže s wiring hooks-path a přesným příkazem, který to opraví, a posuzuje zapojení vlastního adresáře worktree — co bude spouštět po opravě — ne to zastaralé.
Poslední krok je ten, za kterým žádný mechanismus nestojí. Vytvořit worktree je jeden příkaz; odstranit ho je rozhodnutí, k němuž nikoho nic nenutí, a cleanup záměrně nic nemaže — jmenuje, co je sloučeno a čisté, a akt nechává na člověku, protože nástroj, který by odstranil worktree na základě sloučené větve, by nakonec odstranil práci, kterou někdo nedokončil. Cena je skutečná a zaplatila se tu 2026-09-08, osmnáct hodin poté, co topologie přistála: patnáct worktree mezi sebou drželo 104 GB adresářů Rustu target/, devět z nich s větvemi už sloučenými do trunku, a svazek klesl na 124 MiB volného místa. Nic v topologii nic neuvolňuje a pro kontejner není deklarován žádný rozpočet, takže „stůj“ říká disk — a říká to slovníkem, který zrovna použije další selhávající subsystém. Tři řekly, že jejich obsah je špatně. Proč je mechanismus nepřítomný, a ne chybějící, viz ADR 0021.
Pokud držíte worktree, jehož větev přistála, worktree cleanup vám to řekne; git worktree remove <path> a git branch -d <branch> jsou ty dva příkazy a adresář target/ uvnitř je obvykle většinou toho, co stojí.
Vynucování
Vrstvené a poctivé k tomu, co každá vrstva dokáže:
| vrstva | co dělá |
|---|---|
| doktrína | ADR 21 a project.worktree-topology říkají, jaké je pravidlo |
| agent se bootstrapuje | AGENTS.md, CLAUDE.md a další soubory poskytovatelů, generované ze šablon, říkají každému pracovníkovi, aby před implementací ustavil topologii a nesoulad opravil nástrojem |
| workflow | .ai/repo/workflows/task-lifecycle.md začíná na worktree status |
| příkazová řádka | worktree create je způsob, jakým worktree větve vzniká; cesta nikdy není argumentem |
| git hook | .githooks/pre-commit se ptá majordomus worktree guard; feature branch se commituje jen z jejího kanonického worktree, primární checkout jen na trunku |
| kontrola zapojení | seznam enforcement politiky deklaruje stráž, takže majordomus doctor dokazuje, že se jí hook ptá |
| záznam repozitáře | .envrc vykresluje prostředí repozitáře při vstupu přes bin/majordomus-env, které nikdy nesestavuje (ENVIRONMENT.md); stav worktree v tom banneru je cíl |
| povrchy | topologie je na příkazové řádce, v MCP, HTTP, Swagger UI i v Cockpitu, takže špatná je vidět všude |
| testy | sada cratu dokazuje odvození, bezpečnost a migraci proti skutečnému gitu; shell případ dokazuje zapojení |
| kontrola CI | scripts/ci/worktree-check drží konstantu, hook, politiku, dokumenty a případ pohromadě |
Git hook se obchází pomocí --no-verify a CI runner nevidí lokální souborový systém přispěvatele; kontrola proto měří strojové vybavení pravidla, ne adresáře tohoto stroje. V rámci workflow řízených Majordomem je špatný worktree odmítnut; mimo ně je viditelný na každém povrchu.
Migrace
worktree migrate --plan uvádí každý špatně umístěný worktree s větví: kde je, kam patří, jak by se přesunul, nepotvrzenou práci, která se přesune s ním, a co mu brání. Obyvatel kontejneru, pokud nějaký je, je první — každý jiný cíl leží uvnitř kontejneru, a dokud worktree kontejnerem je, vznikly by tyto cíle uvnitř tohoto worktree. Výjimky — odpojené worktree, zastaralé registrace, dočasné pracovní checkouty, primární checkout mimo trunk — jsou uvedeny s tím, co s každou z nich udělat.
worktree migrate přepočítá plán pod zámkem repozitáře a pro každý pohyblivý krok:
- pořídí otisk worktree: větev, HEAD, index (každá sledovaná cesta s režimem, blobem a stagem), staged diff, unstaged diff, každý nesledovaný soubor s velikostí a hashem obsahu, každá ignorovaná položka s cestou a velikostí;
- požádá git o přesun —
git worktree move, jedno přejmenování, včetně špinavého stavu; obsah kontejneru se vyvede do pracovní cesty a pak dovnitř; přesun mezi souborovými systémy se odmítne, pokud není--allow-copy, a pak se provede kopií,git worktree repair, ověřením proti manifestu každé položky stromu a teprve potom odstraněním originálu; - znovu vezme otisk v cíli, ověří, že jej tam git registruje, a krok hlásí jako
movedjen když se oba otisky rovnají — jinakfailed, s tím, co se liší, a nic dalšího se nedotkne; - pojmenovává relativní symbolické odkazy, které se z nové cesty už nerozřeší (
node_modules -> ../sibling/node_modules), které přesun nezměnil a nepřepisuje.
Nic se neresetuje, nestashuje, nečistí, nečekautuje ani nemaže. Zamčený worktree je blokován, dokud není odemčen. Cíl, který existuje — registrovaný worktree jiné větve, cizí git checkout, adresář prázdný či nikoli, soubor, odkaz — je konflikt pojmenovaný druhem a nikdy se nepřepíše. Pokud byl příkaz spuštěn z worktree, který se přesunul, report říká, kam cd.
Způsoby selhání a obnova
| situace | co vidíte | co dělat |
|---|---|---|
| špinavý worktree na špatném místě | worktree.path_mismatch, počty špinavých u kroku plánu | majordomus worktree migrate; práce se přesouvá s ním |
| použije se kanonická cesta | worktree.destination_conflict jmenující, co tam je | odsuňte to ručně stranou; nic to nepřepíše |
| odpojený worktree | worktree.detached, postavení detached | git switch -c <branch> tam, pak migrovat; nebo to nechat |
| chybějící adresář | worktree.missing / worktree.stale_registration | majordomus worktree repair |
| zamčený worktree | worktree.locked, krok blokován | git worktree unlock <path>, poté migrovat |
| špatná větev ve worktree | worktree.branch_already_checked_out při vytvoření | migrujte worktree, který jej drží, nebo pracujte tam |
direnv: error .envrc is blocked ve worktree | envrc differs … nebo envrc the primary checkout's is not approved … ve zprávě o vytvoření či migraci | přečtěte soubor, poté tam direnv allow; nejprve schvalte primární checkout, když report pojmenovává právě ten. Worktree starší než report: schválení nyní nese majordomus worktree ensure <branch> |
| commit ve worktree posuzovaném zastaralými hooky | FAIL wiring hooks-path … runs <primary>/.githooks, not its own z doctor | příkaz, který vypisuje doctor, nebo majordomus worktree repair, který váže každý propojený worktree na jeho vlastní .githooks |
| primární checkout na feature větvi | worktree.primary_on_non_trunk; guard odmítne | když je čistý, git switch <trunk>; pak worktree create <branch> |
| nový worktree založený pozadu za vzdáleným serverem | warning could not fetch <trunk> from <remote> (…): started from the local <trunk>, N commits behind … ve zprávě o vytvoření | jakmile vzdálený server odpoví, git fetch a git reset --hard, které varování vypíše, v novém worktree před jeho prvním commitem |
| pracovní checkout relace na větvi | worktree.ephemeral; guard odmítne | git switch --detach tam a pokračujte v kanonickém worktree, nebo migrate --include-ephemeral --only <branch> |
| úloha v novém worktree odvozeném z mesh | WARN mesh … not claimed on it z start, poté FAIL mesh … never claimed on it z pre-push finish --check | start před převzetím nároku zajistí vlastní server worktree, takže se toto objeví jen tehdy, když to nešlo (MAJORDOMUS_RUNTIME=off, zastaralý spustitelný soubor, server, který nekonvergoval): majordomus serve ensure, pak majordomus check, který nárokuje rozsah běžící úlohy. Žádný částečný finish a druhý start (docs/MESH.md) |
| přesun, který překročil zařízení | worktree.cross_device na kroku, nebo odmítnutí | migrate --allow-copy |
| nesoulad otisku | worktree.migration_verification_failed, rozdíly vypsány | než na cokoli sáhnete, prohlédněte worktree tam, kde jej git registruje; přejmenování nemůže ztratit obsah, takže do něj během přesunu něco zapsalo |
| sestavený spustitelný soubor, který tu byl před minutou | MAJORDOMUS_BIN is not an executable nebo test selhávající na větvi, která byla zelená | událost prostředí, ne vada testované větve: jiný worker si zabral adresář buildu. Sestavte znovu ve svém vlastním worktree a změřte znovu. project.reclaim-only-what-you-own říká, čí byl |
| vypůjčený spustitelný soubor z jiného worktree | artefakty hlášené jako zastaralé na stromě, který je aktuální; derivace, která odstraní tisíce řádků | ověřte před použitím, že odpovídá — majordomus generate --check musí říkat in sync — a před commitem si přečtěte git diff --stat -- docs/generated site/data. project.a-tool-that-cannot-match-the-tree-refuses |
Výkon
worktree status a worktree guard čtou registrace, větve a jeden git status aktuálního worktree: hrstku podprocesů, žádné sestavení indexu, žádnou síť, žádné skenování. topology a list přidávají jeden git status na existující worktree a nic jiného. Migrace hashuje obsah každého změněného a nesledovaného souboru worktrees, která přesouvá, což je jediné místo, kde ta cena sedí. Nic se mezi voláními necachuje: topologie se mění mimo proces.
Kde co je
| konstanta a odvození | apps/majordomus-cli/src/worktree/path.rs |
| identita a trunk | apps/majordomus-cli/src/worktree/identity.rs |
| typovaná topologie | apps/majordomus-cli/src/worktree/model.rs |
| služba | apps/majordomus-cli/src/worktree/service.rs |
| migrace a otisky | apps/majordomus-cli/src/worktree/migrate.rs, fingerprint.rs |
| schopnosti | apps/majordomus-cli/src/capability/builtin/worktree.rs |
| příkazová řádka | apps/majordomus-cli/src/commands/worktree.rs, deklarováno v src/cli.rs |
| stránka Cockpitu | /cockpit/worktrees, apps/majordomus-cli/src/cockpit/pages.rs |
| launcher, který hook používá | bin/majordomus-cli; .envrc prochází přes bin/majordomus-env, launcher, který nikdy nebuilduje, a oba najdou spustitelný soubor přes lib/rust_bin.sh |
| hook | .githooks/pre-commit; položka v .ai/repo/policy.yaml enforcement |
| kontrola | scripts/ci/worktree-check, kontrola worktree-topology v .ai/repo/ci/gates.yaml |
| testy | apps/majordomus-cli/tests/worktree.rs, test/cases/96_worktree_topology.sh |
| rozhodnutí a pravidlo | ADR 21, .ai/repo/rules/project/worktree-topology.v1.md |
Vracení
majordomus worktree cleanup vypíše, co je nadbytečné: každou větev slitou do trunku, jejíž worktree je čistý nebo chybí. --remove odstraní tyto worktrees a nic jiného — větve se nikdy nemažou, protože dochází disk a větev je jediné trvalé jméno, které práce má.
Výpis a odstranění jsou dva okamžiky a vše, co činí worktree bezpečným k odstranění, se mezi nimi může změnit, takže výpis jen nominuje: --remove čte postavení větve vůči jejímu remote, zda uvnitř něco běží a zda je strom opět čistý, u každého kandidáta bezprostředně před jeho odstraněním a jmenuje, co odmítá:
| odmítnutí | proč to nepokrývá „sloučeno a čisto“ |
|---|---|
| před svým remote, bez upstreamu, nebo upstream zmizel | sloučená historie větve je na vzdáleném serveru; commit nad ní je na jednom disku a to, že je větev sloučena, o tom nic neříká |
| proces má uvnitř svůj pracovní adresář | mtime lže: dva worktrees uklizené 2026-09-15 vypadaly od 12. nedotčené a měly v sobě živé procesy |
| necommitnutá práce | přečteno znovu v okamžiku odstranění, nevzato z výpisu |
lsof chybí, nebo git nemohl porovnat s upstreamem | nevědět není totéž co nic tam není |
test/cases/611_the_reclaim_refuses_what_it_cannot_prove.sh nasází fixture s vlastním remote: volný worktree, který odejde, a větev před svým remote, jednu nikdy nepushnutou, jednu, jejíž vzdálená větev byla smazána, jednu s procesem uvnitř a jednu špinavou před výpisem, které zůstanou. Zahrnuje kombinaci, kvůli které zamítnutí existují: větev mergnutá do trunku a nesoucí commit, který její remote nikdy neviděl. Co se změní po výpisu — commit, smazaná vzdálená větev, zapsaný soubor — nasázejí unit testy reclaimu v apps/majordomus-cli/src/commands/worktree.rs. Chybějící lsof se nenasazuje nikde.
Checkout a jeho vzdálenost od trunku
Dne 2026-10-16 byl primární checkout o 779 commitů za origin/master, s necommitnutými změnami, které nikdo nedokázal přiřadit. Jeho sdílený server odpovídal na plánovací otázky každé relace z toho stromu a jeho místní master byl základem, z něhož se dělaly nové větve. Každé čtení říkalo, že server je připraven; žádné neříkalo, že strom, který servíroval, není trunk. project.a-checkout-serves-the-current-trunk z té vzdálenosti dělá čtení.
worktree::trunk::measure počítá pro HEAD checkoutu i pro lokální větev trunku commity vzdáleně sledovaného trunku, které každému chybí (behind), a commity, které každý má a trunku chybí (ahead). Vzdálený trunk je upstream trunku, jinak remote, jehož HEAD trunk jmenuje, jinak první remote s větví tohoto jména. Nic se nestahuje: odpověď platí k poslednímu fetchi a fetched_at (čas FETCH_HEAD) říká, kdy to bylo. Za probíhajícího merge se jeho příchozí commity počítají jako přítomné, takže merge, který větev aktualizuje, se nikdy neposuzuje podle stromu, který nahrazuje.
| postavení | kdy | doctor |
|---|---|---|
current | pozadu o méně než warn_behind, nebo ne pozadu | OK clone trunk |
lagging | na checkout.trunk_distance.warn_behind nebo za ním | WARN, s nápravou |
stale | na checkout.trunk_distance.fail_behind nebo za ním | FAIL, takže commit hook odmítne |
unjudged | pozadu a politika nedeklaruje žádný práh | INFO |
unmeasured | žádný trunk, nebo ho nenese žádná vzdálená sledovaná větev | INFO |
Prahy jsou prahy politiky (.ai/repo/policy.yaml, checkout.trunk_distance) a nic nečte výchozí hodnotu, když chybějí. Stav checkoutu je to, co servíruje jeho server; stav lokálního trunku (local_trunk_standing) je to, z čeho začíná větev z něj vytvořená. V propojeném worktree je lokální trunk věcí posunu jiného checkoutu, takže doctor ho hlásí jako WARN clone local-trunk a nikdy kvůli němu neselže commity tohoto worktree.
Totéž čtení se objeví všude, kde se strom poskytuje:
majordomus worktree trunk(worktree.trunk,majordomus_worktree_trunk,GET /api/v1/worktrees/trunk) na to odpovídá sám a končí kódem 10, když je checkout zastaralý;majordomus worktree statusvytiskne řádekcurrencya vrátítrunk_distance;server.status(majordomus serve status,majordomus_server,GET /api/v1/server) jej nese jakotrunkpro checkout, který server obsluhuje;- instrukce MCP
initializepřidají jednu větu — „Tento server servíruje strom o N commitů za origin/master“ — když je vzdálenost zaostávající, zastaralá nebo neposouzená; majordomus doctorto vykresluje podclone.
Náprava, kterou každé čtení jmenuje, uvede checkout do aktuálního stavu: fast-forward checkoutu trunku, merge vzdáleného trunku do feature větve (pushnutá větev se nikdy nepřepisuje) a fast-forward lokálního trunku tam, kde je checkoutnut. Necommitnutá práce se nejprve zachrání — git stash create, git update-ref refs/rescue/<checkout>-<timestamp> s tím commitem, nesledované soubory zkopírované vedle. Typovaná majordomus worktree update, která dělá přesně toto a odmítá, dokud v checkoutu stojí proces, je dalším řezem pravidla. test/cases/3176_a_checkout_serves_the_current_trunk.sh drží vzdálenosti, postavení, verdikty doctoru a případ rozpracovaného mergu proti fixture remote.
Necommitnutá práce, kterou nikdo nedrží
Větev, jejíž commity nedosáhnou žádného remote, jmenuje doctor (clone unpushed). Práce, která nedosáhla žádného commitu, jmenována nebyla: 2026-10-07 polovina oprav bezpečnosti integrace o důvěře v nahrazení ležela jako 45 nezacommitovaných souborů ve worktree skončené relace a 2026-10-08 doktrína dokončení ležela jako 21 připravených souborů v jiném. Obojí se našlo, protože někdo náhodou četl ty adresáře, a pushlo se jako větve rescue/...; téhož večera mělo datové úložiště volných 125 MiB (I1995).
majordomus worktree uncommitted (worktree.uncommitted, majordomus_worktree_uncommitted, GET /api/v1/worktrees/uncommitted) vypíše každý propojený worktree, jehož index nebo pracovní strom obsahuje změny — připravené, nepřipravené, nesledované i konfliktní cesty dohromady — spolu s:
| pole | co to je |
|---|---|
paths, work | kolik cest a součty, které uvádí worktree status |
oldest_change, age | nejdřívější čas změny mezi změněnými cestami; pro cestu, která už neexistuje (smazání), nahrazuje čas souboru indexu |
task | tam zaznamenaná aktivní úloha: .ai/local/state/current.yaml s výsledkem active, záznam, který start zapisuje a finish uzavírá |
episodes | tamní otevřené epizody (.ai/local/state/sessions-open/), každá s posledním znamením života a informací, zda uvízla |
process_inside | proces má uvnitř svůj pracovní adresář, podle čtení lsof, na kterém cleanup --remove odmítá |
standing | held, když ji drží úloha, epizoda, která není opuštěná, nebo proces; jinak abandoned, když je nejstarší změna starší než práh (nebo nelze přečíst její stáří), jinak recent |
remedy | příkaz, který by tam práci commitnul a pushnul do rescue/<branch>-<YYYYMMDD>; jen navržený, nikdy spuštěný |
Práh je session.stranded_after z politiky, ticho, po jehož uplynutí se klient epizody považuje za odešlého. Odpovídá na obě zde položené otázky — drží otevřená epizoda stále práci a čekala práce, kterou nic nedrží, dost dlouho na to, aby byla opuštěná — takže neexistuje druhé číslo, které by se od něj rozcházelo; --older-than jmenuje jiné pro jedno čtení. Politika, která žádné nedeklaruje, nepovažuje nic za opuštěné a říká to.
majordomus doctor vykreslí odečet: jedno WARN clone <path> na opuštěný worktree s jeho počtem a stářím a příkaz rescue jako nápravu; držená a nedávná práce se počítá v jednom OK. Odečet vyžaduje jedno git status na propojený worktree, asi 15 s na tři sta z nich, proto .githooks/pre-commit spouští doctor jako MAJORDOMUS_DOCTOR_CONTEXT=commit bin/majordomus doctor: v tomto kontextu, jediném, který doctor přijímá, se odečet vynechá a řádek INFO clone uncommitted to řekne a jmenuje majordomus worktree uncommitted. Ani odečet, ani doctor nic nezapisuje: každé git status běží pod GIT_OPTIONAL_LOCKS=0, takže se neobnovuje žádný index a .ai/local/ jiného worktree se čte, nikdy nezapisuje. test/cases/3025_abandoned_work_in_a_worktree_is_named.sh zasadí opuštěný worktree, držený a nedávný a drží git status každého worktree bajt po bajtu shodné napříč odečtem, nástrojem MCP a doctorem.
Build za zámkem jiného worktree
Worktrees sdílejí jeden adresář sestavení (ADR 0139) a cargo zamyká adresář sestavení po celou dobu, kdy jej sestavení používá, takže dvě sestavení najednou jsou jedno sestavení a jedno čekání. Každé sestavení, které nástroje tohoto repozitáře spouštějí, jde přes mj_cargo_bounded (lib/rust_bin.sh):
| co se děje | co worker vidí |
|---|---|
| nikdo jiný nebuilduje | build běží ve sdíleném adresáři jako dříve |
| zámek drží jiný worktree | po MAJORDOMUS_BUILD_LOCK_WAIT sekundách (výchozí 60) jeden řádek řekne, jak dlouho čekal a které procesy cargo existují, a sestavení se znovu spustí ve vlastním adresáři tohoto stromu pod ~/.cargo/build-private/ |
| zámek se uvolnil před limitem | nic: build pokračoval ve sdíleném adresáři |
| sestavení už je ve vlastním adresáři a stále je blokováno | výstupní kód 75 s důvodem místo čekání |
| limit disku odmítne soukromé sestavení | kód 12 s vlastní zprávou hranice |
Soukromý adresář zkompiluje každou závislost jednou, takže jeho první sestavení je pomalé. MAJORDOMUS_BUILD_LOCK_WAIT=0 nikdy nečeká; exportovaný CARGO_BUILD_BUILD_DIR se bere jako vlastní adresář stromu a nikdy se nenahrazuje. Pravidlo je project.a-build-never-waits-in-silence-on-anothers-lock; scripts/liveness-check odmítne sledovaný skript, který volá cargo jakýmkoli jiným způsobem, a test/cases/3002_a_build_never_waits_in_silence.sh dokládá každý řádek výše.
Nečinné inkrementální relace ve sdíleném build adresáři
rustc drží inkrementální stav cratu jako adresáře relací pod <build-dir>/<profile>/incremental/<crate>-<hash>/ a dokončenou odstraní jen pozdější kompilace téhož cratu. Crate, který se přestane kompilovat, zanechá poslední relaci a v build adresáři sdíleném každým worktree to znamená zbytky každého worktree. Dne 2026-10-16 držel sdílený adresář 79 GB takových zbytků ve 454 relacích, z nichž pět bylo živých, a na disku zbývalo volných 6,9 GiB.
Proto před zahájením buildu mj_cargo_bounded a kontrola limitu disku (lib/rust_bin.sh) odstraní nečinné relace adresáře buildu, který tento build používá:
| co | co se děje |
|---|---|
dokončená relace (s-*, ne -working) nezapsaná déle než MAJORDOMUS_INCREMENTAL_IDLE_MIN minut (výchozí 30) | odstraněno poté, co se vypíše jeho stáří a velikost; pak jeden řádek říká, kolik jich bylo odstraněno, co v nich napočítal du, a volné místo svazku před a po |
relace -working, mladší relace, soubory zámků vedle nich | nikdy se nedotklo |
| jiný adresář sestavení, adresář target, adresář profilu, který je symlinkem ven z adresáře sestavení | nikdy se nedotklo |
| cargo neříká, kde je adresář buildu, nenese žádnou značku cargo, nebo výpis nelze přečíst celý | nic se neodstraňuje a jeden řádek říká proč |
| volné místo je i potom stále pod limitem | build selže a odmítnutí jmenuje pět největších build adresářů s jejich velikostmi |
Adresář sestavení je ten, který hlásí cargo metadata, zjištěný zevnitř cratu, aby odpověděl připnutý toolchain. MAJORDOMUS_INCREMENTAL_IDLE_MIN=0 nic neodstraňuje. test/cases/3080_idle_incremental_sessions_are_pruned.sh dokazuje každý řádek proti fixture adresáři sestavení.
Zastaralé artefakty crate a žádný úklid pod běžícím sestavením
Každý build majordomus-cli zapíše svou knihovnu, spustitelný soubor, každou testovací a bench binárku a na macOS jeden objektový soubor na codegen unit do <build-dir>/<profile>/deps pod názvem <target>-<hash>. Hash je metadata jednotky, takže generace, která se přestane stavět — povýšená závislost, změněné nastavení profilu, přejmenovaný test — tam zůstane navždy, a ve sdíleném build adresáři jsou to zbytky každého worktree. Dne 2026-10-16 měl sdílený deps 204 GB; příštího rána svazek dosáhl nuly a každá relace na stroji se zastavila.
Takže stejný úklid, který odstraňuje nečinné inkrementální relace (mj_rust_prune_build_dir v lib/rust_bin.sh, na který se ptá mj_cargo_bounded a kontrola prahu disku před každým buildem), odstraňuje i zastaralé generace tohoto crate, v každém profilu adresáře buildu — debug, release, každého cílového tripletu a běhu pokrytí llvm-cov-target/debug:
| co | co se děje |
|---|---|
generace vlastních jednotek tohoto crate (knihovna, spustitelné soubory, testy, benchmarky), z nichž žádný soubor nebyl zapsán déle než MAJORDOMUS_DEPS_IDLE_MIN minut (výchozí 120), žádná není otevřená | odstraněno celé; jeden řádek říká, kolik generací a souborů a co napočítalo du, vypíše se dvanáct největších podle počtu souborů a jeden řádek uvádí volné místo svazku před a po |
| generace s jedním nedávným souborem nebo jedním souborem, který některý proces drží otevřený (lsof) | nikdy se nedotklo |
| artefakt jiného crate, název bez hashe jednotky, zámek, adresář samotného cargo | nikdy se nedotklo |
| profil, na kterém build drží zámek cargo | neprozkoumáno a jeden řádek to říká |
| profil, který nějaký proces pojmenovává ve své příkazové řádce — testovací binárka, rustdoc doctestu, kompilátor | nezameteno a jeden řádek jmenuje proces |
chybí flock(1) nebo lsof | nic se neodstraňuje a jeden řádek říká proč |
Zámek je ten, který cargo samo drží po celé sestavení, <profile>/.cargo-build-lock (flock(2); .cargo-lock v adresáři target staršího cargo), brán bez čekání. Sám o sobě nestačí a druhá podmínka existuje kvůli tomu, co mine: cargo test uvolní zámek, když kompilace skončí, dřív než se spustí binárky a doctesty, které sestavilo, a doctest čte rlib crate z deps. 2026-10-16 zmizely rlib pod běžícím sestavením, zatímco úklid soudil jen podle stáří. Každá podmínka se zjišťuje znovu v okamžiku před odstraněním čehokoli. Příliš brzy odstraněná generace stojí jednu kompilaci: cargo přestaví jednotku, jejíž výstup chybí. test/cases/3196_stale_crate_artifacts_are_pruned_under_cargos_lock.sh prokazuje každý řádek i to, že odstranění zámku nebo kontroly procesu způsobí selhání.
Soukromé adresáře sestavení
Build, který čeká MAJORDOMUS_BUILD_LOCK_WAIT sekund za zámkem jiného worktree, se znovu buildí v ~/.cargo/build-private/<cksum of the tree's root> (mj_cargo_private_dir; nic v ~/.cargo/config.toml je nevytváří — dělá to mj_cargo_bounded exportem CARGO_BUILD_BUILD_DIR). Každý je studený build všech závislostí, několik GiB, a na stroji, kde buildí mnoho relací, většina buildů čeká tak dlouho, takže skoro každý worktree, který kdy buildil, jeden zanechal. Dne 2026-10-17 držely 178 GiB v 35 adresářích, z nichž 25 bylo nečinných déle než dvě hodiny, a svazek dosáhl 3,9 GiB.
Kontrola limitu disku proto také spouští mj_rust_reclaim_private, nejvýše jednou za deset minut na stroj:
| co | co se děje |
|---|---|
privátní adresář nesoucí značku cargo, nic v něm nezapsáno déle než MAJORDOMUS_PRIVATE_BUILD_IDLE_MIN minut (výchozí 120), žádný držený zámek profilu, žádný proces, který jej jmenuje | přejmenováno mimo dosah cargo, pak odstraněno celé; jeden řádek jmenuje, co bylo odstraněno, a volné místo před a po |
| ten s čerstvým souborem, drženým zámkem nebo procesem, který jej jmenuje; ten bez značky cargo; symlink; ten, ve kterém tento shell staví | zachováno |
| výpis, který nelze přečíst celý | zachováno |
test/cases/3197_idle_private_build_dirs_are_reclaimed.sh dokazuje každý řádek.
Všechny tři zametací průchody jsou v testovací sadě vypnuté (test/run.sh nastavuje MAJORDOMUS_INCREMENTAL_IDLE_MIN, MAJORDOMUS_DEPS_IDLE_MIN a MAJORDOMUS_PRIVATE_BUILD_IDLE_MIN na 0): případ nesmí jednat na stroji, který měří. scripts/ci/backlog-check odmítne strom, v němž build už nezametá před startem, zametání už nebere zámek cargo ani se neptá tabulky procesů, nebo runner nechává zametání zapnuté (pravidlo project.accumulation-is-measured).
Path crate, který sestavil jiný worktree
Sdílení adresáře buildu sdílí každou jednotku, kterou cargo pojmenuje cestou relativní k workspace, takže tentýž path crate zkompilovaný ze dvou worktrees je jeden slot. Build skript majordomus-cli se znovu spouští při MAJORDOMUS_BUILD_TREE (.cargo/config.toml), čímž jeho slot zastarává v každém stromu kromě toho, který ho zapsal. Path crate bez toho build skriptu to nedostane: 2026-10-17 build dávky 71b znovu použil majordomus-core, který jiný worktree zkompiloval proti jiným verzím závislostí, a selhal s „multiple different versions of crate serde_json“. Dotek src/lib.rs toho crate vynutil přestavbu. Každý path crate workspace potřebuje totéž cargo:rerun-if-env-changed=MAJORDOMUS_BUILD_TREE ve vlastním build skriptu.