Skip to content

Commit 3a67773

Browse files
milanoftheclaude
andcommitted
Improve llms.txt with install instructions, quickstart code, and full examples
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a9842fb commit 3a67773

3 files changed

Lines changed: 4837 additions & 917 deletions

File tree

scripts/build-llms-txt.py

Lines changed: 193 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
"handling, and MIMO connections."
2626
)
2727

28+
# Number of example notebooks to include full code for in llms-full.txt
29+
MAX_FULL_EXAMPLES = 3
30+
2831

2932
def load_package_manifest(package_id: str) -> dict | None:
3033
path = STATIC_DIR / package_id / "manifest.json"
@@ -52,6 +55,70 @@ def load_version_data(package_id: str, tag: str) -> tuple[dict | None, dict | No
5255
return api_data, manifest
5356

5457

58+
def load_notebook_code(package_id: str, tag: str, filename: str) -> list[str]:
59+
"""Extract code cells from a notebook, filtering out boilerplate."""
60+
nb_path = STATIC_DIR / package_id / tag / "notebooks" / filename
61+
if not nb_path.exists():
62+
return []
63+
64+
with open(nb_path, "r", encoding="utf-8") as f:
65+
nb = json.load(f)
66+
67+
code_blocks = []
68+
for cell in nb.get("cells", []):
69+
if cell.get("cell_type") != "code":
70+
continue
71+
src = "".join(cell.get("source", [])).strip()
72+
if not src:
73+
continue
74+
# Skip pure matplotlib/style boilerplate
75+
lines = src.split("\n")
76+
meaningful = [
77+
l for l in lines
78+
if not l.strip().startswith("plt.style.use")
79+
and not l.strip().startswith("plt.show")
80+
and not l.strip().startswith("plt.figure")
81+
and not l.strip().startswith("plt.subplot")
82+
and not l.strip().startswith("plt.tight_layout")
83+
and not l.strip().startswith("fig,")
84+
and not l.strip().startswith("fig =")
85+
and not l.strip().startswith("ax.")
86+
and not l.strip().startswith("ax,")
87+
and not l.strip().startswith("axes")
88+
]
89+
cleaned = "\n".join(meaningful).strip()
90+
if cleaned:
91+
code_blocks.append(cleaned)
92+
93+
return code_blocks
94+
95+
96+
def extract_quickstart(api_data: dict) -> str | None:
97+
"""Extract a minimal quickstart from the API data.
98+
99+
Builds a quickstart showing the basic pattern:
100+
import -> create blocks -> connect -> simulate.
101+
"""
102+
if not api_data:
103+
return None
104+
105+
modules = api_data.get("modules", {})
106+
107+
# Collect block class names from blocks modules
108+
block_names = []
109+
for mod_name, mod in modules.items():
110+
if ".blocks." in mod_name:
111+
for cls in mod.get("classes", []):
112+
name = cls["name"]
113+
if not name.startswith("_"):
114+
block_names.append(name)
115+
116+
if not block_names:
117+
return None
118+
119+
return None # We'll use real notebook examples instead
120+
121+
55122
def generate_llms_txt() -> str:
56123
"""Generate the lightweight llms.txt index."""
57124
lines = []
@@ -60,6 +127,14 @@ def generate_llms_txt() -> str:
60127
lines.append(f"> {DESCRIPTION}")
61128
lines.append("")
62129

130+
# Installation
131+
lines.append("## Installation")
132+
lines.append("")
133+
lines.append("```")
134+
lines.append("pip install pathsim")
135+
lines.append("```")
136+
lines.append("")
137+
63138
for package_id, pkg_config in PACKAGES.items():
64139
pkg_manifest = load_package_manifest(package_id)
65140
if not pkg_manifest:
@@ -118,6 +193,7 @@ def generate_llms_txt() -> str:
118193
lines.append("")
119194
lines.append("- [PathSim Homepage](https://pathsim.org): Project homepage")
120195
lines.append("- [PathView Editor](https://view.pathsim.org): Browser-based visual block diagram editor")
196+
lines.append("- [PathSim Codegen](https://code.pathsim.org): Generate C99 code from PathSim models")
121197
lines.append("- [GitHub](https://github.com/pathsim): Source code repositories")
122198
lines.append("- [PyPI](https://pypi.org/project/pathsim): Python package")
123199
lines.append("- [JOSS Paper](https://doi.org/10.21105/joss.07484): Published paper")
@@ -127,13 +203,23 @@ def generate_llms_txt() -> str:
127203

128204

129205
def generate_llms_full_txt() -> str:
130-
"""Generate llms-full.txt with complete API documentation content."""
206+
"""Generate llms-full.txt with complete API documentation and code examples."""
131207
lines = []
132208
lines.append("# PathSim Documentation (Full)")
133209
lines.append("")
134210
lines.append(f"> {DESCRIPTION}")
135211
lines.append("")
136212

213+
# Installation
214+
lines.append("## Installation")
215+
lines.append("")
216+
lines.append("```bash")
217+
lines.append("pip install pathsim")
218+
lines.append("pip install pathsim-chem # Chemical engineering toolbox")
219+
lines.append("pip install pathsim-rf # RF/microwave toolbox")
220+
lines.append("```")
221+
lines.append("")
222+
137223
for package_id, pkg_config in PACKAGES.items():
138224
pkg_manifest = load_package_manifest(package_id)
139225
if not pkg_manifest:
@@ -147,11 +233,35 @@ def generate_llms_full_txt() -> str:
147233
lines.append(f"## {display_name} ({latest})")
148234
lines.append("")
149235

236+
# Quickstart from first example notebook
237+
if manifest:
238+
notebooks = manifest.get("notebooks", [])
239+
if notebooks:
240+
first_nb = notebooks[0]
241+
code_blocks = load_notebook_code(
242+
package_id, latest, first_nb.get("file", "")
243+
)
244+
if code_blocks:
245+
lines.append("### Quickstart")
246+
lines.append("")
247+
lines.append(
248+
f"From the [{first_nb['title']}]"
249+
f"({BASE_URL}/{package_id}/{latest}/examples/{first_nb['slug']}) example:"
250+
)
251+
lines.append("")
252+
lines.append("```python")
253+
lines.append("\n\n".join(code_blocks))
254+
lines.append("```")
255+
lines.append("")
256+
150257
# Full API content
151258
if api_data:
259+
lines.append("### API Reference")
260+
lines.append("")
261+
152262
modules = api_data.get("modules", {})
153263
for module_name, module in modules.items():
154-
lines.append(f"### {module_name}")
264+
lines.append(f"#### {module_name}")
155265
lines.append("")
156266

157267
desc = module.get("description", "")
@@ -165,76 +275,128 @@ def generate_llms_full_txt() -> str:
165275
bases = cls.get("bases", [])
166276

167277
base_str = f"({', '.join(bases)})" if bases else ""
168-
lines.append(f"#### class {cls_name}{base_str}")
278+
lines.append(f"##### class {cls_name}{base_str}")
169279
lines.append("")
170280

171281
if cls_desc:
172282
lines.append(cls_desc)
173283
lines.append("")
174284

285+
# Parameters (constructor args)
286+
params = cls.get("parameters", [])
287+
if params:
288+
lines.append("**Parameters:**")
289+
lines.append("")
290+
for p in params:
291+
p_name = p.get("name", "")
292+
p_type = p.get("type", "")
293+
p_default = p.get("default", None)
294+
p_desc = p.get("description", "")
295+
type_str = f" ({p_type})" if p_type else ""
296+
default_str = f", default={p_default}" if p_default is not None else ""
297+
entry = f"- `{p_name}{type_str}{default_str}`"
298+
if p_desc:
299+
entry += f" — {p_desc}"
300+
lines.append(entry)
301+
lines.append("")
302+
175303
# Attributes
176-
for attr in cls.get("attributes", []):
177-
attr_name = attr.get("name", "")
178-
attr_type = attr.get("type", "")
179-
attr_desc = attr.get("description", "")
180-
if attr_name.startswith("_"):
181-
continue
182-
type_str = f": {attr_type}" if attr_type else ""
183-
entry = f"- `{attr_name}{type_str}`"
184-
if attr_desc:
185-
entry += f" — {attr_desc}"
186-
lines.append(entry)
187-
188-
if cls.get("attributes"):
304+
attrs = [
305+
a for a in cls.get("attributes", [])
306+
if not a.get("name", "").startswith("_")
307+
]
308+
if attrs:
309+
lines.append("**Attributes:**")
310+
lines.append("")
311+
for attr in attrs:
312+
attr_name = attr.get("name", "")
313+
attr_type = attr.get("type", "")
314+
attr_desc = attr.get("description", "")
315+
type_str = f": {attr_type}" if attr_type else ""
316+
entry = f"- `{attr_name}{type_str}`"
317+
if attr_desc:
318+
entry += f" — {attr_desc}"
319+
lines.append(entry)
189320
lines.append("")
190321

191322
# Methods
192-
for method in cls.get("methods", []):
193-
method_name = method.get("name", "")
194-
if method_name.startswith("_") and method_name != "__init__":
195-
continue
196-
sig = method.get("signature", "()")
197-
method_desc = method.get("description", "")
198-
lines.append(f"**{cls_name}.{method_name}**`{sig}`")
199-
if method_desc:
200-
lines.append(f": {method_desc}")
201-
lines.append("")
323+
methods = [
324+
m for m in cls.get("methods", [])
325+
if not m.get("name", "").startswith("_")
326+
or m.get("name") == "__init__"
327+
]
328+
if methods:
329+
for method in methods:
330+
method_name = method.get("name", "")
331+
sig = method.get("signature", "()")
332+
method_desc = method.get("description", "")
333+
lines.append(f"**{cls_name}.{method_name}**`{sig}`")
334+
if method_desc:
335+
lines.append(f": {method_desc}")
336+
# Method parameters
337+
m_params = method.get("parameters", [])
338+
if m_params:
339+
for p in m_params:
340+
p_name = p.get("name", "")
341+
if p_name in ("self", "cls"):
342+
continue
343+
p_desc = p.get("description", "")
344+
p_default = p.get("default", None)
345+
default_str = f", default={p_default}" if p_default is not None else ""
346+
entry = f" - `{p_name}{default_str}`"
347+
if p_desc:
348+
entry += f" — {p_desc}"
349+
lines.append(entry)
350+
lines.append("")
202351

203352
for func in module.get("functions", []):
204353
func_name = func.get("name", "")
205354
sig = func.get("signature", "()")
206355
func_desc = func.get("description", "")
207-
lines.append(f"#### {func_name}`{sig}`")
356+
lines.append(f"##### {func_name}`{sig}`")
208357
if func_desc:
209358
lines.append(func_desc)
210359
lines.append("")
211360

212-
# Examples with descriptions
361+
# Example code from notebooks
213362
if manifest:
214363
notebooks = manifest.get("notebooks", [])
215364
if notebooks:
216-
lines.append(f"### Examples")
365+
lines.append("### Examples")
217366
lines.append("")
218-
for nb in notebooks:
367+
368+
for i, nb in enumerate(notebooks):
219369
slug = nb.get("slug", "")
220370
title = nb.get("title", slug)
221371
desc = nb.get("description", "")
222372
tags = nb.get("tags", [])
223-
category = nb.get("category", "")
224373
url = f"{BASE_URL}/{package_id}/{latest}/examples/{slug}"
225374

226375
lines.append(f"#### [{title}]({url})")
227376
if desc:
228377
lines.append(desc)
229378
if tags:
230379
lines.append(f"Tags: {', '.join(tags)}")
380+
381+
# Include full code for the first few examples
382+
if i < MAX_FULL_EXAMPLES:
383+
code_blocks = load_notebook_code(
384+
package_id, latest, nb.get("file", "")
385+
)
386+
if code_blocks:
387+
lines.append("")
388+
lines.append("```python")
389+
lines.append("\n\n".join(code_blocks))
390+
lines.append("```")
391+
231392
lines.append("")
232393

233394
# Links
234395
lines.append("## Links")
235396
lines.append("")
236397
lines.append("- [PathSim Homepage](https://pathsim.org): Project homepage")
237398
lines.append("- [PathView Editor](https://view.pathsim.org): Browser-based visual block diagram editor")
399+
lines.append("- [PathSim Codegen](https://code.pathsim.org): Generate C99 code from PathSim models")
238400
lines.append("- [GitHub](https://github.com/pathsim): Source code repositories")
239401
lines.append("- [PyPI](https://pypi.org/project/pathsim): Python package")
240402
lines.append("- [JOSS Paper](https://doi.org/10.21105/joss.07484): Published paper")

0 commit comments

Comments
 (0)