Skip to content

NuggetMD: Format Specification v0.3

Part of the LearnSpec suite. Draft.


Core Principle

NuggetMD is the micro-learning format of the LearnSpec suite. A .nugget.md file hosts a collection of nuggets, short, self-contained concepts designed to be read in under 3 minutes each and reviewed over time via spaced repetition.

The author decides how many nuggets to put in a file and when to split into multiple files. A file may contain a single nugget or dozens. Structure follows the topic, not an arbitrary file-per-concept rule.

NuggetMD occupies a distinct position between FlashMD and LearnMD:

FlashMDNuggetMDLearnMD
UnitAtomic factApplicable conceptFull lesson
Review time5–30 sec1–3 min10–60 min
Memory typeDeclarative, knowing thatProcedural, knowing how / whenStructured instruction
File structureMany cards per fileMany nuggets per fileOne document per file

NuggetMD inherits its frontmatter and validation rules from the shared LearnSpec Architecture Charter.

PrincipleDescription
Markdown-firstA .nugget.md file is valid Markdown readable in any editor
File-nativeAll content lives in files, no database required
Graceful degradationRenders as a readable multi-section article in any standard reader
Author-controlled granularityThe author decides how many nuggets per file and when to split
FSRS-readyEach nugget can independently enter a spaced repetition queue
AI-nativeGeneratable and consumable by an LLM without specific tooling

Format Levels

LevelMechanismPurpose
0## headings + ### sub-sectionsNugget collection, readable everywhere
1YAML frontmatter + per-nugget nugget blockMetadata, FSRS, per-nugget attributes
2Recall question in the third ### section (QuizMD Level 0 syntax)FSRS review mechanism

Level 0: Heading-Based Structure

Each nugget is a ## heading. Its sub-sections are ### headings, and their order carries their role: the labels themselves are free text, in any language. The file reads as a natural Markdown document, no tooling required.

markdown
# Python Best Practices

## Prefer enumerate() over range(len())

### Concept

When iterating over a list and needing both the index and the value,
`enumerate()` is the idiomatic Python choice. It returns an iterator of
`(index, value)` pairs, making your code more readable and slightly faster.

### Why it matters

Next time you write `for i in range(len(...))`, stop and ask: do I need
both the index and the value? If yes, switch to `enumerate()`.

---

## Use zip() to iterate over multiple lists simultaneously

### Concept

`zip()` takes two or more iterables and returns an iterator of tuples,
pairing elements at the same index across all iterables.

### Why it matters

Instead of managing indices manually to combine two lists, `zip()` makes
the intent immediately clear and eliminates off-by-one errors.

Structure rules

  • The # H1 is the file title, optional, inferred from frontmatter title if absent.
  • Each ## H2 heading opens one nugget. The heading text is the nugget title.
  • Inside each nugget, up to three ### H3 sub-sections. Position determines the role; the heading text is free:
PositionRoleRequiredPurpose
1st ###ConceptYesThe concept, explained in 2–5 sentences
2nd ###Why it mattersYesOne concrete example or actionable takeaway
3rd ###CheckNo, strongly recommended when FSRS is activeOne recall question
  • Labels are not parsed. ### Concept, ### Le concept and ### Was ist das? are all the first section. A parser takes the first three ### headings of a nugget in document order and assigns the roles above; it never matches on the heading text. Concept / Why it matters / Check remain the recommended English labels for interchange, and files written against v0.2 parse identically under this rule.
  • A fourth or further ### inside a nugget carries no role: it is a warning, and players render its content as part of the nugget after the third section.

The same nugget, authored in French, no marker changes, and a parser needs no knowledge of French:

markdown
## Préférer enumerate() à range(len())

### Le concept

Quand on parcourt une liste en ayant besoin de l'indice *et* de la valeur,
`enumerate()` est la façon idiomatique de le faire en Python.

### Pourquoi c'est important

La prochaine fois que vous écrivez `for i in range(len(...))`, demandez-vous
si vous avez besoin des deux. Si oui, passez à `enumerate()`.

### Question de rappel

? Quelle fonction native donne à la fois l'indice et la valeur ?

- [x] enumerate()
- [ ] range(len())
  • A --- horizontal rule between nuggets is optional but strongly recommended for visual clarity.
  • No #### or deeper headings inside a nugget, if more structure is needed, the content belongs in LearnMD.

Level 1: Frontmatter and Per-Nugget Metadata

File-level frontmatter

yaml
---
title: "Python Best Practices"          # optional: inferred from # H1
lang: en                                 # REQUIRED: BCP-47 code
tags: [python, best-practices]           # optional: file-level tags
spaced_repetition: fsrs                  # optional: fsrs | sm2 | false (default: false)
lesson: ./03-iteration.learn.md          # optional: default source lesson (v0.2)
author: Jane Smith                       # optional
created: 2026-05-10                      # optional: ISO 8601
updated: 2026-05-10                      # optional: ISO 8601
license: CC-BY-4.0                       # optional: SPDX identifier or custom
spec_version: "0.1"                      # optional
---

