Skip to content

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.minConfidence
  • priority < config.minPriority
  • needsReview: true

Regex serializationcommandRegexSources 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 candidate
  • projectCount === 1 → project-specific lesson candidate

Scan state

{
  "files": {
    "/abs/path/to/session.jsonl": 184320,
  },
  "lastFullScanAt": "2026-04-01T00:00:00Z",
}

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.