nnlens 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. nnlens-0.1.0/.gitattributes +5 -0
  2. nnlens-0.1.0/.github/workflows/release.yml +37 -0
  3. nnlens-0.1.0/.gitignore +25 -0
  4. nnlens-0.1.0/CONTRIBUTING.md +73 -0
  5. nnlens-0.1.0/LICENSE +21 -0
  6. nnlens-0.1.0/PKG-INFO +144 -0
  7. nnlens-0.1.0/PROGRESS.md +109 -0
  8. nnlens-0.1.0/README.md +126 -0
  9. nnlens-0.1.0/docs/DESIGN_related_links.md +102 -0
  10. nnlens-0.1.0/docs/img/screenshot.png +0 -0
  11. nnlens-0.1.0/examples/layer_norm.json +91 -0
  12. nnlens-0.1.0/examples/transformer_attention.json +85 -0
  13. nnlens-0.1.0/pyproject.toml +35 -0
  14. nnlens-0.1.0/scripts/build_example.py +185 -0
  15. nnlens-0.1.0/scripts/build_layernorm.py +163 -0
  16. nnlens-0.1.0/scripts/demo_render.py +52 -0
  17. nnlens-0.1.0/src/nnlens/__init__.py +9 -0
  18. nnlens-0.1.0/src/nnlens/__main__.py +6 -0
  19. nnlens-0.1.0/src/nnlens/config.py +37 -0
  20. nnlens-0.1.0/src/nnlens/lint.py +128 -0
  21. nnlens-0.1.0/src/nnlens/models.py +122 -0
  22. nnlens-0.1.0/src/nnlens/prompts.py +94 -0
  23. nnlens-0.1.0/src/nnlens/renderer/__init__.py +24 -0
  24. nnlens-0.1.0/src/nnlens/renderer/build.py +269 -0
  25. nnlens-0.1.0/src/nnlens/renderer/server.py +142 -0
  26. nnlens-0.1.0/src/nnlens/renderer/template.html +112 -0
  27. nnlens-0.1.0/src/nnlens/renderer/viewer.js +575 -0
  28. nnlens-0.1.0/src/nnlens/sandbox.py +148 -0
  29. nnlens-0.1.0/src/nnlens/server.py +118 -0
  30. nnlens-0.1.0/src/nnlens/sources.py +142 -0
  31. nnlens-0.1.0/tests/js/component.test.cjs +67 -0
  32. nnlens-0.1.0/tests/js/package.json +16 -0
  33. nnlens-0.1.0/tests/js/viewer.test.cjs +202 -0
  34. nnlens-0.1.0/tests/test_config.py +46 -0
  35. nnlens-0.1.0/tests/test_integration.py +87 -0
  36. nnlens-0.1.0/tests/test_lint.py +159 -0
  37. nnlens-0.1.0/tests/test_mcp_stdio.py +39 -0
  38. nnlens-0.1.0/tests/test_models.py +139 -0
  39. nnlens-0.1.0/tests/test_prompts.py +21 -0
  40. nnlens-0.1.0/tests/test_renderer.py +156 -0
  41. nnlens-0.1.0/tests/test_sandbox.py +36 -0
  42. nnlens-0.1.0/tests/test_sources.py +42 -0