spaced_repetition at file level sets the default for all nuggets in the file. It can be overridden per nugget.

Per-nugget metadata block

An optional empty fenced nugget block placed immediately after the ## heading carries per-nugget attributes. It contains no body, attributes only.

markdown
## Prefer enumerate() over range(len())

```nugget id:enumerate tags:[iteration] level:beginner related:[zip-function,unpacking]
```

### Concept
...

In a standard Markdown reader, the empty fenced block renders as a small empty code block, readable and unobtrusive. A LearnSpec parser extracts the attributes; the player may render them as a discrete metadata badge.

Per-nugget attribute reference

AttributeStatusTypeDescription
idRecommendedstringStable unique slug within the file. Auto-generated from the heading slug if absent. Enables precise FSRS tracking.
tagsOptionalstring[]Per-nugget tags. Added to file-level tags.
levelOptionalenumbeginner, intermediate, advanced. Overrides file-level default.
spaced_repetitionOptionalenumPer-nugget override: fsrs, sm2, or false. Overrides file-level setting.
relatedOptionalstring[]Slugs of related nuggets within the same file or other .nugget.md files. Informational only, no import dependency. The player may surface these as "next nugget" suggestions.
lessonOptionalstringReference to the lesson that explains this nugget (v0.2). See Lesson References.

Lesson References (Level 1)

A nugget may point back to the lesson that develops it, so a learner can jump from a micro-concept to the full course. Unlike related (nugget→nugget, informational), lesson is a navigation reference to a .learn.md section. It uses hyperlink-style resolution: the same mental model as an HTML/Markdown link, combining a file-level default with per-nugget overrides:

lesson valueResolves toLike an HTML link…
(absent)The file-level default lesson, at its topimplicit link
#section-anchorThe file default lesson, at that section<a href="#frag">
./other.learn.mdAnother lesson, at its top<a href="page.html">
./other.learn.md#anchorAnother lesson, at that section<a href="page.html#frag">

The value is a relative path to a .learn.md file, optionally suffixed with #<anchor>. The anchor resolves against a LearnMD heading slug or an explicit !checkpoint id. The value carries its own path, so no !ref declaration is required.

markdown
---
title: "Iteration in Python"
lang: en
lesson: ./03-iteration.learn.md   # default for every nugget in the file
---

# Iteration in Python

## Prefer enumerate() over range(len())

```nugget id:enumerate tags:[iteration] lesson:#the-enumerate-builtin
```

### Concept
...

The nugget above links to the the-enumerate-builtin section of the default lesson; a nugget with no lesson links to the lesson's top, and lesson:./other.learn.md#x overrides the target entirely.

Player behaviour. The player SHOULD surface the reference as a "back to the lesson" link (typically after the third section), open the target within its track context when one exists (falling back to the standalone lesson view), and, if it navigates in place, make returning to the review session obvious. The #anchor SHOULD resolve to a stable identifier (!checkpoint id or explicit heading anchor) before falling back to the heading slug.


Level 2: Recall Question

The third ### section, conventionally labelled ### Check, contains a single recall question in QuizMD Level 0 syntax: no fenced block wrapper, no scoring configuration.

markdown
### Check

? Which built-in function gives you both the index and the value
when iterating over a list?

- [x] enumerate()
- [ ] range(len())
- [ ] zip()
- [ ] iter()

This is the FSRS recall mechanism. After reading the nugget, the learner answers, then rates their confidence. No scoring, no feedback_mode, recall rating only.


Complete Example

markdown
---
title: "Python Best Practices"
lang: en
tags: [python, best-practices]
spaced_repetition: fsrs
author: Jane Smith
created: 2026-05-10
license: CC-BY-4.0
spec_version: "0.1"
---

# Python Best Practices

## Prefer enumerate() over range(len())

```nugget id:enumerate tags:[iteration] level:beginner related:[zip-function]
```

### Concept

When iterating over a list and needing both the index and the value,
`enumerate()` is the idiomatic Python choice. It returns an iterator of
`(index, value)` pairs, making your code shorter and more readable.

```python
# ❌ Avoid
for i in range(len(fruits)):
    print(i, fruits[i])

# ✅ Prefer
for i, fruit in enumerate(fruits):
    print(i, fruit)
```

You can also set a custom start index: `enumerate(fruits, start=1)`.

### Why it matters

Next time you write `for i in range(len(...))`, stop and ask: do I need
both the index and the value? If yes, switch to `enumerate()`. Your code
reviewer will thank you.

### Check

? Which function gives you both the index and the value when iterating?

- [x] enumerate()
- [ ] range(len())
- [ ] zip()
- [ ] index()

---

