Every ticket as the root of its provenance graph — goal, patch, approval, runs and (as the ticket-class build lands) mandate, manifestations, guarantees, journey, artifacts and children. Rendered through the <<<TicketGraphList>>> tag.
Forge FAMILY TREE / evolution tool: cluster 264 repos into 109 lineages, classify fork pattern (identical/linear/diverged/split-store), name the canonical tip, emit stage-by-stage tree + reconciliation plan
goal
Build the FORGE FAMILY TREE / EVOLUTION tool: cluster every forge repo into lineages, classify how
each family forked, name the CANONICAL tip, and produce a stage-by-stage evolution tree plus a
reconciliation plan for the few families that truly diverged.
WHY (measured, not theoretical — a prototype was run on 2026-07-15 over config/forge/remotes/*.git):
* 264 bare repos collapse to 109 DISTINCT LINEAGES (grouped by root-commit set).
* Of those 109: 68 IDENTICAL (pure duplicates, same tip ingested from several paths)
36 LINEAR (one tip supersets the rest; the others are STALE copies)
3 DIVERGED (true forks: jazz.git, notes_db.git, z3_mcp.git)
2 SPLIT-STORE (objects not shared; need a fetch before they can be compared:
big-jazz.git, code__code.git)
So only ~5 families carry real reconciliation work. The other ~104 need NO merge — just pick the
superset tip and drop the aliases. The 264 is largely an ingestion-path illusion (code__X,
code__rebuild__X, gitlab__<group>__X, gitlab__<group>__jazz-forge__X, and bare X are the SAME
project ingested repeatedly).
* PROVEN CONSEQUENCE FOR THE LIVE REBUILD: jail.git (47 commits) SUPERSETS agent_tools_suite.git
(46) — shared root 379ac05319, ats's tip is a direct ancestor of jail's. jail adds 6 files
(crankws-mcp/server.py +138, start-crank.py, claude-gui.py, registry.crankws.json,
crank_job_backlog.json, crank_job_congruency.json). The in-flight agent_tools_suite rebuild
(#412-436) is therefore ticketed from a NON-CANONICAL tip, and jail's unique modules will be
rebuilt into a SEPARATE forge2/jail/ package — one lineage split across two packages, because
nothing in the pipeline models a family.
DELIVERABLE: checkouts/current/congruency/tools/forge_family_tree.py — standalone, no deps beyond
git + stdlib + sqlite3. It MUST:
1. DISCOVER + CLUSTER. Walk config/forge/remotes/*.git. Group by root-commit SET
(`git rev-list --max-parents=0 --all`). Repos sharing a root are one lineage.
2. RESOLVE TIPS CORRECTLY — the prototype's key gotcha: MOST of these bare repos have an
UNRESOLVABLE HEAD (`git rev-parse HEAD` returns the literal string "HEAD"; `--verify` fails and
silently drops the repo, which produced a wrong "0 diverged" answer on the first run). Resolve
tips via `git for-each-ref refs/heads`, preferring main/master, else the ref with most commits.
3 repos have NO refs at all — report them, never skip silently.
3. CLASSIFY each family:
IDENTICAL — all members share one tip.
LINEAR — the max-commit tip is an ancestor of every other tip (superset). Name the
CANONICAL tip; list the stale aliases.
DIVERGED — some tip is NOT reachable from the candidate: compute `merge-base`, and report
each side's unique commits and file-level diff.
SPLIT-STORE — a member's tip object is absent from the candidate's store. Do NOT guess:
fetch into a scratch object store (or use --alternates) and then re-classify.
4. EVOLUTION IN STAGES. For each family emit the tree: root -> shared spine -> fork points -> tips,
annotated with commit dates (stage boundaries), which repo/alias sits at each node, and what
each fork ADDED vs the spine. Additive-only divergence must be distinguished from conflicting
divergence (both sides modified the same file) — that distinction IS the reconciliation cost.
5. RECONCILIATION PLAN per family:
IDENTICAL -> keep canonical, drop N aliases (zero work).
LINEAR -> fast-forward: canonical already contains everything; drop stale aliases.
ADDITIVE -> union: the merge is mechanical (no conflicting hunks).
CONFLICTING-> flag for synthesis: list the conflicting files; this is the ONLY case needing a
human/competitive-synthesis decision (see #206).
6. OUTPUT: (a) machine-readable — a sqlite db or JSON: families, members, tips, classification,
canonical tip, fork edges, reconciliation verdict; (b) human-readable ASCII family tree.
7. --selftest on THROWAWAY git repos built in a temp dir that synthesize each class (identical /
linear / additive-diverged / conflicting-diverged / split-store / no-refs / unresolvable-HEAD),
proving each is classified correctly. No network, no live db, no writes outside the temp dir.
DO NOT USE omni.git AS THE SOURCE OF TRUTH — it is INCOMPLETE. Measured: omni.git is MISSING
agent_tools_suite's tip 888d3447c6 (it holds the common line but not the fork's feature commits).
config/forge/remotes/*.git is authoritative. Optionally REPORT omni coverage gaps as a side output.
DOWNSTREAM VALUE (state it in the tool's own docstring, do not implement here): the canonical-tip
map is what should drive forge2 rebuild scope — ticket modules ONLY from canonical tips, skip stale
variants (a stale fork's older foo.py hashes to a DIFFERENT module-sha than the tip's foo.py, so
today it mints a second ticket and we rebuild an obsolete module), and rebuild one lineage into ONE
forge2 package.
ACCEPTANCE: --selftest green; a real run over config/forge/remotes reproduces the prototype's
headline numbers (264 repos -> 109 lineages; 68 IDENTICAL / 36 LINEAR / 3 DIVERGED / 2 SPLIT-STORE,
with the 2 split-store families RESOLVED after fetch rather than left unknown); correctly names
jail.git as the canonical tip over agent_tools_suite.git; prints a readable family tree.
RUN THIS ON CLAUDE (multi-file git reasoning; not a GLM task).
patch
none
approval
unapproved
runs
no runs recorded
mandate (clauses)
not yet recorded — lands with the ticket-class build
manifestations
not yet recorded — lands with the ticket-class build
guarantees
not yet recorded — lands with the ticket-class build
journey (blunders & successes)
not yet recorded — lands with the ticket-class build
artifacts (forge)
not yet recorded — lands with the ticket-class build