cd /news/developer-tools/show-hn-commitlore-git-memory-for-de… · home topics developer-tools article
[ARTICLE · art-102442] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Show HN: CommitLore – Git memory for decisions coding agents keep forgetting

CommitLore, a new open-source tool from developer MongLong0214, stores team decisions in Git and feeds them to coding agents before they edit files, preventing agents from re-proposing rejected changes. The tool, available at version 1.2.0, integrates with Claude Code, Codex, Gemini CLI, and Hermes, and requires Node.js 22.23.2+ and Git. It automatically delivers applicable decisions to agents, while capturing new decisions requires agent action, and it does not control data flow once context is passed to the host.

read22 min views1 publishedAug 19, 2026
Show HN: CommitLore – Git memory for decisions coding agents keep forgetting
Image: Michielbdejong (auto-discovered)

Your coding agent keeps re-proposing things your team already rejected. CommitLore keeps those decisions in Git and hands the agent the ones still in force, before it edits the file.

CommitLore has no hosted service; it keeps its records in Git. Once its MCP server or hook returns context, the host handles that context under its own policy; CommitLore does not control that data flow.

Two halves, and only one of them is automatic. Delivery — handing the agent the decisions that still apply, before it edits a path — happens on its own once installed. Capture — writing a new decision down — is something the agent does when a change carries a reason the diff cannot show; an ordinary git commit

cannot start it, because a hook has the diff and a capture needs the session. What happens automatically says exactly which hosts do which.

Contents

InstallWhat the agent receivesWhat happens automaticallyWhen this will not help youThe problem, in one exampleWhat it is, in fullA path queryThis repository as its own demoPath scope vs. retrievalHow it worksA field report from another repositoryWhat makes it differentWhere it pays offHow records get createdA complete recordWhat the repository provesEvidenceUninstall·Documentation·Contributing

Install once. Install the host integration and initialise the repository where you want it to work.

Claude Code — one plugin registers the MCP server, the pre-edit context hook and the skills:

/plugin marketplace add MongLong0214/commitlore
/plugin install commitlore@commitlore

That is the whole plugin: the MCP server, the pre-edit hook and the skills. It puts no commitlore

on PATH

, so the commitlore …

commands below come from install.sh

/ install.ps1

and need that install as well.

Codex — install the native plugin with one command:

commitlore plugin install-codex

It registers the marketplace and plugin through Codex's own CLI, never by editing its configuration or cache, and the install script below runs the same command when it finds Codex. Start a new Codex session afterwards: the plugin's skill and MCP server are loaded at session start, not on install. The CLI below provides the repository commands.

Prerequisites for either path: Node.js 22.23.2+ and Git. The script checks both before it writes anything.

Any other coding agent — install the CLI:

curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.0/install.sh | sh -s v1.2.0

Windows — the same install, in PowerShell:

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.0/install.ps1))) v1.2.0

Host wiring on Windows requires v1.1.1 or later. Before it, detection could not see a .cmd

shim and the installer could not run one, so a Windows install placed the CLI and wired nothing — reported as ok: false

, never as success. Verified on a real machine at 1.1.1 for Codex, Gemini CLI and Hermes.

Hermes — after installing the CLI, configure its host integration:

commitlore hermes install

Which hosts are supported, and what each install path requires: docs/COMPATIBILITY.md.

Give your next agent the judgment your last one earned.

Then run commitlore init

in each repository where you want validation hooks, a local index, and the repository-owned agent procedure. The installer detects supported coding agents and registers the local MCP server where it can do so safely.

cd your-repository
commitlore init
commitlore context .

Re-run the install command. It updates the CLI and the agent registrations, and it cannot reach two things:

Hooks already installed in a repository. One installed before v1.0.2 records the release it came from, so it keeps validating commits with that build. The installer has no way to know which repositories have hooks.Sessions already running. A host loads its runtime once and keeps it.

