Skip to main content
Glama

bernard

Repo-committed, project-scoped AI memory for coding agents β€” zero API keys, zero embeddings, zero external services.

npm version CI node license: MIT

πŸ‡ΉπŸ‡· TΓΌrkΓ§e README

bernard is your codebase's team memory. It stores the decisions you made, the architecture notes, the bugs you fixed, and the preferences you settled on β€” as plain text (JSONL) inside the repo. Then you, or an AI agent, can pull that context back in seconds before starting new work. The knowledge lives where the code lives β€” in git β€” so it's committed, versioned, reviewed, and shared with the whole team.

It connects to AI coding agents (Claude Code, Cursor, and anything that speaks MCP) as an MCP server, so the agent searches past decisions and records new ones on its own.

// .bernard/records.jsonl β€” committed, one JSON object per line
{"id":"ber_a1b2c3","type":"decision","title":"Switched to Postgres","content":"MySQL β†’ Postgres for JSONB and better indexing.","tags":["db"],"created":"2026-06-27T10:00:00Z","invalid":false,"superseded_by":null,"source":"manual"}

Why bernard?

A memory.md file rots: it's unstructured, unsearchable, and nobody trims it. Cloud "agent memory" products want an API key, ship your code context to a third party, and vanish when the subscription lapses.

bernard takes the opposite stance:

  • It lives in your repo. Clone the repo, get the memory. No service to run, nothing to provision.

  • Zero API keys, zero embeddings, zero network calls. Everything is local files + a pure algorithm.

  • It's structured and queryable. Typed records, tags, synonyms, recency weighting, and supersession β€” not a flat dump.

  • It's MCP-native. Your agent uses it automatically; you rarely touch the CLI.

  • It's private by construction. Data never leaves the repo, so it's GDPR/KVKK-friendly by default.

Related MCP server: Claude Habitat

Smart without an API key

How is "no API key" still smart? Intelligence comes from two places, neither of which needs the network:

  1. The agent itself. An agent like Claude Code is already capable. bernard's job isn't to understand β€” it's to put the right past records in front of the agent. bernard retrieves, the agent reasons.

  2. BM25 retrieval. A classic, robust, dependency-free full-text ranking algorithm, plus synonym expansion (e.g. login β†’ auth), recency weighting (newer decisions rank higher), and record invalidation (superseded decisions drop out). All local, all instant.

The bridge between the two is bernard. No keys, no subscription, no privacy leak.

Quick start (with Claude Code)

# In your project, set up the memory store once:
npx -y bernard-mcp init

Add bernard as an MCP server in your project's .mcp.json:

{
  "mcpServers": {
    "bernard": {
      "command": "npx",
      "args": ["-y", "bernard-mcp", "mcp"]
    }
  }
}

That's it. Your agent can now call bernard_search before starting a task and bernard_add to record decisions. Commit .bernard/ and your teammates inherit the same memory.

Installation

Run on demand with npx (no install):

npx -y bernard-mcp <command>

Or install globally to get a bernard binary:

npm i -g bernard-mcp
bernard --help

Or add it as a dev dependency:

npm i -D bernard-mcp

Requires Node.js β‰₯ 20.

CLI usage

Every command has an English name and a Turkish alias (e.g. add / ekle). Output language follows your locale β€” see Internationalization.

Initialize

bernard init

Creates (never overwrites existing files):

  • .bernard/records.jsonl β€” the records (JSONL, committed)

  • .bernard/categories.json β€” types + synonyms (committed)

  • .bernard/config.json β€” project settings (committed)

  • adds .bernard/.cache/ to .gitignore

  • adds .bernard/records.jsonl merge=union to .gitattributes

Add a record

bernard add --type decision --title "Switched to Postgres" \
  --content "MySQL β†’ Postgres for JSONB and better indexing." \
  --tags db,architecture

Run bernard add with no flags in a terminal and it asks interactively.

