diff --git a/README.md b/README.md
index 81c1223..e213764 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,51 @@ codegraph uninstall
---
+## Language Support
+
+Every language below gets the same treatment — full structural extraction and cross-file resolution into one graph, no per-language setup:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Per-language details — extensions, frameworks, and what exactly gets extracted — in [Supported Languages](#supported-languages).
+
+---
+
## Why CodeGraph?
When an AI agent needs to understand code — to answer a question or make a change — it discovers structure the slow way: grep, glob, and Read, one file at a time, rebuilding call paths and dependencies by hand. That's a pile of tool calls and round-trips before it even starts the real work.
diff --git a/assets/generate-language-tiles.py b/assets/generate-language-tiles.py
new file mode 100644
index 0000000..0e78fb4
--- /dev/null
+++ b/assets/generate-language-tiles.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python3
+"""Generate assets/languages/*.svg — the README "Language Support" icon grid.
+
+One tile per supported language: a paper card with the language's vector mark and
+its name centered underneath. Labels are converted to vector outlines (Archivo,
+same pipeline as generate-waitlist.py) so the SVG renders pixel-identical
+everywhere: GitHub loads README SVGs in "secure static mode", which blocks
+@font-face / web-font loading, so outlines are the only reliable way to ship the
+brand typeface. Every tile sits on the brand paper (#f7f6f2), so brand-colored
+marks — including near-black ones like Rust — stay legible on GitHub light *and*
+dark themes with no media-query tricks.
+
+Glyph sources (fetched at generation time, results checked in):
+ - simple-icons via https://cdn.simpleicons.org/ (CC0-1.0)
+ - devicon via jsDelivr, pinned @v2.16.0 (MIT) — Java, C#,
+ Objective-C, which simple-icons doesn't carry
+ - hand-drawn in this file: CFML, COBOL, VB.NET (no usable upstream mark)
+
+All trademarks/logos belong to their respective owners; they're used here only
+to indicate language support.
+
+Adding a language: append to LANGS, re-run, reference the new SVG in README.md
+with a fresh `?v=1`. If you change EXISTING tile bytes, bump that tile's `?v=N`
+in README.md in the same commit (GitHub caches README images aggressively).
+
+Requires: fonttools, brotli (pip install fonttools brotli), network access.
+Font: @fontsource-variable/archivo (latin, wght axis) from the landing-page package.
+Usage: python3 generate-language-tiles.py [--preview /path/to/preview.svg]
+"""
+import os
+import re
+import sys
+import urllib.request
+from fontTools.ttLib import TTFont
+from fontTools.varLib.instancer import instantiateVariableFont
+from fontTools.pens.svgPathPen import SVGPathPen
+from fontTools.pens.transformPen import TransformPen
+from fontTools.misc.transform import Transform
+
+# --- brand palette (mirrors generate-waitlist.py) ---------------------------
+PAPER = "#f7f6f2"
+HAIRLINE = "#d6d3c8"
+INK = "#16150f"
+
+FONT = os.path.join(
+ os.path.dirname(__file__),
+ "../../landing-page/node_modules/@fontsource-variable/archivo/files/archivo-latin-wght-normal.woff2",
+)
+
+# --- tile geometry (px) ------------------------------------------------------
+TILE = 104 # square tile, rx=8 card like the waitlist button
+GLYPH_BOX = 44 # logo box, horizontally centered
+GLYPH_TOP = 18
+LABEL_SIZE = 12.5
+LABEL_WT = 640
+LABEL_GAP = 12 # glyph box bottom -> label cap top
+TRACK = 0.15
+
+DEVICON = "https://cdn.jsdelivr.net/gh/devicons/devicon@v2.16.0/icons/{0}/{0}-{1}.svg"
+
+# (file slug, label, source) — README "Supported Languages" table order.
+# source: ("si", slug) simple-icons | ("devicon", name, variant) | ("custom", key)
+LANGS = [
+ ("typescript", "TypeScript", ("si", "typescript")),
+ ("javascript", "JavaScript", ("si", "javascript")),
+ ("arkts", "ArkTS", ("si", "harmonyos")),
+ ("python", "Python", ("si", "python")),
+ ("go", "Go", ("si", "go")),
+ ("rust", "Rust", ("si", "rust")),
+ ("java", "Java", ("devicon", "java", "original")),
+ ("csharp", "C#", ("devicon", "csharp", "original")),
+ ("php", "PHP", ("si", "php")),
+ ("ruby", "Ruby", ("si", "ruby")),
+ ("c", "C", ("si", "c")),
+ ("cpp", "C++", ("si", "cplusplus")),
+ ("objective-c", "Objective-C", ("devicon", "objectivec", "plain")),
+ ("metal", "Metal", ("si", "apple")),
+ ("cuda", "CUDA", ("si", "nvidia")),
+ ("swift", "Swift", ("si", "swift")),
+ ("kotlin", "Kotlin", ("si", "kotlin")),
+ ("scala", "Scala", ("si", "scala")),
+ ("dart", "Dart", ("si", "dart")),
+ ("svelte", "Svelte", ("si", "svelte")),
+ ("vue", "Vue", ("si", "vuedotjs")),
+ ("astro", "Astro", ("si", "astro")),
+ ("liquid", "Liquid", ("si", "shopify")),
+ ("delphi", "Delphi", ("si", "delphi")),
+ ("lua", "Lua", ("si", "lua")),
+ ("r", "R", ("si", "r")),
+ ("luau", "Luau", ("si", "luau")),
+ ("cfml", "CFML", ("custom", "cfml")),
+ ("cobol", "COBOL", ("custom", "cobol")),
+ ("vbnet", "VB.NET", ("custom", "vbnet")),
+ ("erlang", "Erlang", ("si", "erlang")),
+ ("solidity", "Solidity", ("si", "solidity")),
+ ("terraform", "Terraform", ("si", "terraform")),
+ ("nix", "Nix", ("si", "nixos")),
+]
+
+
+def fetch(url):
+ req = urllib.request.Request(url, headers={"User-Agent": "codegraph-assets"})
+ with urllib.request.urlopen(req, timeout=20) as r:
+ return r.read().decode("utf-8")
+
+
+class Outliner:
+ """Archivo text -> SVG path outlines, one font instance per weight."""
+
+ def __init__(self):
+ self._by_weight = {}
+
+ def _font(self, weight):
+ if weight not in self._by_weight:
+ ft = TTFont(FONT)
+ instantiateVariableFont(ft, {"wght": weight}, inplace=True)
+ self._by_weight[weight] = (ft, ft.getBestCmap(), ft.getGlyphSet(), ft["hmtx"])
+ return self._by_weight[weight]
+
+ def measure(self, text, size, weight, track=TRACK):
+ ft, cmap, _, hmtx = self._font(weight)
+ scale = size / ft["head"].unitsPerEm
+ w = sum(hmtx[cmap[ord(ch)]][0] * scale + track for ch in text)
+ return w - track
+
+ def cap_height(self, size, weight):
+ ft, _, _, _ = self._font(weight)
+ return getattr(ft["OS/2"], "sCapHeight", 700) * size / ft["head"].unitsPerEm
+
+ def outline(self, text, size, weight, start_x, baseline_y, track=TRACK):
+ ft, cmap, glyphs, hmtx = self._font(weight)
+ scale = size / ft["head"].unitsPerEm
+ sink = SVGPathPen(glyphs, ntos=lambda v: f"{round(v, 2):g}")
+ pen_x = start_x
+ for ch in text:
+ gname = cmap[ord(ch)]
+ if ch != " ":
+ t = Transform(scale, 0, 0, -scale, pen_x, baseline_y)
+ glyphs[gname].draw(TransformPen(sink, t))
+ pen_x += hmtx[gname][0] * scale + track
+ return sink.getCommands()
+
+ def centered(self, text, size, weight, center_x, baseline_y, track=TRACK):
+ w = self.measure(text, size, weight, track)
+ return self.outline(text, size, weight, center_x - w / 2, baseline_y, track)
+
+
+def si_glyph(slug):
+ """simple-icons: single brand-colored 24x24 path, scaled into the glyph box."""
+ svg = fetch(f"https://cdn.simpleicons.org/{slug}")
+ fill = re.search(r'fill="(#[0-9A-Fa-f]{3,8})"', svg).group(1)
+ d = re.search(r' '
+
+
+def devicon_glyph(name, variant):
+ """devicon: multi-path 128x128 markup, embedded verbatim and scaled."""
+ svg = fetch(DEVICON.format(name, variant))
+ inner = re.search(r"]*>(.*) ", svg, re.S).group(1).strip()
+ s = round(GLYPH_BOX / 128, 5)
+ x = (TILE - GLYPH_BOX) / 2
+ return f'{inner} '
+
+
+def custom_glyph(key, out):
+ x = (TILE - GLYPH_BOX) / 2 # glyph box left edge (30)
+ cx = TILE / 2 # 52
+ cy = GLYPH_TOP + GLYPH_BOX / 2 # glyph box vertical center (40)
+ if key == "cfml":
+ # The community CFML mark: a tag, set in Archivo.
+ size, wt = 20.0, 700
+ baseline = round(cy + out.cap_height(size, wt) / 2, 2)
+ d = out.centered("", size, wt, cx, baseline, track=0.4)
+ return f' '
+ if key == "cobol":
+ # A punched card: manila stock, clipped corner, punched rows.
+ w, h = GLYPH_BOX, 28
+ top, cut = round(cy - h / 2, 2), 7
+ holes = []
+ for row in range(3):
+ hy = top + 7.4 + row * 6.4
+ for col in range(7):
+ if (row * 3 + col * 5) % 4 != 0: # deterministic "data" pattern
+ holes.append(
+ f' '
+ )
+ card = (
+ f'M{x + cut},{top} H{x + w} V{top + h} H{x} V{top + cut} Z'
+ )
+ return (
+ f' '
+ + "".join(holes)
+ )
+ if key == "vbnet":
+ # The .NET-purple badge with a VB monogram.
+ size, wt = 16.5, 720
+ baseline = round(cy + out.cap_height(size, wt) / 2, 2)
+ d = out.centered("VB", size, wt, cx, baseline, track=0.6)
+ return (
+ f' '
+ f' '
+ )
+ raise KeyError(key)
+
+
+def main():
+ preview_path = None
+ if "--preview" in sys.argv:
+ preview_path = sys.argv[sys.argv.index("--preview") + 1]
+
+ out = Outliner()
+ label_baseline = round(GLYPH_TOP + GLYPH_BOX + LABEL_GAP + out.cap_height(LABEL_SIZE, LABEL_WT), 2)
+
+ out_dir = os.path.join(os.path.dirname(__file__), "languages")
+ os.makedirs(out_dir, exist_ok=True)
+
+ bodies = {}
+ for slug, label, source in LANGS:
+ if source[0] == "si":
+ glyph = si_glyph(source[1])
+ elif source[0] == "devicon":
+ glyph = devicon_glyph(source[1], source[2])
+ else:
+ glyph = custom_glyph(source[1], out)
+ label_d = out.centered(label, LABEL_SIZE, LABEL_WT, TILE / 2, label_baseline)
+ body = (
+ f' \n'
+ f" {glyph}\n"
+ f' '
+ )
+ bodies[slug] = body
+ svg = (
+ f'\n'
+ f" {label} \n"
+ f" {body}\n"
+ f" \n"
+ )
+ path = os.path.join(out_dir, f"{slug}.svg")
+ with open(path, "w") as fh:
+ fh.write(svg)
+ print(f"wrote {os.path.relpath(path, os.path.dirname(__file__))} ({len(svg)}b) {label}")
+
+ if preview_path:
+ cols, pad = 7, 8
+ rows = -(-len(LANGS) // cols)
+ w = cols * (TILE + pad) + pad
+ h = rows * (TILE + pad) + pad
+ cells = []
+ for i, (slug, _, _) in enumerate(LANGS):
+ tx = pad + (i % cols) * (TILE + pad)
+ ty = pad + (i // cols) * (TILE + pad)
+ cells.append(f'{bodies[slug]} ')
+ light = "".join(cells)
+ with open(preview_path, "w") as fh:
+ fh.write(
+ f''
+ f' {light}'
+ f' {light} '
+ f" \n"
+ )
+ print(f"wrote preview {preview_path} (top: light bg, bottom: dark bg)")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/assets/languages/arkts.svg b/assets/languages/arkts.svg
new file mode 100644
index 0000000..7510391
--- /dev/null
+++ b/assets/languages/arkts.svg
@@ -0,0 +1,6 @@
+
+ ArkTS
+
+
+
+
diff --git a/assets/languages/astro.svg b/assets/languages/astro.svg
new file mode 100644
index 0000000..78f10c1
--- /dev/null
+++ b/assets/languages/astro.svg
@@ -0,0 +1,6 @@
+
+ Astro
+
+
+
+
diff --git a/assets/languages/c.svg b/assets/languages/c.svg
new file mode 100644
index 0000000..dda6b43
--- /dev/null
+++ b/assets/languages/c.svg
@@ -0,0 +1,6 @@
+
+ C
+
+
+
+
diff --git a/assets/languages/cfml.svg b/assets/languages/cfml.svg
new file mode 100644
index 0000000..9c53f7d
--- /dev/null
+++ b/assets/languages/cfml.svg
@@ -0,0 +1,6 @@
+
+ CFML
+
+
+
+
diff --git a/assets/languages/cobol.svg b/assets/languages/cobol.svg
new file mode 100644
index 0000000..7a67d90
--- /dev/null
+++ b/assets/languages/cobol.svg
@@ -0,0 +1,6 @@
+
+ COBOL
+
+
+
+
diff --git a/assets/languages/cpp.svg b/assets/languages/cpp.svg
new file mode 100644
index 0000000..8cdc15d
--- /dev/null
+++ b/assets/languages/cpp.svg
@@ -0,0 +1,6 @@
+
+ C++
+
+
+
+
diff --git a/assets/languages/csharp.svg b/assets/languages/csharp.svg
new file mode 100644
index 0000000..cb50063
--- /dev/null
+++ b/assets/languages/csharp.svg
@@ -0,0 +1,6 @@
+
+ C#
+
+
+
+
diff --git a/assets/languages/cuda.svg b/assets/languages/cuda.svg
new file mode 100644
index 0000000..c3c3d72
--- /dev/null
+++ b/assets/languages/cuda.svg
@@ -0,0 +1,6 @@
+
+ CUDA
+
+
+
+
diff --git a/assets/languages/dart.svg b/assets/languages/dart.svg
new file mode 100644
index 0000000..1256a56
--- /dev/null
+++ b/assets/languages/dart.svg
@@ -0,0 +1,6 @@
+
+ Dart
+
+
+
+
diff --git a/assets/languages/delphi.svg b/assets/languages/delphi.svg
new file mode 100644
index 0000000..402c2be
--- /dev/null
+++ b/assets/languages/delphi.svg
@@ -0,0 +1,6 @@
+
+ Delphi
+
+
+
+
diff --git a/assets/languages/erlang.svg b/assets/languages/erlang.svg
new file mode 100644
index 0000000..9fd4fa5
--- /dev/null
+++ b/assets/languages/erlang.svg
@@ -0,0 +1,6 @@
+
+ Erlang
+
+
+
+
diff --git a/assets/languages/go.svg b/assets/languages/go.svg
new file mode 100644
index 0000000..3ce79e6
--- /dev/null
+++ b/assets/languages/go.svg
@@ -0,0 +1,6 @@
+
+ Go
+
+
+
+
diff --git a/assets/languages/java.svg b/assets/languages/java.svg
new file mode 100644
index 0000000..1fa0ca5
--- /dev/null
+++ b/assets/languages/java.svg
@@ -0,0 +1,6 @@
+
+ Java
+
+
+
+
diff --git a/assets/languages/javascript.svg b/assets/languages/javascript.svg
new file mode 100644
index 0000000..2303976
--- /dev/null
+++ b/assets/languages/javascript.svg
@@ -0,0 +1,6 @@
+
+ JavaScript
+
+
+
+
diff --git a/assets/languages/kotlin.svg b/assets/languages/kotlin.svg
new file mode 100644
index 0000000..b08f6ed
--- /dev/null
+++ b/assets/languages/kotlin.svg
@@ -0,0 +1,6 @@
+
+ Kotlin
+
+
+
+
diff --git a/assets/languages/liquid.svg b/assets/languages/liquid.svg
new file mode 100644
index 0000000..bd87194
--- /dev/null
+++ b/assets/languages/liquid.svg
@@ -0,0 +1,6 @@
+
+ Liquid
+
+
+
+
diff --git a/assets/languages/lua.svg b/assets/languages/lua.svg
new file mode 100644
index 0000000..4e530e3
--- /dev/null
+++ b/assets/languages/lua.svg
@@ -0,0 +1,6 @@
+
+ Lua
+
+
+
+
diff --git a/assets/languages/luau.svg b/assets/languages/luau.svg
new file mode 100644
index 0000000..226b523
--- /dev/null
+++ b/assets/languages/luau.svg
@@ -0,0 +1,6 @@
+
+ Luau
+
+
+
+
diff --git a/assets/languages/metal.svg b/assets/languages/metal.svg
new file mode 100644
index 0000000..67d1149
--- /dev/null
+++ b/assets/languages/metal.svg
@@ -0,0 +1,6 @@
+
+ Metal
+
+
+
+
diff --git a/assets/languages/nix.svg b/assets/languages/nix.svg
new file mode 100644
index 0000000..28c0d67
--- /dev/null
+++ b/assets/languages/nix.svg
@@ -0,0 +1,6 @@
+
+ Nix
+
+
+
+
diff --git a/assets/languages/objective-c.svg b/assets/languages/objective-c.svg
new file mode 100644
index 0000000..2109863
--- /dev/null
+++ b/assets/languages/objective-c.svg
@@ -0,0 +1,6 @@
+
+ Objective-C
+
+
+
+
diff --git a/assets/languages/php.svg b/assets/languages/php.svg
new file mode 100644
index 0000000..509dcb3
--- /dev/null
+++ b/assets/languages/php.svg
@@ -0,0 +1,6 @@
+
+ PHP
+
+
+
+
diff --git a/assets/languages/python.svg b/assets/languages/python.svg
new file mode 100644
index 0000000..c3e160e
--- /dev/null
+++ b/assets/languages/python.svg
@@ -0,0 +1,6 @@
+
+ Python
+
+
+
+
diff --git a/assets/languages/r.svg b/assets/languages/r.svg
new file mode 100644
index 0000000..2049b1d
--- /dev/null
+++ b/assets/languages/r.svg
@@ -0,0 +1,6 @@
+
+ R
+
+
+
+
diff --git a/assets/languages/ruby.svg b/assets/languages/ruby.svg
new file mode 100644
index 0000000..caf752d
--- /dev/null
+++ b/assets/languages/ruby.svg
@@ -0,0 +1,6 @@
+
+ Ruby
+
+
+
+
diff --git a/assets/languages/rust.svg b/assets/languages/rust.svg
new file mode 100644
index 0000000..af95860
--- /dev/null
+++ b/assets/languages/rust.svg
@@ -0,0 +1,6 @@
+
+ Rust
+
+
+
+
diff --git a/assets/languages/scala.svg b/assets/languages/scala.svg
new file mode 100644
index 0000000..9d9aa90
--- /dev/null
+++ b/assets/languages/scala.svg
@@ -0,0 +1,6 @@
+
+ Scala
+
+
+
+
diff --git a/assets/languages/solidity.svg b/assets/languages/solidity.svg
new file mode 100644
index 0000000..8767cc3
--- /dev/null
+++ b/assets/languages/solidity.svg
@@ -0,0 +1,6 @@
+
+ Solidity
+
+
+
+
diff --git a/assets/languages/svelte.svg b/assets/languages/svelte.svg
new file mode 100644
index 0000000..6be9944
--- /dev/null
+++ b/assets/languages/svelte.svg
@@ -0,0 +1,6 @@
+
+ Svelte
+
+
+
+
diff --git a/assets/languages/swift.svg b/assets/languages/swift.svg
new file mode 100644
index 0000000..b6aaab9
--- /dev/null
+++ b/assets/languages/swift.svg
@@ -0,0 +1,6 @@
+
+ Swift
+
+
+
+
diff --git a/assets/languages/terraform.svg b/assets/languages/terraform.svg
new file mode 100644
index 0000000..add5df7
--- /dev/null
+++ b/assets/languages/terraform.svg
@@ -0,0 +1,6 @@
+
+ Terraform
+
+
+
+
diff --git a/assets/languages/typescript.svg b/assets/languages/typescript.svg
new file mode 100644
index 0000000..9384ec8
--- /dev/null
+++ b/assets/languages/typescript.svg
@@ -0,0 +1,6 @@
+
+ TypeScript
+
+
+
+
diff --git a/assets/languages/vbnet.svg b/assets/languages/vbnet.svg
new file mode 100644
index 0000000..b3b052e
--- /dev/null
+++ b/assets/languages/vbnet.svg
@@ -0,0 +1,6 @@
+
+ VB.NET
+
+
+
+
diff --git a/assets/languages/vue.svg b/assets/languages/vue.svg
new file mode 100644
index 0000000..0ede0ce
--- /dev/null
+++ b/assets/languages/vue.svg
@@ -0,0 +1,6 @@
+
+ Vue
+
+
+
+