## Use zip() to iterate over multiple lists simultaneously

```nugget id:zip-function tags:[iteration] level:beginner related:[enumerate,unpacking]
```

### Concept

`zip()` takes two or more iterables and returns an iterator of tuples,
pairing elements at the same position. It stops at the shortest iterable.

```python
names = ["Alice", "Bob", "Carol"]
scores = [88, 92, 79]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
```

### Why it matters

Instead of managing a shared index across two lists, `zip()` makes the
pairing explicit and eliminates off-by-one errors entirely.

### Check

? What does zip() do when the two iterables have different lengths?

- [x] It stops at the shortest one
- [ ] It raises a ValueError
- [ ] It pads the shorter one with None
- [ ] It stops at the longest one

---

## Unpack tuples directly in loops

```nugget id:unpacking tags:[iteration,tuples] level:intermediate
```

### Concept

When iterating over a list of tuples, Python lets you unpack each tuple
directly in the `for` statement, eliminating indexed access and making
variable names self-documenting.

```python
# ❌ Less clear
for item in coordinates:
    print(item[0], item[1])

# ✅ Clearer
for x, y in coordinates:
    print(x, y)
```

### Why it matters

Tuple unpacking pairs naturally with `enumerate()` and `zip()`, both
produce tuples. Combining them gives you expressive, readable iteration
with zero boilerplate.

### Check

? Which syntax correctly unpacks a tuple in a for loop?

- [x] for x, y in pairs:
- [ ] for (x, y) = pairs:
- [ ] for pair.unpack() in pairs:
- [ ] for x and y in pairs:

Reading-Time Constraint

The 3-minute limit applies per nugget, not per file. A file with 20 nuggets is valid as long as each individual nugget reads in under 3 minutes.

ConditionLevel
Estimated reading time of a single nugget > 3 minError
Estimated reading time of a single nugget > 2.5 minWarning

Estimation: word_count / 200 wpm, rounded up to the nearest 30 seconds. If a nugget exceeds the limit, the content belongs in LearnMD.


FSRS Integration

When spaced_repetition: fsrs is active (file-level or per-nugget), each nugget enters its own independent FSRS slot in the player's review queue. Nuggets within the same file are tracked individually.

1. Learner reads the nugget (Concept + Why it matters)
2. Learner answers the ### Check question (if present)
3. Learner rates recall confidence:
      Again (1), didn't remember
      Hard  (2), remembered with effort
      Good  (3), remembered correctly
      Easy  (4), instantly recalled
4. FSRS calculates the next review interval for this specific nugget
5. Nugget is scheduled, other nuggets in the same file are unaffected

FlashMD and NuggetMD feed separate FSRS queues: never mixed:

FlashMDNuggetMD
Review duration~10 sec~2 min
Typical "Easy" interval4–7 days14–30 days
Player queue labelCardsConcepts

Interoperability

MechanismSupport
Imported by TrackMD via !import
Imported by LearnMD via !import
!ref ./curriculum.curriculum.md✅, declares curriculum alignment
!ref ./glossary.glossary.md✅, enables term highlighting
!ref ./media.media.md✅, enables media:slug resolution
lesson: reference to a .learn.md section✅, navigation only; resolves a path, declares no dependency (v0.2)
!import of other formats❌, leaf format
FSRS (player-managed, per nugget)✅ when spaced_repetition is set

In a TrackMD

markdown
## Section 3: Iteration in Python

!import ./03-iteration.learn.md
!import ./quiz-iteration.quiz.md
!import ./flashcards-iteration.flash.md
!import ./nuggets-iteration.nugget.md optional:true

Validation

Lenient Mode (default)

ConditionLevel
lang absent from frontmatterWarning
Fewer than two ### sections in a nuggetError
More than one recall question in the third ### sectionError
Duplicate id within the fileError
No third ### section when spaced_repetition is active on that nuggetWarning
Estimated reading time of a nugget > 2.5 minWarning
Estimated reading time of a nugget > 3 minError
#### or deeper heading inside a nuggetWarning
More than three ### sections in a nuggetWarning
lesson value whose path does not end in .learn.mdError
Nugget lesson:#anchor (anchor-only) with no file-level lesson default to resolve itError
lesson anchor not found in the target lessonWarning, checked by the collection-level validator (cross-file)

Strict Mode (--strict)

All warnings are promoted to errors.


Deferred Decisions

FeatureReason for deferral
Contextual triggers (event: before_meeting)Player implementation complexity
Audio / video nuggetsBinary content outside Markdown scope
Cross-file related resolutionRequires corpus-level indexing, player concern

v0.3 made the ### sub-sections positional: their role comes from their order, not from fixed English labels, so a nugget can be authored in any language. v0.2 added the lesson reference (links a nugget back to its source lesson section).


Released under the MIT License. Copyright © 2024-present LearnSpec Contributors

Released under the MIT License.