Skip to main content

lessons-learned — Project Constitution

Stable principles that apply to every feature, lesson, and implementation decision in this repository. When something here conflicts with a one-off spec, the constitution wins unless explicitly amended.

Version: 1.0.0 | Ratified: 2026-04-01


Mission

Prevent Claude from repeating the same mistakes across sessions by automatically capturing error→correction sequences and injecting the relevant lesson before the next tool call that would trigger the same mistake.


Core Principle

Lessons are injected, not enforced.

The plugin adds context to Claude's reasoning — it never blocks tool calls except in explicitly designated cases where a command is known to be destructive and the block is the lesson. Claude remains in control; the plugin informs.


Lesson Quality Bar

A lesson earns a place in the store only if:

  1. It is specific. Vague lessons ("be careful with git") have no trigger and inject into every session. A lesson must describe an exact mistake with an exact fix.
  2. It is actionable. The remediation must be something Claude can do differently right now — not an observation, not a principle, not a recommendation to "check the docs."
  3. It has a trigger. Almost every lesson must have a commandPattern, pathPattern, or toolName that scopes when it fires. Session-start injection is reserved for reasoning-level reminders that have no natural trigger.
  4. It is not a duplicate. Jaccard similarity ≥ 0.5 against existing lessons disqualifies a new lesson. Update the existing lesson instead.
  5. It clears intake validation. Minimum field lengths, no unfilled template placeholders, no prose gerunds as triggers.

Injection Philosophy

Inject at the point of relevance, not on every turn.

  • commandPatterns — fires when a Bash command matches, immediately before execution
  • pathPatterns — fires when a file path is read, edited, or written
  • toolNames — fires on any use of a specific tool (broad; use sparingly)
  • type: 'protocol' — fires once per session startup; reserved for reasoning reminders with no valid trigger

Budget discipline: At most 3 lessons per tool call, capped at 4KB total injection by default (both configurable). High-priority lessons displace low-priority ones. Each lesson is injected at most once per session (dedup by slug).

Blocking is exceptional. type: 'guard' on a lesson denies the tool call entirely. This is reserved for commands with known data-loss or irreversible consequences where the lesson is the prevention. Use it rarely and deliberately.


Lesson Scope

Lessons have a scope that determines where they are injected:

  • Global (scope: null): injected in any project. Appropriate for tool-level mistakes that apply universally (git, pytest, TypeScript, etc.).
  • Project-specific (scope: "<project-id>"): injected only when the hook's cwd derives to a matching project ID. Appropriate for mistakes tied to a project's specific conventions, stack, or architecture.

How scope is determined: The scanner tracks which project directory each candidate originated from. A candidate observed in only one project is a project-specific candidate. A candidate observed in two or more projects is a global candidate. This is not a manual classification — it is a direct output of the cross-project scan.


Scanning Tiers

Tier 1 (structured): Claude emits #lesson … #/lesson tags when it recognizes a mistake during a session. These are high-fidelity candidates — authored by Claude in the moment of correction, with structured fields. Tier 1 candidates auto-promote on interactive scan.

Tier 2 (heuristic): Sliding-window pattern detection over session JSONL files, looking for error→correction sequences in tool results and assistant messages. Lower fidelity — requires human review via /lessons:review or promote --ids <id> before entering the store.

Tier 3 (structural) and Tier 4 (deep scan): Lexical pattern detection over semantic windows, and LLM-assisted extraction (requires ANTHROPIC_API_KEY), respectively — see Project Overview for detail.


What Belongs in the Store vs. CLAUDE.md

ContentWhere
Repeatable mistake with a specific triggerLesson store (data/lessons.db)
One-time setup note or project conventionCLAUDE.md
Reasoning reminder with no triggertype: 'protocol' lesson
Architectural decision or constraintdocs/architecture/

Do not put lessons in CLAUDE.md. CLAUDE.md is loaded on every session unconditionally — it has no trigger awareness and cannot be deduped. Lessons in the wrong place become noise.


Non-Goals

  • No LLM in the pipeline. Lesson evaluation uses deterministic rules only (validation, Jaccard similarity, content hash). No API calls to score or rewrite candidates.
  • No per-user or per-team lesson stores. The store is per-installation. Multi-user sharing is not in scope.
  • No telemetry. No usage data leaves the local machine.
  • No lesson authoring UI. The CLI (lessons.mjs) is the interface. A GUI would add surface area without improving quality.
  • No automatic promotion of Tier 2 candidates in background mode. The --auto background scan never auto-promotes — only interactive scans with explicit human review do.