@@ -0,0 +1,5 @@
1
+ # Normalize line endings; keep them LF in the repo.
2
+ * text=auto eol=lf
3
+ *.png binary
4
+ *.jpg binary
5
+ *.ico binary
@@ -0,0 +1,37 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.12"
16
+ - name: Install
17
+ run: python -m pip install -e ".[dev]" numpy
18
+ - name: Test
19
+ run: python -m pytest -q
20
+
21
+ publish:
22
+ needs: test
23
+ runs-on: ubuntu-latest
24
+ environment: pypi
25
+ permissions:
26
+ id-token: write # OIDC for PyPI Trusted Publishing
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - uses: actions/setup-python@v5
30
+ with:
31
+ python-version: "3.12"
32
+ - name: Build
33
+ run: |
34
+ python -m pip install build
35
+ python -m build
36
+ - name: Publish to PyPI
37
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+
11
+ # nnlens local render store
12
+ .nnlens/
13
+ *.rendered.html
14
+
15
+ # OS / editor
16
+ .DS_Store
17
+ Thumbs.db
18
+ .vscode/
19
+ .idea/
20
+
21
+ # tests
22
+ .pytest_cache/
23
+ .coverage
24
+ tests/js/node_modules/
25
+ tests/js/package-lock.json
@@ -0,0 +1,73 @@
1
+ # Contributing to nnlens
2
+
3
+ ## Dev setup
4
+
5
+ ```bash
6
+ py -3.12 -m venv .venv
7
+ .venv/Scripts/python -m pip install -e ".[dev]" numpy
8
+ .venv/Scripts/python -m pytest
9
+ ```
10
+
11
+ `numpy` isn't a runtime dependency of nnlens itself — it's only needed to run
12
+ the bundled example's naive view (view 4).
13
+
14
+ The renderer JS (`viewer.js`) has its own headless test suite (Node + jsdom),
15
+ which covers the security-critical transforms (HTML escaping, math handling,
16
+ `{{term}}` wiring, link/image policy):
17
+
18
+ ```bash
19
+ cd tests/js
20
+ npm install
21
+ npm test # node --test
22
+ ```
23
+
24
+ ## Layout
25
+
26
+ ```
27
+ src/nnlens/
28
+ server.py # MCP server: tools + the `explain` prompt
29
+ models.py # the Explanation schema (pydantic)
30
+ sources.py # arXiv + GitHub retrieval (stdlib only)
31
+ sandbox.py # run_python (subprocess + timeout)
32
+ prompts.py # the `explain` methodology prompt
33
+ renderer/
34
+ build.py # explanation dict -> self-contained HTML
35
+ server.py # background localhost static server
36
+ template.html # the viewer (markdown-it + mermaid + katex + hover terms)
37
+ examples/ # golden explanation fixtures
38
+ scripts/ # build_example.py, demo_render.py
39
+ tests/
40
+ ```
41
+
42
+ ## Adding an example
43
+
44
+ Explanations are generated on demand by your host — they are **not** committed as a
45
+ content library (that keeps view 5 free of any repo-redistribution concerns). The
46
+ one checked-in example exists as a schema fixture and a format reference.
47
+
48
+ If you improve the format, regenerate the fixture:
49
+
50
+ ```bash
51
+ python scripts/build_example.py
52
+ ```
53
+
54
+ It runs the naive code through the sandbox, so `run_ok` / `run_stdout` stay real.
55
+
56
+ ## Changing the schema
57
+
58
+ Edit `models.py`, then update `prompts.py` (the JSON shape shown to the host) and
59
+ `examples/transformer_attention.json` together — `tests/test_models.py` validates
60
+ the fixture against the schema and will fail if they drift.
61
+
62
+ ## Changing the renderer
63
+
64
+ `template.html` is plain HTML/JS with CDN libs; open the output of
65
+ `python scripts/demo_render.py --once` in a browser to iterate. Keep the
66
+ `{{plain-word}}` → hover-tooltip wiring working (`tests/test_renderer.py` checks
67
+ the data is embedded and the token replaced).
68
+
69
+ ## Before you push
70
+
71
+ - `python -m pytest` is green.
72
+ - If you touched the renderer JS, `cd tests/js && npm test` is green.
73
+ - If you touched anything with logic, run a Codex review over the diff.
nnlens-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nnlens contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
nnlens-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: nnlens
3
+ Version: 0.1.0
4
+ Summary: Explain neural-network architectures layer by layer in five linked views (structure, plain words, math, naive code, optimized code). An MCP server + local renderer driven by your own LLM subscription.
5
+ Project-URL: Homepage, https://github.com/tsuzakiii/nnlens
6
+ Project-URL: Repository, https://github.com/tsuzakiii/nnlens
7
+ Project-URL: Issues, https://github.com/tsuzakiii/nnlens/issues
8
+ Author: nnlens contributors
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: education,explainer,mcp,neural-networks,pytorch
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: mcp>=1.2.0
14
+ Requires-Dist: pydantic>=2.5
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=8; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # nnlens
20
+
21
+ **Throw in a paper, a GitHub repo, or just the name of a technique — get back a
22
+ layer-by-layer explanation of a neural network, in five linked views.**
23
+
24
+ ![nnlens rendering a Transformer block: multi-component sidebar, related-explanation chips, concept ledger, and the structure diagram](docs/img/screenshot.png)
25
+
26
+ nnlens is an **MCP server + local renderer**. You connect it to an MCP host you
27
+ already use (Claude Desktop, Claude Code, Cursor, …). The host's model — driven by
28
+ **your own subscription** — does the explaining; nnlens gives it the methodology,
29
+ fetches the real sources, runs the code, and renders the result to a local web page.
30
+
31
+ > nnlens **never calls an LLM itself and never handles an API key.** The
32
+ > reasoning happens in your MCP host, on your existing plan. That is the whole
33
+ > point: no metered API, no shared credentials, no hosted service borrowing your
34
+ > subscription.
35
+
36
+ ## The five views
37
+
38
+ Every component (a layer, block, or technique) is explained five ways, and the
39
+ views are **linked** by a shared *concept ledger* so the same idea keeps the same
40
+ everyday word, symbol, and formal name across all of them:
41
+
42
+ 1. **構造** — a Mermaid diagram of the data flow, plus a short note.
43
+ 2. **言葉での説明** — plain language only. No symbols, no jargon.
44
+ 3. **数式** — the real notation, carrying over the everyday words from view 2 and
45
+ attaching each to its symbol (hover any highlighted word to see the mapping).
46
+ 4. **素の実装** — a from-scratch implementation that is *literally the math*
47
+ (pure Python / numpy, no torch), **actually executed** so the output is real.
48
+ 5. **最適化された実装** — the fast version, excerpted from the official repository
49
+ (with a source link) or written from scratch when none exists — numerically
50
+ cross-checked against the naive view when it's locally runnable.
51
+
52
+ Beyond a single page:
53
+
54
+ - **Library** — every explanation you generate is saved locally
55
+ (`~/.nnlens/store`) and listed in the sidebar; delete with the hover ✕.
56
+ - **Cross-links** — explanations reference each other (`related` chips and
57
+ `[[slug]]` wikilinks in the prose). Links to explanations you haven't generated
58
+ yet show up greyed out — a built-in "what to explain next" list.
59
+ - **Contract lint** — `render` returns warnings when the views drift apart
60
+ (a ledger term never marked, symbols leaking into the plain-words view, an
61
+ uncited optimized view, an unverified naive run), so the host fixes them.
62
+ - **Self-healing pages** — pages are stamped with a template hash and rebuilt
63
+ automatically when nnlens updates its renderer.
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ pip install nnlens # or: pipx install nnlens
69
+ ```
70
+
71
+ Or from source:
72
+
73
+ ```bash
74
+ git clone https://github.com/tsuzakiii/nnlens
75
+ cd nnlens
76
+ pip install -e .
77
+ ```
78
+
79
+ ## Connect it to your MCP host
80
+
81
+ **Claude Desktop** — add to `claude_desktop_config.json`:
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "nnlens": { "command": "nnlens" }
87
+ }
88
+ }
89
+ ```
90
+
91
+ **Claude Code**:
92
+
93
+ ```bash
94
+ claude mcp add nnlens -- nnlens
95
+ ```
96
+
97
+ (If the `nnlens` script isn't on your PATH, use
98
+ `"command": "python", "args": ["-m", "nnlens"]` instead.)
99
+
100
+ ## Use it
101
+
102
+ In your host, invoke the `explain` prompt (e.g. type `/nnlens` / `/explain`) or
103
+ just ask:
104
+
105
+ > nnlens で Scaled Dot-Product Attention を説明して
106
+
107
+ The host will fetch the paper/repo, write the five views, run the naive code to
108
+ verify it, and hand you a URL like `http://127.0.0.1:8787/e/…` — open it for the
109
+ rendered page with diagrams, math, and hover-linked terms.
110
+
111
+ ## Try the renderer without a host
112
+
113
+ ```bash
114
+ python scripts/build_example.py # (re)build the bundled example, runs its code
115
+ python scripts/demo_render.py --open
116
+ ```
117
+
118
+ ## How it fits together
119
+
120
+ ```
121
+ MCP host (your subscription) ── drives ──► nnlens tools
122
+ │ ├─ fetch_paper / find_official_repo / fetch_repo_code
123
+ │ writes the 5 views ├─ run_python (proves view 4 runs)
124
+ └───────────────────────────────────► render (→ local web page URL)
125
+ ```
126
+
127
+ - **Tools** = the deterministic work (retrieval, code execution, rendering).
128
+ - **`explain` prompt** = the methodology the host follows to assemble the views.
129
+
130
+ ## Limitations (read before trusting it)
131
+
132
+ - **Correctness is not guaranteed.** The prose and math are written by the host
133
+ model. Diagrams are model-generated and are the weakest link — treat view 1 as a
134
+ sketch. View 4 is executed, so its output is real; the rest is best-effort.
135
+ - **`run_python` is not a hardened sandbox.** It runs code your host produced, on
136
+ your machine, with a timeout — not untrusted third-party code. Don't point it at
137
+ untrusted input.
138
+ - **View 5 excerpts** are fetched from public repos at view time and shown with
139
+ attribution; nothing is redistributed. Respect each source repo's license.
140
+ - The renderer loads Markdown/Mermaid/KaTeX from a CDN, so viewing needs internet.
141
+
142
+ ## License
143
+
144
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,109 @@
1
+ # nnlens — progress / task board
2
+
3
+ ## Done (2026-07-03) — PUBLISHED on GitHub
4
+ - [x] Public repo: https://github.com/tsuzakiii/nnlens (topics: mcp, neural-networks, education, explainer, claude).
5
+ - [x] All 21 commits rewritten to `tsuzakiii <236340194+tsuzakiii@users.noreply.github.com>` before push (old name/email fully purged from history, backup refs expired; verified 0 traces).
6
+ - [x] Hero screenshot (Transformer block page) + [project.urls] in pyproject.
7
+ - [x] dist built + `twine check` PASSED. **PyPI upload pending: needs the user's PyPI account + API token** (`~/.pypirc` or `twine upload -u __token__ -p pypi-...`); everything else is ready.
8
+
9
+ ## Done (2026-07-03) — renamed layerlens → (layerlore) → nnlens
10
+ - [x] "layerlens" was taken (PyPI 200; a "LayerLens" GitHub org with a 276★ repo). Interim pick "layerlore" was applied and working, then the user weighed in; final name **nnlens** — verified completely clean (PyPI 404, GitHub 0 repos, the cleanest of all candidates).
11
+ - [x] Renamed package / console script / MCP server name / env vars (NNLENS_STORE, NNLENS_PORT) / branding; repo folder moved to C:\ClaudeCode\nnlens; fresh venv.
12
+ - [x] Store auto-migration on first use: `~/.layerlore/store` or `~/.layerlens/store` → `~/.nnlens/store` (all 4 explanations carried over). Old pages auto-rebuilt under the new branding by the template-hash rebuilder.
13
+ - [x] Re-registered in Claude Code (user scope) and Claude Desktop config (backups kept). Desktop needs a restart to pick it up.
14
+
15
+ Working name: **nnlens** (final). Goal: MCP server + local renderer that
16
+ explains NN components in five linked views, driven by the user's own LLM
17
+ subscription (host does inference; server = tools + methodology + renderer).
18
+
19
+ ## Decisions locked
20
+ - Form factor: **MCP server + local renderer** (not a hosted site, not CLI-shellout).
21
+ - Host = the user's subscription (Claude Desktop / Claude Code / Cursor). Server never calls an LLM.
22
+ - On-demand generation only; **no committed content library** (keeps view 5 free of repo-redistribution/license issues).
23
+ - Backend not auto-detected — it's simply whichever MCP host the user configures.
24
+ - Renderer: Markdown (markdown-it) + Mermaid + KaTeX + `{{term}}` hover tooltips.
25
+
26
+ ## Done (MVP v0.1)
27
+ - [x] Project scaffold (pyproject, MIT, gitignore, README, CONTRIBUTING).
28
+ - [x] Schema (`models.py`): Explanation → Component → 5 views + concept ledger.
29
+ - [x] MCP server (`server.py`): tools `fetch_paper`, `find_official_repo`, `fetch_repo_code`, `run_python`, `render`, `explanation_schema`; `explain` prompt.
30
+ - [x] Retrieval (`sources.py`, stdlib only), sandbox (`sandbox.py`).
31
+ - [x] Renderer (`template.html`, `build.py`, background static `server.py`).
32
+ - [x] Golden example (Transformer/attention) built by `scripts/build_example.py` (runs naive code).
33
+ - [x] Tests: models, sandbox, renderer, sources(net-guarded).
34
+
35
+ ## Done (this session, cont.)
36
+ - [x] venv + install, build example, pytest green (12 Python + 5 JS).
37
+ - [x] Codex review #1 → 11 findings, ALL fixed:
38
+ - Renderer XSS (html:false + texmath math + DOM-walk term wiring + safeHref scheme allowlist).
39
+ - Extracted renderer JS to `viewer.js`; headless-tested via Node+jsdom (`tests/js/viewer.test.cjs`).
40
+ - sandbox: UTF-8 decode, bounded drain threads, process-tree kill on timeout (+regression tests).
41
+ - sources: arXiv via ElementTree (+no_results, old-style ids), capped/quoted repo fetch.
42
+ - static server restricted to `/e/<slug>.html`, no dir listing.
43
+ - prompt `kind` example fixed to a valid literal.
44
+ - [x] git init (25 files staged; not yet committed).
45
+ - [~] Browser visual check: BLOCKED — browser-scenario profile locked by another process. Renderer verified headlessly instead.
46
+
47
+ ## Done (this session, cont. 2)
48
+ - [x] Codex re-review #2: main XSS rewrite / wrapTerms / traversal / sandbox / build escaping all confirmed sound. 3 minor findings, ALL fixed:
49
+ - slug() now ASCII-only (+hash fallback) so Unicode ids don't 404 on the render server.
50
+ - markdown links restricted to http(s)+anchors; images disabled (no external auto-load).
51
+ - arXiv id extraction strips trailing slash.
52
+ - [x] Caught+fixed independently: markdown-it-texmath sets no browser global → createMd probes the bare `texmath` identifier (else math would render as raw `$...$` in a real browser).
53
+ - [x] Regression tests added (link/image policy, unicode slug, arxiv id variants). Python 14 + JS 6 all green.
54
+
55
+ ## Done (this session, cont. 3)
56
+ - [x] Runtime integration tests: render() serves over HTTP (200+content), static server 404s outside /e/<slug>.html, invalid explanation -> validation error. (16 Python tests)
57
+ - [x] renderComponent assembly test via jsdom (5 views, ledger, badges, term wiring, safe link). (8 JS tests)
58
+ - [x] Built wheel; verified template.html + viewer.js are packaged; fresh-install smoke test renders + MCP imports. `pip install` claim holds.
59
+ - [x] 3 local commits (no push).
60
+
61
+ ## Done (2026-07-03, cont.) — real-host E2E passed (multi-component)
62
+ - [x] Headless `claude -p` (a REAL host on the user's subscription), given only the explain prompt + the nnlens MCP tools, autonomously produced "Transformer Block (Pre-Norm)": kind=architecture with 4 components (block wiring / LayerNorm / MHA / FFN), all 4 naive views actually executed (run_ok true; row-sums/causal-mask/identity checks), optimized views excerpted from karpathy/nanoGPT, related+wikilinks to all 3 existing explanations, torch equivalence honestly skipped (no torch), **final render warnings: []**. Library now 5 entries; page visually verified.
63
+ - [x] This validates the design bets: list_library-before-linking, run_python-not-fabricate, lint-as-quality-gate, and the multi-component schema — all under a host I didn't steer.
64
+
65
+ ## Needs the user / external (can't do autonomously)
66
+ - [ ] Browser *visual* of mermaid/katex: browser-scenario profile stayed locked all session. Logic verified headlessly; retry when it frees.
67
+ - [ ] End-to-end host-driven run: needs the user's own MCP host (Claude Desktop / Claude Code) — add nnlens, run /explain, confirm the round-trip.
68
+ - [ ] Naming + PyPI availability before publishing ("nnlens" is a working name).
69
+ - [ ] Open design Q: Codex-as-MCP-client support for ChatGPT-sub users (Claude hosts are solid).
70
+
71
+ ## Done (this session, cont. 4) — real-usage feedback + visual polish
72
+ - [x] **Bug (from real use): `run_python` hung on trivial `print(2+2)` over MCP stdio.** Root-caused empirically (not `sys.executable` — the child inherited the parent's MCP-pipe stdin and deadlocked on Windows). Fix: spawn snippets with `stdin=DEVNULL`. Added an **E2E regression test** (`tests/test_mcp_stdio.py`) that launches the server over real stdio (normal pytest can't reproduce it). Also added a defensive `_python_interpreter()` resolver.
73
+ - [x] **`fetch_paper` now surfaces `repos`** (github URLs from the abstract/links) so view 5 survives find_official_repo name collisions (e.g. PRISM → gpr-prism/prism).
74
+ - [x] **Renderer full-width**: dropped `main { max-width: 900px }` → content uses the whole window (was wasting ~40% on wide screens). Verified visually via Playwright (before/after screenshots): Mermaid, KaTeX, syntax highlight, term hover, run badge, source link all render correctly.
75
+ - [x] Feature scoping: in-page "Claude toolbox" (select-to-explain / revise) researched (workflow) — MCP sampling unsupported by Desktop/Code today; only viable via a separate warm-`claude`-CLI daemon. User declined the feature for now.
76
+
77
+ ## Done (2026-07-02) — cross-links between explanations
78
+ - [x] Design doc `docs/DESIGN_related_links.md`; implemented by a Sonnet-5(high) subagent, reviewed + hardened here.
79
+ - [x] `Explanation.related` (RelatedRef: slug/label/relation×4) + `[[slug|text]]` wikilinks in prose; header chips; two-stage pending→link/missing upgrade against the library index (missing = 「まだ生成されていません」= what to generate next).
80
+ - [x] New MCP tool `list_library` so the host knows existing slugs before linking; prompt updated.
81
+ - [x] Codex review #4 (4 findings, all fixed): prototype-pollution-safe slug map (Object.create(null)+hasOwnProperty), upgradeLinks reconciles in BOTH directions (deleted target downgrades a live link), slug allowlist unified with the server URL regex across RelatedRef/isSafeSlug/_clean_entry, list_library never raises. No XSS path found.
82
+ - [x] Verified in-browser: attention↔layer-norm mutual chips click through both ways.
83
+
84
+ ## Done (2026-07-02, cont.) — template updates now reach old pages
85
+ - [x] Pages are stamped with a template hash (`<meta name="nnlens-template">`; sha1 of template.html+viewer.js).
86
+ - [x] `rebuild_store`: pages whose stamp differs (or is missing = legacy) are re-rendered losslessly from their embedded Explanation JSON; unparseable pages skipped untouched. Runs automatically at `ensure_server` startup.
87
+ - [x] Proven on the real store: the PRISM page (stuck on the old max-width:900px CSS) auto-upgraded to the current layout + wikilink viewer on server restart.
88
+ - [x] `write_explanation` atomic (temp+os.replace). Codex review #5 (2 findings, fixed): renders win over rebuilds via mtime guard (`_replace_if_unchanged`), fixed short temp prefix (Windows path limits), per-page failure isolation, rebuild/reconcile guarded separately at startup.
89
+
90
+ ## Done (2026-07-02, cont. 2) — render() lints the cross-view contract
91
+ - [x] New `lint.py`: render() returns non-blocking `warnings` — ledger terms actually marked in prose, marks resolve to a ledger entry, words view symbol-free ($/non-ASCII symbols; single ASCII letters skipped), math view has notation, optimized view cites a source or declares self-impl, naive run verified (run_ok).
92
+ - [x] Codex review #6 (3 findings, all fixed): lint now mirrors the RENDERER — code/math regions stripped before analysis (`` `{{x}}` `` doesn't count as wired; `` `$PATH` `` doesn't flag), and mark resolution is global across components (matches viewer.js's merged ledgerMap).
93
+ - [x] Prompt: host is told to fix warnings and re-render. Dogfood test: bundled fixtures are lint-clean.
94
+
95
+ ## Verified state
96
+ - Tests: 59 Python + 16 Node/jsdom, all green. Codex reviewed 6 times (23 findings, all fixed). Wheel installs and renders. Rendered page + cross-links visually verified in-browser; legacy-page auto-rebuild verified on the real store.
97
+
98
+ ## Known limitations (documented, not blockers)
99
+ - Browser visual of mermaid/katex not yet eyeballed (profile locked all session); graceful fallback implemented.
100
+ - `run_python` is a timeout+tree-kill subprocess, not a hardened sandbox (documented in README).
101
+ - Renderer needs internet for CDN libs at view time.
102
+
103
+ ## Backlog / open questions
104
+ - [ ] Verify the `explain` flow against a real MCP host end-to-end (needs Claude Desktop/Code).
105
+ - [ ] Codex-as-MCP-client support? (affects ChatGPT-sub users; Claude hosts are solid.)
106
+ - [ ] Architecture decomposition example (full Transformer block → attention/FFN/norm/residual).
107
+ - [ ] Optional: offline/vendored CDN assets for the renderer.
108
+ - [ ] Optional: `run_python` hardening (no-network mode).
109
+ - [x] **Naming: RENAMED to `nnlens`** (2026-07-03, user delegated the pick). The original name "layerlens" was taken (PyPI 200; a "LayerLens" GitHub org with a 276★ repo). `nnlens` verified free on PyPI (404) and GitHub (0 repos) at rename time. Other free candidates at the time: layerscope, nnlens, explayn, fivelens.
nnlens-0.1.0/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # nnlens
2
+
3
+ **Throw in a paper, a GitHub repo, or just the name of a technique — get back a
4
+ layer-by-layer explanation of a neural network, in five linked views.**
5
+
6
+ ![nnlens rendering a Transformer block: multi-component sidebar, related-explanation chips, concept ledger, and the structure diagram](docs/img/screenshot.png)
7
+
8
+ nnlens is an **MCP server + local renderer**. You connect it to an MCP host you
9
+ already use (Claude Desktop, Claude Code, Cursor, …). The host's model — driven by
10
+ **your own subscription** — does the explaining; nnlens gives it the methodology,
11
+ fetches the real sources, runs the code, and renders the result to a local web page.
12
+
13
+ > nnlens **never calls an LLM itself and never handles an API key.** The
14
+ > reasoning happens in your MCP host, on your existing plan. That is the whole
15
+ > point: no metered API, no shared credentials, no hosted service borrowing your
16
+ > subscription.
17
+
18
+ ## The five views
19
+
20
+ Every component (a layer, block, or technique) is explained five ways, and the
21
+ views are **linked** by a shared *concept ledger* so the same idea keeps the same
22
+ everyday word, symbol, and formal name across all of them:
23
+
24
+ 1. **構造** — a Mermaid diagram of the data flow, plus a short note.
25
+ 2. **言葉での説明** — plain language only. No symbols, no jargon.
26
+ 3. **数式** — the real notation, carrying over the everyday words from view 2 and
27
+ attaching each to its symbol (hover any highlighted word to see the mapping).
28
+ 4. **素の実装** — a from-scratch implementation that is *literally the math*
29
+ (pure Python / numpy, no torch), **actually executed** so the output is real.
30
+ 5. **最適化された実装** — the fast version, excerpted from the official repository
31
+ (with a source link) or written from scratch when none exists — numerically
32
+ cross-checked against the naive view when it's locally runnable.
33
+
34
+ Beyond a single page:
35
+
36
+ - **Library** — every explanation you generate is saved locally
37
+ (`~/.nnlens/store`) and listed in the sidebar; delete with the hover ✕.
38
+ - **Cross-links** — explanations reference each other (`related` chips and
39
+ `[[slug]]` wikilinks in the prose). Links to explanations you haven't generated
40
+ yet show up greyed out — a built-in "what to explain next" list.
41
+ - **Contract lint** — `render` returns warnings when the views drift apart
42
+ (a ledger term never marked, symbols leaking into the plain-words view, an
43
+ uncited optimized view, an unverified naive run), so the host fixes them.
44
+ - **Self-healing pages** — pages are stamped with a template hash and rebuilt
45
+ automatically when nnlens updates its renderer.
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ pip install nnlens # or: pipx install nnlens
51
+ ```
52
+
53
+ Or from source:
54
+
55
+ ```bash
56
+ git clone https://github.com/tsuzakiii/nnlens
57
+ cd nnlens
58
+ pip install -e .
59
+ ```
60
+
61
+ ## Connect it to your MCP host
62
+
63
+ **Claude Desktop** — add to `claude_desktop_config.json`:
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "nnlens": { "command": "nnlens" }
69
+ }
70
+ }
71
+ ```
72
+
73
+ **Claude Code**:
74
+
75
+ ```bash
76
+ claude mcp add nnlens -- nnlens
77
+ ```
78
+
79
+ (If the `nnlens` script isn't on your PATH, use
80
+ `"command": "python", "args": ["-m", "nnlens"]` instead.)
81
+
82
+ ## Use it
83
+
84
+ In your host, invoke the `explain` prompt (e.g. type `/nnlens` / `/explain`) or
85
+ just ask:
86
+
87
+ > nnlens で Scaled Dot-Product Attention を説明して
88
+
89
+ The host will fetch the paper/repo, write the five views, run the naive code to
90
+ verify it, and hand you a URL like `http://127.0.0.1:8787/e/…` — open it for the
91
+ rendered page with diagrams, math, and hover-linked terms.
92
+
93
+ ## Try the renderer without a host
94
+
95
+ ```bash
96
+ python scripts/build_example.py # (re)build the bundled example, runs its code
97
+ python scripts/demo_render.py --open
98
+ ```
99
+
100
+ ## How it fits together
101
+
102
+ ```
103
+ MCP host (your subscription) ── drives ──► nnlens tools
104
+ │ ├─ fetch_paper / find_official_repo / fetch_repo_code
105
+ │ writes the 5 views ├─ run_python (proves view 4 runs)
106
+ └───────────────────────────────────► render (→ local web page URL)
107
+ ```
108
+
109
+ - **Tools** = the deterministic work (retrieval, code execution, rendering).
110
+ - **`explain` prompt** = the methodology the host follows to assemble the views.
111
+
112
+ ## Limitations (read before trusting it)
113
+
114
+ - **Correctness is not guaranteed.** The prose and math are written by the host
115
+ model. Diagrams are model-generated and are the weakest link — treat view 1 as a
116
+ sketch. View 4 is executed, so its output is real; the rest is best-effort.
117
+ - **`run_python` is not a hardened sandbox.** It runs code your host produced, on
118
+ your machine, with a timeout — not untrusted third-party code. Don't point it at
119
+ untrusted input.
120
+ - **View 5 excerpts** are fetched from public repos at view time and shown with
121
+ attribution; nothing is redistributed. Respect each source repo's license.
122
+ - The renderer loads Markdown/Mermaid/KaTeX from a CDN, so viewing needs internet.
123
+
124
+ ## License
125
+
126
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,102 @@
1
+ # 設計書: 解説間リンク(related / wikilink)
2
+
3
+ 日付: 2026-07-02 / 対象: nnlens v0.1 / 状態: 実装待ち
4
+
5
+ ## 目的
6
+
7
+ ライブラリが現状フラットで、解説同士の関係(「Transformer block は attention と LayerNorm を含む」「PRISM は TTT の並列化」)を表現できない。
8
+ 解説に **related フィールド**と**本文中の wikilink** を導入し、解説間を相互リンクして知識グラフとして育つようにする。
9
+
10
+ ## 変更一覧(この5点で完結。スコープ外のことはしない)
11
+
12
+ ### 1. スキーマ (`src/nnlens/models.py`)
13
+
14
+ `Explanation` に追加:
15
+
16
+ ```python
17
+ class RelatedRef(BaseModel):
18
+ slug: str = Field(..., description="Slug of the related explanation, e.g. 'layer-normalization'.")
19
+ label: str = Field("", description="Display label; defaults to the slug at render time.")
20
+ relation: Literal["contains", "part-of", "builds-on", "related"] = "related"
21
+
22
+ class Explanation(BaseModel):
23
+ ...
24
+ related: list[RelatedRef] = Field(default_factory=list)
25
+ ```
26
+
27
+ - `slug` は `Explanation.slug()` と同じ safe 形式を期待するが、**バリデーションで弾かない**(正規化は不要、レンダラー側で存在チェックするため)。ただし `/` `\` `..` を含むものは pydantic validator で reject する(リンク先パスに使うため)。
28
+ - 既存 JSON(related 無し)は default `[]` で後方互換。
29
+
30
+ ### 2. MCP ツール追加 (`src/nnlens/server.py`)
31
+
32
+ ```python
33
+ @mcp.tool()
34
+ def list_library() -> dict:
35
+ """Return every explanation in the local library ({slug, title, kind} each)."""
36
+ return {"explanations": reconcile_index(config.store_dir())}
37
+ ```
38
+
39
+ - ホストが**既存の解説の slug を知ってから** related / wikilink を張れるようにするのが目的。
40
+ - `reconcile_index` は `nnlens.renderer` から import(既存関数、ディスク走査つき)。
41
+
42
+ ### 3. プロンプト (`src/nnlens/prompts.py`)
43
+
44
+ `EXPLAIN_PROMPT` に追記(How to work セクションと JSON shape の両方):
45
+
46
+ - 手順に: 「最初に `list_library` を呼び、既存の解説一覧を確認する。関連する解説があれば `related` に張る。本文(words / math / summary / note)中で他の解説に言及するときは `[[slug]]` または `[[slug|表示テキスト]]` と書く(存在する slug のみ)。」
47
+ - JSON shape 例に `"related": [{ "slug": "…", "label": "…", "relation": "contains | part-of | builds-on | related" }]` を追加。
48
+ - 注意: このファイルは `.format(topic=...)` を使うので **literal な `{` `}` は `{{` `}}` にエスケープ**する(既存コード参照)。
49
+
50
+ ### 4. レンダラー (`src/nnlens/renderer/viewer.js` + `template.html` の CSS)
51
+
52
+ #### 4a. 関連チップ(ヘッダー直下)
53
+
54
+ - `DATA.related` が非空なら、header 内(`.src` の後)に `div.related-row` を描画。
55
+ - 各エントリはチップ: `relation` の日本語ラベル + リンク。
56
+ - `contains` →「含む」/ `part-of` →「一部」/ `builds-on` →「基づく」/ `related` →「関連」
57
+ - 表示テキストは `label || slug`(ライブラリ index にあれば index の title を優先して良い)。
58
+
59
+ #### 4b. wikilink(本文中の `[[slug]]` / `[[slug|text]]`)
60
+
61
+ - `wrapTerms` と同じ **DOM text-node walk** 方式で実装(`innerHTML` への文字列注入は禁止。code/pre/katex/mermaid/term/既存リンク内はスキップ — wrapTerms の skip 規則を流用しつつ `A` タグ内もスキップに追加)。
62
+ - 正規表現: `/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g`。slug 部に `/` `\` `..` を含むものはリンク化せずプレーンテキストのまま。
63
+
64
+ #### 4c. 存在チェックと2段階アップグレード
65
+
66
+ - チップ / wikilink は最初 `<span class="wikilink pending" data-slug data-label>` として描画(この時点ではリンクでない)。
67
+ - 既存の `refreshLibrary` が index を fetch した後に `upgradeLinks(doc, indexEntries)` を呼び:
68
+ - slug が index に**ある** → `<a href="./<encodeURIComponent(slug)>.html">` に置換(class `wikilink`)。
69
+ - **ない** → class を `wikilink missing` にし `title="まだ生成されていません"`(グレー表示のまま)。これは「次に生成すべきもの」の可視化を兼ねる。
70
+ - `file://`(index が取れない)ではアップグレードが走らず pending 表示のまま = 安全に劣化。
71
+
72
+ #### 4d. CSS(template.html)
73
+
74
+ - `.related-row`(flex, gap, 小さめ)と `.chip`(角丸・枠線・relation ラベルは muted)。
75
+ - `.wikilink`(accent 色 + 下線なし hover 下線)、`.wikilink.missing` / `.wikilink.pending`(muted、cursor:default)。
76
+
77
+ ### 5. 例・テスト
78
+
79
+ - `examples/transformer_attention.json` と `examples/layer_norm.json` に相互の `related` を追加(relation は両方向 `related` で良い)。attention の words 内の LayerNorm 言及は無いので本文はいじらない(related チップのみで十分)。**ビルドスクリプト(scripts/build_example.py / build_layernorm.py)側の dict にも同じ変更を入れて再生成**すること(fixture は生成物)。
80
+ - Python テスト (`tests/test_models.py` など):
81
+ - related 付き Explanation が validate を通る / 埋め込み JSON(build_html 出力)に related が現れる。
82
+ - `slug` に `../x` を入れると ValidationError。
83
+ - `list_library` ツールが登録されている(`server.mcp.list_tools()` に名前が出る — 既存の introspect パターン参照、または tests/test_integration.py 流に直接関数を呼ぶ)。
84
+ - JS テスト (`tests/js/`):
85
+ - wikilink: `[[layer-normalization]]` がプロース中で span になり、`upgradeLinks` で `<a href="./layer-normalization.html">` になる。index に無い slug は `missing` になる。
86
+ - コードブロック内の `[[x]]` はそのまま(walk の skip)。
87
+ - related チップ: 描画される・XSS 安全(textContent / createElement のみ)。
88
+ - 既存テストが全部 green のまま(23 Python + 8 JS + 追加分)。
89
+
90
+ ## 受け入れ条件
91
+
92
+ 1. `pytest`(venv: `.venv/Scripts/python.exe -m pytest -q`)と `cd tests/js && node --test` が全 green。
93
+ 2. `scripts/build_example.py` → `scripts/build_layernorm.py` を実行し直すと、attention / layer_norm のページ双方のヘッダーに相互の「関連」チップが出て、クリックで行き来できる(store: `~/.nnlens/store`、サーバー再起動が必要なら 8787 の既存プロセスを止めてから `scripts/demo_render.py` を background で)。
94
+ 3. 存在しない slug への related / wikilink はグレー表示(リンク化されない)で、ページは壊れない。
95
+ 4. XSS 安全: すべて createElement / textContent。`innerHTML` への注入を新規に増やさない。
96
+
97
+ ## 実装上の注意(このリポジトリの流儀)
98
+
99
+ - Edit の前に必ず対象ファイルを Read する。
100
+ - viewer.js はブラウザ + Node(jsdom) 両対応(末尾の `module.exports` に新関数 `upgradeLinks` / wikilink 処理関数を追加してテスト可能にする)。
101
+ - `prompts.py` の `{` エスケープを壊さない(`.format()` が通ることをテストで確認: 既存の import + `EXPLAIN_PROMPT.format(topic="x")` がエラーにならない)。
102
+ - コミットはしない(レビュー後にこちらで行う)。
Binary file