So after upgrading:

commitlore doctor          # names any hook still pinned, and any stale session
commitlore hooks install   # in each repository doctor names

Neither is a defect in the release; both are state a release cannot reach.

A hook is a file written at install time, so there are three generations. Installed before v1.0.2, it names one release directly. Installed v1.0.2 through v1.1.2, it follows current

but its older containment stub does not recognise an ordinary upgrade — under the PATH

git gives a hook it refuses the commit. Installed v1.1.3 or later, ordinary upgrades are followed automatically. The first two need commitlore hooks install

once, in each repository, and commitlore doctor

names which.

After that:

  • Commit normally. Most commits carry no record.
  • If a record is present, the commit-msg hook validates it; it never creates one.
  • Delivery and capture are different layers; the next section says exactly which hosts have each one.

Keep working through your coding agent. When a change contains decision context the diff cannot preserve, ask the agent to include a CommitLore record in the commit.

Prefer to inspect or pin the installation? #

The one-liner is for convenience. For a reviewed or pinned install, download and inspect install.sh

first, or clone the repository. The script installs a pinned source checkout and a thin wrapper that runs node <checkout>/dist/commitlore.mjs

— it downloads no compiled artifact and runs no build step, so what it puts on your machine is the source you can read.

curl -fsSLO https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.0/install.sh
sh install.sh v1.2.0

git clone --depth 1 --branch v1.2.0 https://github.com/MongLong0214/commitlore
node commitlore/dist/commitlore.mjs --version

Before editing src/pricing.ts

, the agent receives this payload — the record, not a description of one:

commitlore: active records for src/pricing.ts

Limit
  [claim]      r-price01  87e36511  calculatePrice owns final checkout pricing only

Ruled-out
  [claim]      r-price01  87e36511  Reuse checkout pricing for admin quotes | eligibility
                                    and rounding semantics differ between the two flows

[claim]

matters: its author string did not match one this repository configured for directives, so the agent is told to weigh it as information, not obey it as an order. In the default author-string mode, [directive]

means the commit's author header matched a string this repository configured — not that a trusted author recorded it, and not that the identity is proven. Anyone who can write a commit can set that header. A repository can opt into Git's authenticated boundary with git config --local commitlore.requireSignedDirective true

. In that mode, [directive]

requires Git to verify the signature against this verifier's trust store and the exact signing-key fingerprint Git reports (%GF

) must be listed in the repository-local allowlist:

git config --local --add commitlore.trustedSigner <Git-%GF-fingerprint>

An absent, empty, or unreadable signer allowlist authorizes nobody, so every record remains [claim]

; it never means every valid signer is authorized. Signature mode remains opt-in, so existing author-string policy is unchanged until a repository enables it. A signature proves that a key accepted by this verifier signed the commit. It does not prove that the key has authority for this repository, which is what the local allowlist supplies, nor does it prove the record's truth. Delivery gives the agent context; it does not block the edit.

Delivery means the record reaches the agent before it edits a path. Capture means a decision can enter the verified commit-time flow. They are separate layers:

Host Delivery Capture
Claude Code Yes — automatic through the plugin.
Yes — through the plugin.
Codex Yes — automatic through the plugin.
Yes — through the plugin.
Hermes Yes — commitlore hermes install .
Yes — commitlore hermes install .
Gemini CLI, Cursor, Windsurf, opencode Yes — both installers wire the MCP server, through one shared step rather than each on its own.
Procedure, not automatic. The server states the prepare → verify → stage procedure in its instructions on every connection. The host may or may not act on it.
Any other AGENTS.md -convention host
Procedure, not automatic. commitlore init --agents-md writes it into the repository.
Procedure, not automatic. Same file, same caveat.

“Yes” in the Capture column means the workflow is installed and available — the prepare → verify → stage path exists for that host. It does not mean every eligible commit reaches a terminal assessment on its own. That stronger property, deterministic autocapture, is not certified on any host yet, so treat capture as available rather than guaranteed. Most commits should carry no record in any case.

