Wczorajszy post obramował problem: Claude Code na monorepo z pięcioma repozytoriami spala tysiące świeżych input tokens przy każdym pytaniu cross-repo, bo Glob i Grep nie mają pamięci struktury kodu. Dzisiejszy post to silnik, który to naprawia - jak graf jest budowany, jak jest odpytywany i gdzie wygrywa, a gdzie nie wygrywa z natywnymi prymitywami.

Publiczne repo wpadło dziś rano na AGPL-3.0. To destylat edukacyjny, nie kit do production deployment. Kształt jest w środku; multi-tenantowe rusztowanie - nie.

Dwie ścieżki do grafu

Kod musi stać się grafem, zanim Claude Code będzie mógł go odpytywać. Są dwa sposoby, żeby tak się stało, i jarvis-brain wspiera oba.

graph TD
    A[Źródłowe repo] --> B[Ścieżka A: ekstrakcja LLM]
    A --> C[Ścieżka B: bootstrap CC-local]
    B -->|Qwen local / Gemini fallback| D[graph.json per repo]
    C -->|skill /brain-extract, zero LLM cost| D
    D --> E[Federacja: merge + krawędzie cross-repo + design tokens]
    E --> F[Master graf grupy]
    F --> G[FTS5 + przeszukiwanie JSON]
    G --> H[5 narzędzi MCP serwowanych do Claude Code]

Ścieżka A to pipeline LLM. Wyzwalana webhookiem na push albo manualnie przez admin API. Ekstraktor czyta źródła i prosi model językowy o zidentyfikowanie węzłów (funkcje, komponenty, composables, typy) oraz relacji (importy, calle, override’y, łańcuchy parent-child między warstwami). Do codziennej roboty leci na lokalnym Qwenie po reverse SSH tunnelu - za darmo, szybko, wystarczająco dobrze przy inkrementalnych aktualizacjach poniżej dwudziestu zmienionych plików. Do głębokich re-ekstrakcji albo gdy Qwen jest offline - fallback na Gemini Flash, z Pro dostępnym dla cięższych przebiegów. Koszty są mierzone i logowane. Output to jeden graph.json per repo.

Ścieżka B to bootstrap CC-local. Czasem chcesz graf dla repo, którego jeszcze nie zasiałeś, i nie chcesz płacić Gemini tokenami za pierwszy przebieg. Otwierasz więc Claude Code w docelowym repo, uruchamiasz skill /brain-extract i pozwalasz natywnej analizie Claude Code wygenerować ten sam schemat graph.json. Pushujesz wynik do graphs repo, serwer go odbiera, graf jest live. Zero kosztów API - subskrypcja Claude Code robi robotę.

Obie ścieżki produkują identyczny schemat grafu. Obie lądują w tym samym pipelinie federacyjnym. Serwer nie obchodzi, która go zapisała. To istotne, bo ścieżka precyzji audytowej (B) i ścieżka codziennych merge’y (A) mają zupełnie różne profile kosztowe i chcesz, żeby koegzystowały na jednym silniku.

Federacja to kolejny etap. Grafy per-repo są łączone w master graf per-grupa. Wykrywane są importy cross-repo (core primitives konsumowane przez każdy front). Design tokeny (CSS custom properties, zmienne SCSS) są trackowane między konsumentami - kanoniczne definicje flagowane, naruszenia DRY flagowane, łańcuchy override’ów rejestrowane. Wynik to jeden master graf per grupa, który zna cały ekosystem.

Trik FTS5 z camelCase

Warstwa query to SQLite FTS5. Z grubsza nudna infrastruktura, oprócz jednej decyzji, która decyduje, czy cały system odczuwa się dobrze czy odczuwa się jak zepsuty: jak obsłużyć nazwy identyfikatorów typu useBaseCart?

Domyślny tokenizer FTS5 (unicode61) dzieli na białych znakach i znakach interpunkcyjnych. useBaseCart to jeden token. Użytkownik szukający “Base” dostaje nic - substring nie jest granicą tokena.