bernard search authentication flow
bernard search "token refresh" --type bug
bernard search migration --tag db

Results print with score, type, title, date, tags, and a content snippet.

List

bernard list
bernard list --type decision
bernard list --tag auth

Invalidate (supersede)

bernard invalidate ber_abc123
bernard invalidate ber_abc123 --by ber_def456

Marks a record invalid without deleting it (invalid: true). --by links the record that replaces it, preserving the decision history. Search drops invalid records by default.

To record a new decision that directly supersedes an old one:

bernard add --type decision --title "Switched to Param" \
  --content "Better fees than the previous provider." --tags payment \
  --supersedes ber_abc123

Distill

bernard distill
bernard distill --apply

Surfaces possible conflicts (multiple records with the same type + tags), supersession chains, and suggestions. For each conflict it proposes keep the newest, invalidate the rest and prints ready-to-run bernard invalidate … --by … commands. --apply (TTY) applies them interactively. It never deletes anything β€” it only marks and suggests.

Stats

bernard stats

A summary dashboard: total records, distribution by type, top 5 tags, invalid count, oldest/newest dates, and source (manual/agent) breakdown.

Tags (synonym management)

bernard tags
bernard tags --add "auth=login,session"
bernard tags --remove "auth"

Lists/edits the types and synonym groups in categories.json. Synonyms power query expansion at search time, so the team's vocabulary becomes the team's recall.

Suggest (draft from commits)

bernard suggest --n 3
bernard suggest --add

Drafts records from recent commits: type guessed from the title (feat→decision, fix→bug, refactor→architecture, docs/chore→note) and tags from changed directories. This is only a starting point — the real understanding is the agent's. --add (TTY) saves drafts after you confirm.

Install hook (post-commit reminder)

bernard install-hook
bernard install-hook --remove

Installs a marked, idempotent .git/hooks/post-commit reminder to run bernard suggest after each commit. It never overwrites a non-bernard hook (it prints a snippet to add by hand). --remove removes only bernard's hook.

MCP integration (the main path)

bernard's real power is connecting to an AI agent. As an MCP server it exposes six tools that an agent uses automatically β€” searching prior decisions before it starts, recording new ones, and invalidating stale ones.

Add to your project's .mcp.json:

{
  "mcpServers": {
    "bernard": {
      "command": "npx",
      "args": ["-y", "bernard-mcp", "mcp"]
    }
  }
}

If you installed globally, use the binary directly:

{
  "mcpServers": {
    "bernard": {
      "command": "bernard",
      "args": ["mcp"]
    }
  }
}

The server exposes:

Tool

What it does

bernard_search

Search past decisions, architecture notes, fixed bugs, and preferences. The key tool β€” the agent calls it before starting work.

bernard_add

Add a new record (source: "agent"); validates the schema before writing.

bernard_list

List records, optionally filtered by type/tag.

bernard_invalidate

Invalidate a record (id, optional by). Marks, never deletes.

bernard_update

Update fields of an existing record; only provided fields change.

bernard_suggest

Fetch recent commits + diffs so the agent can extract records worth keeping.

Works with any MCP client (Claude Code, Cursor, and others) β€” the config block is the same, just placed wherever that client reads MCP servers.

Data model

Records live in .bernard/records.jsonl, one JSON object per line:

{
  "id": "ber_a1b2c3d4",
  "type": "decision",
  "title": "Switched to Postgres",
  "content": "MySQL β†’ Postgres for JSONB and better indexing.",
  "tags": ["db", "architecture"],
  "created": "2026-06-27T10:00:00.000Z",
  "invalid": false,
  "superseded_by": null,
  "source": "manual"
}

Record types:

type

Meaning

decision

A decision that was made

architecture

An architecture note / structural fact

bug

A fixed bug and its fix

preference

A style / approach preference

note

A free-form note

