Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 113 additions & 1 deletion .github/workflows/curriculum.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ on:
- "scripts/audit_lessons.py"
- "scripts/build_catalog.py"
- "scripts/check_readme_counts.py"
- "scripts/lesson_run.py"
- "scripts/quiz_coverage.py"
- "scripts/ts_syntax_check.mjs"
- "scripts/_lib.py"
- "README.md"
- "ROADMAP.md"
- "glossary/**"
Expand All @@ -20,6 +24,10 @@ on:
- "scripts/audit_lessons.py"
- "scripts/build_catalog.py"
- "scripts/check_readme_counts.py"
- "scripts/lesson_run.py"
- "scripts/quiz_coverage.py"
- "scripts/ts_syntax_check.mjs"
- "scripts/_lib.py"
- "README.md"
- "ROADMAP.md"
- "glossary/**"
Expand All @@ -30,6 +38,8 @@ permissions:
contents: read

jobs:
# ── Blocking checks ───────────────────────────────────────────────────

audit:
name: invariant checks
runs-on: ubuntu-latest
Expand All @@ -43,6 +53,109 @@ jobs:
- name: run scripts/audit_lessons.py
run: python3 scripts/audit_lessons.py

# ── Advisory code checks ──────────────────────────────────────────────

python-syntax:
name: python syntax check (advisory)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: syntax-check all .py files
run: |
python3 scripts/lesson_run.py --json > /tmp/syntax.json
python3 -c "
import json
data = json.load(open('/tmp/syntax.json'))
failed = [f for f in data['failed']]
skipped = [f for f in data['skipped']]
print(f'Syntax: {data[\"checked\"]} lessons, {len(data[\"passed\"])} passed, {len(failed)} failed, {len(skipped)} skipped')
for f in failed:
print(f'::warning file={f[\"lesson\"]}::syntax error: {f[\"reason\"]}')
"

typescript-syntax:
name: typescript syntax check (advisory)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-node@e62d1c64a114b2d4b223e6e62319cbfa45a42c6a # v4
with:
node-version: "22"
- name: install typescript
run: npm install --no-save typescript
- name: syntax-check all .ts files
continue-on-error: true
run: node scripts/ts_syntax_check.mjs --json
Comment thread
coderabbitai[bot] marked this conversation as resolved.

quiz-coverage:
name: quiz coverage report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: report quiz coverage
run: |
python3 scripts/quiz_coverage.py --json > /tmp/coverage.json
python3 -c "
import json
data = json.load(open('/tmp/coverage.json'))
total = data['total_lessons']
with_q = data['total_with_quiz']
pct = round(with_q / total * 100, 1) if total else 0
print(f'Quiz coverage: {with_q}/{total} ({pct}%)')
for p in sorted(data['phases'], key=lambda x: x['coverage_pct']):
if p['coverage_pct'] < 10:
print(f'::warning title=Quiz coverage phase {p[\"phase_id\"]:02d}::{p[\"phase_name\"]}: {p[\"with_quiz\"]}/{p[\"total_lessons\"]} ({p[\"coverage_pct\"]}%)')
"

# ── Pytest (lessons that have tests) ──────────────────────────────────

pytest:
name: lesson tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# Only phases that actually contain test files — discovered at
# script-write time. Add rows as tests proliferate.
- phase: 19
label: "capstone"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: install test deps
run: |
python3 -m pip install --quiet pytest pytest-timeout
# Many capstone tests import torch / numpy / h5py / zstandard / safetensors.
# Install everything the allowlist permits so tests don't fail on imports.
python3 -m pip install --quiet \
numpy \
torch \
h5py \
zstandard \
safetensors \
|| echo "::warning::Some test dependencies failed to install (may be expected)"
- name: run tests (phase ${{ matrix.phase }})
run: python3 -m pytest phases/${{ matrix.phase }}*/ --timeout=60 -v --tb=short || echo "::warning::Some tests failed"

# ── Auto-sync jobs (main only) ────────────────────────────────────────

readme-counts-sync:
name: README counts auto-fix (main only)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -78,7 +191,6 @@ jobs:
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add README.md
git commit -m "$BOT_COMMIT_PREFIX"
# Retry on non-fast-forward when another merge to main races us
branch="${GITHUB_REF#refs/heads/}"
for attempt in 1 2 3 4 5; do
if git push origin "HEAD:${branch}"; then
Expand Down
47 changes: 47 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
#
# Install: pip install pre-commit && pre-commit install
# Run once: pre-commit run --all-files

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
args: [--allow-multiple]
- id: check-json
- id: check-toml
- id: check-added-large-files
args: [--maxkb=500]
- id: check-merge-conflict
- id: mixed-line-ending
args: [--fix=lf]
- id: detect-private-key

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.8
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
hooks:
- id: mypy
args: [--config-file=pyproject.toml]
additional_dependencies: []
pass_filenames: false
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# mypy checks scripts/ with permissive rules (see pyproject.toml).
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
# It blocks on errors — skip with SKIP=mypy git commit if needed.

- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
types_or: [javascript, css, html, json, markdown, yaml]
args: [--prose-wrap=always, --print-width=100]
exclude: ^glossary/|^phases/|^site/data\.js$
103 changes: 96 additions & 7 deletions LESSON_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,28 @@ Use this template when creating a new lesson. Copy the folder structure and fill
```
NN-lesson-name/
├── code/
│ ├── main.py (primary implementation)
│ ├── main.py (primary implementation — always a code/ dir)
│ ├── main.ts (TypeScript version, if applicable)
│ ├── main.rs (Rust version, if applicable)
│ └── main.jl (Julia version, if applicable)
├── notebook/
│ └── lesson.ipynb (Jupyter notebook for experimentation)
├── docs/
│ └── en.md (lesson documentation)
│ └── en.md (lesson documentation — 6-beat format)
├── quiz.json (exactly 6 questions: 1 pre + 3 check + 2 post)
└── outputs/
├── prompt-*.md (prompts produced by this lesson)
└── skill-*.md (skills produced by this lesson)
├── skill-*.md (skills produced by this lesson)
└── agent-*.md (agent definitions produced by this lesson)
```

A lesson MUST have at minimum:
- `code/` with at least one source or config file (rule L005)
- `docs/en.md` with at least 200 bytes and an H1 header (rules L002-L004)
- `quiz.json` with exactly 6 questions in the canonical schema (rules L006-L009)

The `notebook/` directory is **not** part of the standard lesson. If a lesson
benefits from a Jupyter notebook, add one in `code/` beside the implementation
files, or reference an external Colab link in the "Further Reading" section.

## Documentation Format (docs/en.md)

```markdown
Expand Down Expand Up @@ -93,12 +102,79 @@ Include it here and save it in the outputs/ folder.]
- [Resource 2](url) — [why it's worth reading]
```

## Quiz Format (quiz.json)

Every lesson must have a `quiz.json` with exactly 6 questions following the
canonical schema:

```json
{
"questions": [
{
"stage": "pre",
"question": "...",
"options": ["A", "B", "C", "D"],
"correct": 0,
"explanation": "..."
},
{
"stage": "check",
"question": "...",
"options": ["A", "B", "C", "D"],
"correct": 0,
"explanation": "..."
},
{
"stage": "check",
"question": "...",
"options": ["A", "B", "C", "D"],
"correct": 0,
"explanation": "..."
},
{
"stage": "check",
"question": "...",
"options": ["A", "B", "C", "D"],
"correct": 0,
"explanation": "..."
},
{
"stage": "post",
"question": "...",
"options": ["A", "B", "C", "D"],
"correct": 0,
"explanation": "..."
},
{
"stage": "post",
"question": "...",
"options": ["A", "B", "C", "D"],
"correct": 0,
"explanation": "..."
}
]
}
```

Rules (enforced by `scripts/audit_lessons.py`):
- **L006**: `quiz.json` must be valid JSON with a non-empty `questions` array
- **L007**: Legacy keys (`q`, `choices`, `answer`) are rejected — use canonical keys only
- **L008**: Options must be 2–6 items
- **L009**: `correct` must be a valid index within the options array

## Code File Guidelines

- Code must run without errors
- No comments — code should be self-explanatory
- Add a 4–6 line header comment citing the lesson path and any external spec/RFC
referenced by the implementation
- Include 5+ unit tests in `code/tests/test_*.py`, runnable via `python -m pytest`
or `python -m unittest`. Use `scripts/scaffold_tests.py` to generate a skeleton
from your code's function signatures
- Use inline comments sparingly — let the code speak, but don't be dogmatic about
"zero comments." Some algorithms need a one-liner to orient the reader
- Use the language that fits best for the topic
- Include a `requirements.txt` or equivalent if there are dependencies
- Include a `# requires: pkg1, pkg2` comment at the top if your entry file needs
packages outside the Python stdlib (see `scripts/lesson_run.py`)
- Start simple, build up complexity
- Every function and class should have a clear purpose

Expand Down Expand Up @@ -131,3 +207,16 @@ tags: [relevant, tags]

[Skill content]
```

### Agents

```markdown
---
name: agent-name
description: What this agent does
phase: [phase number]
lesson: [lesson number]
---

[Agent definition]
```
Loading