
backfill
Reconstruct an OKF bundle by event-sourcing a repository's history (git log and Claude session transcripts). Use when creating an .okf/ bundle for an existing repository that predates this skill, or when resuming an interrupted backfill session. Triggers on: "reconstruct the OKF bundle", "backfill the knowledge bundle", "event-source the history".
- Installs
- 342
- Last 24h
- +11
- Trending rank
- #8226
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/scaccogatto/okf-skills --skill backfill- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads backfill automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Reconstruct an OKF bundle from history
This skill replays a repository's decision-making history (git commits and Claude session transcripts) to rebuild its OKF knowledge bundle as if the okf skill's Stop hook had been active from the start.
The extraction layer is deterministic (same repo → byte-identical events); the replay layer is an LLM loop, replayable and auditable but not byte-identical (timestamps, summaries change per run). See the event schema (§1) and skip rules (§3).
1. Event schema
Git commits and session turns become events (JSONL, one per line, sorted by timestamp then source):
{"id":"git:<sha>","source":"git","ts":"<ISO8601 Z>","sha":"...","author":"...","subject":"...","body":"...","files":[{"path":"...","add":N,"del":N}]}
{"id":"session:<file>:<lineno>","source":"session","ts":"<ISO8601 Z>","user":"...","outcome":"...","title":"...","branch":"...","skip":"..."}
- Git events come from
git log --first-parent --numstat. - Session events pair each user message with the last assistant text
block of that turn (the wrap-up), extracted from
~/.claude/projects/<repo-slug>/*.jsonland worktree subdirs. - Timestamps are normalized to UTC ISO 8601 strings ending in
Z. - Skip field (optional, added by extraction): marks low-signal events — see §3, never overridden by replay.
2. Protocol: Preflight → Extract → Bootstrap → Replay (Map+Reduce) → Finalize
Preflight: check if bundle can be rebuilt
if [ -d <repo>/.okf ] && ! [ -f <repo>/.okf/.backfill-state.json ]; then
echo "ERROR: .okf/ exists but is incomplete. Delete it to rebuild from scratch, or pass --resume to continue from the last checkpoint."
exit 1
fi
If .backfill-state.json exists, resume from the cursor; otherwise, fresh bootstrap.
Extract: generate event stream
uv run "${CLAUDE_SKILL_DIR}/scripts/okf_backfill_events.py" <repo-dir> \
--out events.jsonl \
[--no-sessions] [--sessions-dir ~/.claude/projects] \
[--max-text 2000] [--skip-globs "vendor/**"]
Write events.jsonl to the scratchpad (never committed). Report:
- Total events extracted, per-source counts, and per-rule skip counts.
Bootstrap: initialize bundle (fresh only)
Create .okf/ with:
mkdir -p <repo>/.okf
echo 'okf_version: "0.2"' > <repo>/.okf/index.md
echo '# Update Log' > <repo>/.okf/log.md
Cursor (.okf/.backfill-state.json):
{"last_id": null, "done": 0}
Replay: two-phase map/reduce protocol
The replay is now structured in two phases to enforce semantic depth and anti-degeneration rules:
Phase 1: Map (parallel analysis, per-event)
Launch okf:event-analyzer agents for the live events (those without skip field), in waves of
4 to 10 (see the cost note in §5), via Claude Code's Workflow agentType parameter. Each analyzer
receives its event ids, the events.jsonl path, the repo path, the output directory and the
emitter path, and:
- Fetches its own event JSON with
jq(the orchestrator dispatches ids, never content) - Reads git evidence only through the capped diff emitter (§6); session events need no other call
- Writes
analyses/<event-id>.md(event id with:sanitized to-) to scratchpad, with atruncatedflag copied from the emitter's last line - Replies with one line of counts per event; the analysis never travels back in the reply
- Never touches
.okf/ - Is resumable: skip events already in
analyses/
Dispatch rule (deterministic, from events.jsonl, no content read): a live event is
small when it is a session turn or a commit whose numstat totals at most 60 changed lines.
Small events are grouped chronologically, eight per analyzer call; large commits go one per
call. Measured on this repository (benchmark/map-tier/RESULTS.md): 57 calls instead of 147,
a third off the map phase at the same tier, no loss on any per-event metric and no
cross-event contamination.
jq -c 'select(.skip==null) | {id, small: (.source=="session" or (([.files[]?|.add+.del]|add)//0) <= 60)}' events.jsonl
Host-agnostic fallback (if no Workflow support): spawn generic subagents with the same
system prompt as agents/event-analyzer.md, one per event, collecting analyses to scratchpad.
Phase 2: Reduce (sequential folding, one agent)
Run a single okf:bundle-weaver agent that:
- Reads all analyses in chronological order
- Folds them into
.okf/, updating or creating concepts - Enforces anti-degeneration rules (§4)
- Adds a dated
log.mdbullet for each lifecycle event only (a concept created, deprecated or superseded); the "why" of a routine update goes into the concept body - Manages cursor (
.okf/.backfill-state.json) for resume capability - Is the only actor that writes
.okf/ - Replies with one line of counts (folded, created, updated, bullets, conflicts, superseded, truncated inputs); the bundle never travels back in the reply
Resume behavior: both phases support resumption. Phase 1 skips already-analyzed event ids;
Phase 2 restarts from last_id in the cursor.
Domain priming (advisory)
Before starting the map phase, read the repository's README and directory structure to sketch a candidate taxonomy of concepts (skills, integrations, decisions, etc.). This priming is advisory only — it helps the analyzer emit better candidate names. The guarantee that no events are lost comes from the deterministic coverage check in Finalize (§5), not from priming; analyses without candidate names still flow to the weaver.
Context hygiene (orchestrator)
The orchestrator handles ids and counts, never content. Events, analyses and diffs are read by the agents; the orchestrator reads:
wc -l events.jsonl # total events
jq -r 'select(.skip==null) | .id' events.jsonl # live ids to dispatch
ls analyses | wc -l # analyses written
Never cat events.jsonl or open an analysis from the orchestrator: whatever enters its context
is billed at the frontier rate on every following turn, and the routing exists to prevent that.
Finalize: validate and clean up
-
Write
index.mdper directory by hand (do NOT runokf_init.py— it would scaffold placeholder files): one# Sectionper directory, one bullet per concept,* [Title](file.md) - descriptiontaken from each concept's frontmatter (SPEC §8). The rootindex.mdkeeps itsokf_version: "0.2"frontmatter and links every subdirectory. -
Add final log entry (today's date, appended to existing dated section if present):
## 2026-09-01 - **Backfill**: reconstructed from N events by okf-backfill/0.9.5 -
Delete cursor:
rm -f .okf/.backfill-state.json -
Run self-checks for anti-degeneration rules:
# No concept filenames derived from change type ! find .okf -name "*.md" | grep -E "merge-pull-request|(^|/)(feat|fix|chore|docs)[:-]" # No non-kebab-case characters in filenames ! find .okf -name "*.md" | grep -E "[^a-z0-9/.-]" # No identical consecutive log bullets awk 'p==$0 && /^- / {exit 1} {p=$0}' .okf/log.md # Every concept declares its lifecycle (absent status reads as `stable`, SPEC §5.4) [ -z "$(grep -rL '^status:' --include='*.md' .okf | grep -v '/index.md$\|/log.md$')" ]These are lexical guards; they do not catch a bundle that asserts a superseded state. That check is semantic and belongs to the weaver, at fold time, when it has both the analysis and the earlier concepts on disk (§4 rule 5).
-
Run coverage check to guarantee every event is mapped:
uv run "${CLAUDE_SKILL_DIR}/scripts/okf_backfill_events.py" \ --check-coverage events.jsonl .okfExit code 0 means all live events (non-skipped) appear in the bundle's
sourcesor log. Exit code 1 with a list of unmapped ids means the weaver skipped some events; fix and re-run. -
Validate bundle schema:
uv run "${CLAUDE_SKILL_DIR}/../validate/scripts/okf_validate.py" .okf --strictFix every error before finishing.
-
Report the resulting bundle:
- Concept count per directory
- Log entry samples (first and last)
- Coverage check result (all events mapped)
- Validation result (pass/fail, warnings)
- Superseded resolutions (the weaver's
supersededcount) and deprecated concepts:grep -rl '^status: deprecated' --include='*.md' .okf | wc -l - Agents spawned per phase (analyzers, weaver invocations) and, when the host reports it, tokens per phase
- Truncated analyses:
grep -l '^truncated: true' analyses/*.md | wc -l, next to the deterministic estimate of git events whose full diff exceeds the default cap:jq -r 'select(.skip==null and .source=="git") | [.files[]|.add+.del] | add' events.jsonl | awk '$1>300' | wc -l
3. Skip rules
Events matching a rule below are marked skip: <rule-id> and skipped during
replay (cursor advances, no log entry):
| Rule | Condition | Rationale |
|---|---|---|
paths-only-generated | Git commit touches ONLY lockfiles or generated code (vendor/, node_modules/, dist/, .min., *.lock, etc.) | Noise: build byproducts, no knowledge content |
merge-no-files | Git merge commit with no file changes (first-parent only) | Housekeeping |
session-command-noise | Session user message is a slash command or <20 chars and has no useful outcome | Ephemeral UI/chatter |
Extend the first rule with --skip-globs "extra/**" for repo-specific patterns.
4. Anti-degeneration rules
The analyzer and weaver enforce these rules to prevent the bundle from devolving into a mechanical listing of commits or a taxonomy-by-accident:
-
Concept names describe domain entities, not changes. Forbidden:
- Filenames derived from git subject:
merge-pull-request-#2-....md,feat:-add-docs-skill.md,fix:-handle-edge-case.md - Bare action words:
update.md,fix.md,add-feature.md - Non-kebab-case: spaces, underscores, capitals, special characters (allowed:
[a-z0-9-]only)
Example: a commit with subject "feat: add presales pipeline" touches a domain concept. The concept is named
presales-pipeline.md(the entity), notfeat:-add-presales-pipeline.md(the change). The change history lives in frontmattersourcesand the body's## History;log.mdholds only lifecycle events. - Filenames derived from git subject:
-
Prefer update over create. Every event is analyzed for which concepts it touches; if a concept already exists and the analysis fits, update its
sourcesand body. Only create a new concept if the analysis reveals a distinct domain entity not yet captured. -
Log bullets are lifecycle events, and explain intent. Only a concept created, deprecated or superseded gets a bullet; a routine update does not. A bullet answers "why?" from the commit body or session outcome, never just re-reads the subject:
- Bad:
- Added presales-pipeline.md feature - Good:
- **Creation**: [Presales pipeline](...), formalized to clarify handoff points
- Bad:
-
No identical consecutive bullets. A run of same-concept bullets means updates leaked into the log:
- Feature X update - Feature X update - Feature X updateInstead: no bullet at all; the updates live in the concept's body and
sources. -
A reversal is not growth. A replay walks the history forward, so a later event routinely overturns an earlier one (a phase dropped, a limit changed, a target downgraded). "Prefer update over create" is a merge rule and says nothing about this: applied alone it leaves the bundle asserting both states in the present tense, in one concept or in two. The weaver greps the whole bundle before writing and, on contradiction, replaces rather than appends: the body states what holds today, the previous position drops to a dated line under
## History, and an outdated concept that a newer one supersedes getsstatus: deprecatedplus a link to its successor. A deprecated concept keeps itssources, so coverage stays green. None of the guards in Finalize can catch this — they are lexical, and a superseded claim is well-formed. -
Reconstructed concepts are
draft, neverstable. SPEC §5.4 reads an absentstatusasstable("ready for consumption"); a bundle nobody has reread isdraft("not yet reviewed; possibly incomplete"). The weaver writesstatus:on every concept.
5. Implementation notes
- Determinism: extraction is byte-identical; replay is not (time, LLM variance). Both are auditable: events.jsonl is deterministic, map analyses are stored and resumable, reduce loop is human-readable and cursor-backed.
- Privacy: events.jsonl goes to scratchpad, never committed. Session turn text is truncated (head+tail) before extraction.
- Cost note: deep replay reads one capped diff per git commit (§6) to extract
rationale. For histories >500 events, consider splitting into sub-ranges and
replaying sequentially, or use
--skip-globsto exclude low-signal paths (e.g., vendored dependencies, generated code). Map phase is parallel in waves of 4 to 10: the gate benchmark lost 260 of 299 runs to a high-concurrency mass failure and finished at concurrency 4 (benchmark/gate/RESULTS.md). Reduce is sequential but much cheaper (concepts already analyzed). - Future sources (not implemented): GitHub PR/issue text, release notes, CI/deploy logs
— all optional post-MVP. Lore protocol (arXiv 2603.15566): on repos that adopt git
trailers (structured decision metadata), the extracted event's
bodyalready contains trailers in a machine-readable format. The analyzer and weaver should use trailers as the primary source of "why" (overriding generic inference from diff content). - Trust metadata:
generated.byis the backfill agent (okf-backfill/0.9.5), not claimed as human-reviewed (human:...); concepts are correctlyunverified(SPEC §5.3) andstatus: draft(§5.4) — the two axes are independent: trust is who says it, lifecycle is whether it has been reread. Writingstatusis not optional here: absent, §5.4 reads it asstable, so a bundle nobody has reread would declare itself ready for consumption. Map-phase analyses are working artifacts (stored for auditability during reduce); the weaver's output is the canonical bundle.
6. Capped diff emitter
Analyzers never read a raw git show: the harness cuts long tool output blindly (mid-hunk,
character-based, host-dependent) and a cheap worker may not notice the cut. The extractor reads
the whole diff instead and emits a deterministic sample:
uv run "${CLAUDE_SKILL_DIR}/scripts/okf_backfill_events.py" <repo-dir> --show <sha> \
[--only <path>] [--max-diff-lines 300] [--per-file-lines 120] [--max-line-chars 400] \
[--skip-globs "vendor/**"]
- Diff against the first parent, so merge commits agree with the extracted numstat.
- The stat is always complete: entities survive any cap.
- Patches are capped per file (breadth over depth) and cut at hunk or file boundaries, with a
bracketed marker at every cut; lines longer than
--max-line-charsare shortened with a marker (a generated one-line file is "1 line" but can be hundreds of KB). - Patches of generated files (lockfiles,
vendor/**,--skip-globs) are omitted even inside mixed commits; their stat line stays. - The last line is fixed-form:
[diff: shown=X total=Y files_shown=A files_total=B truncated=true|false]. The analyzer copiestruncatedinto its frontmatter; finalize reports the count. --only <path>is the one permitted follow-up when a truncated diff hides the rationale: same cap logic, one file.
Defaults keep one call under the harness limits with margin; tune per repo with the flags.
The analyzer's tier is configuration, not infrastructure: agents/event-analyzer.md carries
model and effort in its frontmatter. Fork the file to retier the map phase; the skill
resolves the agent by name and nothing else changes. The default is haiku since the
map-tier benchmark (benchmark/map-tier/RESULTS.md): parity with sonnet on commits at
under half the map cost, on the condition that the two explicit instructions in the agent
file stay (which summary line the flag copies; claims report intent as intent).
FAQ
What does the backfill skill do?
Reconstruct an OKF bundle by event-sourcing a repository's history (git log and Claude session transcripts). Use when creating an .okf/ bundle for an existing repository that predates this skill, or when resuming an interrupted backfill session. Triggers on: "reconstruct the OKF bundle", "backfill the knowledge bundle", "event-source the history".
How do I install backfill?
Run npx skills add https://github.com/scaccogatto/okf-skills --skill backfill in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/backfill/ (personal) or .claude/skills/backfill/ (project).
Which AI agents can use backfill?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is backfill safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains backfill?
It is published by scaccogatto in scaccogatto/okf-skills. It has 342 recorded installs.
More from scaccogatto/okf-skills
okf
scaccogatto/okf-skills
Author, maintain, and consume Open Knowledge Format (OKF) knowledge bundles — portable markdown + YAML frontmatter that both humans and agents read. Use when capturing project knowledge (services, APIs, schemas, metrics, runbooks, decisions) into an OKF bundle, when updating one after code or docs change, or when a repository contains an .okf/ (or other OKF) bundle that should inform the task. Triggers on: "document this in OKF", "update the knowledge bundle", "capture this as a concept", or any work in a repo that has an OKF bundle.
validate
scaccogatto/okf-skills
Check that an Open Knowledge Format (OKF) bundle is conformant with the v0.2 spec (§11). Use when asked to validate, lint, or check an OKF bundle, or before committing changes to one. Runs a deterministic Python checker — not an eyeball pass. Also migrates a v0.1 bundle to v0.2 in place with --migrate.
visualize
scaccogatto/okf-skills
Render an Open Knowledge Format (OKF) bundle as a single self-contained, interactive HTML graph (viz.html) — concepts as nodes coloured by type and sized by body length, markdown links and bundle-internal sources as edges, a wiki-style detail panel with rendered markdown, v0.2 trust/lifecycle/provenance metadata, and "Links to" / "Cited by" backlinks, layout switching, per-type filter and search. Use when asked to visualize, graph, preview, or explore an OKF bundle.
Popular code quality & workflow skills
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
improve-codebase-architecture
mattpocock/skills
Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
handoff
mattpocock/skills
Compact the current conversation into a handoff document for another agent to pick up.
domain-modeling
mattpocock/skills
Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.
codebase-design
mattpocock/skills
Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
diagnosing-bugs
mattpocock/skills
Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.