Skip to content

Latest commit

 

History

History
141 lines (118 loc) · 5.9 KB

File metadata and controls

141 lines (118 loc) · 5.9 KB

Data model

The source of truth is the commit history itself — the Ai-Codekeep-Ledger trailers (and git-ai/whogitit notes, where present) that travel with every commit. Commits merge, rebase, push, and clone because git already knows how to do that; nothing ai-codekeep stores can get out of sync with them, because everything ai-codekeep stores is derived from them.

Three files live under .ai-codekeep/ at the repo root, split by role:

  • config.json — committed. The team's shared settings (mode, ask-for-message). The only file meant to be pushed.
  • ledger.json and state.json — local caches, never committed. Deterministically rebuilt from commit history: same history in, same ledger out, so two clones can't disagree and there is nothing to merge-conflict. ai-codekeep writes a .ai-codekeep/.gitignore covering both the first time it runs, so this is enforced automatically. If a cache ever seems wrong (heavy rebases, stats accumulated from abandoned branches), ai-codekeep update --rebuild throws it away and re-derives from the current branch's history.

See SECURITY.md if reason text might ever contain sensitive detail — reasons live in commit messages, so they go wherever your commits go.

.ai-codekeep/config.json

{
  "mode": "standard",
  "ask_for_commit_message": false,
  "updated": "2026-07-09T12:00:00.000Z"
}

ask_for_commit_message (default false) controls whether the agent drafts the commit subject/body itself, or asks the user for it first and uses it verbatim before appending the trailer — see SKILL.md.

.ai-codekeep/state.json

{
  "last_processed": "369add62ba9fd676e0d21c8317cfe7ce949e69b6",
  "processed": [
    "1c0ffee4a1b2c3d4e5f60718293a4b5c6d7e8f90",
    "369add62ba9fd676e0d21c8317cfe7ce949e69b6"
  ]
}

processed is the full set of commits ever ingested, and it's what makes update idempotent: re-running it on a commit that's already in the ledger is a no-op, so overlapping triggers (a post-commit hook and an agent both running update --commit HEAD) can't double-count anything. It grows by one 40-char SHA per commit — negligible for years of typical history.

last_processed is only an optimization: it marks how far a bare update has caught up, so the next one can scan last_processed..HEAD instead of the whole log. Targeted --commit/--range runs never advance it — they only looked at the commits they were given. If the commit it points at is rewritten away (rebase, squash, amend), update warns and rescans the full history; the processed set keeps that rescan from double-counting the commits that survived.

.ai-codekeep/ledger.json

One entry per file path, keyed by path relative to repo root. last_ai_commit_at is a Unix timestamp (seconds) taken from the commit's author date (git show --format=%at); ai-codekeep report derives days_since_ai_touch from it at read time rather than storing a constantly-stale precomputed value:

{
  "src/auth/session.ts": {
    "ai_share": 0.71,
    "last_touched_by": "human",
    "last_ai_commit_at": 1751500000,
    "confidence": "self-attested",
    "total_lines_seen": 184,
    "fix_count": 3,
    "revert_count": 0,
    "incident_count": 1,
    "risk_score": 0.64,
    "history": [
      { "commit": "a1b2c3d", "reason": "initial implementation", "class": "feature", "author": "ai", "incident": false },
      { "commit": "d4e5f6a", "reason": "fix race condition (issue #88)", "class": "bugfix-of-ai-code", "author": "human", "incident": true }
    ]
  }
}

Commit trailer: Ai-Codekeep-Ledger

This is the only place ai-codekeep writes into a commit message, and it's the durable record: it merges, pushes, and clones with the commit it belongs to, which is exactly why the derived caches above can be local and disposable. Query the ledger (that's what it's for — nobody should be grepping commit messages for stats), but understand that the trailer is what actually persists and travels; the ledger can always be rebuilt from it. And because the trailer lives in commit messages, it stays deliberately tiny: a growing block of provenance trailer lines is exactly the kind of commit-message noise that disrupts teams with clean-history or squash-merge conventions, so it's capped at two lines, regardless of mode.

Two trailer lines, at most:

Ai-Codekeep-Ledger: ai=82;class=bugfix-of-ai-code;incident=false
Change-Reason: fix null-deref reported in issue #412

Ai-Codekeep-Ledger is a single ;-separated key=value list — never multiple separate trailer lines, so escalating detail across modes (see MODES.md) only ever adds fields to this one value, it doesn't add new lines:

Field Meaning Present from
ai AI-authored percentage for this commit (0-100) minimal
class feature/refactor/bugfix/bugfix-of-ai-code/revert standard
incident true only if genuinely tied to a production incident, otherwise omitted/false standard
model model identifier, e.g. claude-sonnet-5 audit
session opaque session/prompt reference for full traceability audit

Change-Reason is plain free text, not part of the compact trailer — a reason is meant to be read as prose, not parsed, so it stays as ordinary human-readable content rather than being packed into key=value form.

Interop with git-ai / whogitit

bin/ledger.mjs checks refs/notes/ai and refs/notes/whogitit for each commit before falling back to trailers. Neither tool's schema is assumed fixed — notesFileShare() in bin/ledger.mjs looks for a handful of plausible field names (files, attribution, lines, each with an aiShare/ai_share field) and falls back to the trailer-based estimate if none match. If you're on a fork of either tool with a different notes schema, that's the one function to adjust.