JSONL is line-oriented: appends are cheap, git diffs stay clean (one record per line), it's machine-friendly, and still human-readable via git log -p.

Invalidation lifecycle

bernard never deletes knowledge; it invalidates stale decisions and preserves history. A new decision is added (optionally with --supersedes) linking the old one; the agent can do the same via bernard_add + bernard_invalidate. Over time, bernard distill surfaces conflicts and gives you ready commands to keep the newest and invalidate the rest. Invalid records stay in the file but drop out of search; the superseded_by chain answers "why did this change?".

Team sharing (merge=union)

init adds a .gitattributes line:

.bernard/records.jsonl merge=union

Because each record is an independent line in an append-mostly file, git unions both sides when two branches add different records β€” so teammates writing to memory at the same time don't hit needless merge conflicts.

Architecture: what's committed, what's local

your-project/
β”œβ”€β”€ .git/
β”œβ”€β”€ .gitignore                 # .bernard/.cache/ is added here
β”œβ”€β”€ .gitattributes             # .bernard/records.jsonl merge=union
β”œβ”€β”€ .mcp.json                  # agent integration (committed)
└── .bernard/
    β”œβ”€β”€ records.jsonl          # COMMITTED β€” shared memory
    β”œβ”€β”€ categories.json        # COMMITTED β€” types + synonyms
    β”œβ”€β”€ config.json            # COMMITTED β€” project settings
    └── .cache/                # LOCAL β€” gitignored
        └── index.json         # BM25 index (derived, auto-rebuilt)

Committed: records.jsonl, categories.json, config.json. Memory is code β€” it should be shared, reviewed, and versioned with git history.

Local: .bernard/.cache/. The BM25 index.json is fully derivable from records.jsonl (rebuilt automatically when the records change, or when the locale changes). Committing derived data would only create meaningless conflicts.

Internationalization

bernard ships bilingual: English (default) and Turkish. All user-facing output is localized; command names stay English with Turkish aliases, and stable identifiers (record types, field names) stay canonical.

Locale precedence (highest first):

  1. --locale <en|tr> flag (e.g. bernard --locale tr stats)

  2. BERNARD_LOCALE environment variable

  3. locale in .bernard/config.json (set at init, committed β†’ team-wide)

  4. en (default)

Retrieval is locale-aware too: Turkish gets a conservative stemmer (so tokenlarΔ± matches token); English uses lowercase tokenization (BM25 + synonyms carry the recall).

Configuration

.bernard/config.json:

{
  "project": "my-app",
  "locale": "en",
  "retrieval": {
    "max_results": 8,
    "recency_weight": 0.3,
    "half_life_days": 30,
    "type_weights": {
      "decision": 1.2,
      "architecture": 1.2,
      "bug": 1.0,
      "preference": 1.0,
      "note": 0.9
    }
  }
}
  • recency_weight β€” how much newer records are boosted.

  • half_life_days β€” how fast the recency boost decays.

  • type_weights β€” per-type ranking multipliers.

Roadmap

  • Optional local embeddings (Ollama) β€” keep BM25 as the keyless default; add a fully local, keyless semantic layer for those who want it.

  • Automatic extraction from commits/diffs β€” propose decision/architecture records straight from git history.

  • Team conflict resolution β€” a shared flow for the conflicts distill finds: flag, discuss, invalidate.

  • distill automation β€” periodic distillation and warnings via pre-commit hook or CI.

Contributing

Contributions are welcome β€” see CONTRIBUTING.md. The project is intentionally dependency-light (only the MCP SDK + zod for the server) and ships a zero-dependency smoke test:

npm install
npm test

Please also read the Code of Conduct and the Security Policy.

License

MIT Β© Yener Yiğit Γ‡elik


Not generated β€” crafted.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

–Maintainers
–Response time
–Release cycle
–Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yeneryigitcelik-debug/bernard'

If you have feedback or need assistance with the MCP directory API, please join our Discord server