codecharter graph overview [pfad] [optionen]
codecharter graph neighborhood <target> [pfad] [optionen]
codecharter graph impact <target> [pfad] [optionen]
codecharter graph cycles [pfad] [optionen]
codecharter graph hubs [pfad] [optionen]
codecharter graph paths <from> --to <to> [pfad] [optionen]
codecharter graph diff --base <ref> [pfad] [optionen]
codecharter graph projiziert Ihre analysierte Solution auf einen
Kopplungsgraphen und beantwortet darauf sieben Fragen: Wie sieht die Solution
von oben aus (overview), womit ist ein bestimmter Typ verbunden
(neighborhood), was bricht, wenn Sie diesen Typ ändern (impact), wo ist
der Graph kein DAG (cycles), welche Knoten sind tragend (hubs), führt
jeder Pfad zwischen zwei Knoten über einen bestimmten Engpass (paths), und
hat ein Branch im Vergleich zweier Git-Referenzen die Architektur
verschlechtert (diff). Das Kommando richtet sich an zwei Zielgruppen: an
Entwickler auf der Kommandozeile, die sich schnell orientieren wollen, und an
KI-Coding-Agenten, die eine Codebasis verstehen müssen, ohne vorher jede Datei
zu lesen.
Dieselben sieben Fragen stehen auch als MCP-Tools zur Verfügung, siehe
Mit einem KI-Assistenten.
Knoten sind Solution-Typen (auf der aggregierten overview-Ebene zusätzlich
Projekte oder Namespaces). Kanten stammen aus deklarierter Vererbung,
Interface-Implementierung, Member-, Parameter-, Rückgabe-, Feld- und
Event-Typen, aufgelösten Methodenrumpf-Aufrufen, angewendeten Attributen und
Objekterzeugungsausdrücken. Was das genau abdeckt und was nicht, steht unter
Kantenbedeutung und Grenzen.
Der Agenten-Workflow: orientieren, hineinzoomen, Auswirkung einschätzen, Zyklen finden, tragende Knoten finden, Engpässe prüfen, Branches vergleichen
Die sieben Subcommands sind zur Verwendung nacheinander gedacht. Ein Agent, der
ein unbekanntes Repository übernimmt, arbeitet die meisten davon typischerweise durch, bevor
er etwas ändert. hubs steht dabei sinnvollerweise am Anfang, denn es
beantwortet, wo diese Codebasis trägt, und leistet damit etwas, was die
Top-Kanten-Sicht von overview nicht leistet: Ein Typ mit Fan-in 60, verteilt
auf 60 verschiedene Aufrufer, taucht nie als schwere Kante auf, ist aber
genau der Typ, den man nicht beiläufig ändern sollte.
1. Orientieren: overview
Starten Sie mit der Form der Solution: welche Projekte oder Namespaces existieren, und welche sind am stärksten gekoppelt.
codecharter graph overview --level projects --top 10
Totals: 4 project(s), 31 namespace(s), 268 type(s)
Nodes (4):
Acme.Domain 52 type(s)
Acme.Infrastructure 74 type(s)
Acme.Web 96 type(s)
Acme.Tests 46 type(s)
Edges (6, weight desc):
Acme.Web -> Acme.Infrastructure (weight 58)
Acme.Infrastructure -> Acme.Domain (weight 47)
Acme.Web -> Acme.Domain (weight 22)
Acme.Tests -> Acme.Web (weight 19)
Acme.Tests -> Acme.Infrastructure (weight 11)
Acme.Tests -> Acme.Domain (weight 6)
Der Hinweis zur Kantenbedeutung und, bei gekürzter Kantenliste, ein
Kürzungshinweis gehen bei jedem Aufruf nach stderr; das Beispiel oben zeigt
nur stdout. --level namespaces (der Standard) aggregiert eine Ebene tiefer:
Jede Knoten-Id wird zu <Assembly>|<vollständiger Namespace-Name>, weil
derselbe Namespace-Name in mehr als einer Assembly vorkommen kann.
2. Hineinzoomen: neighborhood
Sobald klar ist, wo die Kopplung liegt, lösen Sie einen Typ auf und sehen sich seine unmittelbare Umgebung an: wovon er erbt, was er implementiert, was er nutzt, von wem er genutzt wird, was er aufruft und von wem er aufgerufen wird.
codecharter graph neighborhood OrderService --depth 1
Focus: Acme.Domain.OrderService (class) src/Acme.Domain/OrderService.cs:12
public OrderService(IOrderRepository repository, IClock clock)
public async Task<Order> PlaceAsync(OrderRequest request, CancellationToken cancellationToken)
public async Task CancelAsync(int orderId, CancellationToken cancellationToken)
Implements (1):
Acme.Domain.IOrderService (interface) src/Acme.Domain/IOrderService.cs:8 distance 1 [Implements]
Uses (2):
Acme.Domain.IOrderRepository (interface) src/Acme.Domain/IOrderRepository.cs:6 distance 1 [Uses]
Acme.Domain.IClock (interface) src/Acme.Domain/IClock.cs:4 distance 1 [Uses]
Used by (3):
Acme.Web.OrdersController (class) src/Acme.Web/OrdersController.cs:15 distance 1 [Uses]
Acme.Tests.OrderServiceTests (class) tests/Acme.Tests/OrderServiceTests.cs:9 distance 1 [Uses]
Acme.Infrastructure.OrderServiceRegistration (class) src/Acme.Infrastructure/OrderServiceRegistration.cs:5 distance 1 [Uses]
target wird gegen einen exakten oder groß-/kleinschreibungsunabhängigen
vollständigen Typnamen, ein Namenssuffix (OrderService trifft
Acme.Domain.OrderService) oder einen Quelldateipfad aufgelöst. Der Pfad ist
nützlich für einen Agenten, der die bearbeitete Datei kennt, aber nicht den
vollständigen Typnamen. Ein mehrdeutiges oder nicht aufgelöstes Target rät
nie: Es gibt die gefundenen Kandidaten aus und beendet sich mit einem
Aufruffehler.
3. Auswirkung einschätzen: impact
Bevor Sie einen Typ ändern oder entfernen, fragen Sie, wer von ihm abhängt.
codecharter graph impact OrderService
Dependents by distance (2 level(s)):
Distance 1 (3):
Acme.Web.OrdersController (class) src/Acme.Web/OrdersController.cs:15
Acme.Tests.OrderServiceTests (class) tests/Acme.Tests/OrderServiceTests.cs:9
Acme.Infrastructure.OrderServiceRegistration (class) src/Acme.Infrastructure/OrderServiceRegistration.cs:5
Distance 2 (1):
Acme.Web.Program (class) src/Acme.Web/Program.cs:1
Mit --source wechseln Sie von "wer hängt hiervon ab" zu "wie erreicht A B":
codecharter graph impact IClock --source OrdersController
Paths (1):
Path 1:
Acme.Web.OrdersController (class) src/Acme.Web/OrdersController.cs:15
Acme.Domain.OrderService (class) src/Acme.Domain/OrderService.cs:12 [Uses]
Acme.Domain.IClock (interface) src/Acme.Domain/IClock.cs:4 [Uses]
4. Zyklen finden: cycles
Ein Abhängigkeitszyklus fällt bei der Betrachtung einzelner Kanten kaum auf.
Ob nur zwei Namespaces zirkulär sind oder vier, lässt sich so nicht
zuverlässig unterscheiden. cycles berechnet es exakt: jede starke
Zusammenhangskomponente im Graphen, mit einem konkreten Zyklus zum Handeln
und, pro Kante, ob ihr Entfernen allein den Zyklus bricht.
codecharter graph cycles --level namespaces
Level: Namespaces. Totals: 4 node(s), 6 edge(s), 1 component(s), 2 node(s) in cycles.
Component 1 (size 2):
Members (2):
Acme.Domain (Acme|Acme.Domain) 52 type(s)
Acme.Infrastructure (Acme|Acme.Infrastructure) 74 type(s)
Shortest cycle: Acme|Acme.Domain -> Acme|Acme.Infrastructure -> Acme|Acme.Domain
Closing edges (2):
Acme|Acme.Infrastructure -> Acme|Acme.Domain (weight 47, breaks)
Acme|Acme.Domain -> Acme|Acme.Infrastructure (weight 3, breaks)
Eine Komponente der Größe 1 wird nie gemeldet, ein Knoten kann in diesem
Graphen nicht von sich selbst abhängen. --level namespaces (der Standard)
eignet sich am besten für die Architektur-Review: klein genug zum Lesen, und
ein Zyklus auf Namespace-Ebene ist fast immer ein echtes Problem.
--level types findet mehr, meldet aber viel Rauschen, da Begleit- und
geschachtelte Typen sich routinemäßig gegenseitig referenzieren. Verwenden Sie
types nur, wenn Sie ein konkretes Refactoring verfolgen. --level projects
ist das stärkste Signal und meist leer, da .NET zirkuläre Projektreferenzen
verbietet; ein nicht leeres Ergebnis lohnt dort einen sofortigen Blick.
breaksComponent: false bedeutet nicht, dass eine Kante gefahrlos bleiben
kann, sondern nur, dass das alleinige Entfernen dieser einen Kante den
Zyklus nicht bricht. Die Closing Edges sind so sortiert, dass die brechenden
Kanten zuerst kommen, danach nach Gewicht aufsteigend. Die erste Zeile ist
damit der günstigste wirksame Schnitt, sofern es einen gibt. Gibt es keinen,
etwa bei einer dichten, gegenseitig redundanten Komponente, ist der
kürzeste Zyklus darüber die konkrete Einheit zum Handeln: Wählen Sie eine
Kante auf dieser Kette zum Brechen.
Ist der fragliche Typ bereits bekannt, führt --target direkt dorthin, statt
jede Komponente zu durchsuchen:
codecharter graph cycles --target OrderService
Das beschränkt das Ergebnis auf die einzelne Komponente, die OrderService
enthält (--max-components wird dann ignoriert), oder meldet, dass der Typ
nicht Teil eines Zyklus ist. Die Auflösung erfolgt auf dieselbe Art wie bei
neighborhood.
5. Tragende Knoten finden: hubs
Die Frage, was hier trägt, treibt drei konkrete Entscheidungen: was beim Einarbeiten zuerst zu lesen ist, was als stabiler Vertrag gilt (hoher Fan-in, niedriger Fan-out) im Gegensatz zu einem Blatt, das sich gefahrlos umschreiben lässt, und wo nachzusehen ist, wenn eine Änderung weiter ausstrahlt als erwartet.
codecharter graph hubs --top 5
Level: Types. SortBy: FanIn. Totals: 268 node(s), 812 edge(s), 214 ranked.
Hubs (5):
Acme.Domain.OrderService fanIn=74 fanOut=9 weightedFanIn=318 weightedFanOut=14 instability=0.108
Acme.Domain.IOrderRepository fanIn=41 fanOut=1 weightedFanIn=52 weightedFanOut=1 instability=0.024
Acme.Domain.IClock fanIn=38 fanOut=0 weightedFanIn=61 weightedFanOut=0 instability=0
...
fanIn/fanOut zählen verschiedene Nachbarknoten, entsprechend der
klassischen Definition afferenter und efferenter Kopplung.
weightedFanIn/weightedFanOut summieren stattdessen die Kanten-Gewichte:
Ein Typ, den 60 Aufrufer je einmal berühren, hat fanIn: 60, aber ein
deutlich kleineres weightedFanIn als ein Typ, den ein Aufrufer 60-mal
trifft. instability = fanOut / (fanIn + fanOut): 0 bedeutet, jede
Abhängigkeit zeigt nach innen (ein stabiler, gefahrlos nutzbarer Vertrag),
1 bedeutet, jede Abhängigkeit zeigt nach außen (ein Blatt, gefahrlos
änderbar). Ein Knoten ohne jede Kante hat keine definierte Instabilität und
meldet n/a, nie 0, denn 0 bedeutet maximal stabil, und ein isolierter
Knoten ist das nicht.
Diese Grade stimmen nicht mit CouplingAfferent/CouplingEfferent aus
einer .ccr-Regel überein. Sie werden über dieselbe Kantenmenge berechnet
wie die übrigen Graph-Befehle, sodass sich jedes hubs-Ergebnis gegen
graph neighborhood gegenprüfen lässt. Jedes Ergebnis trägt einen
entsprechenden Hinweis.
codecharter graph hubs --sort-by instability --min-degree 5
--sort-by akzeptiert fan_in (Standard), fan_out oder instability.
total, weightedFanIn und weightedFanOut bleiben unabhängig davon als
Felder auf jeder Zeile verfügbar. Sortierung nach instability allein hebt
vor allem kaum berührte Knoten hervor, die trivial nahe an 1 liegen; ein
höheres --min-degree (Standard 1) sieht daran vorbei. Bei
--sort-by instability trägt jedes Ergebnis einen entsprechenden Hinweis.
codecharter graph hubs --scope Acme.Web
--scope (ein Namespace- oder Assembly-Präfix, Groß-/Kleinschreibung egal)
beschränkt auf einen Teilbaum, welche Knoten in die Rangliste aufgenommen
werden. Das ist nützlich, sobald die globale Top-20 aus einer Handvoll
Kern-Domänentypen besteht und Sie wissen wollen, was innerhalb des gerade
bearbeiteten Moduls trägt. Die Grade bleiben über den gesamten Graphen
berechnet, sodass fanIn/fanOut einer eingeschränkten Zeile die globalen
Zahlen bleiben statt einer nach innen gerichteten Zählung; jedes
eingeschränkte Ergebnis trägt einen entsprechenden Hinweis. Ein --scope, der
auf nichts passt, liefert eine leere Rangliste plus einen Hinweis, der den
Scope nennt, nie eine stillschweigend leere Liste.
--level namespaces/--level projects aggregieren auf dieselbe Art wie bei
cycles. Kanten innerhalb eines Aggregats entfallen dabei, sodass der Fan-out
eines Namespace nur Abhängigkeiten zu anderen Namespaces zählt.
6. Einen Engpass prüfen: paths
impact --source findet einen kürzesten Pfad von A nach B. Das beantwortet
"kann A B erreichen", nicht "muss jede Route von A nach B über diesen einen
Typ laufen", also genau die Frage, die entscheidet, ob es gefahrlos ist,
einen Typ zu löschen, oder ob eine vorgeschlagene Naht ein Modul wirklich von
einem anderen isoliert. paths beantwortet diese Frage direkt, ohne jede
Route zwischen A und B aufzuzählen (was in einer echten Codebasis exponentiell
wäre):
codecharter graph paths OrdersController --to IClock --must-pass-through OrderService
allPathsIntercepted: true
Explored 14 node(s). Exhaustive: true.
Jeder Pfad von OrdersController zu IClock läuft in diesem Fall über
OrderService, ein Entfernen oder Absichern von OrderService würde also
jede Route kappen. Gilt das nicht, meldet das Ergebnis statt eines bloßen
"false" ein konkretes Gegenbeispiel:
codecharter graph paths OrdersController --to IClock --must-pass-through PaymentGateway
allPathsIntercepted: false
Counterexample path:
OrdersController
--Uses--> OrderService
--Uses--> IClock
Explored 9 node(s). Exhaustive: true.
Das Gegenbeispiel ist ein konkreter A-nach-B-Pfad, der PaymentGateway nie
berührt, der Beweis, dass die Behauptung nicht gilt. Ein Agent muss dem
Ergebnis nicht vertrauen, er kann den Pfad lesen.
Ohne --must-pass-through beantwortet paths stattdessen die reine
Erreichbarkeitsfrage ("kann A B überhaupt erreichen"):
codecharter graph paths OrdersController --to LegacyBillingService
reachable: false
Explored 42 node(s). Exhaustive: true.
Die Antwort ist immer einer von drei Zuständen, nie eine gekürzte
Vermutung. Eine gekürzte Suche meldet allPathsIntercepted: null
(im Text als undecided gerendert) statt eines möglicherweise falschen
true, denn "galt für jeden Pfad, den ich gesehen habe" ist nicht dieselbe
Aussage wie "gilt für jeden Pfad". isExhaustive sagt, welche der beiden
Sie bekommen haben, und nur isExhaustive: true darf je zusammen mit einem
true/false-Ergebnis auftreten. Entspricht from dem
--must-pass-through-Ziel oder to dem --must-pass-through-Ziel, beginnt
oder endet jeder Pfad trivial am Engpass, die Antwort ist also true ohne
Suche. (from gleich to nimmt den regulären Weg: die Suche
findet das Ziel sofort, mit einem davon verschiedenen Engpass ist die Antwort
also false, mit dem Ein-Knoten-Pfad als Gegenbeispiel.) Kann A B unabhängig vom
Engpass gar nicht erreichen, ist allPathsIntercepted ebenfalls true
(vakuos wahr: es gibt null Pfade, also läuft jeder dieser null Pfade über X);
ein Hinweis nennt das explizit, damit es nie mit einer echten Absicherung
verwechselt wird.
Auf --level methods (Standard types) durchläuft paths einen bei Bedarf
gebildeten induzierten Teilgraphen aus Methodenaufruf-Kanten, siehe
paths und Methodenebenen-Ehrlichkeit
unten für die Auswirkung auf die Verlässlichkeit der Antwort.
7. Hat der Branch es verschlechtert: diff
Ein git diff zeigt, welche Zeilen sich geändert haben. Er zeigt nicht,
dass der Branch eine Abhängigkeit von Domain zu Infrastructure
eingeführt hat, dass ein Typ elf zusätzliche Aufrufer bekommen hat, oder
dass ein Blatt-Namespace jetzt auf vier andere zeigt, also genau die Befunde,
die ein Mensch im Review benennt und die ein Zeilen-Diff verbirgt.
codecharter graph diff --base main
Building the base graph for 'main' (a cache miss costs a restore plus a full analysis; may take a while)...
Base: main @ a1b2c3d (cached). Head: (working tree) @ ?.
Nodes: 1402 -> 1420 (+22/-4). Edges: 5744 -> 5811 (+91/-24/~13).
Added nodes (22):
+ MyApp.Services.OrderReconciler (Class)
...
Removed nodes (4):
- MyApp.Services.LegacyReconciler (Class)
...
Added edges (91):
+ MyApp|MyApp.Domain -Calls-> MyApp|MyApp.Infrastructure (weight=6)
...
Removed edges (24):
...
Changed edges (13):
~ MyApp|MyApp.Api -Uses-> MyApp|MyApp.Services (weight 31 -> 47)
...
Coupling changes (18):
MyApp.Domain fanIn 6 -> 6 fanOut 0 -> 1
...
--base ist Pflicht, --head ist standardmäßig der Arbeitsbaum. Der häufige
Fall, hat mein Branch gegenüber main etwas verschlechtert, kostet damit
nichts Zusätzliches jenseits der Base-Seite. Die Knotenidentität ist allein
die Id, eine Umbenennungserkennung findet nicht statt: Eine Umbenennung
erscheint als ein entfernter plus ein hinzugefügter Knoten, nie als ein
einzelner Eintrag "umbenannt". Die Kantenidentität ist
(sourceId, targetId, kind). Eine reine Gewichtsänderung, etwa ein Typ, der
vierzig zusätzliche Aufrufstellen bei einem anderen bekommt, ist ein echtes
Signal und wird als changedEdges-Eintrag gemeldet statt in "unverändert"
verschmolzen zu werden.
Ein Cache-Miss auf --base ist nicht kostenlos. Der Vergleich gegen einen
Commit, der nicht Ihr aktueller Checkout ist, erfordert, ihn als separaten
Arbeitsbaum zu materialisieren, seine NuGet-Pakete wiederherzustellen und eine
vollständige Solution-Analyse durchzuführen. Das dauert je nach
Solution-Größe Sekunden bis Minuten, weshalb die CLI vorher einen
Fortschrittshinweis auf stderr ausgibt. Wiederholte Aufrufe gegen dieselbe
--base werden aus einem Cache unter .codecharter/cache/graph/ bedient
(bereits durch den bestehenden .codecharter/cache/-Eintrag in .gitignore
abgedeckt) und sind schnell. --no-cache umgeht den Cache zu
Diagnosezwecken.
codecharter graph diff --base main --level namespaces --top 10
--level (Standard types) steuert die Granularität der
Kopplungsänderungs-Zusammenfassung, dasselbe dreiwertige Enum wie bei
cycles/hubs. Hinzugefügte und entfernte Knoten bleiben immer auf
Typ-Ebene, dort liegt die Evidenz. Bei namespaces/projects werden
zusätzlich die Listen hinzugefügter, entfernter und geänderter Kanten auf
dieselbe Granularität hochgerechnet: Hundert Ergänzungen auf Typ-Ebene
zwischen zwei Namespaces sind ein architektonischer Fakt, nicht hundert. Die
Gewichte einer geänderten Kante sind die aufsummierten Gewichte beider Seiten,
sodass das gemeldete Delta genau der Summe der zugrunde liegenden Einzeldeltas
entspricht. couplingChanges ist nach absoluter Änderung des Gesamtgrads
absteigend sortiert und auf --top (Standard 20) begrenzt.
Optionen
[pfad] gilt für alle sieben Subcommands: der Pfad zu einer .sln-, .slnx-
oder .csproj-Datei, oder zu einem zu durchsuchenden Verzeichnis. Ohne
Angabe, oder wenn ein Verzeichnis angegeben wird, wird automatisch in diesem
Verzeichnis gesucht (ohne Angabe im aktuellen).
--format und --output-file gelten ebenfalls für alle sieben. --format
akzeptiert text (Standard), json, mermaid und dot,
Groß-/Kleinschreibung egal; cycles, hubs, paths und diff akzeptieren
nur text/json, da keiner der vier einen Diagramm-Writer hat. Mit
--format json verwendet die Ausgabe dieselbe Property-Schreibweise,
dieselbe Enum-Schreibweise und denselben Umgang mit Null-Werten wie die
MCP-Tools (camelCase-Properties, PascalCase-Enum-Werte, Null-Properties
werden weggelassen), sodass ein Skript beide Ausgaben für jedes gemeinsame
Feld gleich verarbeiten kann. Die MCP-Ausgabe ist eine Obermenge und liefert
zusätzlich durationMs und isWorkspaceWarm, was die CLI-JSON-Ausgabe nicht
hat. --format mermaid/dot rendert dasselbe Ergebnis stattdessen als
Diagramm, siehe
Ein Diagramm rendern: --format mermaid/dot.
--max-tokens gilt ebenfalls für alle sieben, siehe
Ergebnisgröße begrenzen: --max-tokens.
Dasselbe gilt für --edge-kinds: eine kommagetrennte Liste aus
inherits/implements/uses/calls/attributes/creates (Groß-/Kleinschreibung egal,
standardmäßig alle sechs), die einschränkt, welche Kanten enthalten sind, siehe
Kantenbedeutung und Grenzen. paths nutzt
--edge-kinds auf --level methods gar nicht, siehe
paths und Methodenebenen-Ehrlichkeit.
overview
| Option | Standard | Beschreibung |
|---|---|---|
--level <projects\|namespaces> |
namespaces |
Aggregationsebene. |
--top <n> |
50 |
Maximale Anzahl aggregierter Kanten, absteigend nach Gewicht (Referenzanzahl) sortiert. |
--max-tokens <n> |
nicht gesetzt | Ungefähres Größenbudget der Ausgabe in Tokens, zusätzlich zu --top. |
--edge-kinds <liste> |
alle sechs | Kommagetrennt: inherits/implements/uses/calls/attributes/creates. |
--format <text\|json\|mermaid\|dot> |
text |
Ausgabeformat. mermaid/dot rendern stattdessen ein Diagramm. |
--output-file <pfad> |
stdout | Ausgabe in diese Datei schreiben statt nach stdout. |
neighborhood
| Option | Standard | Beschreibung |
|---|---|---|
<target> |
– | Erforderlich. Der aufzulösende Typ: vollständiger Name, Namenssuffix oder Quelldateipfad. |
--depth <1-3> |
1 |
Breitensuche-Schritte ausgehend vom Fokus-Typ. Auf 1 bis 3 begrenzt. |
--direction <out\|in\|both> |
both |
Welche Kantenrichtungen expandiert werden. |
--signatures <focus\|all\|none> |
focus |
Für welche Knoten vollständige Member-Signaturen gerendert werden: nur der Fokus-Typ, der Fokus-Typ und jeder direkte Nachbar in Distanz 1, oder keiner. |
--max-types <n> |
50 |
Maximale Anzahl Nachbar-Typen, bevor gekürzt wird. |
--max-tokens <n> |
nicht gesetzt | Ungefähres Größenbudget der Ausgabe in Tokens, zusätzlich zu --max-types. |
--edge-kinds <liste> |
alle sechs | Kommagetrennt: inherits/implements/uses/calls/attributes/creates. |
--format <text\|json\|mermaid\|dot> |
text |
Ausgabeformat. mermaid/dot rendern stattdessen ein Diagramm. |
--output-file <pfad> |
stdout | Ausgabe in diese Datei schreiben statt nach stdout. |
impact
| Option | Standard | Beschreibung |
|---|---|---|
<target> |
– | Erforderlich. Der Typ, dessen eingehende Auswirkung (oder, mit --source, dessen Abhängigkeitspfade) berechnet wird. |
--source <typ> |
keine | Wenn angegeben, wechselt die Abfrage vom eingehenden transitiven Abschluss zu den kürzesten Abhängigkeitspfaden von diesem Typ zu target. |
--depth <n> |
10 |
Maximale Anzahl an Schritten für die Suche: begrenzt die Distanz des Abschlusses und den Suchradius der Pfadsuche. |
--max-nodes <n> |
200 |
Maximale Anzahl besuchter Knoten, bevor gekürzt wird. |
--max-paths <n> |
5 |
Maximale Anzahl zurückgegebener kürzester Pfade, wenn --source angegeben ist. |
--no-through-implementations |
aus | Durchläuft nur deklarierte Kantenrichtungen (eine Implements-Kante von einem Typ zu seinem Interface, nie umgekehrt). Siehe unten. |
--max-tokens <n> |
nicht gesetzt | Ungefähres Größenbudget der Ausgabe in Tokens, zusätzlich zu --max-nodes/--max-paths. |
--edge-kinds <liste> |
alle sechs | Kommagetrennt: inherits/implements/uses/calls/attributes/creates. |
--format <text\|json\|mermaid\|dot> |
text |
Ausgabeformat. mermaid/dot rendern stattdessen ein Diagramm. |
--output-file <pfad> |
stdout | Ausgabe in diese Datei schreiben statt nach stdout. |
DI-Code hängt vom Interface ab, nicht von dessen Implementierung:
ChatService implementiert IChatService, also verläuft die Kante
ChatService --Implements--> IChatService. Wird sie nur in ihrer deklarierten
Richtung verfolgt, erreicht ein Pfad von einem Konsumenten von IChatService
nie ChatService (oder das, wovon ChatService selbst abhängt), und der
eingehende Closure von ChatService übersieht jeden Konsumenten, der
ausschließlich IChatService referenziert. Deshalb ist das rückwärts
gerichtete Durchlaufen dieser Kante, vom Interface zu jedem Typ, der es
implementiert, standardmäßig eingeschaltet, zusätzlich zu jeder Kante, die die
Abfrage ohnehin schon verfolgt. Jeder Schritt über eine rückwärts gelaufene
Kante trägt das Label implemented by, das sich vom vorwärts gerichteten
Implements unterscheidet. Die Ausgabe verbirgt damit nie, dass die Suche
einen Schritt genommen hat, den die Kanten für sich genommen nicht deklarieren,
und meldet zusätzlich, wie viele Knoten nur über einen rückwärts gelaufenen
Schritt erreichbar waren:
codecharter graph impact ChatService --source OrdersController
Paths (1):
Path 1:
Acme.Web.OrdersController (class) src/Acme.Web/OrdersController.cs:15
Acme.Domain.IChatService (interface) src/Acme.Domain/IChatService.cs:4 [Uses]
Acme.Domain.ChatService (class) src/Acme.Domain/ChatService.cs:9 [implemented by]
Mit --no-through-implementations durchläuft die Suche stattdessen nur
deklarierte Kantenrichtungen. neighborhood und overview kennen diese
Option nicht, sie rendern Implements/ImplementedBy bereits als getrennte
Richtungen.
Beim Umstieg von einer älteren Version: Jedes Skript und jeder CI-Job, der sich darauf verlässt, dass
graph impactan der Interface-Grenze stehen bleibt, braucht jetzt--no-through-implementations. Closures können wachsen, ein mit--max-nodesbegrenzter Closure kann jetzt kürzen, wo er vorher vollständig war, und die Pfadsuche kann einen anderen kürzesten Pfad liefern. Die alte Option--through-implementationswird für eine Version lang weiterhin akzeptiert, wirkt sich aber nicht mehr aus.
target und --source werden in neighborhood und impact auf dieselbe Art
aufgelöst. codecharter graph respektiert dieselbe
exclude-Liste aus
.codecharter/config.yml, die auch codecharter analyze verwendet: Eine
Datei, die Sie von der Regelanalyse ausnehmen, bleibt auch im Graphen außen
vor.
cycles
| Option | Standard | Beschreibung |
|---|---|---|
--level <types\|namespaces\|projects> |
namespaces |
Aggregationsebene. types ist hier gültig, bei overview nicht. |
--target <typ> |
keiner | Beschränkt das Ergebnis auf die einzelne Komponente, die diesen Typ enthält. --max-components wird dann ignoriert. |
--max-components <n> |
20 |
Maximale Anzahl zurückgegebener Komponenten, größte zuerst. Ignoriert, wenn --target angegeben ist. |
--max-members-per-component <n> |
25 |
Maximale Anzahl Mitglieder (und Closing Edges) pro Komponente, bevor gekürzt wird. |
--min-size <n> |
2 |
Minimale zu meldende Komponentengröße. |
--max-tokens <n> |
nicht gesetzt | Ungefähres Größenbudget der Ausgabe in Tokens. Die Akkumulationseinheit ist eine ganze Komponente, nie eine halb befüllte. |
--edge-kinds <liste> |
alle sechs | Kommagetrennt: inherits/implements/uses/calls/attributes/creates. |
--format <text\|json> |
text |
Ausgabeformat. Noch kein mermaid/dot, siehe Optionen oben. |
--output-file <pfad> |
stdout | Ausgabe in diese Datei schreiben statt nach stdout. |
Anders als bei neighborhood/impact ist ein nicht aufgelöstes oder
mehrdeutiges --target hier kein Aufruffehler. Es wird auf dieselbe Art
gemeldet wie ein Target, das zu keinem Zyklus gehört: als Hinweis, bei
Mehrdeutigkeit zusammen mit den Kandidaten, und mit Exit-Code 0. Beide Fälle
bedeuten, dass es für dieses Target nichts zu zeigen gibt.
hubs
| Option | Standard | Beschreibung |
|---|---|---|
--level <types\|namespaces\|projects> |
types |
Aggregationsebene. Anders als bei cycles ist types der Standard, denn welcher Typ trägt ist die Frage, die ein Agent zuerst stellt. |
--sort-by <fan_in\|fan_out\|instability> |
fan_in |
Sortierschlüssel. total, weightedFanIn und weightedFanOut bleiben unabhängig von --sort-by als Felder auf jeder Zeile verfügbar. |
--scope <präfix> |
keiner | Namespace- oder Assembly-Präfix (Groß-/Kleinschreibung egal), das einschränkt, welche Knoten in die Rangliste kommen. Die Grade bleiben über den gesamten Graphen berechnet. |
--top <n> |
20 |
Maximale Anzahl zurückgegebener Hub-Einträge, sortiert nach --sort-by, bei Gleichstand nach Gesamtgrad absteigend, dann nach Knoten-Id. |
--min-degree <n> |
1 |
Minimaler fanIn + fanOut, den ein Knoten braucht, um gelistet zu werden. |
--max-tokens <n> |
nicht gesetzt | Ungefähres Größenbudget der Ausgabe in Tokens. Die Akkumulationseinheit ist ein ganzer Hub-Eintrag, nie ein halb befüllter. |
--edge-kinds <liste> |
alle sechs | Kommagetrennt: inherits/implements/uses/calls/attributes/creates. Eingeschränkt auf calls beantwortet es, welcher Typ von den meisten Stellen aufgerufen wird; eingeschränkt auf inherits,implements beantwortet es, welche Abstraktion die meisten Implementierer hat. |
--format <text\|json> |
text |
Ausgabeformat. Kein mermaid/dot, eine Rangliste ist kein Graph. |
--output-file <pfad> |
stdout | Ausgabe in diese Datei schreiben statt nach stdout. |
paths
| Option | Standard | Beschreibung |
|---|---|---|
<from> |
– | Erforderlich. Der aufzulösende Start-Typ oder die Start-Methode. |
--to <target> |
– | Erforderlich. Der aufzulösende Ziel-Typ oder die Ziel-Methode. |
--must-pass-through <target> |
keiner | Wenn angegeben, wird gefragt, ob jeder Pfad von from zu --to über diesen Knoten läuft, statt der reinen Erreichbarkeit. |
--level <types\|methods> |
types |
Traversierungsgranularität. methods baut bei Bedarf einen induzierten Teilgraphen aus Methodenaufruf-Kanten, siehe paths und Methodenebenen-Ehrlichkeit. |
--max-tokens <n> |
nicht gesetzt | Ungefähres Größenbudget der Ausgabe in Tokens; begrenzt zusätzlich, wie viele Knoten die Suche erkunden darf, bevor die Antwort undecided wird. |
--edge-kinds <liste> |
alle sechs | Kommagetrennt: inherits/implements/uses/calls/attributes/creates. Wird auf --level methods ignoriert. |
--format <text\|json> |
text |
Ausgabeformat. Kein mermaid/dot, ein Dreizustands-Ergebnis plus eine Gegenbeispiel-Kette ist kein Diagramm. |
--output-file <pfad> |
stdout | Ausgabe in diese Datei schreiben statt nach stdout. |
from, --to und --must-pass-through werden auf --level types auf
dieselbe Art aufgelöst wie das target von neighborhood (vollständiger
Name, Namenssuffix oder Quelldateipfad). Auf --level methods werden sie
gegen die kanonische Signatur einer Methode, ihren um den deklarierenden Typ
ergänzten Namen oder ein bloßes Namenssuffix aufgelöst, in derselben
Reihenfolge exakt-dann-suffix, die nie rät; ein mehrdeutiger oder unbekannter
Name gibt jede Kandidaten-Signatur (falls vorhanden) nach stderr aus und
beendet sich mit Exit-Code 0 und dem Ergebnis undecided, wie cycles,
nicht mit dem Aufruffehler von neighborhood/impact.
paths und Methodenebenen-Ehrlichkeit
Auf --level methods liest paths keine zweite, persistierte Graph-Ebene.
Es baut den Methodenaufruf-Teilgraphen bei Bedarf aus demselben analysierten
Modell und durchläuft dabei calls-, implements-, implemented by- und
overridden by-Kanten, sodass ein Aufruf über ein Interface (die übliche
DI-Form: ein Aufrufer hält IOrderService, nicht OrderService) nicht an der
Interface-Methode endet. Das hält den persistierten Graphen bei genau einer
Granularität (Typen), weshalb --level methods allein zu diesem Subcommand
gehört.
Ein true-Ergebnis auf Methodenebene trägt einen Vorbehalt, den ein
Ergebnis auf Typebene nicht hat. calls-Kanten stammen aus aufgelösten
Methodenrumpf-Aufrufen, derselben Direktaufruf-Analyse, die
overview/neighborhood/impact für ihre calls-Kanten verwenden, was eine
Unterapproximation ist: ein Aufruf, der nur über ein Delegate, Reflection
oder einen von einem Source-Generator erzeugten Rumpf erfolgt, ist für diese
Analyse unsichtbar. Das bedeutet, ein false auf Methodenebene (ein
Gegenbeispiel-Pfad wurde gefunden) ist immer belastbar, der gezeigte Pfad
existiert wirklich, aber ein true auf Methodenebene (jeder gefundene Pfad
läuft über den Engpass) sagt nur "jeder Pfad, den diese Analyse sehen kann,
tut das"; eine Route über eine unsichtbare Kante könnte weiterhin existieren.
Das Ergebnis trägt bei --level methods mit einem true-Ergebnis immer
einen entsprechenden Hinweis; die strukturellen Kanten auf Typebene haben
diese Lücke nicht, ihre Ergebnisse tragen keinen solchen Vorbehalt.
diff
| Option | Standard | Beschreibung |
|---|---|---|
--base <ref> |
Pflicht | Referenz, gegen die verglichen wird. |
--head <ref> |
Arbeitsbaum | Referenz, die verglichen wird. |
--level <types\|namespaces\|projects> |
types |
Granularität der Kopplungsänderungs-Zusammenfassung und des Kanten-Rollups. Knotenlisten bleiben immer auf Typ-Ebene; Kantenlisten werden bei namespaces/projects mit hochgerechnet. |
--max-nodes <n> |
50 |
Maximale Einträge pro Bucket hinzugefügter/entfernter Knoten. |
--max-edges <n> |
100 |
Maximale Einträge pro Bucket hinzugefügter/entfernter/geänderter Kanten. |
--top <n> |
20 |
Maximale Anzahl Einträge der Kopplungsänderungs-Zusammenfassung, sortiert nach absoluter Änderung des Gesamtgrads absteigend. |
--no-cache |
aus | Umgeht den Disk-Cache unter .codecharter/cache/graph/ (Diagnosezwecke). |
--max-tokens <n> |
nicht gesetzt | Ungefähres Größenbudget der Ausgabe in Tokens. |
--edge-kinds <liste> |
alle sechs | Kommagetrennt: inherits/implements/uses/calls/attributes/creates. |
--format <text\|json> |
text |
Ausgabeformat. Kein mermaid/dot, ein Diff zwischen zwei Graphen ist selbst kein Diagramm. |
--output-file <pfad> |
stdout | Ausgabe in diese Datei schreiben statt nach stdout. |
Ein Diagramm rendern: --format mermaid/dot
--format mermaid und --format dot sind auf cycles, hubs, paths und
diff ein Aufruffehler: Für cycles ist der Diagramm-Writer auf einen
Folgeschritt verschoben, für hubs ist eine Rangliste kein Graph, ein
Dreizustands-Ergebnis plus eine Gegenbeispiel-Kette ist kein Diagramm, und ein
Diff zwischen zwei Graphen ist selbst kein Diagramm. Alles Folgende gilt für
overview, neighborhood und impact.
Beide Formate rendern dieselbe Abfrage als Diagramm statt als Text oder JSON, sodass Sie es direkt in einen PR-Kommentar, ein Design-Dokument oder eine Graphviz-Pipeline einfügen können. Das gibt es nur in der CLI, siehe Mit einem KI-Assistenten für den Grund, warum die MCP-Tools immer JSON liefern.
codecharter graph overview --level namespaces --top 6 --format mermaid
%% CodeCharter graph overview — namespaces
%% Edge styles: thick = inherits, dashed = implements, solid = uses
graph TD
subgraph c0["Acme.Domain"]
n0["Orders"]
end
subgraph c1["Acme.Infrastructure"]
n1["Persistence"]
end
n0 -->|58| n1
codecharter graph neighborhood OrderService --format dot
// CodeCharter graph neighborhood — focus Acme.Domain.OrderService
digraph codecharter {
rankdir=TB;
n0 [label="OrderService", shape=box, style=bold];
n1 [label="IOrderService", shape=box];
n0 -> n1 [style=dashed, arrowhead=onormal];
}
Für jedes Diagramm gilt:
- Knoten-Ids sind synthetisch (
n0,n1, ...) und werden in der Reihenfolge des Ergebnisses vergeben, nicht aus dem Typnamen abgeleitet. Zwei Namen, die auf dieselbe Kennung zusammenfielen (etwa zwei generische Instanzen), verschmelzen so nie zu einem Knoten. Das Label ist der kurze Typname, ergänzt um den Namespace in Klammern, wenn ein anderer Knoten im selben Diagramm denselben kurzen Namen trägt. - Die Kantenart bestimmt den Linienstil, damit das Diagramm ohne Legende
lesbar ist:
Inheritsist dick (mermaid==>) oder mit hohlem Dreieckskopf (DOTarrowhead=onormal),Implementsist gestrichelt,Usesist einfach,Callsist ein beschrifteter|calls|-Pfeil (mermaid) oder eine dickere Linie (DOTpenwidth=2). Ein rückwärts gelaufener Implements-Schritt (siehe oben) bekommt zusätzlich zum gestricheltenImplements-Stil das Labelimplemented byund sieht damit nie wie eine gewöhnliche vorwärts gerichteteImplements-Kante aus. - Ein Gewicht über 1 wird als Kantenlabel gerendert. Nur die aggregierten
Kanten von
overviewtragen ein Gewicht,neighborhood- undimpact-Diagramme zeigen daher nie eines. - Die Legende ist immer ein Kommentar (
%%///), nie ein Knoten des Graphen, damit ein nachgelagerter Parser sie nicht für einen Teil des Graphen halten kann. - Ein leeres Ergebnis rendert weiterhin ein syntaktisch gültiges, leeres
Diagramm (
graph TD/digraph codecharter {}) statt eines leeren Strings. overview --level namespacesgruppiert Knoten in ein Subgraph pro Assembly.--level projectsgruppiert nicht, dort ist ein Knoten pro Assembly bereits die Gruppe.- Das Dependents-Diagramm von
impact(ohne--source) zieht eine Kante von einem Distanz-1-Dependent direkt zum Ziel, weil das ein bekannter, echter Schritt ist. Tiefere Distanzen werden als nach Distanz gruppierte Knoten ohne Kante zu einem bestimmten Vorgänger gerendert, weil dieses Ergebnis nur meldet, dass ein Knoten das Ziel in N Schritten erreicht, nicht über welchen Typ in Distanz N-1. Für eine vollständig gezeichnete Kette verwenden Sie--source.
--output-file und die Anzahl-Budgets (--top, --max-types,
--max-nodes/--max-paths) funktionieren genau wie bei text/json.
Verwenden Sie ein niedrigeres --top/--max-types/--max-nodes als bei
JSON, denn ein Diagramm mit hunderten Knoten ist unabhängig vom Format nicht
lesbar. --max-tokens wird bei einem Diagramm-Format nicht unterstützt,
siehe den nächsten Abschnitt.
Ergebnisgröße begrenzen: --max-tokens
Jedes Anzahl-Budget (--top, --max-types, --max-nodes/--max-paths)
begrenzt, wie viele Elemente ein Ergebnis enthält. Das ist die falsche
Einheit für das, was das Kontextfenster eines Agenten tatsächlich begrenzt:
Zwei Aufrufe mit identischer Anzahl können sich um eine Größenordnung in der
Größe unterscheiden. neighborhood --signatures all rendert vollständige
Member-Signaturen für jeden Nachbarn, dasselbe --max-types mit
--signatures none ist winzig. --max-tokens begrenzt stattdessen die
ungefähre Größe der Ausgabe, zusätzlich zu einem bereits geltenden
Anzahl-Budget: Es zählt, welche Grenze zuerst greift.
Die Schätzung wird aus einer kompakten JSON-Serialisierung mit rund 4 Bytes
pro Token berechnet. Sie ist eine Schätzung, kein echter Tokenizer-Wert, und
die --format json-Ausgabe der CLI ist eingerückt und daher etwas größer als
die Schätzung; ein Skript, das CLI-JSON in ein Kontextfenster pipet, sollte
diesen Unterschied einkalkulieren. Jedes Ergebnis meldet estimatedTokens,
unabhängig davon, ob gekürzt wurde oder nicht, damit ein Aufrufer den nächsten
Aufruf anhand des letzten kalibrieren kann.
Wurde ein Ergebnis gekürzt, meldet es außerdem boundBy: eine geordnete Liste
der Optionsnamen, die das Ergebnis tatsächlich begrenzt haben, etwa
["max_types", "max_tokens"], sowie einen gröberen truncationReason
(None/CountBudget/TokenBudget/Both). Das ist relevant, weil ein
Anzahl-Budget schon während der zugrunde liegenden Suche greifen kann, bevor
--max-tokens überhaupt zum Tragen kommt: Nennt boundBy eine
Anzahl-Option, bringt ein höheres --max-tokens allein nichts mehr, und der
begleitende Hinweis sagt das explizit.
--max-tokens kürzt immer ganze Elemente, nie teilweise:
overviewverwirft die Kanten mit dem niedrigsten Gewicht.neighborhoodsammelt Nachbarn in der Reihenfolge der Breitensuche, also in der Reihenfolge, die die entferntesten Nachbarn zuerst preisgibt, und gruppiert die Überlebenden erst danach nach Relation. Ein gekürztes Ergebnis verliert also entfernte Nachbarn, nie eine ganze Relations-Kategorie. Ist--signaturesaufalloderfocusgesetzt und hat das Token-Budget das Ergebnis begrenzt, nennt der Hinweis zusätzlich den günstigeren Hebel: Ein engerer--signatures-Bereich liefert innerhalb desselben Budgets deutlich mehr Nachbarn.impactverwirft ganze Distanz-Gruppen nach Knoten im Closure-Modus, oder ganze kürzeste Pfade im Pfad-Modus. Ein zurückgegebener Pfad ist immer die vollständige Kette von Quelle zu Ziel, nie mittendrin abgeschnitten.cyclesverwirft ganze Komponenten, größte zuerst, nie eine halb befüllte.hubsverwirft nach der--top-Kürzung ganze Hub-Einträge, in Rangfolge, die niedrigst-platzierten Überlebenden zuerst.pathsliefert nie ein gekürztestrue/false: Ist das Knoten-Budget erschöpft, bevor die Suche abschließen kann, wird die Antwort stattdessenundecided/nullmitisExhaustive: false.
--max-tokens ist standardmäßig nicht gesetzt, es gilt dann kein
Größenlimit. Ein Wert von 0 oder darunter ist ein Aufruffehler, nicht
"unbegrenzt". Die Option wird nicht zusammen mit
--format mermaid/dot unterstützt, da ein mitten im Graphen
abgeschnittenes Diagramm syntaktisch kaputt wäre; ein Anzahl-Budget bleibt
dort der richtige Hebel für die Diagrammgröße.
Exit-Codes
| Code | Bedeutung |
|---|---|
0 |
Erfolg. |
2 |
Aufruffehler: ein nicht auflösbarer Pfad, ein nicht erkannter Optionswert, --max-tokens ≤ 0 oder in Kombination mit --format mermaid/dot, oder (nur bei neighborhood/impact) ein target/--source, das auf keinen oder mehr als einen Knoten aufgelöst wurde. Bei einem mehrdeutigen oder nicht aufgelösten Target werden die gefundenen Kandidaten (falls vorhanden) nach stderr ausgegeben. cycles und paths nutzen diesen Code bei einem nicht aufgelösten oder mehrdeutigen Target nie: sie geben die Kandidaten nach stderr aus und beenden sich mit 0 und einem undecided-/No-op-Ergebnis, siehe cycles oben. hubs hat kein Target. Bei diff auch für ein fehlendes --base. Bei paths auch für ein fehlendes --to. |
3 |
Das aufgelöste Analyseziel konnte nicht analysiert werden, bei diff auch eine nicht auflösbare --base/--head-Referenz oder eine fehlgeschlagene Materialisierung der Base-Seite (etwa dotnet restore offline). |
Die Ausgabe geht immer nach stdout (oder in --output-file), jeder Hinweis
nach stderr, einschließlich des Erinnerungssatzes zur Kantenbedeutung, den
jedes Ergebnis trägt. Das ist dieselbe Trennung wie bei
codecharter coverage. Wie das zu den übrigen
Befehlen passt, steht unter Exit-Codes.
Mit einem KI-Assistenten
Dieselben sieben Abfragen stehen KI-Coding-Assistenten als MCP-Tools zur
Verfügung, neben den Analyse- und Coverage-Tools:
graph_overview, graph_neighborhood, graph_impact, graph_cycles,
graph_hubs, graph_paths und graph_diff. Die Parameter sind das
snake_case-Gegenstück der obigen CLI-Optionen (max_types, max_nodes,
max_paths, max_components, max_members_per_component, min_size,
sort_by, min_degree, max_tokens, source, base, head, max_edges,
must_pass_through, level), mit denselben Standardwerten, und
target/source/scope/from/to/must_pass_through werden auf dieselbe
Art aufgelöst. Die ersten sechs teilen sich den warmen Per-Workspace-Cache der
Analyse-Tools: Der erste Aufruf nach dem Öffnen oder Ändern eines Workspace
ist ein Cold Start und liefert statt eines Ergebnisses einen Retry-Hinweis,
die folgenden Aufrufe sind schnell.
graph_diff verhält sich anders. Es hat keinen no_cache-Parameter, dieser
Diagnose-Notausgang bleibt CLI-exklusiv, weil er einem Client erlauben würde,
bei jedem Aufruf eine unbegrenzte Neuanalyse auszulösen. Ein Cache-Miss auf
der Base-Seite blockiert den Tool-Aufruf zudem nicht: Das Materialisieren und
Analysieren einer Revision läuft im Hintergrund, und der erste Aufruf für ein
gegebenes base/head-Paar liefert sofort isPending: true statt eines
Retry-Hinweises. Rufen Sie mit denselben base/head/path-Werten erneut
auf, sobald Sie annehmen, dass es fertig ist. Ein wiederholter Aufruf für eine
bereits materialisierte base wird aus einem Per-Commit-Disk-Cache bedient
und ist schnell, wie die warmen Aufrufe der anderen fünf Tools.
Die MCP-Tools liefern immer das getypte JSON-Ergebnis. Ein Agent, der das JSON bereits hat, kann daraus jedes
gewünschte Diagramm selbst bauen oder für die kanonische Variante
codecharter graph … --format mermaid aufrufen.
Ein Agent nutzt diese Tools typischerweise, bevor er unbekannten Code anfasst:
graph_hubs zuerst, um zu sehen, was trägt; graph_overview, um zu sehen, wo
die Kopplung liegt; graph_neighborhood auf der Datei, die er gerade
bearbeiten will, um zu sehen, was sie berührt; graph_impact, um vor der
Änderung zu prüfen, was sonst noch betroffen wäre; graph_cycles vor
einer Aufgabe wie "das in ein eigenes Paket auslagern" oder "die Architektur
aufräumen", bei der der eigentliche Blocker oft ein Zyklus ist, den keine
einzelne Kanten-Inspektion gefunden hätte; und graph_paths, wenn die Frage
enger ist als beides: "wenn ich diesen einen Typ lösche oder absichere, ist
A dann wirklich von B abgeschnitten, oder gibt es eine Route drumherum",
was der Kürzeste-Pfad-Modus von graph_impact nicht beantworten kann, denn
ein kürzester Pfad beweist Erreichbarkeit, sagt aber nichts über jeden
anderen Pfad. Weil graph_neighborhood, graph_impact und graph_paths
auch einen Quelldateipfad direkt entgegennehmen, muss ein Agent, der bereits
eine Datei geöffnet hat, nicht erst den vollständigen Typnamen nachschlagen.
Kantenbedeutung und Grenzen
Jedes Ergebnis, ob CLI oder MCP, trägt Hinweise, die genau beschreiben, was seine Kanten sind und was nicht. Es gibt drei Kantenfamilien:
- Strukturelle Kanten sind signatur-basiert: der deklarierte Basistyp eines Typs, seine direkt implementierten Interfaces sowie jeder Typ, der in einem Konstruktor- oder Methodenparameter, einem Methoden-Rückgabetyp, einer Property, einem Feld oder einem Event-Delegate-Typ referenziert wird. Das reicht aus, um die Kopplung zu rekonstruieren, die DI-basierte, geschichtete Codebasen interessiert: wer wovon über die öffentliche Form abhängt.
calls-Kanten stammen aus Methodenrumpf-Aufrufen, die zu einem eindeutigen Methodensymbol aufgelöst wurden, und werden dem deklarierenden Typ des Aufgerufenen zugeordnet, nicht dessen Methode; dies ist ein Graph auf Typebene, ohne Methodenknoten. Sie sind standardmäßig enthalten und decken genau die Kategorie ab, die strukturelle Kanten nicht sehen können: Ein statischer Helfer (PathNormalizer.Normalize(p)), eine Extension-Methode oder ein konkreter Typ, der komplett innerhalb eines Methodenrumpfs erzeugt und aufgerufen wird, erreicht nie eine Signatur. Einecalls-Kante ist dort oft der einzige Weg zu einer eingehenden Kante. Das Gewicht ist eine Aufrufstellen-Anzahl, kein Ort:weight: 6bedeutet sechs aufgelöste Aufrufstellen zu diesem Typ, nicht dass das Ergebnis sagen kann, wo sie stehen.attributes- undcreates-Kanten stammen ebenfalls aus aufgelösten Bindungen statt aus Signaturen.attributesverbindet einen Typ (oder ein auf ihm deklariertes Member) mit der Klasse eines angewendeten Attributs; Quelle der Kante ist immer der deklarierende Typ, ein Attribut auf Member-Ebene wird also dem Knoten des deklarierenden Typs zugeordnet.createsverbindet einen Typ mit jedem anderen Typ, den seine Methoden- oder Konstruktorrümpfe mitnew T(...)(oder der zieltypisierten Formnew(...)) erzeugen und der zu einem eindeutigen Konstruktorsymbol aufgelöst wurde. Beide sind standardmäßig enthalten und nutzen das Gewicht auf dieselbe Art wiecalls, also als Stellenzahl, nicht als Ort. Eine Attributklasse oder ein erzeugter Typ außerhalb der Solution, oder mit mehrdeutigem gestripptem Namen innerhalb der Solution, erzeugt keine Kante.
Mit --edge-kinds/edge_kinds schränken Sie ein, welche Kantenarten
enthalten sind (inherits, implements, uses, calls, attributes,
creates, kommagetrennt, Groß-/Kleinschreibung egal; standardmäßig alle
sechs). Der Filter entfernt nur Kanten. Ein Knoten, dessen einzige Kante
herausgefiltert wurde, bleibt in der Knotenliste und den Summen enthalten,
sodass Zahlen über verschiedene --edge-kinds-Werte hinweg vergleichbar
bleiben.
Was calls weiterhin nicht sieht, und wie das Ergebnis das sagt:
- Mehrdeutige oder nicht-methodenbasierte Aufrufe. Eine
Überladungsauflösung, die sich nicht auf einen Kandidaten festlegen kann,
oder ein
dynamic-Aufruf, liefert kein Symbol zum Auflösen. Dercalls-Hinweis nennt die Anzahl solcher Aufrufe. Ein Delegate-,Action- oderFunc<>-Aufruf gehört nicht dazu, er wird wie jeder andere Aufruf erfasst und zeigt meist auf einen externen Delegate-Typ, der so oder so keine Kante beiträgt. - Mehrdeutige Zieltyp-Namen. Ein aufgelöster Aufruf, dessen gestrippter
Zieltyp-Name mehr als einen Solution-Typ trifft (etwa ein
Result/Result<T>-Geschwisterpaar), lässt sich keinem eindeutigen Knoten zuordnen. Er wird verworfen und separat gezählt, mit eigenem Hinweissatz, da dies ein anderer Verlust als ein nicht aufgelöster Aufruf ist. - Multi-Targeting-Solutions. Ein Projekt mit
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>erzeugt eine Projektinstanz pro TFM; nur die Methodenrümpfe des zuerst analysierten TFM fließen incalls-Kanten ein, sodass ein Aufruf innerhalb von#if NETFRAMEWORK-Code für ein anderes Target nicht abgebildet wird. Ein Hinweis nennt das, wenn es zutrifft. - Externe Typen sind keine Knoten. Ein Parameter-, Basis- oder Aufrufzieltyp aus einem NuGet-Paket oder der .NET-Runtime ist nicht Teil des Graphen, nur Typen, die in der analysierten Solution deklariert sind.
- Namespaces sind pro Assembly. Derselbe Namespace-Name, in zwei Projekten
deklariert, erzeugt bei
--level namespaceszwei getrennte Namespace-Knoten; die Knoten-Id vonoverviewkodiert beides. - Multi-Targeting-Projekte werden für Knoten nur einmal gezählt. Jede
TFM-Kopie eines Typs fällt auf einen einzigen Knoten zusammen, statt
Knotenzahlen und strukturelle Kantengewichte pro TFM zu vervielfachen. Die
oben genannte Regel für
callsist die einzige Stelle, an der die TFM-Wahl noch eine Rolle spielt.
Nichts davon steht als Fußnote unter dem Ergebnis, sondern ist Teil jeder Antwort, sodass ein Agent oder ein Skript nie raten muss, ob eine Kantenmenge die ganze Wahrheit bedeutet.
Weiterführend
- MCP-Regel-Authoring: die vollständige Tool-Liste, einschließlich der sieben Graph-Tools.
- codecharter analyze und codecharter coverage: die beiden anderen CLI-Befehle, die dieselbe Solution lesen.
- Konfigurationsdatei: die
exclude-Liste, die sichcodecharter graphmitanalyzeteilt. - Guardrails für KI-generierten Code: wie CLI und MCP in eine KI-Coding-Loop passen.