The first three rows install a skill that drives capture; the fourth receives the same procedure over MCP, which is what a host that loads no skills has to work from — verified with the plugin disabled, and it captured. Whether a given host surfaces those instructions to its model is the host's choice, and nothing here detects it. A host still has to start capture, and the candidate must pass verification before the commit hook attaches it. The commit-msg hook validates a record when present; it never invents one.

Read this before installing, not after.

The measurement is of the weaker tier. Every record in the 1,160-run study rendered[claim]

, which tells the agent to weigh the record rather than obey it. The[directive]

tier became reachable only afterwards, and nothing here measured it — the study's own verdict says this number "does not transfer to the stronger one". Whether a directive does better, worse, or the same is unmeasured in both directions.One model, one harness, ten constructed fixtures. The oracle reads the final implementation state, so it shows that agents which received records re-proposed less often. It does not show that any of them read anything.Guard is an experimental advisory, not a safety net: precision 44.8% (95% Wilson CI 32.7%–57.5%), recall 22.0% on the 417-decision corpus (ADR-0020). An empty guard result does not mean a proposal avoids every ruled-out alternative — at 22% recall, a miss is the common case.Signature verification is opt-in, not key distribution. Default mode matches a forgeable author string. Settingcommitlore.requireSignedDirective=true

also requires Git's verified signature status from this verifier's trust store and an exact%GF

fingerprint in repository-localcommitlore.trustedSigner

; a missing, empty, or unreadable allowlist authorizes nobody. That verifies neither a person's authority nor the record's truth. Repository-wide coverage, symbol anchors, and an interactive record builder remain open:#32,#33,#34.M4 did not test a guard effect: its rows carry noguard_exposure

