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.
- nnlens-0.1.0/.gitattributes +5 -0
- nnlens-0.1.0/.github/workflows/release.yml +37 -0
- nnlens-0.1.0/.gitignore +25 -0
- nnlens-0.1.0/CONTRIBUTING.md +73 -0
- nnlens-0.1.0/LICENSE +21 -0
- nnlens-0.1.0/PKG-INFO +144 -0
- nnlens-0.1.0/PROGRESS.md +109 -0
- nnlens-0.1.0/README.md +126 -0
- nnlens-0.1.0/docs/DESIGN_related_links.md +102 -0
- nnlens-0.1.0/docs/img/screenshot.png +0 -0
- nnlens-0.1.0/examples/layer_norm.json +91 -0
- nnlens-0.1.0/examples/transformer_attention.json +85 -0
- nnlens-0.1.0/pyproject.toml +35 -0
- nnlens-0.1.0/scripts/build_example.py +185 -0
- nnlens-0.1.0/scripts/build_layernorm.py +163 -0
- nnlens-0.1.0/scripts/demo_render.py +52 -0
- nnlens-0.1.0/src/nnlens/__init__.py +9 -0
- nnlens-0.1.0/src/nnlens/__main__.py +6 -0
- nnlens-0.1.0/src/nnlens/config.py +37 -0
- nnlens-0.1.0/src/nnlens/lint.py +128 -0
- nnlens-0.1.0/src/nnlens/models.py +122 -0
- nnlens-0.1.0/src/nnlens/prompts.py +94 -0
- nnlens-0.1.0/src/nnlens/renderer/__init__.py +24 -0
- nnlens-0.1.0/src/nnlens/renderer/build.py +269 -0
- nnlens-0.1.0/src/nnlens/renderer/server.py +142 -0
- nnlens-0.1.0/src/nnlens/renderer/template.html +112 -0
- nnlens-0.1.0/src/nnlens/renderer/viewer.js +575 -0
- nnlens-0.1.0/src/nnlens/sandbox.py +148 -0
- nnlens-0.1.0/src/nnlens/server.py +118 -0
- nnlens-0.1.0/src/nnlens/sources.py +142 -0
- nnlens-0.1.0/tests/js/component.test.cjs +67 -0
- nnlens-0.1.0/tests/js/package.json +16 -0
- nnlens-0.1.0/tests/js/viewer.test.cjs +202 -0
- nnlens-0.1.0/tests/test_config.py +46 -0
- nnlens-0.1.0/tests/test_integration.py +87 -0
- nnlens-0.1.0/tests/test_lint.py +159 -0
- nnlens-0.1.0/tests/test_mcp_stdio.py +39 -0
- nnlens-0.1.0/tests/test_models.py +139 -0
- nnlens-0.1.0/tests/test_prompts.py +21 -0
- nnlens-0.1.0/tests/test_renderer.py +156 -0
- nnlens-0.1.0/tests/test_sandbox.py +36 -0
- nnlens-0.1.0/tests/test_sources.py +42 -0
|
@@ -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
|
nnlens-0.1.0/.gitignore
ADDED
|
@@ -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
|
+

|
|
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).
|
nnlens-0.1.0/PROGRESS.md
ADDED
|
@@ -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
|
+

|
|
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
|