SPB Git

spb/modelmap Public License

Internal cartography of local LLMs on Apple Silicon — registered, gated, negative-first. Public atlas at modelmap.io.

Python 66.3% JavaScript 24.5% CSS 8.1% Shell 0.7%

Bootstrap modelmap: charter-compliant skeleton, tooling, and modelmap.io platform

- Repo skeleton per charter §3 with mandatory author headers everywhere
- tools/check_headers.py (gates commits), new_experiment.py, new_map.py
- benchmarks: hardware_manifest.py (sysctl-based) + harness skeleton
- experiments/micro A-H scaffolded with empty seven-field blocks
- site/: modelmap.io platform (Express, light theme, English), coherent
  with localvm-research/web — Home, Research, Experiments, Atlas (with
  confidence Levels 0-3), Results, Code, About; attribution footer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 4 h ago (Aug 12, 2026)

Showing 58 changed files with +3,907 and −0

added .gitignore +13 −0
@@ -0,0 +1,13 @@
1 +.DS_Store
2 +__pycache__/
3 +*.pyc
4 +.venv/
5 +node_modules/
6 +site/content/
7 +results/**/*.bin
8 +*.log
9 +.ruff_cache/
10 +.pytest_cache/
11 +# large binary artifacts live outside git (local artifact store)
12 +atlas/**/*.zarr
13 +atlas/**/activations/
added CITATION.cff +28 −0
@@ -0,0 +1,28 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : CITATION.cff
4 +# Purpose : Citation metadata for the modelmap research project
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +cff-version: 1.2.0
14 +message: "If you use findings or maps from this project, please cite it as below."
15 +title: "modelmap — Internal Cartography of Local Large Language Models"
16 +authors:
17 + - family-names: "Boucher"
18 + given-names: "Simon-Pierre"
19 + email: "contact@spboucher.ai"
20 +url: "https://modelmap.io"
21 +date-released: "2026-08-12"
22 +version: "0.1.0"
23 +keywords:
24 + - mechanistic interpretability
25 + - large language models
26 + - Apple Silicon
27 + - model atlas
28 + - reproducibility
added CLAUDE.md +631 −0
@@ -0,0 +1,631 @@
1 +# CLAUDE.md
2 +
3 +**Project codename:** `modelmap` — Internal Cartography of Local Large Language Models
4 +**Public site:** https://modelmap.io (the living research atlas & documentation site)
5 +**Principal investigator / Author:** Simon-Pierre Boucher — <contact@spboucher.ai>
6 +**Primary platform:** Apple Silicon Mac (macOS 14+)
7 +**Sister project:** `localvm-research` (out-of-core execution) — findings must cross-pollinate
8 +**Document status:** Living research charter. Claude must treat this as the authoritative project specification.
9 +
10 +---
11 +
12 +## 0. Administrative conventions (MANDATORY — read before anything else)
13 +
14 +### 0.1 Author header requirement
15 +
16 +**Every single source file created in this project — without exception — must begin with a standardized author header.**
17 +
18 +This applies to: Python, C++, Swift, Objective-C, Metal shaders, JavaScript/TypeScript (for the modelmap.io site), shell scripts, Makefiles, CMake files, and any configuration file that supports comments.
19 +
20 +#### Python / shell / YAML / TOML header
21 +
22 +```python
23 +# =============================================================================
24 +# Project : modelmap
25 +# File : <relative/path/to/file.py>
26 +# Purpose : <one-line description of what this file does>
27 +# Author : Simon-Pierre Boucher
28 +# Contact : contact@spboucher.ai
29 +# Website : https://modelmap.io
30 +# Created : <YYYY-MM-DD>
31 +# Modified : <YYYY-MM-DD>
32 +# Platform : macOS / Apple Silicon (arm64)
33 +# License : All rights reserved (research code)
34 +# =============================================================================
35 +```
36 +
37 +#### C++ / Metal / Swift / JS / TS header
38 +
39 +```cpp
40 +// ============================================================================
41 +// Project : modelmap
42 +// File : <relative/path/to/file>
43 +// Purpose : <one-line description>
44 +// Author : Simon-Pierre Boucher
45 +// Contact : contact@spboucher.ai
46 +// Website : https://modelmap.io
47 +// Created : <YYYY-MM-DD>
48 +// Modified : <YYYY-MM-DD>
49 +// Platform : macOS / Apple Silicon (arm64) — MLX / Metal / MPS
50 +// License : All rights reserved (research code)
51 +// ============================================================================
52 +```
53 +
54 +#### Markdown research documents (front matter)
55 +
56 +```markdown
57 +---
58 +project: modelmap
59 +document: <name>
60 +author: Simon-Pierre Boucher
61 +contact: contact@spboucher.ai
62 +website: https://modelmap.io
63 +created: <YYYY-MM-DD>
64 +status: draft | reviewed | final
65 +---
66 +```
67 +
68 +Rules:
69 +
70 +1. The header must be the **first content** of the file (after a shebang if present).
71 +2. `Modified` must be updated on every substantial change.
72 +3. Write `tools/check_headers.py` early; it must fail if any tracked source file lacks a conforming header, and it runs before every commit.
73 +4. Generated artifacts (maps, JSON atlases, plots) must embed `"author": "Simon-Pierre Boucher", "contact": "contact@spboucher.ai", "website": "https://modelmap.io"` in their metadata wherever the format permits.
74 +5. Every published map on modelmap.io must carry a visible attribution footer.
75 +
76 +### 0.2 macOS-first design constraint
77 +
78 +**Everything must be designed to run on a Mac.** Specifically:
79 +
80 +* Target machine: Apple Silicon (M1–M4 family), 16–64 GB unified memory, internal NVMe SSD.
81 +* Default compute paths: **MLX**, **PyTorch with MPS backend**, **NumPy/Accelerate**, custom **Metal** kernels where hooks/probes need speed. CUDA is allowed only as an isolated optional validation path (`src/backends/cuda_optional/`), never a core dependency.
82 +* Activation capture at scale generates large tensors: design the capture pipeline for **unified memory limits** (streaming to disk, memory-mapped activation stores, chunked capture) — never assume activations fit in RAM.
83 +* Storage of maps/activations must respect APFS behavior and Apple NVMe characteristics; use efficient columnar/array formats (safetensors, zarr, parquet, HDF5) benchmarked **on macOS**, not assumed from Linux numbers.
84 +* Instrumentation must use macOS-native sources: mach `task_info`, `vm_stat`, `fs_usage`, `powermetrics` (graceful degradation without sudo), `sysctl hw.*`.
85 +* Build tooling: `uv`/`pip` + venv for Python; `cmake` + AppleClang for C++; `xcrun -sdk macosx metal` for shaders; standard Node toolchain for the modelmap.io site (static-site friendly — the site must build and preview locally on the Mac).
86 +* A hardware manifest (`benchmarks/hardware_manifest.py`) records chip, P/E core counts, GPU cores, RAM, SSD model, macOS version, and all software versions into every result and every published map.
87 +
88 +### 0.3 Repository discipline
89 +
90 +* Git from day one; meaningful commits; no result reported from an uncommitted tree.
91 +* Every map must be reproducible from: commit hash + config file + model hash + seed + hardware manifest.
92 +* Python: type hints, `ruff`, `pytest` (numerical probes get correctness tests against tiny reference models).
93 +* Datasets of prompts used for mapping are versioned artifacts with checksums.
94 +* Large binary artifacts (activation dumps) live outside git (local artifact store with an index file committed to git).
95 +
96 +---
97 +
98 +## 1. Mission
99 +
100 +Your objective is to investigate, design, implement, and experimentally validate a systematic methodology — and the tooling behind it — to **discover, measure, and map the internal structure of pretrained open-weight LLMs running locally on consumer Apple Silicon hardware**, and to publish the resulting maps as a rigorous, reproducible public atlas at **modelmap.io**.
101 +
102 +The target problem is **not**:
103 +
104 +* training new models;
105 +* fine-tuning models to change their behavior;
106 +* reproducing an existing interpretability library (TransformerLens, NNsight, SAELens, Captum) without adding anything;
107 +* producing pretty but unfalsifiable visualizations;
108 +* benchmarking model *outputs* (that is leaderboard work, not cartography);
109 +* speculative "the model thinks X" storytelling without measurement.
110 +
111 +The target is broader and more rigorous:
112 +
113 +> **Given an already-trained open-weight model, determine what can actually be known, measured, localized, and mapped about its internal organization — where knowledge lives, how computation is distributed, which structures are stable across inputs, layers, scales, and model families — using only local, consumer-grade Mac hardware, and turn that knowledge into reproducible, comparable, publishable maps.**
114 +
115 +Do not assume in advance which mapping techniques are informative.
116 +Do not force a particular interpretability framework.
117 +Do not begin implementation before understanding the state of the art.
118 +Your job is to discover what can genuinely be mapped — and what cannot.
119 +
120 +---
121 +
122 +## 2. Core research questions
123 +
124 +Investigate:
125 +
126 +> **Q1 — Localization.** Where inside a pretrained LLM do specific capabilities, knowledge domains, languages, and behaviors reside? Are they localized (layers, heads, neurons, weight blocks, feature directions) or diffuse?
127 +
128 +> **Q2 — Structure.** What stable internal structures exist across inputs — circuits, feature directions, attention patterns, activation manifolds, weight-space geometry — and at what granularity are they real rather than artifacts of the probing method?
129 +
130 +> **Q3 — Comparability.** Can internal maps be compared across model sizes, checkpoints, quantization levels, and model families? Is there a common "coordinate system" for model internals?
131 +
132 +> **Q4 — Cost.** Which mapping techniques are feasible on a 16–64 GB Mac, at which model sizes, and what is the accuracy/cost frontier of local interpretability?
133 +
134 +> **Q5 — Utility.** Do the maps predict anything useful — quantization sensitivity, pruning tolerance, working-set behavior (feeding `localvm-research`), failure modes, editing targets?
135 +
136 +The most important distinction to keep sharp throughout:
137 +
138 +```text
139 +what a probing method reports
140 +
141 +what the model actually computes
142 +
143 +what is stable across methods, seeds, and datasets
144 +
145 +what is causally verified by intervention
146 +```
147 +
148 +A claim only enters the atlas at the confidence level its evidence supports. Correlational maps and causally-verified maps must never be visually or textually conflated.
149 +
150 +### Guiding principle
151 +
152 +Do not optimize first for spectacular visuals. Initially optimize for discovering **which measurements are real** — reproducible, method-robust, causally grounded.
153 +
154 +Negative results ("technique X produces maps that do not replicate across seeds") are first-class publishable findings on modelmap.io.
155 +
156 +---
157 +
158 +## 3. Project structure (real research-project layout)
159 +
160 +Create this skeleton (with header-compliant placeholder files) before Phase 1 concludes:
161 +
162 +```text
163 +modelmap/
164 +├── CLAUDE.md # this charter
165 +├── README.md # public summary (written last, updated continuously)
166 +├── CITATION.cff # author: Simon-Pierre Boucher
167 +├── LICENSE
168 +├── pyproject.toml
169 +├── Makefile # setup, lint, test, capture, map, site, headers
170 +
171 +├── research/ # the scientific paper trail
172 +│ ├── LOG.md # dated, append-only research log
173 +│ ├── state_of_the_art.md # Phase 2 deliverable
174 +│ ├── research_gaps.md # Phase 3 deliverable
175 +│ ├── candidate_ranking.md # Phase 4 deliverable
176 +│ ├── methodology.md # the formal mapping methodology (Phase 9)
177 +│ ├── novelty_check.md # Phase 11 deliverable
178 +│ ├── bibliography.md # every source, links + access dates
179 +│ └── notes/ # per-topic reading notes
180 +
181 +├── src/
182 +│ └── modelmap/
183 +│ ├── __init__.py
184 +│ ├── models/ # loading (safetensors/GGUF), architecture introspection
185 +│ ├── capture/ # activation hooks, streaming capture, mmap stores
186 +│ ├── probes/ # linear probes, logit lens, tuned lens, causal probes
187 +│ ├── interventions/ # ablation, patching, steering, weight zeroing
188 +│ ├── features/ # SAE training/inference, dictionary methods
189 +│ ├── geometry/ # weight-space analysis: SVD, spectra, similarity (CKA…)
190 +│ ├── graphs/ # circuit extraction, attribution graphs
191 +│ ├── atlas/ # map schema, serialization, versioning, provenance
192 +│ ├── viz/ # figure + interactive-map generation
193 +│ ├── backends/ # mlx_backend / mps_backend / cpu_backend / cuda_optional
194 +│ ├── instrumentation/ # macOS-native cost measurement
195 +│ └── stats/ # replication tests, bootstrap CIs, multiple-comparison control
196 +
197 +├── experiments/
198 +│ ├── micro/
199 +│ │ ├── expA_probe_reliability/
200 +│ │ ├── expB_localization_vs_diffusion/
201 +│ │ ├── expC_causal_verification/
202 +│ │ ├── expD_cross_input_stability/
203 +│ │ ├── expE_weight_geometry/
204 +│ │ ├── expF_quantization_map_drift/
205 +│ │ ├── expG_cross_model_alignment/
206 +│ │ └── expH_capture_cost_frontier/
207 +│ ├── candidate_01/
208 +│ ├── candidate_02/
209 +│ └── candidate_03/
210 +│ └── (each: README.md, hypothesis.md, implementation/,
211 +│ benchmark.py, results/, analysis.md)
212 +
213 +├── atlas/ # the maps themselves (versioned data products)
214 +│ └── <model_id>/<map_type>/<version>/
215 +│ ├── map.json / map.zarr
216 +│ ├── provenance.json # commit, config, model hash, hardware, dates
217 +│ └── confidence.md # evidence level: correlational | replicated | causal
218 +
219 +├── site/ # modelmap.io source (static-site, builds locally on Mac)
220 +│ ├── content/ # one page per model, per map type, per finding
221 +│ ├── components/ # interactive map viewers
222 +│ └── data/ # published atlas exports
223 +
224 +├── benchmarks/
225 +│ ├── harness.py
226 +│ ├── hardware_manifest.py
227 +│ └── promptsets/ # versioned mapping corpora: code, math, FR, EN, facts…
228 +
229 +├── results/ # raw + aggregated results
230 +│ └── <experiment_id>/<timestamp>/
231 +
232 +├── tools/
233 +│ ├── check_headers.py
234 +│ ├── new_experiment.py # scaffolds compliant experiment dirs
235 +│ ├── new_map.py # scaffolds a compliant atlas entry with provenance
236 +│ └── publish.py # atlas → site/data export with attribution
237 +
238 +└── docs/
239 +```
240 +
241 +Every experiment directory is scaffolded by `tools/new_experiment.py` with the seven-field scientific block (§10). Every atlas entry is scaffolded by `tools/new_map.py` and **cannot be published without a completed `provenance.json` and `confidence.md`**.
242 +
243 +---
244 +
245 +## 4. Phase 1 — Ultra-deep literature research
246 +
247 +Before proposing a methodology, perform an extremely deep search of current literature, repositories, technical reports, papers, preprints, blog posts, implementations, and discussions.
248 +
249 +Search arXiv, OpenReview, conference proceedings (NeurIPS, ICML, ICLR, ACL, EMNLP), the mechanistic-interpretability community corpus (Anthropic interpretability publications, Transformer Circuits thread, Neel Nanda's work, EleutherAI, LessWrong/Alignment Forum technical posts), GitHub (TransformerLens, NNsight, SAELens, Penzai, Captum, baukit), Hugging Face, and Apple/MLX engineering material.
250 +
251 +Prefer primary sources. For every important technique find the paper **and** the implementation, and note whether it runs on Apple Silicon. Log everything in `research/bibliography.md` with URLs and access dates. Follow citations backward and forward.
252 +
253 +### Areas that must be investigated
254 +
255 +#### 4.1 Observational probing
256 +Linear probes; logit lens; tuned lens; early decoding; representation reading; concept erasure (LEACE-style); probing classifier pitfalls and controls; selectivity/controls literature (what probes overfit to).
257 +
258 +#### 4.2 Causal / interventional methods
259 +Activation patching; path patching; attribution patching; causal tracing (ROME-style locating); ablations (zero, mean, resample); causal scrubbing; interchange interventions; steering vectors; representation engineering; distributed alignment search.
260 +
261 +#### 4.3 Feature decomposition
262 +Sparse autoencoders (SAEs) and their variants (gated, top-k, jumpReLU, crosscoders, transcoders); dictionary learning; superposition theory; polysemanticity; feature splitting; SAE evaluation problems (reconstruction vs interpretability vs faithfulness); automated feature labeling and its failure modes.
263 +
264 +#### 4.4 Circuits and computational graphs
265 +Induction heads; IOI-style circuit analyses; attention head taxonomies; MLP key–value memory view; attribution graphs; automated circuit discovery (ACDC and successors); edge/node pruning of computational graphs; faithfulness metrics for circuits.
266 +
267 +#### 4.5 Weight-space cartography (no forward pass required)
268 +SVD spectra of weight matrices; effective rank per layer; weight statistics across depth; outlier channels/dimensions; cross-layer similarity (CKA, CCA, Procrustes); model stitching; permutation alignment / git re-basin; mode connectivity; delta analysis between checkpoints and between base/instruct variants; MoE router and expert structure.
269 +
270 +#### 4.6 Knowledge localization and editing
271 +ROME, MEMIT and successors; where facts are stored vs where they are retrieved; the localization-vs-editing critique literature (does editing success actually imply localization?); knowledge neurons; multilingual knowledge sharing.
272 +
273 +#### 4.7 Cross-model and cross-scale comparison
274 +Representation similarity across scales and families; universality of features/circuits; platonic representation hypothesis debates; tokenizer effects on comparability; quantization effects on internal representations (largely unmapped — note carefully).
275 +
276 +#### 4.8 Statistics and epistemology of interpretability
277 +Illusions in interpretability results; seed sensitivity; dataset sensitivity of probes; multiple-comparison problems when scanning thousands of neurons/heads; pre-registration analogues; faithfulness vs plausibility; benchmarking interpretability methods (e.g., finding planted circuits in known models).
278 +
279 +#### 4.9 Systems side (local mapping at scale)
280 +Activation caching formats and costs; hook overhead in MLX vs PyTorch-MPS; streaming SAE training on limited memory; disk layouts for random access into activation stores; what interpretability workloads exist for GGUF/quantized runtimes (llama.cpp introspection); GPU capture on Metal.
281 +
282 +#### 4.10 Visualization and atlas design
283 +Prior art on model atlases (Neuroscope, Neuronpedia, OpenAI microscope-style efforts, feature browsers); what made them useful or misleading; provenance and versioning practices for data products; how to encode uncertainty visually.
284 +
285 +---
286 +
287 +## 5. Phase 2 — State-of-the-art map
288 +
289 +Produce `research/state_of_the_art.md`. Organize every relevant technique by:
290 +
291 +```text
292 +Technique
293 +What it claims to reveal
294 +Observational or causal?
295 +Model access required (weights / activations / gradients)
296 +Compute + memory cost class
297 +Feasible on 16–64 GB Apple Silicon? (measured or estimated, at which model sizes)
298 +Known failure modes / illusions
299 +Reproducibility record
300 +Open-source implementation (and does it build on macOS arm64 / MPS / MLX?)
301 +Main limitation
302 +Opportunity for extension
303 +```
304 +
305 +Do not simply summarize papers. Identify overlaps, already-tried combinations, and ideas that seem novel but are known. Explicitly flag the epistemic status of each technique (established / contested / debunked-in-part).
306 +
307 +---
308 +
309 +## 6. Phase 3 — Identify genuine gaps
310 +
311 +Produce `research/research_gaps.md`. For every promising gap explain:
312 +
313 +1. what existing work does;
314 +2. what it does not do;
315 +3. why the missing capability might matter;
316 +4. whether there is a plausible mathematical or empirical reason it could work;
317 +5. the biggest reason it might fail;
318 +6. the smallest experiment capable of falsifying it — **runnable on a Mac**.
319 +
320 +Generate at least **20 substantially different candidate directions**. Do not make them superficial variants.
321 +
322 +Example categories (non-binding): quantization-aware internal maps; weight-only cheap cartography as a pre-screen for expensive activation methods; cross-model coordinate systems; maps that predict systems properties (working sets, pruning tolerance — bridge to `localvm`); method-agreement scoring (map = intersection of techniques); longitudinal maps across a model family; multilingual localization atlases; local-hardware-feasible SAE recipes; causal-verification pipelines that scale down; uncertainty-first atlas schemas.
323 +
324 +These are examples only. Do not constrain the search to them.
325 +
326 +---
327 +
328 +## 7. Phase 4 — Rank candidate approaches
329 +
330 +Create `research/candidate_ranking.md`. Score every idea 1–10 with written reasoning on:
331 +
332 +```text
333 +Scientific value (what new knowledge does the map contain?)
334 +Epistemic soundness (can claims be causally verified?)
335 +Novelty
336 +Feasibility on Apple Silicon (memory, time, kernel availability)
337 +Reproducibility potential
338 +Cross-model generality
339 +Public atlas value (is it worth publishing on modelmap.io?)
340 +Implementation complexity
341 +Synergy with localvm-research
342 +Risk
343 +```
344 +
345 +Select roughly **3–5 strongest candidates** for prototyping.
346 +
347 +Prefer directions that produce **comparable, versionable, falsifiable map artifacts** rather than one-off analyses.
348 +
349 +---
350 +
351 +## 8. Phase 5 — Experimental framework
352 +
353 +Build the framework **before** mapping large models. Correctness and measurement first; optimization later.
354 +
355 +Preferred stack (Mac-first):
356 +
357 +```text
358 +Python (MLX, PyTorch-MPS, NumPy, safetensors, zarr) for research tooling
359 +C++ / Metal where hook or SAE throughput demands it
360 +TypeScript + static-site generator for modelmap.io (local build/preview on the Mac)
361 +CUDA only as isolated optional validation
362 +```
363 +
364 +### 8.1 Model progression
365 +
366 +```text
367 +0.5B–1B → 3B → 7B–8B → 14B → 32B
368 +```
369 +
370 +Move larger only when evidence and the cost frontier (Experiment H) support it. Prefer modern open-weight families with multiple sizes (to enable cross-scale maps) and both base + instruct checkpoints (to enable delta maps).
371 +
372 +### 8.2 Instrumentation
373 +
374 +Measure, for every mapping run:
375 +
376 +```text
377 +resident/peak RAM activation bytes captured
378 +disk bytes written/read capture overhead vs plain inference
379 +wall-clock per mapped unit GPU/CPU utilization (P vs E cores)
380 +hook dispatch overhead SAE training throughput
381 +thermal state / throttling energy via powermetrics when available
382 +```
383 +
384 +And for every scientific result:
385 +
386 +```text
387 +replication across ≥3 seeds replication across ≥2 prompt sets
388 +bootstrap confidence intervals effect sizes, not just p-values
389 +method agreement (≥2 techniques) causal verification status
390 +multiple-comparison correction when scanning many units
391 +```
392 +
393 +### 8.3 Most important scientific metric
394 +
395 +Track, for every map:
396 +
397 +```text
398 +REPLICATION RATE (does the map reproduce under resampling of seeds/data?)
399 +CAUSAL CONFIRMATION RATE (what fraction of localized claims survive intervention?)
400 +```
401 +
402 +A beautiful map that does not replicate is worth less than a boring one that does.
403 +
404 +### 8.4 Baselines and controls
405 +
406 +Every mapping technique must be run with controls: shuffled labels for probes; random-direction baselines for steering; randomly-initialized-model baselines where meaningful; resample ablations vs zero ablations. Never claim structure without showing the null.
407 +
408 +---
409 +
410 +## 9. Phase 6 — Micro-experiments
411 +
412 +Each lives in `experiments/micro/` with the standard scaffold.
413 +
414 +### Experiment A — Probe reliability
415 +For a small model, train linear probes for a set of properties across all layers, with shuffled-label controls, ≥5 seeds, ≥2 datasets. Quantify how much probe accuracy varies with seed/data. Establish the noise floor every later map must exceed.
416 +
417 +### Experiment B — Localization vs diffusion
418 +For selected capabilities (a language, a knowledge domain, arithmetic, code syntax), measure how concentrated the supporting signal is across layers/heads/neurons/weight blocks. Ask: does removing the top-k localized units remove the capability, or does it survive (diffusion)? Test across code, math, chat, French, English, factual recall, reasoning.
419 +
420 +### Experiment C — Causal verification pipeline
421 +Take the top findings from A/B and verify them with activation patching and ablation. Measure the correlational→causal survival rate. This number calibrates all future confidence labels in the atlas.
422 +
423 +### Experiment D — Cross-input stability
424 +For fixed internal structures (heads, features, directions), measure stability across prompts, domains, context lengths, and sampling temperature. Compute overlap/Jaccard of "important units" across inputs. Determine which map types are input-conditional vs input-invariant.
425 +
426 +### Experiment E — Weight-space geometry (zero-forward-pass maps)
427 +Compute per-layer SVD spectra, effective ranks, outlier dimensions, and cross-layer CKA purely from weights. Test whether these cheap maps predict anything measured in A–D. If yes, weight-only cartography becomes a fast pre-screen for any model.
428 +
429 +### Experiment F — Quantization map drift
430 +Map the same model at FP16, Q8, Q4, Q2 (where practical). Measure how much probes, feature directions, and localized circuits drift under quantization. This is under-explored territory and directly relevant to local models, which are almost always quantized.
431 +
432 +### Experiment G — Cross-model alignment
433 +For two sizes of the same family, attempt to align internal maps (representation similarity, feature matching, permutation alignment). Determine whether a shared coordinate system is achievable and at what granularity.
434 +
435 +### Experiment H — Capture cost frontier (macOS-specific)
436 +Measure the real cost of activation capture on the Mac: hook overhead in MLX vs PyTorch-MPS, streaming write throughput to the internal NVMe, storage formats (safetensors vs zarr vs raw mmap), maximum feasible capture per model size within 16/32/64 GB. Produce a published "what can you map on which Mac" table — itself a modelmap.io artifact.
437 +
438 +---
439 +
440 +## 10. Scientific discipline
441 +
442 +For every experiment write, in `hypothesis.md` / `analysis.md`:
443 +
444 +```text
445 +Hypothesis
446 +Falsification criterion
447 +Method (including controls)
448 +Baseline / null
449 +Result (with effect sizes and CIs)
450 +Interpretation (with explicit confidence level)
451 +Next experiment
452 +```
453 +
454 +Avoid confirmation bias. If a technique produces illusory structure, record it — that is a finding. **Do not silently discard failures.**
455 +
456 +### Evidence standard
457 +
458 +Never write "this neuron does X", "this layer stores Y", "feature Z represents W" without stating: the method, the controls, the replication status, and whether the claim is correlational or causally verified. Report: number of seeds, prompt sets, effect sizes, CIs, hardware, model+quantization, software versions.
459 +
460 +### Confidence taxonomy (used everywhere, including on the site)
461 +
462 +```text
463 +Level 0 — anecdotal (single run, no controls; never published alone)
464 +Level 1 — correlational (controlled, replicated ≥3 seeds, ≥2 datasets)
465 +Level 2 — method-robust (Level 1 + agreement across ≥2 independent techniques)
466 +Level 3 — causal (Level 2 + intervention confirms the claim)
467 +```
468 +
469 +Every atlas entry carries its level in `confidence.md` and visibly on modelmap.io.
470 +
471 +---
472 +
473 +## 11. Phase 7 — Prototype candidate methodologies
474 +
475 +For each of the strongest candidates create `experiments/candidate_0N/` with the standard contents. All implementations must run on the primary Mac target without CUDA. Each candidate's `analysis.md` must end with an explicit recommendation: promote to atlas pipeline, iterate, or reject (with reasons).
476 +
477 +---
478 +
479 +## 12. Phase 8 — Automatic research loop
480 +
481 +Operate as a research agent. After every experiment: analyze; pick the most informative next experiment; re-search the literature when results surprise; update hypotheses; modify the methodology; rerun; compare; continue until evidence strongly favors or rejects the approach. Do not follow a rigid roadmap if experiments contradict it.
482 +
483 +Maintain `research/LOG.md` (append-only): date/time with timezone, question, experiment, result, interpretation, decision. The entire reasoning process must be auditable.
484 +
485 +---
486 +
487 +## 13. Phase 9 — Formalize the mapping methodology
488 +
489 +Once micro-experiments and candidates converge, write `research/methodology.md`: the formal, reusable pipeline that turns *(model checkpoint + prompt corpora + budget)* into *(versioned atlas entries with confidence levels)*.
490 +
491 +It must specify: capture protocol; probe/feature/circuit procedures actually retained; mandatory controls; replication requirements; causal-verification requirements per confidence level; cost model per Mac tier; map schema; provenance schema; publication checklist.
492 +
493 +The methodology is itself a primary deliverable — it should let any researcher with a Mac reproduce a map.
494 +
495 +---
496 +
497 +## 14. Phase 10 — Build the atlas pipeline and modelmap.io
498 +
499 +Build the production pipeline supported by evidence. A possible CLI shape (finalize only after research):
500 +
501 +```bash
502 +modelmap capture MODEL_PATH --promptset benchmarks/promptsets/core --out atlas/
503 +modelmap analyze atlas/<model_id> --maps probes,features,geometry,circuits
504 +modelmap verify atlas/<model_id> --level causal
505 +modelmap publish atlas/<model_id> --site site/
506 +```
507 +
508 +### The site (modelmap.io)
509 +
510 +* Static-site architecture, built and previewed locally on the Mac; every page generated from atlas data, never hand-edited numbers.
511 +* One page per model; per map type; interactive viewers (layer/head/feature browsers) built from `atlas/` exports.
512 +* Every visualization displays: confidence level, provenance (commit, model hash, date, hardware), and how to reproduce.
513 +* A methodology section publishing `research/methodology.md`.
514 +* A negative-results section. Publishing what does **not** replicate is part of the mission.
515 +* Attribution footer on every page: *Simon-Pierre Boucher — contact@spboucher.ai*.
516 +
517 +### Hard constraints
518 +
519 +* Only open-weight models are mapped; the original checkpoint is always the object of study.
520 +* No claim appears on modelmap.io above its evidence level.
521 +* Nothing is published that cannot be regenerated from committed code + versioned data.
522 +
523 +---
524 +
525 +## 15. Target hardware
526 +
527 +Primary research target:
528 +
529 +```text
530 +Apple Silicon Mac (laptop or desktop)
531 +16–64 GB unified memory
532 +internal Apple NVMe SSD
533 +Metal-capable GPU sharing memory with CPU
534 +```
535 +
536 +Apple Silicon matters here because: unified memory lets capture pipelines avoid device copies; MLX enables custom, low-overhead hooks; fast internal SSDs make large activation stores workable; and — crucially — **local interpretability on consumer hardware is itself an under-served research niche**: most interpretability tooling assumes CUDA clusters. Making rigorous mapping feasible on a Mac is part of the contribution.
537 +
538 +Document (without necessarily implementing) how each pipeline stage would map to x86 + discrete GPU.
539 +
540 +### Stretch target
541 +
542 +```text
543 +A full, causally-verified, versioned atlas of a 7B–14B open-weight model,
544 +produced end-to-end on a single 32–64 GB Mac,
545 +comparable across ≥2 quantization levels and ≥2 model sizes,
546 +published on modelmap.io with complete provenance.
547 +```
548 +
549 +Do not assume this is achievable. The research must establish the actual limits.
550 +
551 +---
552 +
553 +## 16. What counts as a breakthrough
554 +
555 +A result is scientifically interesting if at least one of the following is demonstrated:
556 +
557 +**A.** A mapping methodology whose claims survive causal verification at a measured, reported rate — with the rate itself published.
558 +**B.** Cheap (weight-only or low-capture) maps that reliably predict expensive properties (capability localization, quantization sensitivity, pruning tolerance, working sets).
559 +**C.** A cross-model coordinate system allowing internal maps to be meaningfully compared across sizes or families.
560 +**D.** The first systematic map of how quantization deforms internal structure of local models.
561 +**E.** A demonstrated bridge to systems research: maps that materially improve out-of-core execution decisions in `localvm-research`.
562 +**F.** A rigorous negative result: demonstration that a widely-used mapping technique fails replication or causal verification under controlled conditions.
563 +
564 +## 17. Failure criteria
565 +
566 +Be willing to conclude that an approach does not work. For example:
567 +
568 +```text
569 +probe results are dominated by seed/dataset noise
570 +localized structure evaporates under causal testing
571 +SAE features do not replicate across training runs
572 +maps are quantization-fragile to the point of uselessness
573 +cross-model alignment finds no stable correspondence
574 +capture costs exceed consumer-Mac feasibility at useful model sizes
575 +method agreement is near chance
576 +```
577 +
578 +If these occur, document them in `research/LOG.md` and the relevant `analysis.md`, publish the negative result where warranted, and pivot.
579 +
580 +---
581 +
582 +## 18. Deliverables
583 +
584 +```text
585 +README.md, CITATION.cff
586 +research/ (state_of_the_art, research_gaps, candidate_ranking,
587 + methodology, novelty_check, LOG, bibliography)
588 +src/ (the modelmap library)
589 +experiments/, benchmarks/, results/
590 +atlas/ (versioned map artifacts with provenance + confidence)
591 +site/ (modelmap.io source)
592 +tools/, docs/
593 +```
594 +
595 +The final README must explain: the problem; what existing interpretability work does; what gap was found; the methodology; why it is sound; experimental evidence (numbers, named Mac hardware); the atlas and site; limitations; exact Mac reproduction instructions; future research.
596 +
597 +---
598 +
599 +## 19. Phase 11 — Novelty verification
600 +
601 +Before claiming novelty of any methodology, map type, or finding, perform a dedicated final literature search using terminology derived from what was actually built — and every plausible synonym and conceptual equivalent (e.g., "model atlas", "neuron catalog", "feature browser", "representation cartography", "interpretability benchmark", "quantized-model interpretability"). Assume an idea is **not** novel until evidence suggests otherwise. Record process and conclusion in `research/novelty_check.md`.
602 +
603 +---
604 +
605 +## 20. Most important instruction
606 +
607 +Do not become attached to any particular technique suggested in this document.
608 +
609 +The purpose of this project is **not to implement a preconceived SAE browser, neuron catalog, or circuit viewer**. Those ideas are merely clues.
610 +
611 +The actual assignment is:
612 +
613 +> **Search deeply enough, reason independently enough, control and replicate rigorously enough, and intervene causally enough to discover what can truly be known about the inside of a local LLM on consumer Mac hardware — and publish only that, at its honest confidence level, on modelmap.io.**
614 +
615 +Start with literature. Then generate hypotheses. Then falsify them experimentally. Let evidence determine the methodology.
616 +
617 +The ideal outcome is not another gallery of suggestive visualizations. The ideal outcome is a **reproducible cartographic standard for open-weight models** — where every map is versioned, provenanced, confidence-labeled, and regenerable by anyone with a Mac — turning:
618 +
619 +```text
620 +"we think the model does X"
621 +```
622 +
623 +into:
624 +
625 +```text
626 +"here is the map, its evidence level, and the script that rebuilds it"
627 +```
628 +
629 +---
630 +
631 +*Author: Simon-Pierre Boucher — contact@spboucher.ai — https://modelmap.io — All research artifacts in this repository carry this attribution.*
added LICENSE +9 −0
@@ -0,0 +1,9 @@
1 +All rights reserved (research code)
2 +
3 +Copyright (c) 2026 Simon-Pierre Boucher <contact@spboucher.ai>
4 +https://modelmap.io
5 +
6 +This repository contains research code and research artifacts. No permission is
7 +granted to use, copy, modify, or distribute this software or the published maps
8 +without prior written consent from the author, except for reading and citing
9 +the published research with attribution.
added Makefile +34 −0
@@ -0,0 +1,34 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : Makefile
4 +# Purpose : Common tasks — setup, lint, test, headers check, site build/run
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +.PHONY: setup lint test headers site-sync site-run site-deps
14 +
15 +setup:
16 + python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
17 +
18 +lint:
19 + .venv/bin/ruff check src tools benchmarks
20 +
21 +test:
22 + .venv/bin/pytest -q
23 +
24 +headers:
25 + python3 tools/check_headers.py
26 +
27 +site-deps:
28 + cd site && npm install
29 +
30 +site-sync:
31 + site/sync-content.sh
32 +
33 +site-run: site-sync
34 + cd site && PORT=8140 node server.js
added README.md +71 −0
@@ -0,0 +1,71 @@
1 +---
2 +project: modelmap
3 +document: README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# modelmap — Internal Cartography of Local Large Language Models
12 +
13 +**modelmap** investigates what can actually be known, measured, localized, and mapped
14 +about the internal organization of pretrained open-weight LLMs — where knowledge lives,
15 +how computation is distributed, which structures are stable across inputs, layers,
16 +scales, and model families — **using only local, consumer-grade Apple Silicon hardware**,
17 +and publishes the resulting maps as a rigorous, reproducible public atlas at
18 +**[modelmap.io](https://modelmap.io)**.
19 +
20 +## Core research questions
21 +
22 +- **Q1 — Localization.** Where do capabilities, knowledge domains, languages, and behaviors reside? Localized or diffuse?
23 +- **Q2 — Structure.** Which internal structures are stable across inputs — and at what granularity are they real rather than probing artifacts?
24 +- **Q3 — Comparability.** Can internal maps be compared across sizes, checkpoints, quantization levels, and families?
25 +- **Q4 — Cost.** Which mapping techniques are feasible on a 16–64 GB Mac, and what is the accuracy/cost frontier of local interpretability?
26 +- **Q5 — Utility.** Do the maps predict anything useful — quantization sensitivity, pruning tolerance, working sets, failure modes?
27 +
28 +The distinction kept sharp throughout:
29 +
30 +```text
31 +what a probing method reports
32 +≠ what the model actually computes
33 +≠ what is stable across methods, seeds, and datasets
34 +≠ what is causally verified by intervention
35 +```
36 +
37 +## Confidence taxonomy
38 +
39 +Every atlas entry carries an explicit evidence level, visible on modelmap.io:
40 +
41 +```text
42 +Level 0 — anecdotal (single run, no controls; never published alone)
43 +Level 1 — correlational (controlled, replicated ≥3 seeds, ≥2 datasets)
44 +Level 2 — method-robust (Level 1 + agreement across ≥2 independent techniques)
45 +Level 3 — causal (Level 2 + intervention confirms the claim)
46 +```
47 +
48 +## Status
49 +
50 +Project bootstrapped 2026-08-12. Phase 1 (ultra-deep literature research) is next.
51 +The full research charter is in [CLAUDE.md](CLAUDE.md); the auditable paper trail
52 +lives in [research/LOG.md](research/LOG.md).
53 +
54 +Sister project: **localvm-research** (out-of-core LLM execution on consumer Macs) —
55 +findings cross-pollinate, in particular working-set prediction (Q5).
56 +
57 +## Layout
58 +
59 +```text
60 +research/ paper trail: log, state of the art, gaps, ranking, methodology
61 +src/modelmap/ the mapping library (MLX / PyTorch-MPS / Metal; CUDA never core)
62 +experiments/ micro-experiments A–H and prototype candidates (registered hypotheses)
63 +atlas/ versioned map artifacts with provenance + confidence levels
64 +site/ modelmap.io platform source (builds and previews locally on the Mac)
65 +benchmarks/ harness, hardware manifest, versioned prompt corpora
66 +tools/ check_headers, new_experiment, new_map, publish
67 +```
68 +
69 +---
70 +
71 +*Author: Simon-Pierre Boucher — contact@spboucher.ai — https://modelmap.io — All rights reserved (research code).*
added atlas/README.md +21 −0
@@ -0,0 +1,21 @@
1 +---
2 +project: modelmap
3 +document: Atlas index
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Atlas — versioned map artifacts
12 +
13 +Layout: `atlas/<model_id>/<map_type>/<version>/` containing `map.json` (or
14 +`map.zarr`), `provenance.json` (commit, config, model hash, hardware, dates)
15 +and `confidence.md` (evidence level: correlational | replicated | causal).
16 +
17 +Entries are scaffolded by `tools/new_map.py` and **cannot be published without
18 +a completed provenance.json and confidence.md**. No claim appears on
19 +modelmap.io above its evidence level.
20 +
21 +*(No maps yet — the atlas fills only after the methodology (Phase 9) exists.)*
added benchmarks/hardware_manifest.py +71 −0
@@ -0,0 +1,71 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : modelmap
4 +# File : benchmarks/hardware_manifest.py
5 +# Purpose : Record chip, cores, RAM, SSD, macOS + software versions per run
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Website : https://modelmap.io
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""Hardware manifest for reproducibility (charter §0.2).
15 +
16 +Every result file and every published map embeds this manifest so that any
17 +number can be traced to the exact machine and software stack that produced it.
18 +"""
19 +
20 +from __future__ import annotations
21 +
22 +import json
23 +import platform
24 +import subprocess
25 +import sys
26 +from importlib import metadata
27 +
28 +
29 +def _sysctl(key: str) -> str:
30 + try:
31 + return subprocess.run(
32 + ["sysctl", "-n", key], capture_output=True, text=True, check=True
33 + ).stdout.strip()
34 + except subprocess.CalledProcessError:
35 + return ""
36 +
37 +
38 +def _pkg_version(name: str) -> str | None:
39 + try:
40 + return metadata.version(name)
41 + except metadata.PackageNotFoundError:
42 + return None
43 +
44 +
45 +def manifest() -> dict:
46 + mem_bytes = int(_sysctl("hw.memsize") or 0)
47 + return {
48 + "author": "Simon-Pierre Boucher",
49 + "contact": "contact@spboucher.ai",
50 + "website": "https://modelmap.io",
51 + "chip": {
52 + "brand": _sysctl("machdep.cpu.brand_string"),
53 + "cores_total": int(_sysctl("hw.ncpu") or 0),
54 + "cores_performance": int(_sysctl("hw.perflevel0.physicalcpu") or 0),
55 + "cores_efficiency": int(_sysctl("hw.perflevel1.physicalcpu") or 0),
56 + },
57 + "memory": {"unified_gb": round(mem_bytes / 2**30, 1), "pagesize": int(_sysctl("hw.pagesize") or 0)},
58 + "os": {"system": platform.system(), "version": platform.mac_ver()[0], "arch": platform.machine()},
59 + "software": {
60 + "python": platform.python_version(),
61 + "numpy": _pkg_version("numpy"),
62 + "mlx": _pkg_version("mlx"),
63 + "torch": _pkg_version("torch"),
64 + "safetensors": _pkg_version("safetensors"),
65 + },
66 + }
67 +
68 +
69 +if __name__ == "__main__":
70 + json.dump(manifest(), sys.stdout, indent=2)
71 + print()
added benchmarks/harness.py +61 −0
@@ -0,0 +1,61 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : modelmap
4 +# File : benchmarks/harness.py
5 +# Purpose : Benchmark harness skeleton — runs experiments, embeds manifests
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Website : https://modelmap.io
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""Experiment harness (skeleton — finalized during Phase 5).
15 +
16 +Contract established by the charter: every result JSON written under
17 +results/<experiment_id>/<UTC timestamp>/ embeds the hardware manifest, the
18 +git commit, the config, and the seed(s) used, so any map is reproducible from
19 +commit + config + model hash + seed + manifest.
20 +"""
21 +
22 +from __future__ import annotations
23 +
24 +import datetime
25 +import json
26 +import subprocess
27 +from pathlib import Path
28 +
29 +from hardware_manifest import manifest
30 +
31 +ROOT = Path(__file__).resolve().parent.parent
32 +
33 +
34 +def git_commit() -> str:
35 + try:
36 + return subprocess.run(
37 + ["git", "rev-parse", "HEAD"], cwd=ROOT, capture_output=True, text=True, check=True
38 + ).stdout.strip()
39 + except subprocess.CalledProcessError:
40 + return "unknown"
41 +
42 +
43 +def result_dir(experiment_id: str) -> Path:
44 + ts = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
45 + out = ROOT / "results" / experiment_id / ts
46 + out.mkdir(parents=True)
47 + return out
48 +
49 +
50 +def write_result(experiment_id: str, payload: dict, config: dict | None = None) -> Path:
51 + out = result_dir(experiment_id)
52 + doc = {
53 + "experiment": experiment_id,
54 + "commit": git_commit(),
55 + "config": config or {},
56 + "manifest": manifest(),
57 + **payload,
58 + }
59 + path = out / "results.json"
60 + path.write_text(json.dumps(doc, indent=2) + "\n")
61 + return path
added benchmarks/promptsets/README.md +17 −0
@@ -0,0 +1,17 @@
1 +---
2 +project: modelmap
3 +document: Prompt sets index
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Mapping prompt corpora
12 +
13 +Versioned artifacts with checksums (charter §0.3). Planned corpora: code,
14 +math, French, English, factual recall, reasoning, chat. Each corpus ships as
15 +a checksummed JSONL file plus a manifest recording its version and origin.
16 +
17 +*(No corpora committed yet — designed during Phase 5.)*
added docs/README.md +14 −0
@@ -0,0 +1,14 @@
1 +---
2 +project: modelmap
3 +document: Docs index
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Documentation
12 +
13 +Reserved for user-facing documentation of the modelmap library and pipeline
14 +(written once the methodology stabilizes, Phase 9+).
added experiments/candidate_01/README.md +14 −0
@@ -0,0 +1,14 @@
1 +---
2 +project: modelmap
3 +document: candidate_01
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# candidate_01
12 +
13 +Reserved for a Phase 7 prototype candidate — selected from `research/candidate_ranking.md` (Phase 4).
14 +Not yet assigned: candidates are chosen only after the Phase 1-3 literature work.
added experiments/candidate_02/README.md +14 −0
@@ -0,0 +1,14 @@
1 +---
2 +project: modelmap
3 +document: candidate_02
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# candidate_02
12 +
13 +Reserved for a Phase 7 prototype candidate — selected from `research/candidate_ranking.md` (Phase 4).
14 +Not yet assigned: candidates are chosen only after the Phase 1-3 literature work.
added experiments/candidate_03/README.md +14 −0
@@ -0,0 +1,14 @@
1 +---
2 +project: modelmap
3 +document: candidate_03
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# candidate_03
12 +
13 +Reserved for a Phase 7 prototype candidate — selected from `research/candidate_ranking.md` (Phase 4).
14 +Not yet assigned: candidates are chosen only after the Phase 1-3 literature work.
added experiments/micro/expA_probe_reliability/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: modelmap
3 +document: expA_probe_reliability
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expA_probe_reliability
12 +
13 +Probe reliability — noise floor of linear probes across seeds and datasets
14 +
15 +Status: scaffolded — hypothesis to be registered before any benchmark runs (charter §10).
added experiments/micro/expA_probe_reliability/hypothesis.md +23 −0
@@ -0,0 +1,23 @@
1 +---
2 +project: modelmap
3 +document: expA_probe_reliability — hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expA_probe_reliability
12 +
13 +> Probe reliability — noise floor of linear probes across seeds and datasets
14 +
15 +```text
16 +Hypothesis : (to be registered before the first run)
17 +Falsification criterion : (explicit kill-number, registered in advance)
18 +Method : (including controls)
19 +Baseline / null : (shuffled labels / random directions / random init)
20 +Result : (pending)
21 +Interpretation : (pending — with explicit confidence level)
22 +Next experiment : (pending)
23 +```
added experiments/micro/expB_localization_vs_diffusion/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: modelmap
3 +document: expB_localization_vs_diffusion
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expB_localization_vs_diffusion
12 +
13 +Localization vs diffusion — how concentrated is the signal supporting a capability
14 +
15 +Status: scaffolded — hypothesis to be registered before any benchmark runs (charter §10).
added experiments/micro/expB_localization_vs_diffusion/hypothesis.md +23 −0
@@ -0,0 +1,23 @@
1 +---
2 +project: modelmap
3 +document: expB_localization_vs_diffusion — hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expB_localization_vs_diffusion
12 +
13 +> Localization vs diffusion — how concentrated is the signal supporting a capability
14 +
15 +```text
16 +Hypothesis : (to be registered before the first run)
17 +Falsification criterion : (explicit kill-number, registered in advance)
18 +Method : (including controls)
19 +Baseline / null : (shuffled labels / random directions / random init)
20 +Result : (pending)
21 +Interpretation : (pending — with explicit confidence level)
22 +Next experiment : (pending)
23 +```
added experiments/micro/expC_causal_verification/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: modelmap
3 +document: expC_causal_verification
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expC_causal_verification
12 +
13 +Causal verification pipeline — correlational-to-causal survival rate
14 +
15 +Status: scaffolded — hypothesis to be registered before any benchmark runs (charter §10).
added experiments/micro/expC_causal_verification/hypothesis.md +23 −0
@@ -0,0 +1,23 @@
1 +---
2 +project: modelmap
3 +document: expC_causal_verification — hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expC_causal_verification
12 +
13 +> Causal verification pipeline — correlational-to-causal survival rate
14 +
15 +```text
16 +Hypothesis : (to be registered before the first run)
17 +Falsification criterion : (explicit kill-number, registered in advance)
18 +Method : (including controls)
19 +Baseline / null : (shuffled labels / random directions / random init)
20 +Result : (pending)
21 +Interpretation : (pending — with explicit confidence level)
22 +Next experiment : (pending)
23 +```
added experiments/micro/expD_cross_input_stability/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: modelmap
3 +document: expD_cross_input_stability
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expD_cross_input_stability
12 +
13 +Cross-input stability — which map types are input-invariant
14 +
15 +Status: scaffolded — hypothesis to be registered before any benchmark runs (charter §10).
added experiments/micro/expD_cross_input_stability/hypothesis.md +23 −0
@@ -0,0 +1,23 @@
1 +---
2 +project: modelmap
3 +document: expD_cross_input_stability — hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expD_cross_input_stability
12 +
13 +> Cross-input stability — which map types are input-invariant
14 +
15 +```text
16 +Hypothesis : (to be registered before the first run)
17 +Falsification criterion : (explicit kill-number, registered in advance)
18 +Method : (including controls)
19 +Baseline / null : (shuffled labels / random directions / random init)
20 +Result : (pending)
21 +Interpretation : (pending — with explicit confidence level)
22 +Next experiment : (pending)
23 +```
added experiments/micro/expE_weight_geometry/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: modelmap
3 +document: expE_weight_geometry
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expE_weight_geometry
12 +
13 +Weight-space geometry — zero-forward-pass maps (SVD, effective rank, CKA)
14 +
15 +Status: scaffolded — hypothesis to be registered before any benchmark runs (charter §10).
added experiments/micro/expE_weight_geometry/hypothesis.md +23 −0
@@ -0,0 +1,23 @@
1 +---
2 +project: modelmap
3 +document: expE_weight_geometry — hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expE_weight_geometry
12 +
13 +> Weight-space geometry — zero-forward-pass maps (SVD, effective rank, CKA)
14 +
15 +```text
16 +Hypothesis : (to be registered before the first run)
17 +Falsification criterion : (explicit kill-number, registered in advance)
18 +Method : (including controls)
19 +Baseline / null : (shuffled labels / random directions / random init)
20 +Result : (pending)
21 +Interpretation : (pending — with explicit confidence level)
22 +Next experiment : (pending)
23 +```
added experiments/micro/expF_quantization_map_drift/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: modelmap
3 +document: expF_quantization_map_drift
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expF_quantization_map_drift
12 +
13 +Quantization map drift — how FP16/Q8/Q4/Q2 deform internal structure
14 +
15 +Status: scaffolded — hypothesis to be registered before any benchmark runs (charter §10).
added experiments/micro/expF_quantization_map_drift/hypothesis.md +23 −0
@@ -0,0 +1,23 @@
1 +---
2 +project: modelmap
3 +document: expF_quantization_map_drift — hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expF_quantization_map_drift
12 +
13 +> Quantization map drift — how FP16/Q8/Q4/Q2 deform internal structure
14 +
15 +```text
16 +Hypothesis : (to be registered before the first run)
17 +Falsification criterion : (explicit kill-number, registered in advance)
18 +Method : (including controls)
19 +Baseline / null : (shuffled labels / random directions / random init)
20 +Result : (pending)
21 +Interpretation : (pending — with explicit confidence level)
22 +Next experiment : (pending)
23 +```
added experiments/micro/expG_cross_model_alignment/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: modelmap
3 +document: expG_cross_model_alignment
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expG_cross_model_alignment
12 +
13 +Cross-model alignment — shared coordinate systems across model sizes
14 +
15 +Status: scaffolded — hypothesis to be registered before any benchmark runs (charter §10).
added experiments/micro/expG_cross_model_alignment/hypothesis.md +23 −0
@@ -0,0 +1,23 @@
1 +---
2 +project: modelmap
3 +document: expG_cross_model_alignment — hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expG_cross_model_alignment
12 +
13 +> Cross-model alignment — shared coordinate systems across model sizes
14 +
15 +```text
16 +Hypothesis : (to be registered before the first run)
17 +Falsification criterion : (explicit kill-number, registered in advance)
18 +Method : (including controls)
19 +Baseline / null : (shuffled labels / random directions / random init)
20 +Result : (pending)
21 +Interpretation : (pending — with explicit confidence level)
22 +Next experiment : (pending)
23 +```
added experiments/micro/expH_capture_cost_frontier/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: modelmap
3 +document: expH_capture_cost_frontier
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expH_capture_cost_frontier
12 +
13 +Capture cost frontier — what can you map on which Mac (macOS-specific)
14 +
15 +Status: scaffolded — hypothesis to be registered before any benchmark runs (charter §10).
added experiments/micro/expH_capture_cost_frontier/hypothesis.md +23 −0
@@ -0,0 +1,23 @@
1 +---
2 +project: modelmap
3 +document: expH_capture_cost_frontier — hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expH_capture_cost_frontier
12 +
13 +> Capture cost frontier — what can you map on which Mac (macOS-specific)
14 +
15 +```text
16 +Hypothesis : (to be registered before the first run)
17 +Falsification criterion : (explicit kill-number, registered in advance)
18 +Method : (including controls)
19 +Baseline / null : (shuffled labels / random directions / random init)
20 +Result : (pending)
21 +Interpretation : (pending — with explicit confidence level)
22 +Next experiment : (pending)
23 +```
added pyproject.toml +39 −0
@@ -0,0 +1,39 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : pyproject.toml
4 +# Purpose : Python project configuration for the modelmap library
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +[project]
14 +name = "modelmap"
15 +version = "0.1.0"
16 +description = "Internal cartography of local large language models on Apple Silicon"
17 +authors = [{ name = "Simon-Pierre Boucher", email = "contact@spboucher.ai" }]
18 +requires-python = ">=3.11"
19 +dependencies = [
20 + "numpy",
21 + "safetensors",
22 +]
23 +
24 +[project.optional-dependencies]
25 +dev = ["ruff", "pytest"]
26 +
27 +[build-system]
28 +requires = ["setuptools>=68"]
29 +build-backend = "setuptools.build_meta"
30 +
31 +[tool.setuptools.packages.find]
32 +where = ["src"]
33 +
34 +[tool.ruff]
35 +line-length = 100
36 +target-version = "py311"
37 +
38 +[tool.pytest.ini_options]
39 +testpaths = ["tests", "src"]
added research/LOG.md +35 −0
@@ -0,0 +1,35 @@
1 +---
2 +project: modelmap
3 +document: Research log (append-only)
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# modelmap — Research log
12 +
13 +Append-only. Every entry records: date/time with timezone, question, experiment,
14 +result, interpretation, decision. The entire reasoning process must be auditable.
15 +
16 +---
17 +
18 +## 2026-08-12 00:30 EDT — Project bootstrap
19 +
20 +**Question.** None yet — administrative bootstrap.
21 +
22 +**Action.** Repository initialized from the research charter (`CLAUDE.md`):
23 +full skeleton per charter §3, mandatory author headers on every source file,
24 +`tools/check_headers.py` written first (charter §0.1 rule 3), experiment
25 +directories A–H scaffolded with empty seven-field scientific blocks
26 +(hypotheses will be registered before any run), and the public platform
27 +(`site/`) built and deployed to https://www.modelmap.io on MacLustr node
28 +M3U96b (PM2 + ngrok), mirroring the architecture of the sister project
29 +localvm-research (www.localvm.dev) so both research platforms stay coherent.
30 +
31 +**Decision.** Next step is Phase 1: ultra-deep literature research across the
32 +ten areas of charter §4 (observational probing, causal methods, feature
33 +decomposition, circuits, weight-space cartography, knowledge editing,
34 +cross-model comparison, statistics/epistemology, systems, atlas design).
35 +No mapping code is written before the state-of-the-art map (Phase 2) exists.
added research/bibliography.md +20 −0
@@ -0,0 +1,20 @@
1 +---
2 +project: modelmap
3 +document: Bibliography
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Bibliography
12 +
13 +Every consulted source, with URL and access date. Populated during Phase 1
14 +(charter §4). Entries use the form:
15 +
16 +```text
17 +- <Authors> — <Title> (<year>). <URL> — accessed <YYYY-MM-DD>. [themes]
18 +```
19 +
20 +*(No sources logged yet — Phase 1 has not started.)*
added research/notes/README.md +16 −0
@@ -0,0 +1,16 @@
1 +---
2 +project: modelmap
3 +document: Reading notes index
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +website: https://modelmap.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Phase 1 reading notes
12 +
13 +One markdown file per theme (charter §4.1–§4.10). Each note records: the
14 +primary sources read, what each technique claims to reveal, whether an
15 +open-source implementation exists and runs on Apple Silicon, known failure
16 +modes, and candidate extensions.
added site/lib/content.js +225 −0
@@ -0,0 +1,225 @@
1 +// ============================================================================
2 +// Project : modelmap
3 +// File : site/lib/content.js
4 +// Purpose : Content index over the synced research-repo snapshot (content/)
5 +// Author : Simon-Pierre Boucher
6 +// Contact : contact@spboucher.ai
7 +// Website : https://modelmap.io
8 +// Created : 2026-08-12
9 +// Modified : 2026-08-12
10 +// Platform : macOS / Apple Silicon (arm64) — Node.js (deployed on MacLustr)
11 +// License : All rights reserved (research code)
12 +// ============================================================================
13 +"use strict";
14 +
15 +const fs = require("fs");
16 +const path = require("path");
17 +const matter = require("gray-matter");
18 +
19 +const CONTENT_ROOT = path.join(__dirname, "..", "content");
20 +
21 +const TEXT_EXT = new Set([
22 + ".md", ".py", ".sh", ".toml", ".yaml", ".yml", ".cff", ".json",
23 + ".js", ".css", ".metal", ".cpp", ".h", ".hpp", ".swift", ".m", ".txt", ".log",
24 +]);
25 +
26 +/** Resolve a repo-relative path safely inside content/, or return null. */
27 +function safeResolve(rel) {
28 + const abs = path.resolve(CONTENT_ROOT, rel);
29 + if (!abs.startsWith(CONTENT_ROOT + path.sep) && abs !== CONTENT_ROOT) return null;
30 + return abs;
31 +}
32 +
33 +function exists(rel) {
34 + const abs = safeResolve(rel);
35 + return abs !== null && fs.existsSync(abs);
36 +}
37 +
38 +function readText(rel) {
39 + const abs = safeResolve(rel);
40 + if (!abs || !fs.existsSync(abs) || !fs.statSync(abs).isFile()) return null;
41 + return fs.readFileSync(abs, "utf8");
42 +}
43 +
44 +/** Read a markdown file, returning { data (front matter), content }. */
45 +function readMarkdown(rel) {
46 + const raw = readText(rel);
47 + if (raw === null) return null;
48 + try {
49 + return matter(raw);
50 + } catch {
51 + return { data: {}, content: raw };
52 + }
53 +}
54 +
55 +/** List a directory inside content/: [{name, rel, dir, size}] */
56 +function listDir(rel) {
57 + const abs = safeResolve(rel || ".");
58 + if (!abs || !fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) return null;
59 + return fs
60 + .readdirSync(abs)
61 + .filter((n) => !n.startsWith(".") && n !== "node_modules")
62 + .map((n) => {
63 + const st = fs.statSync(path.join(abs, n));
64 + return {
65 + name: n,
66 + rel: path.posix.join(rel || "", n),
67 + dir: st.isDirectory(),
68 + size: st.size,
69 + };
70 + })
71 + .sort((a, b) => (a.dir !== b.dir ? (a.dir ? -1 : 1) : a.name.localeCompare(b.name)));
72 +}
73 +
74 +/** Recursively collect files under rel matching a predicate. */
75 +function walk(rel, predicate, out = []) {
76 + const entries = listDir(rel);
77 + if (!entries) return out;
78 + for (const e of entries) {
79 + if (e.dir) walk(e.rel, predicate, out);
80 + else if (predicate(e)) out.push(e);
81 + }
82 + return out;
83 +}
84 +
85 +function isTextFile(rel) {
86 + return TEXT_EXT.has(path.extname(rel).toLowerCase()) || path.basename(rel) === "Makefile";
87 +}
88 +
89 +/** Parse research/LOG.md into entries [{title, body}] (## headings). */
90 +function parseLogEntries() {
91 + const md = readMarkdown("research/LOG.md");
92 + if (!md) return [];
93 + const parts = md.content.split(/^## /m).slice(1);
94 + return parts.map((p) => {
95 + const nl = p.indexOf("\n");
96 + return { title: p.slice(0, nl).trim(), body: p.slice(nl + 1).replace(/^---\s*$/m, "").trim() };
97 + });
98 +}
99 +
100 +/** Experiment cards: micro experiments + candidates. */
101 +function listExperiments() {
102 + const out = [];
103 + for (const base of ["experiments/micro", "experiments"]) {
104 + const entries = listDir(base) || [];
105 + for (const e of entries) {
106 + if (!e.dir || e.name === "micro") continue;
107 + const readme = readMarkdown(path.posix.join(e.rel, "README.md"));
108 + const hasAnalysis = (readText(path.posix.join(e.rel, "analysis.md")) || "").length > 400;
109 + const runs = listResultRuns().filter((r) => r.experiment === e.name);
110 + let purpose = "";
111 + if (readme) {
112 + const line = readme.content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
113 + purpose = line ? line.trim() : "";
114 + }
115 + out.push({
116 + id: e.name,
117 + rel: e.rel,
118 + purpose,
119 + status: runs.length > 0 && hasAnalysis ? "completed" : runs.length > 0 ? "has results" : "scaffolded",
120 + runs: runs.length,
121 + });
122 + }
123 + }
124 + return out;
125 +}
126 +
127 +/** Result runs: results/<experiment>/<timestamp>/*.json */
128 +function listResultRuns() {
129 + const out = [];
130 + for (const exp of listDir("results") || []) {
131 + if (!exp.dir) continue;
132 + for (const run of listDir(exp.rel) || []) {
133 + if (!run.dir) continue;
134 + const files = (listDir(run.rel) || []).filter((f) => !f.dir);
135 + out.push({ experiment: exp.name, timestamp: run.name, rel: run.rel, files });
136 + }
137 + }
138 + return out.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
139 +}
140 +
141 +/**
142 + * Atlas entries: atlas/<model_id>/<map_type>/<version>/ with provenance.json
143 + * and confidence.md. The confidence level is parsed from confidence.md; an
144 + * entry with no committed level is reported as "unrated" and the platform
145 + * never presents it above Level 0.
146 + */
147 +function listAtlasEntries() {
148 + const out = [];
149 + for (const model of listDir("atlas") || []) {
150 + if (!model.dir) continue;
151 + for (const mapType of listDir(model.rel) || []) {
152 + if (!mapType.dir) continue;
153 + for (const version of listDir(mapType.rel) || []) {
154 + if (!version.dir) continue;
155 + const provenance = readJson(path.posix.join(version.rel, "provenance.json"));
156 + const confidence = readText(path.posix.join(version.rel, "confidence.md")) || "";
157 + const m = confidence.match(/^Level\s*:\s*([0-3])\b/m);
158 + out.push({
159 + model: model.name,
160 + mapType: mapType.name,
161 + version: version.name,
162 + rel: version.rel,
163 + provenance,
164 + level: m ? Number(m[1]) : null,
165 + files: (listDir(version.rel) || []).filter((f) => !f.dir),
166 + });
167 + }
168 + }
169 + }
170 + return out;
171 +}
172 +
173 +function readJson(rel) {
174 + const raw = readText(rel);
175 + if (raw === null) return null;
176 + try {
177 + return JSON.parse(raw);
178 + } catch {
179 + return null;
180 + }
181 +}
182 +
183 +/** Build info written by sync-content.sh (commit, counts, date). */
184 +function buildInfo() {
185 + return readJson("build-info.json") || {};
186 +}
187 +
188 +/** Site-wide stats for the home page. */
189 +function stats() {
190 + const bib = readText("research/bibliography.md") || "";
191 + const sources = bib.split("\n").filter((l) => l.trim().startsWith("- ")).length;
192 + const gapsDoc = readText("research/research_gaps.md") || "";
193 + const gaps = (gapsDoc.match(/^#{2,3}\s+G\d{2}/gm) || []).length;
194 + const experiments = listExperiments();
195 + const notes = (listDir("research/notes") || []).filter((f) => f.name.endsWith(".md") && f.name !== "README.md").length;
196 + return {
197 + sources,
198 + gaps,
199 + notes,
200 + experiments: experiments.length,
201 + experimentsDone: experiments.filter((e) => e.status === "completed").length,
202 + resultRuns: listResultRuns().length,
203 + logEntries: parseLogEntries().length,
204 + atlasEntries: listAtlasEntries().length,
205 + build: buildInfo(),
206 + };
207 +}
208 +
209 +module.exports = {
210 + CONTENT_ROOT,
211 + safeResolve,
212 + exists,
213 + readText,
214 + readMarkdown,
215 + readJson,
216 + listDir,
217 + walk,
218 + isTextFile,
219 + parseLogEntries,
220 + listExperiments,
221 + listResultRuns,
222 + listAtlasEntries,
223 + buildInfo,
224 + stats,
225 +};
added site/lib/render.js +134 −0
@@ -0,0 +1,134 @@
1 +// ============================================================================
2 +// Project : modelmap
3 +// File : site/lib/render.js
4 +// Purpose : HTML layout, markdown/code rendering, confidence-level widgets
5 +// Author : Simon-Pierre Boucher
6 +// Contact : contact@spboucher.ai
7 +// Website : https://modelmap.io
8 +// Created : 2026-08-12
9 +// Modified : 2026-08-12
10 +// Platform : macOS / Apple Silicon (arm64) — Node.js (deployed on MacLustr)
11 +// License : All rights reserved (research code)
12 +// ============================================================================
13 +"use strict";
14 +
15 +const { marked } = require("marked");
16 +const hljs = require("highlight.js");
17 +
18 +function esc(s) {
19 + return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
20 +}
21 +
22 +// Rewrite relative markdown links to platform routes; highlight code blocks.
23 +const renderer = new marked.Renderer();
24 +renderer.code = function ({ text, lang }) {
25 + let html;
26 + if (lang && hljs.getLanguage(lang)) {
27 + html = hljs.highlight(text, { language: lang }).value;
28 + } else {
29 + html = esc(text);
30 + }
31 + return `<pre class="codeblock"><code class="hljs">${html}</code></pre>`;
32 +};
33 +
34 +function markdownToHtml(md, baseRel) {
35 + marked.use({
36 + renderer,
37 + walkTokens(token) {
38 + if (token.type === "link" && token.href && !/^(https?:|mailto:|#|\/)/.test(token.href)) {
39 + const base = baseRel.includes("/") ? baseRel.slice(0, baseRel.lastIndexOf("/")) : "";
40 + const joined = (base ? base + "/" : "") + token.href;
41 + const norm = joined.split("/").reduce((acc, part) => {
42 + if (part === "..") acc.pop();
43 + else if (part !== "." && part !== "") acc.push(part);
44 + return acc;
45 + }, []).join("/");
46 + token.href = norm.endsWith(".md") ? "/doc/" + norm : "/file/" + norm;
47 + }
48 + },
49 + });
50 + return marked.parse(md);
51 +}
52 +
53 +function highlightFile(text, filename) {
54 + const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();
55 + const langMap = {
56 + py: "python", sh: "bash", js: "javascript", css: "css", json: "json",
57 + toml: "ini", yaml: "yaml", yml: "yaml", cff: "yaml", md: "markdown",
58 + metal: "cpp", cpp: "cpp", h: "cpp", hpp: "cpp", swift: "swift", m: "objectivec",
59 + };
60 + const lang = filename === "Makefile" ? "makefile" : langMap[ext];
61 + if (lang && hljs.getLanguage(lang)) return hljs.highlight(text, { language: lang }).value;
62 + return esc(text);
63 +}
64 +
65 +const NAV = [
66 + ["/", "Home"],
67 + ["/research", "Research"],
68 + ["/experiments", "Experiments"],
69 + ["/atlas", "Atlas"],
70 + ["/results", "Results"],
71 + ["/code", "Code"],
72 + ["/about", "About"],
73 +];
74 +
75 +const LEVELS = [
76 + ["0", "anecdotal", "single run, no controls — never published alone"],
77 + ["1", "correlational", "controlled, replicated ≥3 seeds, ≥2 datasets"],
78 + ["2", "method-robust", "Level 1 + agreement across ≥2 independent techniques"],
79 + ["3", "causal", "Level 2 + intervention confirms the claim"],
80 +];
81 +
82 +/** Confidence badge for an atlas entry (null level → "unrated"). */
83 +function levelBadge(level) {
84 + if (level === null || level === undefined) {
85 + return `<span class="badge badge-pending">unrated</span>`;
86 + }
87 + return `<span class="badge badge-level-${level}">Level ${level} — ${LEVELS[level][1]}</span>`;
88 +}
89 +
90 +/** The confidence taxonomy ladder (used on Home / Atlas / About). */
91 +function confidenceLadder() {
92 + return `<ol class="ladder">` + LEVELS.map(
93 + ([n, name, desc]) => `<li class="ladder-step"><span class="ladder-num lv${n}">L${n}</span>
94 + <div><strong>${name}</strong><span class="ladder-desc">${desc}</span></div></li>`
95 + ).join("") + `</ol>`;
96 +}
97 +
98 +function layout({ title, active, body, buildInfo = {} }) {
99 + const nav = NAV.map(
100 + ([href, label]) =>
101 + `<a href="${href}" class="${active === label ? "active" : ""}">${label}</a>`
102 + ).join("");
103 + const commit = buildInfo.commit ? buildInfo.commit.slice(0, 7) : "dev";
104 + const synced = buildInfo.generated ? buildInfo.generated.slice(0, 10) : "";
105 + return `<!DOCTYPE html>
106 +<html lang="en">
107 +<head>
108 +<meta charset="utf-8">
109 +<meta name="viewport" content="width=device-width, initial-scale=1">
110 +<title>${esc(title)} · modelmap</title>
111 +<meta name="description" content="modelmap — internal cartography of local large language models on consumer Apple Silicon. A reproducible, confidence-labeled research atlas by Simon-Pierre Boucher.">
112 +<link rel="stylesheet" href="/static/style.css">
113 +<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='0.9em' font-size='90'>🗺️</text></svg>">
114 +</head>
115 +<body>
116 +<header class="site-header">
117 + <div class="wrap header-row">
118 + <a class="brand" href="/">modelmap<span class="brand-dim">.io</span></a>
119 + <nav class="site-nav">${nav}</nav>
120 + </div>
121 +</header>
122 +<main class="wrap">${body}</main>
123 +<footer class="site-footer">
124 + <div class="wrap">
125 + <span>© 2026 Simon-Pierre Boucher — <a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a> · <a href="https://modelmap.io">modelmap.io</a> · All rights reserved (research code)</span>
126 + <span class="footer-meta">snapshot ${esc(commit)}${synced ? " · synced " + esc(synced) : ""} · sister project: <a href="https://www.localvm.dev">localvm-research</a></span>
127 + </div>
128 +</footer>
129 +<script src="/static/app.js" defer></script>
130 +</body>
131 +</html>`;
132 +}
133 +
134 +module.exports = { esc, markdownToHtml, highlightFile, layout, levelBadge, confidenceLadder, LEVELS };
added site/package-lock.json +961 −0
@@ -0,0 +1,961 @@
1 +{
2 + "name": "modelmap-web",
3 + "version": "1.0.0",
4 + "lockfileVersion": 3,
5 + "requires": true,
6 + "packages": {
7 + "": {
8 + "name": "modelmap-web",
9 + "version": "1.0.0",
10 + "license": "UNLICENSED",
11 + "dependencies": {
12 + "express": "^4.21.0",
13 + "gray-matter": "^4.0.3",
14 + "highlight.js": "^11.10.0",
15 + "marked": "^14.1.0"
16 + }
17 + },
18 + "node_modules/accepts": {
19 + "version": "1.3.8",
20 + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
21 + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
22 + "license": "MIT",
23 + "dependencies": {
24 + "mime-types": "~2.1.34",
25 + "negotiator": "0.6.3"
26 + },
27 + "engines": {
28 + "node": ">= 0.6"
29 + }
30 + },
31 + "node_modules/argparse": {
32 + "version": "1.0.10",
33 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
34 + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
35 + "license": "MIT",
36 + "dependencies": {
37 + "sprintf-js": "~1.0.2"
38 + }
39 + },
40 + "node_modules/array-flatten": {
41 + "version": "1.1.1",
42 + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
43 + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
44 + "license": "MIT"
45 + },
46 + "node_modules/body-parser": {
47 + "version": "1.20.6",
48 + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz",
49 + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==",
50 + "license": "MIT",
51 + "dependencies": {
52 + "bytes": "~3.1.2",
53 + "content-type": "~1.0.5",
54 + "debug": "2.6.9",
55 + "depd": "2.0.0",
56 + "destroy": "~1.2.0",
57 + "http-errors": "~2.0.1",
58 + "iconv-lite": "~0.4.24",
59 + "on-finished": "~2.4.1",
60 + "qs": "~6.15.1",
61 + "raw-body": "~2.5.3",
62 + "type-is": "~1.6.18",
63 + "unpipe": "~1.0.0"
64 + },
65 + "engines": {
66 + "node": ">= 0.8",
67 + "npm": "1.2.8000 || >= 1.4.16"
68 + }
69 + },
70 + "node_modules/bytes": {
71 + "version": "3.1.2",
72 + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
73 + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
74 + "license": "MIT",
75 + "engines": {
76 + "node": ">= 0.8"
77 + }
78 + },
79 + "node_modules/call-bind-apply-helpers": {
80 + "version": "1.0.2",
81 + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
82 + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
83 + "license": "MIT",
84 + "dependencies": {
85 + "es-errors": "^1.3.0",
86 + "function-bind": "^1.1.2"
87 + },
88 + "engines": {
89 + "node": ">= 0.4"
90 + }
91 + },
92 + "node_modules/call-bound": {
93 + "version": "1.0.4",
94 + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
95 + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
96 + "license": "MIT",
97 + "dependencies": {
98 + "call-bind-apply-helpers": "^1.0.2",
99 + "get-intrinsic": "^1.3.0"
100 + },
101 + "engines": {
102 + "node": ">= 0.4"
103 + },
104 + "funding": {
105 + "url": "https://github.com/sponsors/ljharb"
106 + }
107 + },
108 + "node_modules/content-disposition": {
109 + "version": "0.5.4",
110 + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
111 + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
112 + "license": "MIT",
113 + "dependencies": {
114 + "safe-buffer": "5.2.1"
115 + },
116 + "engines": {
117 + "node": ">= 0.6"
118 + }
119 + },
120 + "node_modules/content-type": {
121 + "version": "1.0.5",
122 + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
123 + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
124 + "license": "MIT",
125 + "engines": {
126 + "node": ">= 0.6"
127 + }
128 + },
129 + "node_modules/cookie": {
130 + "version": "0.7.2",
131 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
132 + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
133 + "license": "MIT",
134 + "engines": {
135 + "node": ">= 0.6"
136 + }
137 + },
138 + "node_modules/cookie-signature": {
139 + "version": "1.0.7",
140 + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
141 + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
142 + "license": "MIT"
143 + },
144 + "node_modules/debug": {
145 + "version": "2.6.9",
146 + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
147 + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
148 + "license": "MIT",
149 + "dependencies": {
150 + "ms": "2.0.0"
151 + }
152 + },
153 + "node_modules/depd": {
154 + "version": "2.0.0",
155 + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
156 + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
157 + "license": "MIT",
158 + "engines": {
159 + "node": ">= 0.8"
160 + }
161 + },
162 + "node_modules/destroy": {
163 + "version": "1.2.0",
164 + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
165 + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
166 + "license": "MIT",
167 + "engines": {
168 + "node": ">= 0.8",
169 + "npm": "1.2.8000 || >= 1.4.16"
170 + }
171 + },
172 + "node_modules/dunder-proto": {
173 + "version": "1.0.1",
174 + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
175 + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
176 + "license": "MIT",
177 + "dependencies": {
178 + "call-bind-apply-helpers": "^1.0.1",
179 + "es-errors": "^1.3.0",
180 + "gopd": "^1.2.0"
181 + },
182 + "engines": {
183 + "node": ">= 0.4"
184 + }
185 + },
186 + "node_modules/ee-first": {
187 + "version": "1.1.1",
188 + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
189 + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
190 + "license": "MIT"
191 + },
192 + "node_modules/encodeurl": {
193 + "version": "2.0.0",
194 + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
195 + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
196 + "license": "MIT",
197 + "engines": {
198 + "node": ">= 0.8"
199 + }
200 + },
201 + "node_modules/es-define-property": {
202 + "version": "1.0.1",
203 + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
204 + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
205 + "license": "MIT",
206 + "engines": {
207 + "node": ">= 0.4"
208 + }
209 + },
210 + "node_modules/es-errors": {
211 + "version": "1.3.0",
212 + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
213 + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
214 + "license": "MIT",
215 + "engines": {
216 + "node": ">= 0.4"
217 + }
218 + },
219 + "node_modules/es-object-atoms": {
220 + "version": "1.1.2",
221 + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
222 + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
223 + "license": "MIT",
224 + "dependencies": {
225 + "es-errors": "^1.3.0"
226 + },
227 + "engines": {
228 + "node": ">= 0.4"
229 + }
230 + },
231 + "node_modules/escape-html": {
232 + "version": "1.0.3",
233 + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
234 + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
235 + "license": "MIT"
236 + },
237 + "node_modules/esprima": {
238 + "version": "4.0.1",
239 + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
240 + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
241 + "license": "BSD-2-Clause",
242 + "bin": {
243 + "esparse": "bin/esparse.js",
244 + "esvalidate": "bin/esvalidate.js"
245 + },
246 + "engines": {
247 + "node": ">=4"
248 + }
249 + },
250 + "node_modules/etag": {
251 + "version": "1.8.1",
252 + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
253 + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
254 + "license": "MIT",
255 + "engines": {
256 + "node": ">= 0.6"
257 + }
258 + },
259 + "node_modules/express": {
260 + "version": "4.22.2",
261 + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
262 + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
263 + "license": "MIT",
264 + "dependencies": {
265 + "accepts": "~1.3.8",
266 + "array-flatten": "1.1.1",
267 + "body-parser": "~1.20.5",
268 + "content-disposition": "~0.5.4",
269 + "content-type": "~1.0.4",
270 + "cookie": "~0.7.1",
271 + "cookie-signature": "~1.0.6",
272 + "debug": "2.6.9",
273 + "depd": "2.0.0",
274 + "encodeurl": "~2.0.0",
275 + "escape-html": "~1.0.3",
276 + "etag": "~1.8.1",
277 + "finalhandler": "~1.3.1",
278 + "fresh": "~0.5.2",
279 + "http-errors": "~2.0.0",
280 + "merge-descriptors": "1.0.3",
281 + "methods": "~1.1.2",
282 + "on-finished": "~2.4.1",
283 + "parseurl": "~1.3.3",
284 + "path-to-regexp": "~0.1.12",
285 + "proxy-addr": "~2.0.7",
286 + "qs": "~6.15.1",
287 + "range-parser": "~1.2.1",
288 + "safe-buffer": "5.2.1",
289 + "send": "~0.19.0",
290 + "serve-static": "~1.16.2",
291 + "setprototypeof": "1.2.0",
292 + "statuses": "~2.0.1",
293 + "type-is": "~1.6.18",
294 + "utils-merge": "1.0.1",
295 + "vary": "~1.1.2"
296 + },
297 + "engines": {
298 + "node": ">= 0.10.0"
299 + },
300 + "funding": {
301 + "type": "opencollective",
302 + "url": "https://opencollective.com/express"
303 + }
304 + },
305 + "node_modules/extend-shallow": {
306 + "version": "2.0.1",
307 + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
308 + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
309 + "license": "MIT",
310 + "dependencies": {
311 + "is-extendable": "^0.1.0"
312 + },
313 + "engines": {
314 + "node": ">=0.10.0"
315 + }
316 + },
317 + "node_modules/finalhandler": {
318 + "version": "1.3.2",
319 + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
320 + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
321 + "license": "MIT",
322 + "dependencies": {
323 + "debug": "2.6.9",
324 + "encodeurl": "~2.0.0",
325 + "escape-html": "~1.0.3",
326 + "on-finished": "~2.4.1",
327 + "parseurl": "~1.3.3",
328 + "statuses": "~2.0.2",
329 + "unpipe": "~1.0.0"
330 + },
331 + "engines": {
332 + "node": ">= 0.8"
333 + }
334 + },
335 + "node_modules/forwarded": {
336 + "version": "0.2.0",
337 + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
338 + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
339 + "license": "MIT",
340 + "engines": {
341 + "node": ">= 0.6"
342 + }
343 + },
344 + "node_modules/fresh": {
345 + "version": "0.5.2",
346 + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
347 + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
348 + "license": "MIT",
349 + "engines": {
350 + "node": ">= 0.6"
351 + }
352 + },
353 + "node_modules/function-bind": {
354 + "version": "1.1.2",
355 + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
356 + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
357 + "license": "MIT",
358 + "funding": {
359 + "url": "https://github.com/sponsors/ljharb"
360 + }
361 + },
362 + "node_modules/get-intrinsic": {
363 + "version": "1.3.0",
364 + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
365 + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
366 + "license": "MIT",
367 + "dependencies": {
368 + "call-bind-apply-helpers": "^1.0.2",
369 + "es-define-property": "^1.0.1",
370 + "es-errors": "^1.3.0",
371 + "es-object-atoms": "^1.1.1",
372 + "function-bind": "^1.1.2",
373 + "get-proto": "^1.0.1",
374 + "gopd": "^1.2.0",
375 + "has-symbols": "^1.1.0",
376 + "hasown": "^2.0.2",
377 + "math-intrinsics": "^1.1.0"
378 + },
379 + "engines": {
380 + "node": ">= 0.4"
381 + },
382 + "funding": {
383 + "url": "https://github.com/sponsors/ljharb"
384 + }
385 + },
386 + "node_modules/get-proto": {
387 + "version": "1.0.1",
388 + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
389 + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
390 + "license": "MIT",
391 + "dependencies": {
392 + "dunder-proto": "^1.0.1",
393 + "es-object-atoms": "^1.0.0"
394 + },
395 + "engines": {
396 + "node": ">= 0.4"
397 + }
398 + },
399 + "node_modules/gopd": {
400 + "version": "1.2.0",
401 + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
402 + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
403 + "license": "MIT",
404 + "engines": {
405 + "node": ">= 0.4"
406 + },
407 + "funding": {
408 + "url": "https://github.com/sponsors/ljharb"
409 + }
410 + },
411 + "node_modules/gray-matter": {
412 + "version": "4.0.3",
413 + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
414 + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
415 + "license": "MIT",
416 + "dependencies": {
417 + "js-yaml": "^3.13.1",
418 + "kind-of": "^6.0.2",
419 + "section-matter": "^1.0.0",
420 + "strip-bom-string": "^1.0.0"
421 + },
422 + "engines": {
423 + "node": ">=6.0"
424 + }
425 + },
426 + "node_modules/has-symbols": {
427 + "version": "1.1.0",
428 + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
429 + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
430 + "license": "MIT",
431 + "engines": {
432 + "node": ">= 0.4"
433 + },
434 + "funding": {
435 + "url": "https://github.com/sponsors/ljharb"
436 + }
437 + },
438 + "node_modules/hasown": {
439 + "version": "2.0.4",
440 + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
441 + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
442 + "license": "MIT",
443 + "dependencies": {
444 + "function-bind": "^1.1.2"
445 + },
446 + "engines": {
447 + "node": ">= 0.4"
448 + }
449 + },
450 + "node_modules/highlight.js": {
451 + "version": "11.12.0",
452 + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.12.0.tgz",
453 + "integrity": "sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==",
454 + "license": "BSD-3-Clause",
455 + "engines": {
456 + "node": ">=12.0.0"
457 + }
458 + },
459 + "node_modules/http-errors": {
460 + "version": "2.0.1",
461 + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
462 + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
463 + "license": "MIT",
464 + "dependencies": {
465 + "depd": "~2.0.0",
466 + "inherits": "~2.0.4",
467 + "setprototypeof": "~1.2.0",
468 + "statuses": "~2.0.2",
469 + "toidentifier": "~1.0.1"
470 + },
471 + "engines": {
472 + "node": ">= 0.8"
473 + },
474 + "funding": {
475 + "type": "opencollective",
476 + "url": "https://opencollective.com/express"
477 + }
478 + },
479 + "node_modules/iconv-lite": {
480 + "version": "0.4.24",
481 + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
482 + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
483 + "license": "MIT",
484 + "dependencies": {
485 + "safer-buffer": ">= 2.1.2 < 3"
486 + },
487 + "engines": {
488 + "node": ">=0.10.0"
489 + }
490 + },
491 + "node_modules/inherits": {
492 + "version": "2.0.4",
493 + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
494 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
495 + "license": "ISC"
496 + },
497 + "node_modules/ipaddr.js": {
498 + "version": "1.9.1",
499 + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
500 + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
501 + "license": "MIT",
502 + "engines": {
503 + "node": ">= 0.10"
504 + }
505 + },
506 + "node_modules/is-extendable": {
507 + "version": "0.1.1",
508 + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
509 + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
510 + "license": "MIT",
511 + "engines": {
512 + "node": ">=0.10.0"
513 + }
514 + },
515 + "node_modules/js-yaml": {
516 + "version": "3.15.1",
517 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz",
518 + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==",
519 + "license": "MIT",
520 + "dependencies": {
521 + "argparse": "^1.0.7",
522 + "esprima": "^4.0.0"
523 + },
524 + "bin": {
525 + "js-yaml": "bin/js-yaml.js"
526 + }
527 + },
528 + "node_modules/kind-of": {
529 + "version": "6.0.3",
530 + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
531 + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
532 + "license": "MIT",
533 + "engines": {
534 + "node": ">=0.10.0"
535 + }
536 + },
537 + "node_modules/marked": {
538 + "version": "14.1.4",
539 + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.4.tgz",
540 + "integrity": "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==",
541 + "license": "MIT",
542 + "bin": {
543 + "marked": "bin/marked.js"
544 + },
545 + "engines": {
546 + "node": ">= 18"
547 + }
548 + },
549 + "node_modules/math-intrinsics": {
550 + "version": "1.1.0",
551 + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
552 + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
553 + "license": "MIT",
554 + "engines": {
555 + "node": ">= 0.4"
556 + }
557 + },
558 + "node_modules/media-typer": {
559 + "version": "0.3.0",
560 + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
561 + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
562 + "license": "MIT",
563 + "engines": {
564 + "node": ">= 0.6"
565 + }
566 + },
567 + "node_modules/merge-descriptors": {
568 + "version": "1.0.3",
569 + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
570 + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
571 + "license": "MIT",
572 + "funding": {
573 + "url": "https://github.com/sponsors/sindresorhus"
574 + }
575 + },
576 + "node_modules/methods": {
577 + "version": "1.1.2",
578 + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
579 + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
580 + "license": "MIT",
581 + "engines": {
582 + "node": ">= 0.6"
583 + }
584 + },
585 + "node_modules/mime": {
586 + "version": "1.6.0",
587 + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
588 + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
589 + "license": "MIT",
590 + "bin": {
591 + "mime": "cli.js"
592 + },
593 + "engines": {
594 + "node": ">=4"
595 + }
596 + },
597 + "node_modules/mime-db": {
598 + "version": "1.52.0",
599 + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
600 + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
601 + "license": "MIT",
602 + "engines": {
603 + "node": ">= 0.6"
604 + }
605 + },
606 + "node_modules/mime-types": {
607 + "version": "2.1.35",
608 + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
609 + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
610 + "license": "MIT",
611 + "dependencies": {
612 + "mime-db": "1.52.0"
613 + },
614 + "engines": {
615 + "node": ">= 0.6"
616 + }
617 + },
618 + "node_modules/ms": {
619 + "version": "2.0.0",
620 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
621 + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
622 + "license": "MIT"
623 + },
624 + "node_modules/negotiator": {
625 + "version": "0.6.3",
626 + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
627 + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
628 + "license": "MIT",
629 + "engines": {
630 + "node": ">= 0.6"
631 + }
632 + },
633 + "node_modules/object-inspect": {
634 + "version": "1.13.4",
635 + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
636 + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
637 + "license": "MIT",
638 + "engines": {
639 + "node": ">= 0.4"
640 + },
641 + "funding": {
642 + "url": "https://github.com/sponsors/ljharb"
643 + }
644 + },
645 + "node_modules/on-finished": {
646 + "version": "2.4.1",
647 + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
648 + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
649 + "license": "MIT",
650 + "dependencies": {
651 + "ee-first": "1.1.1"
652 + },
653 + "engines": {
654 + "node": ">= 0.8"
655 + }
656 + },
657 + "node_modules/parseurl": {
658 + "version": "1.3.3",
659 + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
660 + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
661 + "license": "MIT",
662 + "engines": {
663 + "node": ">= 0.8"
664 + }
665 + },
666 + "node_modules/path-to-regexp": {
667 + "version": "0.1.13",
668 + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
669 + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
670 + "license": "MIT"
671 + },
672 + "node_modules/proxy-addr": {
673 + "version": "2.0.7",
674 + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
675 + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
676 + "license": "MIT",
677 + "dependencies": {
678 + "forwarded": "0.2.0",
679 + "ipaddr.js": "1.9.1"
680 + },
681 + "engines": {
682 + "node": ">= 0.10"
683 + }
684 + },
685 + "node_modules/qs": {
686 + "version": "6.15.3",
687 + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
688 + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
689 + "license": "BSD-3-Clause",
690 + "dependencies": {
691 + "es-define-property": "^1.0.1",
692 + "side-channel": "^1.1.1"
693 + },
694 + "engines": {
695 + "node": ">=0.6"
696 + },
697 + "funding": {
698 + "url": "https://github.com/sponsors/ljharb"
699 + }
700 + },
701 + "node_modules/range-parser": {
702 + "version": "1.2.1",
703 + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
704 + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
705 + "license": "MIT",
706 + "engines": {
707 + "node": ">= 0.6"
708 + }
709 + },
710 + "node_modules/raw-body": {
711 + "version": "2.5.3",
712 + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
713 + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
714 + "license": "MIT",
715 + "dependencies": {
716 + "bytes": "~3.1.2",
717 + "http-errors": "~2.0.1",
718 + "iconv-lite": "~0.4.24",
719 + "unpipe": "~1.0.0"
720 + },
721 + "engines": {
722 + "node": ">= 0.8"
723 + }
724 + },
725 + "node_modules/safe-buffer": {
726 + "version": "5.2.1",
727 + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
728 + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
729 + "funding": [
730 + {
731 + "type": "github",
732 + "url": "https://github.com/sponsors/feross"
733 + },
734 + {
735 + "type": "patreon",
736 + "url": "https://www.patreon.com/feross"
737 + },
738 + {
739 + "type": "consulting",
740 + "url": "https://feross.org/support"
741 + }
742 + ],
743 + "license": "MIT"
744 + },
745 + "node_modules/safer-buffer": {
746 + "version": "2.1.2",
747 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
748 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
749 + "license": "MIT"
750 + },
751 + "node_modules/section-matter": {
752 + "version": "1.0.0",
753 + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
754 + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
755 + "license": "MIT",
756 + "dependencies": {
757 + "extend-shallow": "^2.0.1",
758 + "kind-of": "^6.0.0"
759 + },
760 + "engines": {
761 + "node": ">=4"
762 + }
763 + },
764 + "node_modules/send": {
765 + "version": "0.19.2",
766 + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
767 + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
768 + "license": "MIT",
769 + "dependencies": {
770 + "debug": "2.6.9",
771 + "depd": "2.0.0",
772 + "destroy": "1.2.0",
773 + "encodeurl": "~2.0.0",
774 + "escape-html": "~1.0.3",
775 + "etag": "~1.8.1",
776 + "fresh": "~0.5.2",
777 + "http-errors": "~2.0.1",
778 + "mime": "1.6.0",
779 + "ms": "2.1.3",
780 + "on-finished": "~2.4.1",
781 + "range-parser": "~1.2.1",
782 + "statuses": "~2.0.2"
783 + },
784 + "engines": {
785 + "node": ">= 0.8.0"
786 + }
787 + },
788 + "node_modules/send/node_modules/ms": {
789 + "version": "2.1.3",
790 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
791 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
792 + "license": "MIT"
793 + },
794 + "node_modules/serve-static": {
795 + "version": "1.16.3",
796 + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
797 + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
798 + "license": "MIT",
799 + "dependencies": {
800 + "encodeurl": "~2.0.0",
801 + "escape-html": "~1.0.3",
802 + "parseurl": "~1.3.3",
803 + "send": "~0.19.1"
804 + },
805 + "engines": {
806 + "node": ">= 0.8.0"
807 + }
808 + },
809 + "node_modules/setprototypeof": {
810 + "version": "1.2.0",
811 + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
812 + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
813 + "license": "ISC"
814 + },
815 + "node_modules/side-channel": {
816 + "version": "1.1.1",
817 + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
818 + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
819 + "license": "MIT",
820 + "dependencies": {
821 + "es-errors": "^1.3.0",
822 + "object-inspect": "^1.13.4",
823 + "side-channel-list": "^1.0.1",
824 + "side-channel-map": "^1.0.1",
825 + "side-channel-weakmap": "^1.0.2"
826 + },
827 + "engines": {
828 + "node": ">= 0.4"
829 + },
830 + "funding": {
831 + "url": "https://github.com/sponsors/ljharb"
832 + }
833 + },
834 + "node_modules/side-channel-list": {
835 + "version": "1.0.1",
836 + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
837 + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
838 + "license": "MIT",
839 + "dependencies": {
840 + "es-errors": "^1.3.0",
841 + "object-inspect": "^1.13.4"
842 + },
843 + "engines": {
844 + "node": ">= 0.4"
845 + },
846 + "funding": {
847 + "url": "https://github.com/sponsors/ljharb"
848 + }
849 + },
850 + "node_modules/side-channel-map": {
851 + "version": "1.0.1",
852 + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
853 + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
854 + "license": "MIT",
855 + "dependencies": {
856 + "call-bound": "^1.0.2",
857 + "es-errors": "^1.3.0",
858 + "get-intrinsic": "^1.2.5",
859 + "object-inspect": "^1.13.3"
860 + },
861 + "engines": {
862 + "node": ">= 0.4"
863 + },
864 + "funding": {
865 + "url": "https://github.com/sponsors/ljharb"
866 + }
867 + },
868 + "node_modules/side-channel-weakmap": {
869 + "version": "1.0.2",
870 + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
871 + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
872 + "license": "MIT",
873 + "dependencies": {
874 + "call-bound": "^1.0.2",
875 + "es-errors": "^1.3.0",
876 + "get-intrinsic": "^1.2.5",
877 + "object-inspect": "^1.13.3",
878 + "side-channel-map": "^1.0.1"
879 + },
880 + "engines": {
881 + "node": ">= 0.4"
882 + },
883 + "funding": {
884 + "url": "https://github.com/sponsors/ljharb"
885 + }
886 + },
887 + "node_modules/sprintf-js": {
888 + "version": "1.0.3",
889 + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
890 + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
891 + "license": "BSD-3-Clause"
892 + },
893 + "node_modules/statuses": {
894 + "version": "2.0.2",
895 + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
896 + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
897 + "license": "MIT",
898 + "engines": {
899 + "node": ">= 0.8"
900 + }
901 + },
902 + "node_modules/strip-bom-string": {
903 + "version": "1.0.0",
904 + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
905 + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
906 + "license": "MIT",
907 + "engines": {
908 + "node": ">=0.10.0"
909 + }
910 + },
911 + "node_modules/toidentifier": {
912 + "version": "1.0.1",
913 + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
914 + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
915 + "license": "MIT",
916 + "engines": {
917 + "node": ">=0.6"
918 + }
919 + },
920 + "node_modules/type-is": {
921 + "version": "1.6.18",
922 + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
923 + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
924 + "license": "MIT",
925 + "dependencies": {
926 + "media-typer": "0.3.0",
927 + "mime-types": "~2.1.24"
928 + },
929 + "engines": {
930 + "node": ">= 0.6"
931 + }
932 + },
933 + "node_modules/unpipe": {
934 + "version": "1.0.0",
935 + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
936 + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
937 + "license": "MIT",
938 + "engines": {
939 + "node": ">= 0.8"
940 + }
941 + },
942 + "node_modules/utils-merge": {
943 + "version": "1.0.1",
944 + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
945 + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
946 + "license": "MIT",
947 + "engines": {
948 + "node": ">= 0.4.0"
949 + }
950 + },
951 + "node_modules/vary": {
952 + "version": "1.1.2",
953 + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
954 + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
955 + "license": "MIT",
956 + "engines": {
957 + "node": ">= 0.8"
958 + }
959 + }
960 + }
961 +}
added site/package.json +18 −0
@@ -0,0 +1,18 @@
1 +{
2 + "name": "modelmap-web",
3 + "version": "1.0.0",
4 + "description": "modelmap.io — living research atlas for the internal cartography of local LLMs — Simon-Pierre Boucher <contact@spboucher.ai>",
5 + "author": "Simon-Pierre Boucher <contact@spboucher.ai>",
6 + "license": "UNLICENSED",
7 + "private": true,
8 + "main": "server.js",
9 + "scripts": {
10 + "start": "node server.js"
11 + },
12 + "dependencies": {
13 + "express": "^4.21.0",
14 + "gray-matter": "^4.0.3",
15 + "highlight.js": "^11.10.0",
16 + "marked": "^14.1.0"
17 + }
18 +}
added site/public/app.js +32 −0
@@ -0,0 +1,32 @@
1 +// ============================================================================
2 +// Project : modelmap
3 +// File : site/public/app.js
4 +// Purpose : Client-side interactivity (chart hover tooltips)
5 +// Author : Simon-Pierre Boucher
6 +// Contact : contact@spboucher.ai
7 +// Website : https://modelmap.io
8 +// Created : 2026-08-12
9 +// Modified : 2026-08-12
10 +// Platform : Web (deployed from macOS / Apple Silicon)
11 +// License : All rights reserved (research code)
12 +// ============================================================================
13 +"use strict";
14 +
15 +(function () {
16 + const fig = document.querySelector(".chart-fig");
17 + if (!fig) return;
18 + const tip = fig.querySelector(".chart-tip");
19 + if (!tip) return;
20 + fig.querySelectorAll(".pt").forEach((pt) => {
21 + pt.addEventListener("mouseenter", () => {
22 + tip.textContent = pt.getAttribute("data-tip");
23 + tip.hidden = false;
24 + });
25 + pt.addEventListener("mousemove", (ev) => {
26 + const r = fig.getBoundingClientRect();
27 + tip.style.left = Math.min(ev.clientX - r.left + 14, r.width - tip.offsetWidth - 4) + "px";
28 + tip.style.top = ev.clientY - r.top - 34 + "px";
29 + });
30 + pt.addEventListener("mouseleave", () => { tip.hidden = true; });
31 + });
32 +})();
added site/public/style.css +230 −0
@@ -0,0 +1,230 @@
1 +/* ============================================================================
2 + * Project : modelmap
3 + * File : site/public/style.css
4 + * Purpose : Light-theme styling for the modelmap.io research atlas
5 + * Author : Simon-Pierre Boucher
6 + * Contact : contact@spboucher.ai
7 + * Website : https://modelmap.io
8 + * Created : 2026-08-12
9 + * Modified : 2026-08-12
10 + * Platform : Web (deployed from macOS / Apple Silicon)
11 + * License : All rights reserved (research code)
12 + * ==========================================================================*/
13 +
14 +:root {
15 + color-scheme: light;
16 + --page: #f9f9f7;
17 + --surface: #fcfcfb;
18 + --ink: #0b0b0b;
19 + --ink-2: #52514e;
20 + --muted: #898781;
21 + --grid: #e1e0d9;
22 + --baseline: #c3c2b7;
23 + --border: rgba(11, 11, 11, 0.10);
24 + --accent: #6d4fc4;
25 + --accent-dark: #53389e;
26 + --accent-bg: #f1edfa;
27 + --good: #0ca30c;
28 + --good-text: #006300;
29 + --warn: #eda100;
30 +}
31 +
32 +* { box-sizing: border-box; }
33 +html { -webkit-text-size-adjust: 100%; }
34 +body {
35 + margin: 0;
36 + font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
37 + background: var(--page);
38 + color: var(--ink);
39 + line-height: 1.6;
40 + font-size: 16px;
41 +}
42 +.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
43 +a { color: var(--accent-dark); text-decoration: none; }
44 +a:hover { text-decoration: underline; }
45 +code, pre, .mono-small { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
46 +
47 +/* header */
48 +.site-header {
49 + background: var(--surface);
50 + border-bottom: 1px solid var(--grid);
51 + position: sticky; top: 0; z-index: 10;
52 +}
53 +.header-row { display: flex; align-items: center; justify-content: space-between; height: 56px; }
54 +.brand { font-weight: 700; font-size: 18px; color: var(--ink); letter-spacing: -0.02em; }
55 +.brand-dim { color: var(--muted); font-weight: 500; }
56 +.site-nav { display: flex; gap: 4px; flex-wrap: wrap; }
57 +.site-nav a {
58 + padding: 6px 12px; border-radius: 6px; color: var(--ink-2); font-size: 14px; font-weight: 500;
59 +}
60 +.site-nav a:hover { background: var(--page); text-decoration: none; color: var(--ink); }
61 +.site-nav a.active { color: var(--accent-dark); background: var(--accent-bg); }
62 +
63 +/* hero */
64 +.hero { padding: 56px 0 8px; }
65 +.kicker {
66 + text-transform: uppercase; letter-spacing: 0.08em; font-size: 12px; font-weight: 600;
67 + color: var(--accent-dark); margin: 0 0 12px;
68 +}
69 +.hero h1 { font-size: 40px; line-height: 1.15; letter-spacing: -0.02em; margin: 0 0 16px; }
70 +.lede { font-size: 18px; color: var(--ink-2); max-width: 760px; margin: 0 0 12px; }
71 +.lede-small { color: var(--ink-2); max-width: 760px; }
72 +.lede-eq code {
73 + font-size: 13px; background: var(--surface); border: 1px solid var(--grid);
74 + border-radius: 6px; padding: 6px 10px; color: var(--ink-2); display: inline-block;
75 +}
76 +
77 +/* stat tiles */
78 +.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin: 32px 0; }
79 +.tile {
80 + background: var(--surface); border: 1px solid var(--grid); border-radius: 10px;
81 + padding: 20px 22px; display: flex; flex-direction: column; gap: 2px;
82 +}
83 +.tile-value { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; }
84 +.tile-denom { font-size: 20px; color: var(--muted); font-weight: 500; }
85 +.tile-label { color: var(--ink-2); font-size: 13.5px; }
86 +
87 +/* layout blocks */
88 +.split { display: grid; grid-template-columns: 3fr 2fr; gap: 20px; margin: 8px 0 20px; }
89 +@media (max-width: 880px) { .split { grid-template-columns: 1fr; } .hero h1 { font-size: 30px; } }
90 +.card {
91 + background: var(--surface); border: 1px solid var(--grid); border-radius: 10px;
92 + padding: 24px 26px; margin-bottom: 20px;
93 +}
94 +.card h2 { margin-top: 0; font-size: 20px; letter-spacing: -0.01em; }
95 +.more { font-weight: 600; font-size: 14px; }
96 +
97 +/* phases */
98 +.phases { list-style: none; margin: 0; padding: 0; }
99 +.phase { display: flex; align-items: flex-start; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--grid); }
100 +.phase:last-child { border-bottom: 0; }
101 +.phase strong { display: block; font-size: 14.5px; }
102 +.phase-detail { font-size: 13px; color: var(--muted); }
103 +.phase-dot { width: 10px; height: 10px; border-radius: 50%; margin-top: 7px; flex: 0 0 auto; background: var(--baseline); }
104 +.phase.done .phase-dot { background: var(--good); }
105 +.phase.in-progress .phase-dot { background: var(--accent); }
106 +.phase .badge { margin-left: auto; }
107 +.badge {
108 + font-size: 11.5px; font-weight: 600; padding: 3px 9px; border-radius: 20px;
109 + white-space: nowrap; align-self: center; border: 1px solid var(--grid); color: var(--ink-2);
110 +}
111 +.badge-done { color: var(--good-text); background: #eef7ee; border-color: #cfe8cf; }
112 +.badge-in-progress, .badge-has-results { color: var(--accent-dark); background: var(--accent-bg); border-color: #dcd2f2; }
113 +.badge-pending, .badge-scaffolded { color: var(--muted); background: var(--page); }
114 +
115 +/* confidence levels — the atlas ladder and per-map badges */
116 +.ladder { list-style: none; margin: 16px 0; padding: 0; }
117 +.ladder-step { display: flex; align-items: flex-start; gap: 14px; padding: 9px 0; border-bottom: 1px solid var(--grid); }
118 +.ladder-step:last-child { border-bottom: 0; }
119 +.ladder-step strong { display: block; font-size: 14.5px; }
120 +.ladder-desc { font-size: 13px; color: var(--muted); display: block; }
121 +.ladder-num {
122 + flex: 0 0 auto; width: 34px; height: 34px; border-radius: 8px; display: flex;
123 + align-items: center; justify-content: center; font-weight: 700; font-size: 13px;
124 + font-family: ui-monospace, Menlo, monospace; margin-top: 2px;
125 + background: var(--page); border: 1px solid var(--grid); color: var(--muted);
126 +}
127 +.ladder-num.lv1 { background: #fdf3e2; border-color: #f3ddb0; color: #9a6a00; }
128 +.ladder-num.lv2 { background: var(--accent-bg); border-color: #dcd2f2; color: var(--accent-dark); }
129 +.ladder-num.lv3 { background: #eef7ee; border-color: #cfe8cf; color: var(--good-text); }
130 +.badge-level-0 { color: var(--muted); background: var(--page); }
131 +.badge-level-1 { color: #9a6a00; background: #fdf3e2; border-color: #f3ddb0; }
132 +.badge-level-2 { color: var(--accent-dark); background: var(--accent-bg); border-color: #dcd2f2; }
133 +.badge-level-3 { color: var(--good-text); background: #eef7ee; border-color: #cfe8cf; }
134 +
135 +/* doc cards */
136 +.page-title { font-size: 30px; letter-spacing: -0.02em; margin: 40px 0 8px; }
137 +.section-title { font-size: 20px; margin: 32px 0 8px; }
138 +.doc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; margin: 16px 0; }
139 +.doc-card {
140 + background: var(--surface); border: 1px solid var(--grid); border-radius: 10px;
141 + padding: 18px 20px; color: var(--ink); display: block;
142 +}
143 +.doc-card:hover { border-color: var(--accent); text-decoration: none; }
144 +.doc-card.disabled { opacity: 0.55; pointer-events: none; }
145 +.doc-card h3 { margin: 0 0 6px; font-size: 16px; }
146 +.doc-card p { margin: 0 0 8px; font-size: 13.5px; color: var(--ink-2); }
147 +.runs { font-size: 12px; color: var(--muted); margin-left: 8px; }
148 +.note-line { color: var(--ink-2); font-size: 14px; }
149 +
150 +/* markdown body */
151 +.doc { max-width: 860px; margin: 32px auto; }
152 +.crumb { font-size: 13px; color: var(--muted); margin: 24px 0 4px; }
153 +.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 18px; }
154 +.chip {
155 + font-size: 12px; background: var(--surface); border: 1px solid var(--grid);
156 + border-radius: 20px; padding: 3px 12px; color: var(--ink-2);
157 +}
158 +.chip-k { color: var(--muted); margin-right: 6px; text-transform: uppercase; font-size: 10px; letter-spacing: 0.05em; }
159 +.md { overflow-wrap: break-word; }
160 +.md h1 { font-size: 28px; letter-spacing: -0.02em; }
161 +.md h2 { font-size: 21px; margin-top: 36px; border-bottom: 1px solid var(--grid); padding-bottom: 6px; }
162 +.md h3 { font-size: 17px; margin-top: 28px; }
163 +.md code { background: #f1f0ec; border-radius: 4px; padding: 1px 5px; font-size: 0.88em; }
164 +.md pre code, .codeblock code { background: none; padding: 0; font-size: 13px; }
165 +.codeblock, .md pre {
166 + background: #f6f5f2; border: 1px solid var(--grid); border-radius: 8px;
167 + padding: 14px 16px; overflow-x: auto; line-height: 1.5;
168 +}
169 +.md table { border-collapse: collapse; width: 100%; font-size: 14px; display: block; overflow-x: auto; }
170 +.md th, .md td { border: 1px solid var(--grid); padding: 6px 10px; text-align: left; vertical-align: top; }
171 +.md th { background: var(--page); }
172 +.md blockquote { border-left: 3px solid var(--accent); margin-left: 0; padding-left: 16px; color: var(--ink-2); }
173 +
174 +/* log */
175 +.log-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }
176 +.log-entry h3 { font-size: 15px; margin: 0 0 8px; }
177 +.log-entry .md { font-size: 13.5px; color: var(--ink-2); }
178 +
179 +/* results table / kv table / file lists */
180 +.results-table, .kv-table { border-collapse: collapse; width: 100%; font-size: 14.5px; background: var(--surface); }
181 +.results-table th, .results-table td, .kv-table th, .kv-table td {
182 + border: 1px solid var(--grid); padding: 8px 12px; text-align: left;
183 +}
184 +.results-table th { background: var(--page); }
185 +.kv-table th { background: var(--page); width: 180px; }
186 +.file-list { list-style: none; padding: 0; }
187 +.file-list li { padding: 6px 0; border-bottom: 1px solid var(--grid); }
188 +.tree { list-style: none; padding-left: 0; }
189 +.tree ul { list-style: none; padding-left: 20px; }
190 +.tree-root { margin-bottom: 14px; }
191 +.tree li { padding: 2px 0; font-size: 14.5px; }
192 +.mono-small { font-size: 12.5px; color: var(--muted); }
193 +.file-card { padding: 0; overflow: hidden; }
194 +.file-head { display: flex; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid var(--grid); background: var(--page); }
195 +.file-card .codeblock { border: 0; border-radius: 0; margin: 0; }
196 +
197 +/* charts (server-rendered SVG, used once atlas data exists) */
198 +.chart-fig { margin: 18px 0 8px; position: relative; }
199 +.chart-fig svg { width: 100%; height: auto; }
200 +.chart-fig .grid { stroke: var(--grid); stroke-width: 1; }
201 +.chart-fig .axis { stroke: var(--baseline); stroke-width: 1; }
202 +.chart-fig .tick { fill: var(--muted); font-size: 11px; font-family: ui-monospace, Menlo, monospace; }
203 +.chart-fig .axis-title { fill: var(--ink-2); font-size: 12px; }
204 +.chart-fig .series-label { font-size: 12px; font-weight: 600; }
205 +.chart-fig .pt { cursor: pointer; }
206 +.chart-fig figcaption { font-size: 12.5px; color: var(--muted); margin-top: 4px; }
207 +.chart-tip {
208 + position: absolute; pointer-events: none; background: var(--ink); color: #fff;
209 + font-size: 12.5px; padding: 5px 10px; border-radius: 6px; white-space: nowrap; z-index: 5;
210 +}
211 +
212 +/* hljs light theme (subset, GitHub-like) */
213 +.hljs { color: #24292e; }
214 +.hljs-keyword, .hljs-meta .hljs-keyword { color: #d73a49; }
215 +.hljs-string, .hljs-attr { color: #032f62; }
216 +.hljs-comment, .hljs-quote { color: #6a737d; font-style: italic; }
217 +.hljs-number, .hljs-literal { color: #005cc5; }
218 +.hljs-title, .hljs-function .hljs-title { color: #6f42c1; }
219 +.hljs-built_in, .hljs-type { color: #e36209; }
220 +.hljs-section { color: #005cc5; font-weight: 600; }
221 +.hljs-params { color: #24292e; }
222 +.hljs-variable, .hljs-template-variable { color: #e36209; }
223 +
224 +/* footer */
225 +.site-footer {
226 + border-top: 1px solid var(--grid); margin-top: 48px; padding: 20px 0 32px;
227 + background: var(--surface); font-size: 13px; color: var(--ink-2);
228 +}
229 +.site-footer .wrap { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
230 +.footer-meta { color: var(--muted); }
added site/server.js +386 −0
@@ -0,0 +1,386 @@
1 +// ============================================================================
2 +// Project : modelmap
3 +// File : site/server.js
4 +// Purpose : modelmap.io platform — routes and pages (English, light theme)
5 +// Author : Simon-Pierre Boucher
6 +// Contact : contact@spboucher.ai
7 +// Website : https://modelmap.io
8 +// Created : 2026-08-12
9 +// Modified : 2026-08-12
10 +// Platform : macOS / Apple Silicon (arm64) — Node.js (deployed on MacLustr)
11 +// License : All rights reserved (research code)
12 +// ============================================================================
13 +"use strict";
14 +
15 +const path = require("path");
16 +const express = require("express");
17 +const C = require("./lib/content");
18 +const R = require("./lib/render");
19 +
20 +const app = express();
21 +const PORT = process.env.PORT || 8140;
22 +
23 +app.use("/static", express.static(path.join(__dirname, "public"), { maxAge: "1h" }));
24 +
25 +function page(res, opts) {
26 + res.send(R.layout({ ...opts, buildInfo: C.buildInfo() }));
27 +}
28 +
29 +function metaChips(data) {
30 + if (!data || !Object.keys(data).length) return "";
31 + const fields = ["document", "author", "created", "modified", "status"];
32 + const chips = fields
33 + .filter((f) => data[f])
34 + .map((f) => `<span class="chip"><span class="chip-k">${f}</span>${R.esc(data[f])}</span>`)
35 + .join("");
36 + return chips ? `<div class="chips">${chips}</div>` : "";
37 +}
38 +
39 +// ---------------------------------------------------------------- home
40 +app.get("/", (req, res) => {
41 + const s = C.stats();
42 + const log = C.parseLogEntries().slice(-3).reverse();
43 + const phases = [
44 + ["Phase 1 — Ultra-deep literature research (§4.1–4.10)", s.notes > 0 ? "in progress" : "pending", s.notes > 0 ? `${s.notes} theme notes · ${s.sources} sources` : "ten mandatory areas, primary sources first"],
45 + ["Phase 2 — State-of-the-art map", C.exists("research/state_of_the_art.md") ? "done" : "pending", "technique taxonomy with epistemic status"],
46 + ["Phase 3 — Research gaps", s.gaps ? "done" : "pending", s.gaps ? `${s.gaps} falsifiable candidate directions` : "≥20 substantially different candidate directions"],
47 + ["Phase 4 — Candidate ranking", C.exists("research/candidate_ranking.md") ? "done" : "pending", "10-axis scoring, 3–5 prototype candidates"],
48 + ["Phases 5–6 — Framework & micro-experiments A–H", s.experimentsDone ? "in progress" : "pending", `${s.experimentsDone}/${s.experiments} experiments completed`],
49 + ["Phases 7–9 — Prototypes → methodology", C.exists("research/methodology.md") ? "done" : "pending", "evidence-driven; the methodology is a primary deliverable"],
50 + ["Phase 10 — Atlas pipeline & this platform", "in progress", `platform live · ${s.atlasEntries} map${s.atlasEntries === 1 ? "" : "s"} published`],
51 + ["Phase 11 — Novelty verification", C.exists("research/novelty_check.md") ? "done" : "pending", "assume not novel until evidence suggests otherwise"],
52 + ];
53 + const phaseHtml = phases
54 + .map(
55 + ([name, st, detail]) => `<li class="phase ${st.replace(" ", "-")}">
56 + <span class="phase-dot"></span><div><strong>${name}</strong><span class="phase-detail">${detail}</span></div>
57 + <span class="badge badge-${st.replace(" ", "-")}">${st}</span></li>`
58 + )
59 + .join("");
60 + const logHtml = log
61 + .map(
62 + (e) => `<article class="log-entry"><h3>${R.esc(e.title)}</h3>
63 + <div class="md">${R.markdownToHtml(e.body.split("\n").slice(0, 8).join("\n"), "research/LOG.md")}</div>
64 + <a class="more" href="/doc/research/LOG.md">Full research log →</a></article>`
65 + )
66 + .join("");
67 +
68 + const body = `
69 +<section class="hero">
70 + <p class="kicker">Independent interpretability research · Apple Silicon</p>
71 + <h1>An internal cartography of<br>local large language models</h1>
72 + <p class="lede">Given an already-trained open-weight model, what can actually be <em>known, measured,
73 + localized, and mapped</em> about its internal organization — where knowledge lives, how computation
74 + is distributed, which structures are stable across inputs, layers, scales, and model families —
75 + using only a consumer-grade Mac? Every map published here is versioned, provenanced,
76 + confidence-labeled, and regenerable.</p>
77 + <p class="lede-eq"><code>what a probe reports ≠ what the model computes ≠ what is stable across methods ≠ what survives intervention</code></p>
78 +</section>
79 +
80 +<section class="tiles">
81 + <div class="tile"><span class="tile-value">${s.sources}</span><span class="tile-label">sources reviewed</span></div>
82 + <div class="tile"><span class="tile-value">${s.gaps}</span><span class="tile-label">research gaps identified</span></div>
83 + <div class="tile"><span class="tile-value">${s.experimentsDone}<span class="tile-denom">/${s.experiments}</span></span><span class="tile-label">experiments completed</span></div>
84 + <div class="tile"><span class="tile-value">${s.atlasEntries}</span><span class="tile-label">atlas maps published</span></div>
85 +</section>
86 +
87 +<section class="split">
88 + <div class="card">
89 + <h2>The evidence standard</h2>
90 + <p>A claim enters the atlas only at the confidence level its evidence supports.
91 + Correlational maps and causally-verified maps are never conflated — visually or textually.
92 + The two metrics tracked for every map are its <strong>replication rate</strong> (does it
93 + reproduce under resampling of seeds and data?) and its <strong>causal confirmation rate</strong>
94 + (what fraction of localized claims survive intervention?).</p>
95 + ${R.confidenceLadder()}
96 + <p class="note-line">Negative results — techniques whose maps do not replicate — are first-class,
97 + publishable findings here.</p>
98 + </div>
99 + <div class="card">
100 + <h2>Research phases</h2>
101 + <ul class="phases">${phaseHtml}</ul>
102 + </div>
103 +</section>
104 +
105 +<section class="card">
106 + <h2>Latest from the research log</h2>
107 + <div class="log-grid">${logHtml || "<p>No log entries yet.</p>"}</div>
108 +</section>`;
109 + page(res, { title: "Home", active: "Home", body });
110 +});
111 +
112 +// ---------------------------------------------------------------- research index
113 +app.get("/research", (req, res) => {
114 + const docs = [
115 + ["research/LOG.md", "Research log", "Append-only, auditable record of every question, experiment, result, and decision."],
116 + ["research/state_of_the_art.md", "State of the art", "Phase 2 — technique taxonomy with epistemic status (established / contested / debunked-in-part)."],
117 + ["research/research_gaps.md", "Research gaps", "Phase 3 — ≥20 falsifiable candidate directions, each with its smallest Mac-runnable falsifying experiment."],
118 + ["research/candidate_ranking.md", "Candidate ranking", "Phase 4 — 10-axis scoring and 3–5 selected prototype candidates."],
119 + ["research/methodology.md", "Methodology", "Phase 9 — the formal pipeline turning (checkpoint + corpora + budget) into confidence-labeled atlas entries."],
120 + ["research/novelty_check.md", "Novelty check", "Phase 11 — final novelty verification (assume not novel until shown otherwise)."],
121 + ["research/bibliography.md", "Bibliography", "Every consulted source with URL and access date."],
122 + ];
123 + const cards = docs
124 + .map(([rel, title, desc]) => {
125 + const ok = C.exists(rel);
126 + return `<a class="doc-card ${ok ? "" : "disabled"}" href="${ok ? "/doc/" + rel : "#"}">
127 + <h3>${title}</h3><p>${desc}</p>${ok ? "" : '<span class="badge badge-pending">not yet written</span>'}</a>`;
128 + })
129 + .join("");
130 + const notes = (C.listDir("research/notes") || [])
131 + .filter((f) => f.name.endsWith(".md") && f.name !== "README.md")
132 + .map((f) => {
133 + const md = C.readMarkdown(f.rel);
134 + const doc = md && md.data.document ? md.data.document : f.name;
135 + return `<a class="doc-card" href="/doc/${f.rel}"><h3>${R.esc(f.name.replace(".md", "").replace(/_/g, " "))}</h3>
136 + <p class="mono-small">${R.esc(doc)}</p></a>`;
137 + })
138 + .join("");
139 + const body = `<h1 class="page-title">Research documents</h1>
140 +<div class="doc-grid">${cards}</div>
141 +<h2 class="section-title">Phase 1 literature notes</h2>
142 +<div class="doc-grid">${notes || '<p class="note-line">No literature notes yet — Phase 1 has not started.</p>'}</div>
143 +<p class="note-line">The project charter itself is public: <a href="/doc/CLAUDE.md">read the full research charter</a>.</p>`;
144 + page(res, { title: "Research", active: "Research", body });
145 +});
146 +
147 +// ---------------------------------------------------------------- markdown viewer
148 +app.get(/^\/doc\/(.+)$/, (req, res) => {
149 + const rel = req.params[0];
150 + const md = rel.endsWith(".md") ? C.readMarkdown(rel) : null;
151 + if (!md) return notFound(res);
152 + const body = `<article class="doc">
153 + <p class="crumb"><a href="/research">Research</a> / ${R.esc(rel)}</p>
154 + ${metaChips(md.data)}
155 + <div class="md">${R.markdownToHtml(md.content, rel)}</div>
156 + </article>`;
157 + page(res, { title: md.data.document || rel, active: "Research", body });
158 +});
159 +
160 +// ---------------------------------------------------------------- experiments
161 +app.get("/experiments", (req, res) => {
162 + const exps = C.listExperiments();
163 + const cards = exps
164 + .map(
165 + (e) => `<a class="doc-card" href="/experiments/${e.id}">
166 + <h3>${R.esc(e.id)}</h3><p>${R.esc(e.purpose)}</p>
167 + <span class="badge badge-${e.status === "completed" ? "done" : e.status === "has results" ? "in-progress" : "pending"}">${e.status}</span>
168 + ${e.runs ? `<span class="runs">${e.runs} result run${e.runs > 1 ? "s" : ""}</span>` : ""}</a>`
169 + )
170 + .join("");
171 + const body = `<h1 class="page-title">Experiments</h1>
172 +<p class="lede-small">Every experiment carries a registered hypothesis with an explicit falsification criterion
173 +(seven-field scientific block), mandatory controls and nulls, raw results, and an analysis. Negative results are kept.
174 +Micro-experiments A–H establish the noise floor, the correlational→causal survival rate, and the capture cost
175 +frontier before any large model is mapped.</p>
176 +<div class="doc-grid">${cards}</div>`;
177 + page(res, { title: "Experiments", active: "Experiments", body });
178 +});
179 +
180 +app.get("/experiments/:id", (req, res) => {
181 + const exps = C.listExperiments();
182 + const exp = exps.find((e) => e.id === req.params.id);
183 + if (!exp) return notFound(res);
184 + const sections = [];
185 + for (const [file, title] of [["hypothesis.md", "Hypothesis"], ["analysis.md", "Analysis"], ["README.md", "README"]]) {
186 + const md = C.readMarkdown(path.posix.join(exp.rel, file));
187 + if (md && md.content.trim().length > 40) {
188 + sections.push(`<section class="card"><h2>${title}</h2>${metaChips(md.data)}
189 + <div class="md">${R.markdownToHtml(md.content, path.posix.join(exp.rel, file))}</div></section>`);
190 + }
191 + }
192 + const runs = C.listResultRuns().filter((r) => r.experiment === exp.id);
193 + const runsHtml = runs.length
194 + ? `<section class="card"><h2>Result runs</h2><ul class="file-list">` +
195 + runs.map((r) => r.files.map((f) => `<li><a href="/results/${f.rel}">${r.timestamp} / ${f.name}</a> <span class="mono-small">${(f.size / 1024).toFixed(1)} KiB</span></li>`).join("")).join("") +
196 + `</ul></section>`
197 + : "";
198 + const codeLink = C.exists(path.posix.join(exp.rel, "benchmark.py"))
199 + ? `<p><a class="more" href="/file/${exp.rel}/benchmark.py">View benchmark implementation (benchmark.py) →</a></p>`
200 + : "";
201 + const body = `<p class="crumb"><a href="/experiments">Experiments</a> / ${R.esc(exp.id)}</p>
202 +<h1 class="page-title">${R.esc(exp.id)}</h1><p class="lede-small">${R.esc(exp.purpose)}</p>
203 +${codeLink}${sections.join("")}${runsHtml}`;
204 + page(res, { title: exp.id, active: "Experiments", body });
205 +});
206 +
207 +// ---------------------------------------------------------------- atlas
208 +app.get("/atlas", (req, res) => {
209 + const entries = C.listAtlasEntries();
210 + const byModel = {};
211 + for (const e of entries) (byModel[e.model] ||= []).push(e);
212 + const groups = Object.keys(byModel).sort().map((model) => {
213 + const cards = byModel[model]
214 + .map(
215 + (e) => `<a class="doc-card" href="/atlas/${e.model}/${e.mapType}/${e.version}">
216 + <h3>${R.esc(e.mapType)} <span class="mono-small">${R.esc(e.version)}</span></h3>
217 + <p>${e.provenance && e.provenance.created ? "created " + R.esc(e.provenance.created) : ""}</p>
218 + ${R.levelBadge(e.level)}</a>`
219 + )
220 + .join("");
221 + return `<h2 class="section-title">${R.esc(model)}</h2><div class="doc-grid">${cards}</div>`;
222 + }).join("");
223 + const empty = `<div class="card">
224 + <h2>No maps published yet</h2>
225 + <p>The atlas fills only after the mapping methodology exists (Phase 9) and each map clears its
226 + evidence bar. Every entry will carry a completed <code>provenance.json</code> (commit, config,
227 + model hash, hardware, dates) and a <code>confidence.md</code> stating its evidence level.
228 + Nothing is published that cannot be regenerated from committed code and versioned data.</p>
229 + ${R.confidenceLadder()}
230 + </div>`;
231 + const body = `<h1 class="page-title">Atlas</h1>
232 +<p class="lede-small">Versioned map artifacts: <code>atlas/&lt;model&gt;/&lt;map type&gt;/&lt;version&gt;</code>.
233 +Only open-weight models are mapped, and no claim appears here above its evidence level.</p>
234 +${groups || empty}`;
235 + page(res, { title: "Atlas", active: "Atlas", body });
236 +});
237 +
238 +app.get("/atlas/:model/:mapType/:version", (req, res) => {
239 + const { model, mapType, version } = req.params;
240 + const entry = C.listAtlasEntries().find(
241 + (e) => e.model === model && e.mapType === mapType && e.version === version
242 + );
243 + if (!entry) return notFound(res);
244 + const confidence = C.readMarkdown(path.posix.join(entry.rel, "confidence.md"));
245 + const prov = entry.provenance
246 + ? `<pre class="codeblock"><code class="hljs">${R.highlightFile(JSON.stringify(entry.provenance, null, 2), "provenance.json")}</code></pre>`
247 + : "<p>provenance.json missing — this entry is not publishable.</p>";
248 + const files = entry.files
249 + .map((f) => `<li><a href="/results/${f.rel}">${f.name}</a> <span class="mono-small">${(f.size / 1024).toFixed(1)} KiB</span></li>`)
250 + .join("");
251 + const body = `<p class="crumb"><a href="/atlas">Atlas</a> / ${R.esc(entry.rel)}</p>
252 +<h1 class="page-title">${R.esc(model)} — ${R.esc(mapType)} <span class="mono-small">${R.esc(version)}</span></h1>
253 +<p>${R.levelBadge(entry.level)}</p>
254 +${confidence ? `<section class="card"><h2>Confidence</h2><div class="md">${R.markdownToHtml(confidence.content, entry.rel + "/confidence.md")}</div></section>` : ""}
255 +<section class="card"><h2>Provenance</h2>${prov}</section>
256 +<section class="card"><h2>Files</h2><ul class="file-list">${files}</ul></section>`;
257 + page(res, { title: `${model}/${mapType}/${version}`, active: "Atlas", body });
258 +});
259 +
260 +// ---------------------------------------------------------------- results
261 +app.get("/results", (req, res) => {
262 + const runs = C.listResultRuns();
263 + const rows = runs
264 + .map(
265 + (r) => `<tr><td><a href="/experiments/${r.experiment}">${r.experiment}</a></td>
266 + <td class="mono-small">${r.timestamp}</td>
267 + <td>${r.files.map((f) => `<a href="/results/${f.rel}">${f.name}</a>`).join(" · ")}</td></tr>`
268 + )
269 + .join("");
270 + const body = `<h1 class="page-title">Raw results</h1>
271 +<p class="lede-small">Every result is reproducible from commit hash + config + model hash + seed +
272 +hardware manifest, and each JSON embeds the manifest of the exact machine that produced it.</p>
273 +<table class="results-table"><thead><tr><th>Experiment</th><th>Run (UTC)</th><th>Files</th></tr></thead>
274 +<tbody>${rows || '<tr><td colspan="3">No result runs yet.</td></tr>'}</tbody></table>`;
275 + page(res, { title: "Results", active: "Results", body });
276 +});
277 +
278 +app.get(/^\/results\/(.+)$/, (req, res) => {
279 + const raw0 = req.params[0];
280 + const rel = raw0.startsWith("results/") || raw0.startsWith("atlas/") ? raw0 : "results/" + raw0;
281 + const raw = C.readText(rel);
282 + if (raw === null) return notFound(res);
283 + let bodyContent;
284 + if (rel.endsWith(".json")) {
285 + const obj = C.readJson(rel);
286 + bodyContent = `<pre class="codeblock"><code class="hljs">${R.highlightFile(JSON.stringify(obj, null, 2), "x.json")}</code></pre>`;
287 + } else {
288 + bodyContent = `<pre class="codeblock"><code>${R.esc(raw.slice(0, 200000))}</code></pre>`;
289 + }
290 + const body = `<p class="crumb"><a href="/results">Results</a> / ${R.esc(rel)}</p><div class="card">${bodyContent}</div>`;
291 + page(res, { title: rel, active: "Results", body });
292 +});
293 +
294 +// ---------------------------------------------------------------- code browser
295 +const CODE_ROOTS = ["src", "tools", "benchmarks", "experiments", "Makefile", "pyproject.toml", "CITATION.cff"];
296 +app.get("/code", (req, res) => {
297 + const sections = CODE_ROOTS.map((root) => {
298 + if (!C.exists(root)) return "";
299 + const st = C.listDir(root);
300 + if (st === null) {
301 + return `<li><a href="/file/${root}">${root}</a></li>`;
302 + }
303 + const files = C.walk(root, (f) => C.isTextFile(f.rel) && !f.rel.includes("results/"));
304 + return `<li class="tree-root"><strong>${root}/</strong><ul>` +
305 + files.map((f) => `<li><a href="/file/${f.rel}">${f.rel.slice(root.length + 1)}</a></li>`).join("") +
306 + `</ul></li>`;
307 + }).join("");
308 + const body = `<h1 class="page-title">Code</h1>
309 +<p class="lede-small">The mapping library (<code>src/modelmap/</code>), tooling, benchmark harness, and experiment
310 +implementations. Every file carries the project's author header; MLX / PyTorch-MPS / Metal is the primary compute
311 +path — CUDA is never a core dependency.</p>
312 +<ul class="tree">${sections}</ul>`;
313 + page(res, { title: "Code", active: "Code", body });
314 +});
315 +
316 +app.get(/^\/file\/(.+)$/, (req, res) => {
317 + const rel = req.params[0];
318 + if (!C.isTextFile(rel)) return notFound(res);
319 + const raw = C.readText(rel);
320 + if (raw === null) return notFound(res);
321 + const name = rel.split("/").pop();
322 + const body = `<p class="crumb"><a href="/code">Code</a> / ${R.esc(rel)}</p>
323 +<div class="card file-card"><div class="file-head"><span class="mono-small">${R.esc(rel)}</span>
324 +<span class="mono-small">${raw.split("\n").length} lines</span></div>
325 +<pre class="codeblock"><code class="hljs">${R.highlightFile(raw.slice(0, 400000), name)}</code></pre></div>`;
326 + page(res, { title: name, active: "Code", body });
327 +});
328 +
329 +// ---------------------------------------------------------------- about
330 +app.get("/about", (req, res) => {
331 + const questions = [
332 + ["Q1 — Localization", "Where inside a pretrained LLM do capabilities, knowledge domains, languages, and behaviors reside? Localized (layers, heads, neurons, weight blocks, directions) or diffuse?"],
333 + ["Q2 — Structure", "Which stable internal structures exist across inputs — circuits, feature directions, attention patterns, activation manifolds, weight-space geometry — and at what granularity are they real rather than artifacts of the probing method?"],
334 + ["Q3 — Comparability", "Can internal maps be compared across model sizes, checkpoints, quantization levels, and families? Is there a common coordinate system for model internals?"],
335 + ["Q4 — Cost", "Which mapping techniques are feasible on a 16–64 GB Mac, at which model sizes, and what is the accuracy/cost frontier of local interpretability?"],
336 + ["Q5 — Utility", "Do the maps predict anything useful — quantization sensitivity, pruning tolerance, working-set behavior, failure modes, editing targets?"],
337 + ];
338 + const qHtml = questions
339 + .map(([q, d]) => `<tr><th>${q}</th><td>${d}</td></tr>`)
340 + .join("");
341 + const body = `<h1 class="page-title">About this project</h1>
342 +<div class="card md">
343 +<p><strong>modelmap</strong> is an independent research project by
344 +<strong>Simon-Pierre Boucher</strong> (<a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a>) building a
345 +systematic methodology — and the tooling behind it — to discover, measure, and map the internal structure of
346 +pretrained open-weight LLMs running locally on consumer Apple Silicon hardware, and publishing the resulting maps
347 +as a rigorous, reproducible public atlas on this site.</p>
348 +<p>The ideal outcome is not a gallery of suggestive visualizations. It is a <strong>reproducible cartographic
349 +standard for open-weight models</strong> — every map versioned, provenanced, confidence-labeled, and regenerable
350 +by anyone with a Mac — turning “we think the model does X” into “here is the map, its evidence level, and the
351 +script that rebuilds it.”</p>
352 +<h2>Core research questions</h2>
353 +<table class="kv-table">${qHtml}</table>
354 +<h2>The evidence standard</h2>
355 +${R.confidenceLadder()}
356 +<p>Every mapping technique runs with controls: shuffled labels for probes, random-direction baselines for steering,
357 +randomly-initialized-model baselines where meaningful, resample vs zero ablations. Structure is never claimed
358 +without showing the null. Replication across ≥3 seeds and ≥2 prompt sets, bootstrap confidence intervals, effect
359 +sizes, and multiple-comparison correction are mandatory for every published number.</p>
360 +<h2>Target hardware</h2>
361 +<p>Apple Silicon Mac (M1–M4 family), 16–64 GB unified memory, internal NVMe SSD, Metal GPU sharing memory with the
362 +CPU. Local interpretability on consumer hardware is itself an under-served niche: most tooling assumes CUDA
363 +clusters. Making rigorous mapping feasible on a Mac is part of the contribution. The stretch target is a full,
364 +causally-verified, versioned atlas of a 7B–14B model produced end-to-end on a single 32–64 GB Mac, comparable
365 +across ≥2 quantization levels and ≥2 model sizes.</p>
366 +<h2>Sister project</h2>
367 +<p><a href="https://www.localvm.dev">localvm-research</a> investigates out-of-core LLM execution on the same
368 +hardware class — running models larger than memory. The projects cross-pollinate: modelmap's working-set and
369 +localization maps (Q5) feed localvm's execution decisions, and localvm's cost measurements calibrate modelmap's
370 +capture-feasibility frontier (Experiment H).</p>
371 +<p>Failure is an acceptable outcome — the charter defines explicit failure criteria, and the
372 +<a href="/doc/research/LOG.md">log</a> records why an approach died, not just what survived.</p>
373 +</div>`;
374 + page(res, { title: "About", active: "About", body });
375 +});
376 +
377 +// ---------------------------------------------------------------- misc
378 +app.get("/health", (req, res) => res.json({ ok: true, app: "modelmap-web", author: "Simon-Pierre Boucher", website: "https://modelmap.io" }));
379 +
380 +function notFound(res) {
381 + res.status(404);
382 + page(res, { title: "Not found", active: "", body: `<div class="card"><h1>404</h1><p>That page does not exist. <a href="/">Back to home</a>.</p></div>` });
383 +}
384 +app.use((req, res) => notFound(res));
385 +
386 +app.listen(PORT, () => console.log(`modelmap-web listening on :${PORT}`));
added site/sync-content.sh +45 −0
@@ -0,0 +1,45 @@
1 +#!/bin/zsh
2 +# =============================================================================
3 +# Project : modelmap
4 +# File : site/sync-content.sh
5 +# Purpose : Snapshot the research repo into site/content/ + build-info.json
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Website : https://modelmap.io
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +set -euo pipefail
15 +
16 +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17 +CONTENT="$REPO_ROOT/site/content"
18 +
19 +mkdir -p "$CONTENT"
20 +# NOTE: rsync applies filters in order — excludes MUST precede the broad
21 +# includes, or large binary artifacts (activation dumps, zarr stores) slip in.
22 +rsync -a --delete \
23 + --exclude='*.bin' --exclude='*.log' --exclude='__pycache__' --exclude='.git' \
24 + --exclude='node_modules' --exclude='site' --exclude='*.zarr' --exclude='activations' \
25 + --include='CLAUDE.md' --include='README.md' --include='CITATION.cff' \
26 + --include='Makefile' --include='pyproject.toml' \
27 + --include='research/***' --include='experiments/***' --include='benchmarks/***' \
28 + --include='src/***' --include='tools/***' --include='results/***' \
29 + --include='atlas/***' --include='docs/***' \
30 + --exclude='*' \
31 + "$REPO_ROOT/" "$CONTENT/"
32 +
33 +COMMIT=$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown")
34 +COMMITS=$(git -C "$REPO_ROOT" rev-list --count HEAD 2>/dev/null || echo 0)
35 +cat > "$CONTENT/build-info.json" <<EOF
36 +{
37 + "author": "Simon-Pierre Boucher",
38 + "contact": "contact@spboucher.ai",
39 + "website": "https://modelmap.io",
40 + "commit": "$COMMIT",
41 + "commits": $COMMITS,
42 + "generated": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
43 +}
44 +EOF
45 +echo "content synced at commit ${COMMIT:0:7} ($COMMITS commits)"
added src/modelmap/__init__.py +17 −0
@@ -0,0 +1,17 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/__init__.py
4 +# Purpose : modelmap package root — internal cartography of local LLMs
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +__version__ = "0.1.0"
15 +__author__ = "Simon-Pierre Boucher"
16 +__contact__ = "contact@spboucher.ai"
17 +__website__ = "https://modelmap.io"
added src/modelmap/atlas/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/atlas/__init__.py
4 +# Purpose : Map schema, serialization, versioning, provenance
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/backends/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/backends/__init__.py
4 +# Purpose : Compute backends: MLX / MPS / CPU (CUDA optional, isolated)
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/capture/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/capture/__init__.py
4 +# Purpose : Activation hooks, streaming capture, memory-mapped stores
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/features/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/features/__init__.py
4 +# Purpose : SAE training/inference and dictionary-learning methods
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/geometry/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/geometry/__init__.py
4 +# Purpose : Weight-space analysis: SVD spectra, effective rank, CKA
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/graphs/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/graphs/__init__.py
4 +# Purpose : Circuit extraction and attribution graphs
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/instrumentation/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/instrumentation/__init__.py
4 +# Purpose : macOS-native cost measurement (task_info, powermetrics)
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/interventions/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/interventions/__init__.py
4 +# Purpose : Ablation, activation patching, steering, weight zeroing
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/models/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/models/__init__.py
4 +# Purpose : Model loading (safetensors/GGUF) and architecture introspection
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/probes/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/probes/__init__.py
4 +# Purpose : Linear probes, logit lens, tuned lens, causal probes
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/stats/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/stats/__init__.py
4 +# Purpose : Replication tests, bootstrap CIs, multiple-comparison control
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added src/modelmap/viz/__init__.py +12 −0
@@ -0,0 +1,12 @@
1 +# =============================================================================
2 +# Project : modelmap
3 +# File : src/modelmap/viz/__init__.py
4 +# Purpose : Figure and interactive-map generation
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Website : https://modelmap.io
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
added tools/check_headers.py +106 −0
@@ -0,0 +1,106 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : modelmap
4 +# File : tools/check_headers.py
5 +# Purpose : Fail if any tracked source file lacks the mandatory author header
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Website : https://modelmap.io
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""Header compliance checker (charter §0.1).
15 +
16 +Scans git-tracked source files and verifies each begins (after an optional
17 +shebang) with the standardized author header, or — for markdown — with YAML
18 +front matter carrying the author fields. Exits non-zero on any violation,
19 +so it can gate commits.
20 +"""
21 +
22 +from __future__ import annotations
23 +
24 +import subprocess
25 +import sys
26 +from pathlib import Path
27 +
28 +ROOT = Path(__file__).resolve().parent.parent
29 +
30 +HASH_COMMENT_EXT = {".py", ".sh", ".zsh", ".yaml", ".yml", ".toml", ".cff"}
31 +SLASH_COMMENT_EXT = {".c", ".cpp", ".h", ".hpp", ".metal", ".swift", ".m", ".js", ".ts", ".css"}
32 +MARKDOWN_EXT = {".md"}
33 +HASH_COMMENT_NAMES = {"Makefile"}
34 +
35 +REQUIRED_TOKENS = (
36 + "Project : modelmap",
37 + "Author : Simon-Pierre Boucher",
38 + "Contact : contact@spboucher.ai",
39 +)
40 +MD_REQUIRED_TOKENS = (
41 + "project: modelmap",
42 + "author: Simon-Pierre Boucher",
43 + "contact: contact@spboucher.ai",
44 +)
45 +
46 +# Files that carry no comments / are data or licenses. CLAUDE.md is the
47 +# authoritative charter (its content is the spec itself, not a research doc).
48 +EXEMPT = {"LICENSE", ".gitignore", "package.json", "package-lock.json", "CLAUDE.md"}
49 +
50 +
51 +def tracked_files() -> list[Path]:
52 + out = subprocess.run(
53 + ["git", "ls-files"], cwd=ROOT, capture_output=True, text=True, check=True
54 + ).stdout
55 + return [ROOT / line for line in out.splitlines() if line.strip()]
56 +
57 +
58 +def head_of(path: Path, n_bytes: int = 4096) -> str:
59 + try:
60 + return path.read_text(errors="replace")[:n_bytes]
61 + except OSError:
62 + return ""
63 +
64 +
65 +def check(path: Path) -> str | None:
66 + """Return an error string, or None if compliant / not applicable."""
67 + name = path.name
68 + if name in EXEMPT:
69 + return None
70 + ext = path.suffix.lower()
71 + text = head_of(path)
72 + if text.startswith("#!"):
73 + text = text.split("\n", 1)[1] if "\n" in text else ""
74 +
75 + if ext in MARKDOWN_EXT:
76 + if not text.lstrip().startswith("---"):
77 + return "missing YAML front matter"
78 + missing = [t for t in MD_REQUIRED_TOKENS if t not in text]
79 + return f"front matter missing: {', '.join(missing)}" if missing else None
80 +
81 + if ext in HASH_COMMENT_EXT or ext in SLASH_COMMENT_EXT or name in HASH_COMMENT_NAMES:
82 + missing = [t for t in REQUIRED_TOKENS if t not in text]
83 + return f"header missing: {', '.join(missing)}" if missing else None
84 +
85 + return None # extension not governed by the header rule
86 +
87 +
88 +def main() -> int:
89 + errors: list[tuple[Path, str]] = []
90 + for f in tracked_files():
91 + if not f.is_file():
92 + continue
93 + err = check(f)
94 + if err:
95 + errors.append((f, err))
96 + if errors:
97 + print(f"check_headers: {len(errors)} non-compliant file(s):")
98 + for f, err in errors:
99 + print(f" {f.relative_to(ROOT)}: {err}")
100 + return 1
101 + print("check_headers: all tracked source files carry the mandatory header.")
102 + return 0
103 +
104 +
105 +if __name__ == "__main__":
106 + sys.exit(main())
added tools/new_experiment.py +80 −0
@@ -0,0 +1,80 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : modelmap
4 +# File : tools/new_experiment.py
5 +# Purpose : Scaffold a charter-compliant experiment directory (§10 block)
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Website : https://modelmap.io
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""Scaffold an experiment directory with the seven-field scientific block.
15 +
16 +Usage:
17 + python3 tools/new_experiment.py experiments/micro/expX_name "one-line purpose"
18 +"""
19 +
20 +from __future__ import annotations
21 +
22 +import datetime
23 +import sys
24 +from pathlib import Path
25 +
26 +ROOT = Path(__file__).resolve().parent.parent
27 +
28 +FRONT = """---
29 +project: modelmap
30 +document: {doc}
31 +author: Simon-Pierre Boucher
32 +contact: contact@spboucher.ai
33 +website: https://modelmap.io
34 +created: {date}
35 +status: draft
36 +---
37 +"""
38 +
39 +BLOCK = """```text
40 +Hypothesis : (to be registered before the first run)
41 +Falsification criterion : (explicit kill-number, registered in advance)
42 +Method : (including controls)
43 +Baseline / null : (shuffled labels / random directions / random init)
44 +Result : (pending)
45 +Interpretation : (pending — with explicit confidence level)
46 +Next experiment : (pending)
47 +```
48 +"""
49 +
50 +
51 +def main() -> int:
52 + if len(sys.argv) < 3:
53 + print(__doc__)
54 + return 2
55 + rel, purpose = sys.argv[1], sys.argv[2]
56 + date = datetime.date.today().isoformat()
57 + exp = ROOT / rel
58 + name = exp.name
59 + if exp.exists():
60 + print(f"error: {rel} already exists")
61 + return 1
62 + (exp / "implementation").mkdir(parents=True)
63 + (exp / "results").mkdir()
64 + (exp / "README.md").write_text(
65 + FRONT.format(doc=name, date=date) + f"\n# {name}\n\n{purpose}\n"
66 + )
67 + (exp / "hypothesis.md").write_text(
68 + FRONT.format(doc=f"{name} — hypothesis", date=date)
69 + + f"\n# Hypothesis — {name}\n\n> {purpose}\n\n" + BLOCK
70 + )
71 + (exp / "analysis.md").write_text(
72 + FRONT.format(doc=f"{name} — analysis", date=date)
73 + + f"\n# Analysis — {name}\n\n*(pending — written only after results exist)*\n"
74 + )
75 + print(f"scaffolded {rel}")
76 + return 0
77 +
78 +
79 +if __name__ == "__main__":
80 + sys.exit(main())
added tools/new_map.py +103 −0
@@ -0,0 +1,103 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : modelmap
4 +# File : tools/new_map.py
5 +# Purpose : Scaffold a compliant atlas entry with provenance + confidence
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Website : https://modelmap.io
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""Scaffold an atlas entry: atlas/<model_id>/<map_type>/<version>/.
15 +
16 +An entry cannot be published without a completed provenance.json and
17 +confidence.md (charter §3). This tool creates both as explicit stubs that
18 +tools/publish.py refuses to export until completed.
19 +
20 +Usage:
21 + python3 tools/new_map.py <model_id> <map_type> [version]
22 +"""
23 +
24 +from __future__ import annotations
25 +
26 +import datetime
27 +import json
28 +import subprocess
29 +import sys
30 +from pathlib import Path
31 +
32 +ROOT = Path(__file__).resolve().parent.parent
33 +
34 +
35 +def git_commit() -> str:
36 + try:
37 + return subprocess.run(
38 + ["git", "rev-parse", "HEAD"], cwd=ROOT, capture_output=True, text=True, check=True
39 + ).stdout.strip()
40 + except subprocess.CalledProcessError:
41 + return "unknown"
42 +
43 +
44 +CONFIDENCE_TEMPLATE = """---
45 +project: modelmap
46 +document: {model_id}/{map_type}/{version} — confidence
47 +author: Simon-Pierre Boucher
48 +contact: contact@spboucher.ai
49 +website: https://modelmap.io
50 +created: {date}
51 +status: draft
52 +---
53 +
54 +# Confidence — {model_id} / {map_type} / {version}
55 +
56 +```text
57 +Level : (0 anecdotal | 1 correlational | 2 method-robust | 3 causal)
58 +Seeds : (n)
59 +Prompt sets: (n)
60 +Methods in agreement : (list)
61 +Causal verification : (none | patching | ablation | ...)
62 +```
63 +
64 +**NOT PUBLISHABLE until this file states an evidence level with its support.**
65 +"""
66 +
67 +
68 +def main() -> int:
69 + if len(sys.argv) < 3:
70 + print(__doc__)
71 + return 2
72 + model_id, map_type = sys.argv[1], sys.argv[2]
73 + version = sys.argv[3] if len(sys.argv) > 3 else "v0"
74 + date = datetime.date.today().isoformat()
75 + entry = ROOT / "atlas" / model_id / map_type / version
76 + if entry.exists():
77 + print(f"error: {entry.relative_to(ROOT)} already exists")
78 + return 1
79 + entry.mkdir(parents=True)
80 + provenance = {
81 + "author": "Simon-Pierre Boucher",
82 + "contact": "contact@spboucher.ai",
83 + "website": "https://modelmap.io",
84 + "model_id": model_id,
85 + "map_type": map_type,
86 + "version": version,
87 + "commit": git_commit(),
88 + "model_hash": "PENDING",
89 + "config": "PENDING",
90 + "seed": None,
91 + "hardware_manifest": "PENDING",
92 + "created": date,
93 + }
94 + (entry / "provenance.json").write_text(json.dumps(provenance, indent=2) + "\n")
95 + (entry / "confidence.md").write_text(
96 + CONFIDENCE_TEMPLATE.format(model_id=model_id, map_type=map_type, version=version, date=date)
97 + )
98 + print(f"scaffolded atlas/{model_id}/{map_type}/{version}")
99 + return 0
100 +
101 +
102 +if __name__ == "__main__":
103 + sys.exit(main())
104