Configuration

Every setting Learner reads, where to put it, and how to make it stop.

Two layers, read in this order:

  1. $CLAUDE_CONFIG_DIR/learner.json (default ~/.claude/learner.json) — your defaults for every repository. Written by the installer, edited by learner config ….
  2. <repo>/.claude/learner.local.json — optional, gitignored, a partial override for one repository. Written by learner off, learner on and learner config project ….

The project layer wins key by key, and arrays are replaced wholesale rather than merged: a project untrackGlobs is the whole list for that repo, not an addition to the global one. A key you did not set keeps its global value, and "enabled": false survives the merge — it is a deliberate override, not an absent key.

KeyValuesDefaultEffect
levelD/J/C/S/E— requiredHow hard a question is. No valid level means no questions
enabledbooltrueMaster switch for the automatic quiz
questionStyles"auto", or a subset of code/architecture/fill"auto"Which formats a question may take
synthesisFrequencyoff/rare/normal/oftennormalA synthesis question every 0 / 8 / 4 / 2 questions
blanksPerExerciseint ≥ 12// LEARNER-TODO holes cut in a fill exercise
untrackGlobsarray of globs[]Extra paths excluded from quiz material
disabledPathsarray of path prefixes[]Repositories where Learner stays silent

level is the only one of the seven with no default. The other six have theirs in LEARNER_DEFAULTS at the top of hooks/learner-config.sh; level is left out on purpose, so a half-finished install asks nothing rather than guessing how much you know. SessionStart mentions it once when that happens. A full config looks like this:

{
  "level": "C",
  "enabled": true,
  "questionStyles": "auto",
  "synthesisFrequency": "normal",
  "blanksPerExercise": 2,
  "untrackGlobs": ["*.md", "*.json"],
  "disabledPaths": []
}

You do not have to edit it by hand: learner config level=S validates and writes for you, and learner config project synthesisFrequency=often does the same for the current repository only. The same shape ships in the repository as learner.json.example, for reference or copy-paste.

Levels

The canonical value is the letter. The full word is accepted too, in any case, so senior, Senior and S are the same setting.

LetterNameWhat a question targets
DDiscoveringsyntax, what a block is for, basic vocabulary
JJuniorwhat the function does, where the code lives
CCompetentwhy this split, edge cases, error handling
SSeniortrade-offs, rejected alternatives, impact on performance and coupling
EExpertinvariants, failure modes, what breaks at scale

The level is a claim about you on this code, not a job title, and it is one word to change. Set it high and the easy questions stop; set it low for a stack you have never touched. Anything that is not one of the five letters or names is not a level at all: the hook treats it as unset and asks nothing.

Turning it off

Three ways, by scope. The first two leave the guardrail in place; the third silences that too, which is rather the point of it.

  1. Everywhere. Set "enabled": false in the global config, or say learner config enabled=false. An explicit learner quiz still runs when you ask for one — you asked.
  2. One repository you own. Say learner off in it. That writes <repo>/.claude/learner.local.json and adds the file to the repo's .gitignore if it is not already there. learner on reverses it. That file is the only thing Learner ever adds to a repository, and only on request — a fill exercise (see above) is a different kind of change: it temporarily edits one of your own source files rather than adding a new one.
  3. One repository you do not own, where nothing of yours should be committed. Add its path to disabledPaths in the global config — learner config disabledPaths='["/path/to/repo"]'. Nothing is written into that repository at all. Matching is a path-prefix check on whole components, so disabling /a/b silences /a/b/c but not a sibling /a/bee; a leading ~/ is expanded, and an entry that exists on disk is resolved to its physical path first, so an entry that reaches the repo through a symlink still matches.

Next: Safety — what a fill exercise does to your files.