Data Model¶
All data lives in data/ as JSON files. There is no database.
| File | Edit directly? | Purpose |
|---|---|---|
lessons.json |
Yes | Source of truth — all lesson records |
lesson-manifest.json |
No | Pre-compiled runtime manifest (run lessons build) |
config.json |
Yes | Injection and scanning configuration |
cross-project-candidates.json |
No | T2 scan candidates awaiting review |
scan-state.json |
No | Per-file byte offsets for incremental scanning |
Lesson record¶
{
"lessons": [
{
// ULID, generated on add
"id": "01JQSEED00000000000000001",
// kebab-case summary + 4-char random suffix
"slug": "pytest-tty-hanging-k9m2",
// One-line description, max 120 chars
"summary": "pytest hangs in non-interactive envs due to TTY detection",
// Root cause (required, min 20 chars)
"problem": "Running bare `pytest` in Claude Code causes the process to hang...",
// Concrete fix (required, min 20 chars)
"solution": "Use `python -m pytest --no-header -p no:faulthandler`",
// Optional — overrides default injection text
// Default: "## Lesson: {summary}\n{problem}\n**Fix**: {solution}"
"injection": "## REQUIRED: pytest flags for Claude Code\n...",
// Set true to deny the tool call instead of injecting context
"block": true,
// {command} substituted with actual command at block time (max 120 chars)
"blockReason": "pytest command is missing required flags. Rerun as: {command} --no-header -p no:faulthandler",
"triggers": {
// Exact tool name match — fires on any use of these tools
"toolNames": ["Bash"],
// Regex patterns matched against the Bash command string
// Invalid regex is rejected at add time
"commandPatterns": ["\\bpytest\\b(?!.*(--no-header|-p no:faulthandler))"],
// Glob patterns matched against Read/Edit/Write file paths
"pathPatterns": [],
// Reserved for future use
"contentPatterns": [],
// true = inject at session start instead of PreToolUse
// Use for reasoning reminders with no valid command/path trigger
"sessionStart": false,
},
// Lesson scope — determines which projects receive this lesson
"scope": { "type": "global" },
// OR:
// "scope": { "type": "project", "path": "/abs/path/to/project" }
// 1–10; higher priority wins budget conflicts
"priority": 8,
// 0.0–1.0; lessons below config.minConfidence excluded from manifest
"confidence": 0.95,
// true = excluded from manifest; set when confidence < 0.7 at add time
"needsReview": false,
// category:value taxonomy
"tags": ["lang:python", "tool:pytest", "severity:hang"],
// Session IDs where this mistake was observed (up to 5)
"sourceSessionIds": [],
// How many times this pattern was seen across all scans
"occurrenceCount": 0,
"createdAt": "2026-03-29T00:00:00Z",
"updatedAt": "2026-03-29T00:00:00Z",
// sha256 of "mistake|remediation|JSON.stringify(triggers)"
// Used for exact duplicate detection at add time
"contentHash": "sha256:...",
},
],
}
Field constraints¶
| Field | Constraint |
|---|---|
summary |
≥ 20 chars, no ... suffix, no template placeholders |
problem |
≥ 20 chars, no template placeholders |
solution |
≥ 20 chars, no template placeholders |
triggers.commandPatterns |
Valid regex — invalid patterns rejected at add time, silently dropped at build time |
triggers.sessionStart |
Use sparingly — fires on every session startup with no per-call dedup |
confidence |
< 0.7 → needsReview: true, excluded from manifest |
block + blockReason |
Must be used together; blockReason supports {command} substitution |
Lesson manifest¶
Generated by lessons build. This is what the injection hook reads at runtime.
{
"type": "lessons-learned-manifest",
"version": 1,
"generatedAt": "2026-04-01T00:00:00Z",
"config": {
"injectionBudgetBytes": 4096,
"maxLessonsPerInjection": 3,
"minConfidence": 0.5,
"minPriority": 1,
"compactionReinjectionThreshold": 7,
},
"lessons": {
"<ulid>": {
"slug": "pytest-tty-hanging-k9m2",
"priority": 8,
"toolNames": ["Bash"],
// Regex stored as {source, flags} for JSON-safe serialization
// Reconstructed with new RegExp(source, flags) at match time
"commandRegexSources": [{ "source": "\\bpytest\\b(?!...)", "flags": "" }],
"pathRegexSources": [],
"tags": ["lang:python", "tool:pytest", "severity:hang"],
"injection": "## REQUIRED: pytest flags...",
"summary": "pytest hangs in non-interactive envs due to TTY detection",
"block": true,
"blockReason": "...",
"sessionStart": false,
// Only present when scope.type === 'project'
"projectPath": null,
},
},
}
Exclusion criteria — lessons are excluded from the manifest if any of:
confidence < config.minConfidencepriority < config.minPriorityneedsReview: true
Regex serialization — commandRegexSources stores { source, flags } objects rather than regex strings. RegExp is not JSON-serializable. At match time, the hook reconstructs regex objects with new RegExp(source, flags).
Config¶
{
// Max bytes of additionalContext per tool call
"injectionBudgetBytes": 4096,
// Max lessons injected per tool call
"maxLessonsPerInjection": 3,
// Exclude lessons below this confidence from manifest
"minConfidence": 0.5,
// Exclude lessons below this priority from manifest
"minPriority": 1,
// Lessons above this priority re-inject after context compaction
"compactionReinjectionThreshold": 7,
"scanPaths": ["~/.claude/projects/"],
"autoScanIntervalHours": 24,
"maxCandidatesPerScan": 50,
"scoring": {
"multiSessionBonus": 2,
"multiProjectBonus": 1,
"hangTimeoutBonus": 1,
"userCorrectionBonus": 1,
"singleOccurrencePenalty": -1,
},
}
Candidate record¶
Written by lessons scan. Read by lessons scan promote and lessons review.
{
"generatedAt": "2026-04-01T00:00:00Z",
"candidates": [
{
// 1-based, used by `lessons scan promote <index>`
"index": 1,
"tool": "Bash",
"confidence": 0.75,
"priority": 6,
"occurrenceCount": 3,
// Distinct sessions where this pattern was observed
"sessionCount": 2,
// Distinct project directories
"projectCount": 1,
"projects": ["jobsearch-tracker"],
"problem": "...",
"solution": "...",
"tags": [],
// Raw trigger text from the session
"trigger": "git stash",
"sourceSessionIds": ["..."],
"signals": {
// Whether a user correction message was detected near the error
"userCorrection": true,
},
},
],
}
Scope inference:
projectCount >= 2→ global lesson candidateprojectCount === 1→ project-specific lesson candidate
Scan state¶
Each file maps to the last byte offset read. The scanner resumes from this point on the next incremental scan. A --full flag resets all offsets to 0 for a complete re-scan.
Tag taxonomy¶
Tags follow category:value format:
| Category | Examples |
|---|---|
lang |
python, typescript, javascript, go |
tool |
pytest, git, npm, docker |
severity |
hang, data-loss, silent-failure, error |
topic |
testing, auth, networking, types, agents |
candidate |
node-gotchas-skill — flagged for future skill aggregation |
See the full tag reference.