city.json
The code city: one flat, sorted, JSON-shaped tree. No classes, no cycles, no Maps — it must survive JSON.stringify unchanged and arrive intact in a browser. Written by city; the same shape with an extra replay block is written by replay and by history --city.
Field names are those of CityModel in packages/city/src/city.ts.
Top level
| Field | Type | Meaning |
|---|---|---|
kind |
string | always codegraph.city/1 |
generatedBy |
string | always @codegraph/city |
view |
ViewDescriptor |
{name, filters} — the view the city was built under |
corpus |
{name, roots, repository?} |
display name, the model roots verbatim, and the union’s repository facts when every model that carries one agrees |
conventions |
object | what the renderer may assume; see below |
bindings |
ResolvedBinding[] |
the legend in machine form, one entry per bound channel |
districts |
District[] |
sorted by id |
buildings |
Building[] |
sorted by id |
arrows |
Arrow[] |
type-level dependencies, sorted by (from, to) |
districtArrows |
Arrow[] |
module-level dependencies between districts, from the analyzer’s fold at level: "module" under the same view |
roles |
{framework, values} |
present only with --framework: which framework spoke and the distinct roles it assigned, sorted |
diagnostics |
CityDiagnostics |
what the transform left out |
layout |
ResolvedLayout |
present only after --layout |
bounds |
Bounds |
present only after --layout: the whole ground plane |
replay |
CityReplay |
present only in a replay city |
conventions
| Field | Value | Meaning |
|---|---|---|
arrowAttachment |
"roof" |
arrows attach at the roof of each building, at both ends |
heightAxis |
"y" |
height grows along this axis |
groundPlane |
"xz" |
the plane the districts tile |
units |
"city" |
abstract city units — not metres, not pixels; only ratios mean anything |
bindings[] — ResolvedBinding
| Field | Type | Meaning |
|---|---|---|
channel |
"height" | "footprint" |
|
metric |
string | the resolved metric name (loc, sum:cyclomatic, …) |
unit |
string | what one unit of the value is, for the legend |
describe |
string | the metric source’s own one-line description |
scale |
"linear" | "sqrt" | "log" |
|
range |
{min, max} |
the dimension given to the smallest and largest measured value |
domain |
{min, max} |
observed across the buildings that had a value; absent when none did |
unmeasured |
integer | buildings the model could not measure on this channel |
See City metrics.
districts[] — District
| Field | Type | Meaning |
|---|---|---|
id |
EntityId | the module’s rendered id |
name |
string | undefined | |
kind |
string | the entity kind, verbatim from the model |
isStub |
boolean | |
identity |
{lang, module, symbol, disambiguator?} |
symbol is empty exactly when the element IS the module |
parent |
EntityId | the nearest ancestor module that is itself a district, when the model declares module containment; never derived from the id or the name |
buildings |
EntityId[] | sorted; every building whose type folds into this module |
footprintDemand |
number | total base area its buildings occupy, in city units² |
bounds |
{x, y, width, depth} |
after --layout |
buildings[] — Building
| Field | Type | Meaning |
|---|---|---|
id |
EntityId | |
name |
string | undefined | |
kind |
string | the entity kind, verbatim: class, interface, enum… |
isStub |
boolean | |
district |
EntityId | |
identity |
{lang, module, symbol, disambiguator?} |
|
source |
{file, span?} |
the anchor, for source links; absent when the model anchors nothing (stubs) |
role |
string | the architectural role a framework profile assigns, with --framework; absent means the framework says nothing about it, never “plain” |
height |
number | along the height axis |
footprint |
{width, depth} |
base rectangle |
metrics |
Record<string, number | null> |
raw measurements by metric name — the bound channels plus anything --carry asked for; null means the model does not say |
attributes |
{name, type?, value?}[] |
sorted by name; empty for stubs and memberless types, never absent |
operations |
{signature, parameters?}[] |
sorted by signature; a parameter is {name, type?} |
owner |
{name, share} |
dominant author of the element’s file, replay cities only and only when a history was joined |
position |
{x, y} |
after --layout: minimum corner of the footprint, absolute in city coordinates |
arrows[] and districtArrows[] — Arrow
| Field | Type | Meaning |
|---|---|---|
from, to |
EntityId | |
count |
integer | base edges aggregated into this arrow |
kinds |
EdgeKind[] | sorted |
provenances |
Provenance[] | sorted |
inferred |
boolean | true when at least one base edge is not declared — precomputed so the renderer cannot get the rule wrong |
crossDistrict |
boolean | true when the endpoints stand in different districts; trivially true on districtArrows |
feedback |
boolean | true when this dependency is in the minimum feedback set of its strongly connected component at its own fold level |
Buildings reference their district and districts list their buildings: both directions are stored in the artifact even though one is derivable, because a renderer walks both ways per frame. This is not the interchange model, where inverse indexes are never serialized.
diagnostics — CityDiagnostics
| Field | Type | Meaning |
|---|---|---|
unplacedBuildings |
EntityId[] | types whose module the model does not give; excluded, sorted |
droppedArrows |
integer | arrows dropped because an endpoint was not placed |
selfArrows |
integer | type-level self-dependencies, excluded |
droppedDistrictArrows |
integer | district arrows dropped because an endpoint module is not a district here |
selfDistrictArrows |
integer | module-level self-dependencies, excluded — internal cohesion, not an arrow |
unmeasured |
Record<string, number> |
per metric name, how many buildings the model could not measure |
fold |
{unfoldableEntities, droppedEdges, foldedEdges} |
passed through from the fold that produced the buildings |
layout — ResolvedLayout
| Field | Value on the default run | Meaning |
|---|---|---|
algorithm |
"shelf-rows" |
|
order |
"area-desc,id-asc" |
|
buildingGap |
2 |
street kept open between two buildings of one district |
districtPadding |
3 |
sidewalk between a district’s border and its outermost buildings |
districtGap |
6 |
avenue kept open between two districts |
replay — CityReplay
Present in a replay city only.
| Field | Type | Meaning |
|---|---|---|
clock |
"commits" | "revisions" |
what a tick IS: every commit of a mined history, or the sampled revisions of a temporal store |
ticks |
{hash, time, author, fix?}[] |
chronological; time is unix seconds |
series |
Record<string, [tick, height, heat?][]> |
building id → sparse keyframes. A keyframe holds until the next one; before the first the building does not exist (height 0), and height 0 later means the element is deleted |
coChange |
{a, b, support, confidence}[] |
logical-coupling arcs between buildings, mined from history — an INFERENCE, never a dependency. Absent when no history was joined |
Excerpt
codegraph city fixtures/java/expected/model.jsonl --layout, trimmed to one district, one building and one arrow of each kind:
{
"kind": "codegraph.city/1",
"generatedBy": "@codegraph/city",
"view": { "name": "all", "filters": [] },
"corpus": { "name": "src", "roots": ["fixtures/java/src"] },
"conventions": { "arrowAttachment": "roof", "heightAxis": "y", "groundPlane": "xz", "units": "city" },
"bindings": [
{ "channel": "height", "metric": "loc", "unit": "source lines",
"describe": "Lines the type's own source anchor spans (end - start + 1).",
"scale": "linear", "range": { "min": 1, "max": 40 }, "domain": { "min": 4, "max": 51 },
"unmeasured": 18 },
{ "channel": "footprint", "metric": "members", "unit": "entities",
"describe": "Base entities that folded into the building, the type included.",
"scale": "sqrt", "range": { "min": 2, "max": 20 }, "domain": { "min": 1, "max": 22 },
"unmeasured": 0 }
],
"districts": [
{ "id": "java:com.megacorp.ledger", "name": "com.megacorp.ledger", "kind": "package",
"isStub": true,
"identity": { "lang": "java", "module": "com.megacorp.ledger", "symbol": "" },
"buildings": ["java:com.megacorp.ledger/LedgerClient"],
"footprintDemand": 4,
"bounds": { "x": 46, "y": 84.673, "width": 8, "depth": 8 } }
],
"buildings": [
{ "id": "java:com.acme.order.adapter/LedgerAdapter", "name": "LedgerAdapter", "kind": "class",
"isStub": false, "district": "java:com.acme.order.adapter",
"identity": { "lang": "java", "module": "com.acme.order.adapter", "symbol": "LedgerAdapter" },
"source": { "file": "com/acme/order/adapter/LedgerAdapter.java", "span": [6, 15] },
"height": 5.979, "footprint": { "width": 8.029, "depth": 8.029 },
"metrics": { "loc": 10, "members": 5 },
"attributes": [{ "name": "trail", "type": "AuditTrail" }],
"operations": [
{ "signature": "<init>()", "parameters": [] },
{ "signature": "post(java.lang.Object)",
"parameters": [{ "name": "document", "type": "Object" }] }
],
"position": { "x": 24.013, "y": 28 } }
],
"arrows": [
{ "from": "java:com.acme.order.adapter/LedgerAdapter",
"to": "java:com.megacorp.ledger/LedgerClient",
"count": 1, "kinds": ["inheritance"], "provenances": ["declared"],
"inferred": false, "crossDistrict": true, "feedback": false }
],
"districtArrows": [
{ "from": "java:com.acme.order", "to": "java:com.acme.order.legacy",
"count": 2, "kinds": ["invocation", "reference"], "provenances": ["declared"],
"inferred": false, "crossDistrict": true, "feedback": false }
],
"diagnostics": {
"unplacedBuildings": ["java:com.acme.order.adapter/AuditTrail", "java:com.acme.order/Invoice"],
"droppedArrows": 2, "selfArrows": 12,
"droppedDistrictArrows": 0, "selfDistrictArrows": 3,
"unmeasured": { "loc": 18, "members": 0 },
"fold": { "unfoldableEntities": 10, "droppedEdges": 11, "foldedEdges": 177 }
},
"layout": { "algorithm": "shelf-rows", "order": "area-desc,id-asc",
"buildingGap": 2, "districtPadding": 3, "districtGap": 6 },
"bounds": { "x": 0, "y": 0, "width": 85.55, "depth": 96.673 }
}Why the city is a model rather than a picture: The city is a model.