Możesz napisać własny tokenizer. To podręcznikowa odpowiedź. To również obciążenie utrzymaniowe, mina na ścieżce upgrade’u i dodatkowa zależność w C. Nie chciałem tego.

Trik to preprocessing w czasie indeksowania, nie tokenizacja w czasie zapytania. Każdy identyfikator emituje dwie wartości do indeksu: oryginał (useBaseCart) plus wersję rozdzieloną spacjami (use Base Cart). Domyślny tokenizer FTS5 indeksuje oba. Użytkownik szukający “Base” trafia w wersję rozdzieloną. Użytkownik szukający dokładnej nazwy trafia w oryginał. Ta sama kolumna, to samo zapytanie, ten sam tokenizer. Jedna dodatkowa linia Pythona w indekserze.

Mniej eleganckie niż własny tokenizer. Też wdrożenie w dziesięć minut, zero ryzyka przy upgrade’ach SQLite, działa na każdej wersji. Decyzja zapisana w notatkach architektury jako “FTS5 camelCase = preprocessing w czasie indeksowania, nie własny tokenizer”. Ten sam wzorzec działa dla kebab-case, snake_case albo dowolnej konwencji złożonych identyfikatorów.

Pięć narzędzi MCP

Gdy graf jest zbudowany i zaindeksowany, serwowany jest do Claude Code przez pięć narzędzi MCP. To nie jest interfejs web search. To natywne prymitywy MCP, które Claude Code widzi w tej samej liście narzędzi co Glob i Read, i wybiera na podstawie kształtu pytania.

NarzędzieCo robi
brain_queryWyszukiwanie freetext z FTS5, zwraca ranked hits plus sąsiadów 2-hop plus podpowiedzi cross-repo
brain_graphZwraca surowy graph.json dla repo albo master grupy, do przeszukiwania w kodzie
brain_pathNajkrótsza ścieżka między dwoma węzłami - “jak ten core primitive dochodzi do tej feature w UI”
brain_explainDetal węzła plus inbound/outbound sąsiedzi plus git-blame, zero LLM cost
brain_ffcssDesign tokeny: lista, count usage per repo, surfowanie naruszeń DRY

Sens MCP-native jest taki, że Claude Code nie potrzebuje aktualizacji system prompta, żeby ich użyć. To narzędzia na liście. Model decyduje. Dodanie szóstego narzędzia jutro to jeden kontrakt API, nie re-engineering tego, jak pytania są routowane.

brain_explain to niedoceniany jeden. Zero LLM cost - to czysty lookup grafu z metadanymi git-blame doczepionymi. Dla pytań “kto to napisał i co od tego zależy” zastępuje sekwencję Read + Grep + git log jednym wywołaniem narzędzia.

Benchmark - 50 pytań na monorepo z pięcioma repo

Dwa przebiegi, pięćdziesiąt pytań każdy, ten sam kod, ten sam model. Jeden przebieg z Claude Code natywnym (Glob, Grep, Read only). Drugi z jarvis-brain MCP włączonym na wierzchu. Kategorie: code discovery, usage tracing, cross-repo, dependency path, architecture - po dziesięć pytań na kategorię.

Liczby nagłówkowe:

MetrykaBaseline (CC natywny)Z jarvis-brainDelta
Total wall time36m 44s26m 03s-29,1%
Świeże input tokens4 1452 003-51,7%
Total koszt $$12,21$12,30-0,7%
Wywołania narzędzi (avg)4,384,58+5%
Błędy01-

Oszczędność czasu to dziesięć i pół minuty na całym przebiegu. Oszczędność świeżych tokens to model czytający o połowę mniej źródła za pierwszym razem. Liczba dolarowa jest płaska, bo prompt cache Anthropic absorbuje prawie cały różnicowy input - cached reads kosztują ułamek fresh reads, a cache jest identyczny między oboma przebiegami na poziomie system prompta.

Per kategoria breakdown jest ostrzejszy:

KategoriaMean baselineMean brainDelta
Architecture105,6s49,9s-53%
Cross-repo50,5s39,3s-22%
Usage tracing19,6s18,0s-8%
Dependency path26,7s27,6s+3%
Code discovery17,9s21,5s+20%

Pytania o architekturę to miejsce, gdzie graf niesie najwięcej sygnału: “jakie są god-nody w tym kodzie”, “gdzie skupiają się override’y cross-repo”, “która warstwa ma najgęstsze krawędzie wewnętrzne”. Pytania cross-repo następne, bo federacja prekomputuje to, co Grep musiałby derywować od zera.

Gdzie brain nie wygrywa

Code discovery przegrywa. Dwadzieścia procent gorzej, średnio. Kształt pytania to “znajdź plik X” albo “znajdź pliki zaczynające się od Y” - dokładnie to, do czego zbudowany jest Glob. Przejście przez brain_query dodaje hop bez zmiany odpowiedzi. Model i tak ląduje na tym samym pliku .vue; tylko jedno wywołanie narzędzia więcej zajęło.

Dependency path to w zasadzie remis. Graf ma dane, ale model jest równie zadowolony ścigając importy przez Grep i Read na prostych łańcuchach. Brain wygrywa, gdy łańcuch jest długi albo przechodzi granice repo; w przeciwnym razie podejście natywne jest równoważne.

Jeden błąd na pięćdziesiąt - pytanie o wykrycie cyklicznej zależności, gdzie traversal grafu trafił na edge case i nie zwrócił nic. Baseline trafił z Grep. Uczciwa liczba to 49/50 poprawnych, nie 50/50. Fix jest w kolejce; jeszcze nie shipped.

Koszt to ten, na którym spodziewałem się wygrać i nie wygrałem. Cache Anthropic jest na tyle agresywny, że oszczędność na świeżych input tokens paruje na poziomie rachunku. Jeśli płacisz za API tak, jak płacisz dziś, brain nie obetnie ci rachunku. Co obetnie - to wall time i exploration burn - rzeczy, które wpływają na to, jak szybko shipujesz, nie ile bierze od ciebie provider.

Co wpada do publicznego repo dziś

github.com/darco81/jarvis-brain-core, AGPL-3.0. Kształt silnika:

  • brain/extractors/ - jak źródło staje się ustrukturyzowanym JSON-em node/edge
  • brain/federation/ - merge grafów per-repo, detekcja krawędzi cross-repo, federacja design tokens
  • brain/llm/prompts.py - prompty ekstrakcyjne
  • brain/api/mcp.py plus mcp_tools.py - pięć narzędzi MCP i ich schematy
  • brain/api/query.py plus query_path.py - FTS5 z trikiem preprocessing camelCase
  • brain/viz/ - adapter wizualizacji grafu
  • benchmark/ - metodologia, przykładowe pytania, runner

Czego tam nie ma: production scaffolding - auth, handlery webhooków, admin UI, cost tracking, alerting, worker queue, deployment configs, schemat config multi-tenant. Możesz to sklonować i przeczytać metodę. Nie możesz tego sklonować i odpalić multi-tenantowego production deployment. To jest granica i jest tam celowo.

Live demo nadal na brain.sdet.it. Raport benchmarku jako static page na brain.sdet.it/benchmark/ - wszystkie pięćdziesiąt pytań, wszystkie kategorie, oba przebiegi obok siebie.

Setup na jutro

Jutro Part 3 - post na pytanie, które powinieneś faktycznie zadać przed adopcją tego: kiedy silnik się opłaca, a kiedy Grep już wystarcza.

Plus warstwa V0.5 - jak ten sam silnik wygląda, gdy przestajesz udawać, że masz jedno repo, i zaczynasz traktować design system org z dziesięcioma konsumenckimi frontami jako jednostkę pracy. Cross-repo dedup, federacja tokens, atomic patches przez wszystkich konsumentów. Inna klasa problemu, ta sama architektura pod spodem.

#FromTheField - dzień 3 ląduje jutro rano.