Skip to content

Commit a86fe40

Browse files
authored
docs: add missing example categories to examples catalogue (#645) (#672)
* docs: add missing example categories to examples catalogue (#645) Add plugins, m_decompose, tutorial, notebooks, and hello_world to the examples index page so all runnable example directories are discoverable from the published docs. Also add a note to CONTRIBUTING.md reminding authors to update the catalogue when adding new example directories. * docs: add examples catalogue validation check to doc validator Add validate_examples_catalogue() to validate.py that checks every example directory under docs/examples/ (containing .py files) has a corresponding entry in docs/docs/examples/index.md. Also refine the CONTRIBUTING.md guideline to frame it as a check rather than only an instruction for when adding examples.
1 parent a4ec484 commit a86fe40

4 files changed

Lines changed: 174 additions & 0 deletions

File tree

docs/docs/examples/index.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ to run.
4949
| `tools/` | `@tool` definition, code interpreter integration, tool argument validation, safe `eval` patterns |
5050
| `mini_researcher/` | Complete research assistant: multi-model architecture, document retrieval, safety checks, custom validation pipeline |
5151

52+
### Extensibility
53+
54+
| Category | What it shows |
55+
| -------- | ------------- |
56+
| `plugins/` | Plugin system end-to-end: function hooks, class-based plugins, payload modification, scoped and session-scoped plugins, `PluginSet` composition, execution modes, tool hooks, and testing patterns |
57+
5258
### Safety and validation
5359

5460
| Category | What it shows |
@@ -60,6 +66,7 @@ to run.
6066
| Category | What it shows |
6167
| -------- | ------------- |
6268
| `m_serve/` | Deploying Mellea programs as REST APIs with production deployment patterns |
69+
| `m_decompose/` | Decomposing complex prompts into sub-tasks via the CLI and Python API |
6370
| `library_interop/` | LangChain message conversion, OpenAI format compatibility, cross-library workflows |
6471
| `mcp/` | MCP tool creation, Claude Desktop integration, Langflow integration |
6572
| `bedrock/` | Amazon Bedrock backend configuration and usage |
@@ -90,6 +97,14 @@ to run.
9097
| -------- | ------------- |
9198
| `melp/` | ⚠️ Experimental lazy evaluation — thunks, deferred execution, advanced control flow |
9299

100+
### Getting started and tutorials
101+
102+
| Category | What it shows |
103+
| -------- | ------------- |
104+
| `hello_world.py` | Minimal single-file starting point |
105+
| `tutorial/` | Python script versions of the tutorials: email generation, IVR, generative slots, contexts, MObjects, model options, and more |
106+
| `notebooks/` | Jupyter notebook versions of the same tutorials for interactive, cell-by-cell exploration |
107+
93108
---
94109

95110
## Running the examples

docs/docs/guide/CONTRIBUTING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,15 @@ Where a CI-tested example exists in `docs/examples/`, link it:
251251

252252
Only link examples that are current and in CI.
253253

254+
### Keeping the examples catalogue up to date
255+
256+
Check that every example directory under `docs/examples/` has a corresponding
257+
row in the catalogue table in `docs/docs/examples/index.md`. When adding a new
258+
example directory, add a row with a short description of what the examples
259+
demonstrate. This ensures users browsing the published docs can discover all
260+
available examples, not only the ones linked from individual guide or concept
261+
pages.
262+
254263
---
255264

256265
## Missing content

tooling/docs-autogen/test_validate.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from validate import (
99
generate_report,
1010
validate_doc_imports,
11+
validate_examples_catalogue,
1112
validate_internal_links,
1213
validate_mdx_syntax,
1314
validate_source_links,
@@ -263,5 +264,85 @@ def test_generate_report_all_pass():
263264
assert report["overall_passed"] is True
264265

265266

267+
def test_validate_examples_catalogue_pass():
268+
"""Test examples catalogue check passes when all dirs are listed."""
269+
with tempfile.TemporaryDirectory() as tmpdir:
270+
docs_root = Path(tmpdir)
271+
examples_dir = docs_root / "examples"
272+
examples_dir.mkdir()
273+
index_dir = docs_root / "docs" / "examples"
274+
index_dir.mkdir(parents=True)
275+
276+
# Create an example directory with a .py file
277+
(examples_dir / "my_example").mkdir()
278+
(examples_dir / "my_example" / "demo.py").write_text("# demo")
279+
280+
# Create index listing it
281+
(index_dir / "index.md").write_text("| `my_example/` | A demo example |")
282+
283+
error_count, errors = validate_examples_catalogue(docs_root)
284+
assert error_count == 0
285+
assert len(errors) == 0
286+
287+
288+
def test_validate_examples_catalogue_missing():
289+
"""Test examples catalogue check catches unlisted directories."""
290+
with tempfile.TemporaryDirectory() as tmpdir:
291+
docs_root = Path(tmpdir)
292+
examples_dir = docs_root / "examples"
293+
examples_dir.mkdir()
294+
index_dir = docs_root / "docs" / "examples"
295+
index_dir.mkdir(parents=True)
296+
297+
# Create an example directory with a .py file
298+
(examples_dir / "unlisted_example").mkdir()
299+
(examples_dir / "unlisted_example" / "demo.py").write_text("# demo")
300+
301+
# Create index that does NOT mention it
302+
(index_dir / "index.md").write_text("| `other/` | Something else |")
303+
304+
error_count, errors = validate_examples_catalogue(docs_root)
305+
assert error_count == 1
306+
assert "unlisted_example" in errors[0]
307+
308+
309+
def test_validate_examples_catalogue_skips_helper():
310+
"""Test examples catalogue check skips the helper directory."""
311+
with tempfile.TemporaryDirectory() as tmpdir:
312+
docs_root = Path(tmpdir)
313+
examples_dir = docs_root / "examples"
314+
examples_dir.mkdir()
315+
index_dir = docs_root / "docs" / "examples"
316+
index_dir.mkdir(parents=True)
317+
318+
# Create helper directory (should be skipped)
319+
(examples_dir / "helper").mkdir()
320+
(examples_dir / "helper" / "utils.py").write_text("# utils")
321+
322+
(index_dir / "index.md").write_text("# Examples")
323+
324+
error_count, _errors = validate_examples_catalogue(docs_root)
325+
assert error_count == 0
326+
327+
328+
def test_validate_examples_catalogue_skips_empty_dirs():
329+
"""Test examples catalogue check skips directories with no .py files."""
330+
with tempfile.TemporaryDirectory() as tmpdir:
331+
docs_root = Path(tmpdir)
332+
examples_dir = docs_root / "examples"
333+
examples_dir.mkdir()
334+
index_dir = docs_root / "docs" / "examples"
335+
index_dir.mkdir(parents=True)
336+
337+
# Create directory with only non-Python files
338+
(examples_dir / "data_only").mkdir()
339+
(examples_dir / "data_only" / "data.json").write_text("{}")
340+
341+
(index_dir / "index.md").write_text("# Examples")
342+
343+
error_count, _errors = validate_examples_catalogue(docs_root)
344+
assert error_count == 0
345+
346+
266347
if __name__ == "__main__":
267348
pytest.main([__file__, "-v"])

tooling/docs-autogen/validate.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,55 @@ def validate_stale_files(docs_root: Path) -> tuple[int, list[str]]:
323323
return len(errors), errors
324324

325325

326+
def validate_examples_catalogue(docs_root: Path) -> tuple[int, list[str]]:
327+
"""Check that every example directory is listed in the examples index page.
328+
329+
Scans ``docs/examples/`` for subdirectories that contain at least one
330+
``.py`` file and verifies each directory name appears in the catalogue
331+
table in ``docs/docs/examples/index.md``.
332+
333+
Args:
334+
docs_root: The ``docs/`` directory (parent of ``docs/docs/``).
335+
336+
Returns:
337+
Tuple of (error_count, error_messages).
338+
"""
339+
errors: list[str] = []
340+
examples_dir = docs_root / "examples"
341+
index_file = docs_root / "docs" / "examples" / "index.md"
342+
343+
if not examples_dir.is_dir():
344+
return 0, []
345+
if not index_file.exists():
346+
errors.append("Examples index page not found: docs/docs/examples/index.md")
347+
return len(errors), errors
348+
349+
index_content = index_file.read_text()
350+
351+
# Directories to skip: not standalone example categories
352+
skip = {"__pycache__", "helper"}
353+
354+
for child in sorted(examples_dir.iterdir()):
355+
if not child.is_dir():
356+
continue
357+
if child.name in skip or child.name.startswith("."):
358+
continue
359+
# Only check directories that contain at least one .py file
360+
if not any(child.rglob("*.py")):
361+
continue
362+
# Check the directory name appears in the index (as a table entry or heading)
363+
if (
364+
f"`{child.name}/" not in index_content
365+
and f"`{child.name}`" not in index_content
366+
):
367+
errors.append(
368+
f"Example directory '{child.name}/' is not listed in "
369+
f"docs/docs/examples/index.md"
370+
)
371+
372+
return len(errors), errors
373+
374+
326375
def validate_doc_imports(docs_dir: Path) -> tuple[int, list[str]]:
327376
"""Verify that mellea imports in documentation code blocks still resolve.
328377
@@ -417,6 +466,7 @@ def generate_report(
417466
rst_docstring_errors: list[str] | None = None,
418467
stale_errors: list[str] | None = None,
419468
import_errors: list[str] | None = None,
469+
examples_catalogue_errors: list[str] | None = None,
420470
) -> dict:
421471
"""Generate validation report.
422472
@@ -427,6 +477,8 @@ def generate_report(
427477
stale_errors = []
428478
if import_errors is None:
429479
import_errors = []
480+
if examples_catalogue_errors is None:
481+
examples_catalogue_errors = []
430482

431483
return {
432484
"source_links": {
@@ -471,6 +523,11 @@ def generate_report(
471523
"error_count": len(import_errors),
472524
"errors": import_errors,
473525
},
526+
"examples_catalogue": {
527+
"passed": len(examples_catalogue_errors) == 0,
528+
"error_count": len(examples_catalogue_errors),
529+
"errors": examples_catalogue_errors,
530+
},
474531
"overall_passed": (
475532
len(source_link_errors) == 0
476533
and coverage_passed
@@ -479,6 +536,7 @@ def generate_report(
479536
and len(anchor_errors) == 0
480537
and len(stale_errors) == 0
481538
and len(import_errors) == 0
539+
and len(examples_catalogue_errors) == 0
482540
# rst_docstrings is a warning only — does not fail the build
483541
),
484542
}
@@ -559,6 +617,9 @@ def main():
559617
static_docs_dir = docs_root / "docs" if docs_root else docs_dir.parent
560618
_, import_errors = validate_doc_imports(static_docs_dir)
561619

620+
print("Checking examples catalogue...")
621+
_, examples_catalogue_errors = validate_examples_catalogue(docs_root)
622+
562623
# Generate report
563624
report = generate_report(
564625
source_link_errors,
@@ -570,6 +631,7 @@ def main():
570631
rst_docstring_errors,
571632
stale_errors,
572633
import_errors,
634+
examples_catalogue_errors,
573635
)
574636

575637
# Print results
@@ -619,6 +681,12 @@ def main():
619681
if not report["doc_imports"]["passed"]:
620682
print(f" {report['doc_imports']['error_count']} errors found")
621683

684+
print(
685+
f"✅ Examples catalogue: {'PASS' if report['examples_catalogue']['passed'] else 'FAIL'}"
686+
)
687+
if not report["examples_catalogue"]["passed"]:
688+
print(f" {report['examples_catalogue']['error_count']} errors found")
689+
622690
print("\n" + "=" * 60)
623691
print(f"Overall: {'✅ PASS' if report['overall_passed'] else '❌ FAIL'}")
624692
print("=" * 60)
@@ -634,6 +702,7 @@ def main():
634702
+ rst_docstring_errors
635703
+ stale_errors
636704
+ import_errors
705+
+ examples_catalogue_errors
637706
)
638707
for error in all_errors:
639708
print(f" • {error}")

0 commit comments

Comments
 (0)