, so treatment exposure there is unverifiable (#122).

The full method, the exclusions and the per-arm truncation split are in bench/VERDICT-M5.md and what it does not show. Delivery methodology and the retrieval evidence are in bench/DECISION-DELIVERY.md.

Stop re-reviewing the same bad idea.

Without CommitLore. A new session sees two functions with similar inputs and reuses one.

calculatePrice(input, { isAdminPreview: true, skipCoupon: true });

The team now has another flag, another wrapper, and another compatibility branch protecting a use case the function was never meant to own. The reviewer writes "we already rejected this" for the second time.

With CommitLore. Before editing, the agent receives the active record shown above, rather than an instruction reconstructed from a review comment.

The module boundary is in front of the agent before it proposes the change, rather than in a review comment after.

Whether it acts on that is now measured. Across 1,160 registered runs, an agent handed the repository's active records re-proposed a ruled-out approach in 2.8% of them (16/580). Without them: 18.8% (109/579).

arm re-proposed a ruled-out approach
the agent alone 18.8% (109/579)
with CommitLore
2.8% (16/580)

The threshold was registered before the run, and the preregistration predicted a smaller effect than it got — that prediction, with its stated probabilities, is in bench/PREREGISTRATION-M5.md §A.2, and it was wrong. The significance test, the interval and the registered threshold are in bench/VERDICT-M5.md rather than here: a statistic retyped into prose drifts from the log that produced it, and this repository gates against exactly that (scripts/check-readme-numbers.mjs

).

The Git-native decision layer for coding agents.

Every fresh agent inherits the implementation. None of them inherit the constraints, the alternatives your team rejected, the warnings, or the verification gaps — those do not travel with the code unless something carries them.

CommitLore preserves that engineering judgment in Git, and surfaces only the decisions still in force before the next edit. A decision that was later superseded or expired does not reach the agent as if it still stood.

Repository-owned · Lifecycle-aware · Quote-checked · Agent-independent

Claude Code · Codex · Cursor · Gemini CLI · OpenCode · Windsurf

No hosted memory service. No vendor-specific chat history. Just reviewable decision context, owned by the repository. Commit trailers travel with their commits; notes-backed records need the notes fetch configured after a clone.

A fresh agent. Zero chat history. It is still handed why the obvious fix was rejected. Query a path before changing it:

commitlore context install.sh

The output includes the active record that ruled out publishing a -musl

target as the fix for the installer defect, including its reason. The hook returns context; it does not claim to block the edit.

context for install.sh as of <timestamp> — 0 limits, 1 ruled-out, 1 warnings, 2 other in 1 record (no index, 1 commit record(s) scanned)

ruled-out
  r-instci99a  <commit>  [claim]  Publish a -musl release target | a release.yml/build-matrix change, not an install.sh or CI-verification fix

warnings
  r-instci99a  <commit>  [claim]  Revisit this wording if a musl target ships

Reproducing that exact PreToolUse

hook path, and every other command: docs/cli.md.

A tool that claims to stop agents re-deciding settled questions should be able to show what it caught in itself. This one keeps that list in public, including the entries where the thing that turned out to be false was something this project had already published:

No install could produce the trust tier the README's claims rested on. Records reach an agent gradeddirective

orclaim

. It turned out no installed surface configured a directive author string, so grading failed closed toclaim

for everyone — while the injected legend advertised the tier nobody could reach. Both prior benchmarks had measuredclaim

-graded delivery (#415).The registered benchmark analysis would have read four different experiments at once— and because its stopping rule was a row count, the contamination would have made the studypassits own completeness gate (#441).The result-schema gate was not run by anything, so the schema drifted five fields behind the runner and nobody noticed for two days (#392).A shipped pre-push hook hung every— 1,240 hook invocations in 40 seconds — because the function had been tested eleven times and the hook path zero times (git push

#422).

Every one of those is a Ruled-out:

, Warn:

or Limit:

line in a commit trailer, validated by the hook this project asks you to install, and readable with the same commitlore context

you would run anywhere else.

The full list, with what each one cost: docs/SELF-AUDIT.md.

Before an agent's first edit, how much of a repository's still-active decision set actually reaches it? On this repository, at the 800-token budget the hook ships with:

route budget active decisions delivered reversed ones delivered tokens
the code alone 0.0% 0 0
git log for the path
800 42.0% 7 673,134
CommitLore path scope
800
81.7%
0
511,412
CommitLore, cap removed none 92.3% 0 741,429

With the cap removed, path scope recovers exactly what a whole-repository dump recovers — 2,047 of 2,217 — for a fraction of its 92,175,612 tokens and none of its 7,322 reversed records. The scope costs nothing. The cap costs 10.6 points. The remaining 170 are records the trust grader withholds.

This measures delivery, not effect. No agent ran, so it bounds what one could recover, not what one does — and a retrieval number can climb while the outcome it is meant to predict falls. SWE-bench measured BM25 recall rising from 29.58 to 51.06 across its context budgets and reported that "even when increasing the maximum context size for BM25 would increase recall with respect to the oracle files, performance drops … as models are simply ineffective at localizing problematic code" (arXiv:2310.06770). One corpus, one repository. Seven superseded records and no expired ones, so zero-reversed-delivered says nothing yet about expiry. Method and full tables: bench/DECISION-DELIVERY.md.

The git log baseline is not an artifact of measuring ourselves. The same measurement on four repositories this project did not write — Django, SymPy, scikit-learn and Requests, at pinned commits — puts the share of a path's history that survives an 800-token cut between

37.4% and 55.6%. The 42.0% above sits inside that band. Losing something close to half a file's history to a fixed budget is what

git log

does on large, long-lived repositories generally, not something peculiar to this one. What did nottransfer is the mechanism: our paths carry a median of one commit at 687 tokens where Django's carry eight at 213, so long commit messages make the ordinary-Git baseline worse at a fixed budget — a cost of this project's own practice.

bench/EXTERNAL-CORPUS.mdalso reports a delivery figure on those repositories; read §9.0 and §9.5 first, because the records there were generated from revert commits by a program and the headline number is one the attachment predicate forces rather than a retrieval result.

Missing a record costs the model context. Handing it a decision that was already reversed costs it correctness. In this retrieval measurement, at every size from 0 to 10,000 distractors, BM25, embedding top-k, hybrid RRF, and embedding with a path filter each returned one superseded record. CommitLore path scope with lifecycle returned zero stale records and both current records (2/2).

Recall is the supporting result: retrieval finds broadly the same records either way, but only one route knows which are still current. The advantage appears when decisions have been reversed—the case this product exists for.

The separate #167 exposure run still matters: only 2 of 10,002 records reached the model.

route model-visible records relevant records model-visible tokens
inject everything 10,002 2/2 1,004,554
top-k lexical 2 1/2 190
CommitLore path scope 2 2/2 335

This measures exposure and recall at a fixed two-record output budget—not token cost, billed cost, accuracy, or agent behaviour. It is one corpus, one query, and one pinned embedding model. Where recall ties, and what else has and has not been measured: docs/evidence.md.

Capture— the agent drafts only the decision context a diff cannot show.** Verify**— CommitLore checks that draft against the session and the staged diff.** Preserve**— the verified record lives in Git, with identity and a lifecycle.** Deliver**— before editing a path, the next agent receives only the decisions still in force.

From a field report on a ~768-commit Swift MCP server, one day after installing. Naming one file path surfaced a merged pull request the engineer did not know existed, and it changed what the surviving code meant.

I did not know that commit existed.It is a merged PR from two weeks earlier that had already removed eight of these sites and replaced each with an accessibility-native equivalent, every one fail-closed and live-verified.None of this was in any chat history. It was in the repository, and I got it by naming a file path.

The alternative was reading two weeks of merged pull requests to find it. That is not something an agent does spontaneously, and not something a person does before every edit. Adoption cost, from the same report: one command, and 7.4 seconds to index 768 commits. Nothing touched history or the working tree. The console output and the full report are in docs/evidence.md.

That was a 768-commit repository. At 100,000 commits an indexed context query answers in 496 ms at p50, and the hooks behind it cost 185.85 ms p50 for

commit-msg

and 102.40 ms p50 for the injection hook. Those are the numbers that decide whether this stays installed on a large repository, and they are measured rather than asserted. The same run carries the figure that looks bad: without the index, that query at 100,000 commits takes 86,673 ms. The index is not an optimisation on top of a working query — it is what makes the query possible at that size, which is why init

builds one and doctor

checks it.Three properties no hosted chat-history product can offer, and the reason the authority is Git rather than a service:

Reviewable. A decision arrives as a commit trailer in a pull request, where it can be argued with before it becomes authority.Owned by the repository. No account, no vendor, nothing to lose access to.Commit trailers travel with a clone. A record inrefs/notes/commitlore

does not arrive in an ordinary clone: Git does not fetchrefs/notes/*

by default.commitlore init

configures that mirror;the sharing documentationexplains the remaining fetch and push boundary.

Tool What it remembers
CLAUDE.md / AGENTS.md
how the agent should work
ADRs large architecture decisions, as documents
Chat memory / RAG related text from the past

CommitLore****which decisions still apply to this code path Similarity search can find a related decision. CommitLore also knows whether that decision is still active, superseded, or expired — and shows only the first.

On that third row. Lore (March 2026) proposed decision records in native git trailers four months before this repository existed, with a vocabulary that maps almost one-to-one onto this one. The protocol idea is not novel here and saying otherwise would not survive anyone reading the paper. What Lore has no counterpart for is the lifecycle — Supersedes:

and Expires:

, and the filtering that makes the row above true — or the trust grading; and it states that it "outlines an empirical validation path" rather than running one. That validation, including the parts that failed, is what this project has that the paper does not (ADR-0029).

The authority is ordinary commit trailers and refs/notes/commitlore

. Indexes and reports are derived and rebuildable from those Git records.

Protect a module boundary. " calculatePrice owns final checkout pricing only. Do not reuse it for admin previews."

Preserve a rejected workaround. "Raising the timeout hides the connection leak. Fix the cleanup path instead."

Mark temporary compatibility code. "This caller is temporary and is not part of the supported contract."

Carry a verification gap. "Single-user behaviour was tested. Concurrent refresh remains unverified."

Each is a sentence a diff cannot carry and a reviewer would otherwise have to say twice.

You do not hand-write a trailer for every commit. Most commits should carry no record at all. Add one only for a decision the diff cannot recover: an external constraint, a rejected alternative, a warning, or a verification gap.

Ask the agent to commit normally and preserve only the decision context the diff cannot explain:

Commit this change. Add a CommitLore record only if the diff cannot recover an important constraint, rejected alternative, warning, or verification gap.

The agent instructions live in skills/commitlore-commits/

, and the commit-msg hook validates any record the agent adds — it never invents or silently adds one. The harvest

route, the capture

transaction, and the escape hatch of writing trailers by hand are all in docs/capture.md.

A record is an ordinary set of Git commit trailers, and a small one is usually enough:

Fix expired-token refresh

Ruled-out: Extend token TTL to 24h | security policy violation
Warn: Do not narrow the 4xx handler without verifying upstream behavior

The worked example that uses the whole vocabulary, the table of every trailer key, and how to read records with plain Git are in docs/protocol.md. The normative definitions are in SPEC §3.

  • Decision history survives rebase, remote transfer, and path renames in the tested Git workflows. Squash-merge discards the trailer block, as any ordinary trailer would be: commitlore squash-preserve

or its GitHub Action carries the records across, and the tested workflows cover that route. - Routes share one grading core, so untrusted text is information rather than an instruction — but the index caches a signature status, and a signature status is not a property of the repository. The same record can therefore grade directive

on one route andclaim

on another from a single installation, if the index was built where the signing keys were not reachable. Comparing a route against--no-index

tells you whether you are reading a stale verdict (#653,#631,#635). - Injection-like text in free-form trailers is withheld from model-readable routes.

  • A readable repository with no records is distinct from incomplete history or an unfetched notes mirror.

These are product claims about Git-bound, human-verifiable decision history. They do not depend on a claim that CommitLore improves agent performance.

112 experiments were recorded, but M4 recorded no per-run guard exposure. Whether the treatment was present is unverifiable, so it does not test, support, or refute the agent-behavior claim. The narrower product claim above rests on independently testable behavior; read the M4 verdict for the clean dataset and withdrawal.

What is measured — retrieval, exposure, latency and scaling, hook overhead — and what is not — break-even, and any effect on agent behaviour — is set out in docs/evidence.md.

commitlore uninstall

Removes what install.sh

or install.ps1

wrote — the wrapper, the pinned checkout, and the MCP entry it added to each agent config. It removes nothing it did not write, and names what it leaves: the per-repository hooks, the agent hook, and the Claude Code plugin. --dry-run

reports without changing anything. What removes each of those, and how to run from a source checkout instead: docs/install.md.

docs/install.md— the install paths, what each one writes, and how to undo itdocs/cli.md— every command, with its flagsdocs/capture.md— how a record gets writtendocs/protocol.md— the record format, and reading it with plain Gitdocs/evidence.md— what is measured, and what is notspec/SPEC.md— the normative protocol

Read the spec, the ADRs, and CONTRIBUTING.md. CommitLore is free forever and open source under the

MIT License.

── more in #developer-tools 4 stories · sorted by recency
── more on @commitlore 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-commitlore-g…] indexed:0 read:22min 2026-08-19 ·