From b8798db398eaa136b263b749158088f5272e669b Mon Sep 17 00:00:00 2001
From: m3tam3re
Date: Fri, 2 Oct 2026 09:37:50 +0200
Subject: [PATCH] fix agent definitions
---
agent-lib.lock.json | 51 -
agents/chiron-forge.md | 70 +-
agents/chiron.md | 109 +-
external/okf/agents/bundle-weaver.md | 187 ---
external/okf/agents/event-analyzer.md | 144 ---
external/okf/skills/backfill/SKILL.md | 338 ------
.../backfill/scripts/okf_backfill_events.py | 1007 ----------------
external/okf/skills/okf/SKILL.md | 159 ---
.../okf/skills/okf/reference/APACHE-2.0.txt | 202 ----
external/okf/skills/okf/reference/SPEC.md | 1016 -----------------
external/okf/skills/okf/scripts/okf_init.py | 112 --
external/okf/skills/okf/templates/concept.md | 38 -
external/okf/skills/okf/templates/index.md | 8 -
external/okf/skills/okf/templates/log.md | 8 -
external/okf/skills/validate/SKILL.md | 85 --
.../skills/validate/scripts/okf_validate.py | 573 ----------
external/okf/skills/visualize/SKILL.md | 64 --
.../skills/visualize/scripts/okf_visualize.py | 432 -------
18 files changed, 128 insertions(+), 4475 deletions(-)
delete mode 100644 external/okf/agents/bundle-weaver.md
delete mode 100644 external/okf/agents/event-analyzer.md
delete mode 100644 external/okf/skills/backfill/SKILL.md
delete mode 100644 external/okf/skills/backfill/scripts/okf_backfill_events.py
delete mode 100644 external/okf/skills/okf/SKILL.md
delete mode 100644 external/okf/skills/okf/reference/APACHE-2.0.txt
delete mode 100644 external/okf/skills/okf/reference/SPEC.md
delete mode 100644 external/okf/skills/okf/scripts/okf_init.py
delete mode 100644 external/okf/skills/okf/templates/concept.md
delete mode 100644 external/okf/skills/okf/templates/index.md
delete mode 100644 external/okf/skills/okf/templates/log.md
delete mode 100644 external/okf/skills/validate/SKILL.md
delete mode 100644 external/okf/skills/validate/scripts/okf_validate.py
delete mode 100644 external/okf/skills/visualize/SKILL.md
delete mode 100644 external/okf/skills/visualize/scripts/okf_visualize.py
diff --git a/agent-lib.lock.json b/agent-lib.lock.json
index e293250..32be6c2 100644
--- a/agent-lib.lock.json
+++ b/agent-lib.lock.json
@@ -52,57 +52,6 @@
"writing-shape"
]
}
- },
- "okf": {
- "url": "https://github.com/scaccogatto/okf-skills.git",
- "ref": "main",
- "rev": "8e3187875e66051bb52f91a5ed27342e2c3208da",
- "selection": {
- "mode": "all"
- },
- "inventory": {
- "agents": [
- "bundle-weaver",
- "event-analyzer"
- ],
- "skills": [
- "backfill",
- "okf",
- "validate",
- "visualize"
- ]
- },
- "warnings": [
- "agents/bundle-weaver: malformed frontmatter line (missing colon): Fold event analyses into an OKF knowledge bundle in deterministic sequence. This is the",
- "agents/bundle-weaver: malformed frontmatter line (missing colon): concepts over time, enforce anti-degeneration rules, and guarantee no events are lost.",
- "agents/bundle-weaver: malformed frontmatter line (missing colon): only agent that writes `.okf/`, executing the reduce phase of backfill replay to evolve",
- "agents/event-analyzer: malformed frontmatter line (missing colon): # Retier by forking this file; the backfill skill resolves the agent by name.",
- "agents/event-analyzer: malformed frontmatter line (missing colon): # on the condition that the two explicit instructions below (flag source, evidence-only claims) stay.",
- "agents/event-analyzer: malformed frontmatter line (missing colon): Analyze a single repository history event (git commit or session turn) to extract domain",
- "agents/event-analyzer: malformed frontmatter line (missing colon): concepts and semantic content. Use in parallel during the map phase of OKF backfill replay",
- "agents/event-analyzer: malformed frontmatter line (missing colon): to materialize decision rationale from raw commit diffs and session outcomes.",
- "skills/backfill: malformed frontmatter line (missing colon): Reconstruct an OKF bundle by event-sourcing a repository's history (git log",
- "skills/backfill: malformed frontmatter line (missing colon): and Claude session transcripts). Use when creating an `.okf/` bundle for an",
- "skills/backfill: malformed frontmatter line (missing colon): existing repository that predates this skill, or when resuming an interrupted",
- "skills/backfill: malformed frontmatter line (missing colon): knowledge bundle\", \"event-source the history\".",
- "skills/okf: malformed frontmatter line (missing colon): \"capture this as a concept\", or any work in a repo that has an OKF bundle.",
- "skills/okf: malformed frontmatter line (missing colon): Author, maintain, and consume Open Knowledge Format (OKF) knowledge bundles —",
- "skills/okf: malformed frontmatter line (missing colon): capturing project knowledge (services, APIs, schemas, metrics, runbooks,",
- "skills/okf: malformed frontmatter line (missing colon): decisions) into an OKF bundle, when updating one after code or docs change, or",
- "skills/okf: malformed frontmatter line (missing colon): portable markdown + YAML frontmatter that both humans and agents read. Use when",
- "skills/okf: malformed frontmatter line (missing colon): when a repository contains an `.okf/` (or other OKF) bundle that should inform",
- "skills/validate: malformed frontmatter line (missing colon): Check that an Open Knowledge Format (OKF) bundle is conformant with the v0.2",
- "skills/validate: malformed frontmatter line (missing colon): committing changes to one. Runs a deterministic Python checker — not an",
- "skills/validate: malformed frontmatter line (missing colon): eyeball pass. Also migrates a v0.1 bundle to v0.2 in place with `--migrate`.",
- "skills/validate: malformed frontmatter line (missing colon): spec (§11). Use when asked to validate, lint, or check an OKF bundle, or before",
- "skills/visualize: malformed frontmatter line (missing colon): Render an Open Knowledge Format (OKF) bundle as a single self-contained,",
- "skills/visualize: malformed frontmatter line (missing colon): Use when asked to visualize, graph, preview, or explore an OKF bundle.",
- "skills/visualize: malformed frontmatter line (missing colon): a wiki-style detail panel with rendered markdown, v0.2 trust/lifecycle/provenance",
- "skills/visualize: malformed frontmatter line (missing colon): filter and search.",
- "skills/visualize: malformed frontmatter line (missing colon): interactive HTML graph (viz.html) — concepts as nodes coloured by type and",
- "skills/visualize: malformed frontmatter line (missing colon): metadata, and \"Links to\" / \"Cited by\" backlinks, layout switching, per-type",
- "skills/visualize: malformed frontmatter line (missing colon): sized by body length, markdown links and bundle-internal `sources` as edges,"
- ]
}
}
}
diff --git a/agents/chiron-forge.md b/agents/chiron-forge.md
index 982a42c..2ce754c 100644
--- a/agents/chiron-forge.md
+++ b/agents/chiron-forge.md
@@ -2,28 +2,56 @@
name: chiron-forge
description: Personal AI assistant (Build Mode). Full execution and task completion capabilities with safety prompts
mode: primary
-tools: [bash, edit, fetch_content, web_search, read, grep, find, ls]
-model: zai/glm-5.3
+model: zai-coding-plan/glm-5.3
permissions:
- bash:
- "*": allow
- "rm -rf *": ask
- "git reset --hard*": ask
- "git push*": ask
- "git push --force*": deny
- "git push -f *": deny
- edit:
- "*": allow
- "/run/agenix/**": deny
- external_directory:
- "*": ask
- "~/p/**": allow
- "~/.config/opencode/**": allow
- "/tmp/**": allow
- "/run/agenix/**": allow
- question: allow
- webfetch: allow
- websearch: allow
+ - action: shell
+ resource: "*"
+ effect: allow
+ - action: shell
+ resource: rm -rf *
+ effect: ask
+ - action: shell
+ resource: git reset --hard*
+ effect: ask
+ - action: shell
+ resource: git push*
+ effect: ask
+ - action: shell
+ resource: git push --force*
+ effect: deny
+ - action: shell
+ resource: git push -f *
+ effect: deny
+ - action: edit
+ resource: "*"
+ effect: allow
+ - action: edit
+ resource: /run/agenix/**
+ effect: deny
+ - action: external_directory
+ resource: "*"
+ effect: ask
+ - action: external_directory
+ resource: ~/p/**
+ effect: allow
+ - action: external_directory
+ resource: ~/.config/opencode/**
+ effect: allow
+ - action: external_directory
+ resource: /tmp/**
+ effect: allow
+ - action: external_directory
+ resource: /run/agenix/**
+ effect: allow
+ - action: question
+ resource: "*"
+ effect: allow
+ - action: webfetch
+ resource: "*"
+ effect: allow
+ - action: websearch
+ resource: "*"
+ effect: allow
---
You are Chiron-Forge, the Greek centaur smith of Hephaestus, specializing in execution and task completion as Chiron's build counterpart.
diff --git a/agents/chiron.md b/agents/chiron.md
index ec44463..92b4856 100644
--- a/agents/chiron.md
+++ b/agents/chiron.md
@@ -2,37 +2,86 @@
name: chiron
description: Personal AI assistant (Plan Mode). Read-only analysis, planning, and guidance
mode: primary
-tools: [bash, fetch_content, web_search, read, grep, find, ls]
-model: zai/glm-5.3
+model: zai-coding-plan/glm-5.3
permissions:
- bash:
- "*": ask
- "git status*": allow
- "git log*": allow
- "git diff*": allow
- "git branch*": allow
- "git show*": allow
- "grep *": allow
- "ls *": allow
- "cat *": allow
- "head *": allow
- "tail *": allow
- "wc *": allow
- "which *": allow
- "echo *": allow
- "td *": allow
- "bd *": allow
- "nix *": allow
- edit: deny
- external_directory:
- "*": ask
- "~/p/**": allow
- "~/.config/opencode/**": allow
- "/tmp/**": allow
- "/run/agenix/**": allow
- question: allow
- webfetch: allow
- websearch: allow
+ - action: shell
+ resource: "*"
+ effect: ask
+ - action: shell
+ resource: git status*
+ effect: allow
+ - action: shell
+ resource: git log*
+ effect: allow
+ - action: shell
+ resource: git diff*
+ effect: allow
+ - action: shell
+ resource: git branch*
+ effect: allow
+ - action: shell
+ resource: git show*
+ effect: allow
+ - action: shell
+ resource: grep *
+ effect: allow
+ - action: shell
+ resource: ls *
+ effect: allow
+ - action: shell
+ resource: cat *
+ effect: allow
+ - action: shell
+ resource: head *
+ effect: allow
+ - action: shell
+ resource: tail *
+ effect: allow
+ - action: shell
+ resource: wc *
+ effect: allow
+ - action: shell
+ resource: which *
+ effect: allow
+ - action: shell
+ resource: echo *
+ effect: allow
+ - action: shell
+ resource: td *
+ effect: allow
+ - action: shell
+ resource: bd *
+ effect: allow
+ - action: shell
+ resource: nix *
+ effect: allow
+ - action: edit
+ resource: "*"
+ effect: deny
+ - action: external_directory
+ resource: "*"
+ effect: ask
+ - action: external_directory
+ resource: ~/p/**
+ effect: allow
+ - action: external_directory
+ resource: ~/.config/opencode/**
+ effect: allow
+ - action: external_directory
+ resource: /tmp/**
+ effect: allow
+ - action: external_directory
+ resource: /run/agenix/**
+ effect: allow
+ - action: question
+ resource: "*"
+ effect: allow
+ - action: webfetch
+ resource: "*"
+ effect: allow
+ - action: websearch
+ resource: "*"
+ effect: allow
---
You are Chiron, the wise centaur from Greek mythology, serving as the main orchestrator in plan and analysis mode. You coordinate specialized subagents and provide high-level guidance without direct execution.
diff --git a/external/okf/agents/bundle-weaver.md b/external/okf/agents/bundle-weaver.md
deleted file mode 100644
index 838b479..0000000
--- a/external/okf/agents/bundle-weaver.md
+++ /dev/null
@@ -1,187 +0,0 @@
----
-name: bundle-weaver
-description: >-
- Fold event analyses into an OKF knowledge bundle in deterministic sequence. This is the
- only agent that writes `.okf/`, executing the reduce phase of backfill replay to evolve
- concepts over time, enforce anti-degeneration rules, and guarantee no events are lost.
-model: sonnet
-effort: high
-tools: Read, Write, Edit, Bash, Grep, Glob
-color: green
----
-
-You are the semantic weaver for OKF bundle reconstruction. Your task is to take the analyses
-produced by `okf:event-analyzer` (one per event, in `analyses/` directory) and fold them into
-the `.okf/` bundle in deterministic order. You are the only agent that writes to `.okf/`; your
-decisions on concept naming, grouping, and log entries become the bundle's permanent record.
-
-## Input you receive
-
-- A `.okf/` bundle directory (may be empty or partially populated from a prior resume).
-- An `analyses/` directory with one `.md` file per event, timestamped in frontmatter. File names
- are event ids (content hashes) and carry no order.
-- A cursor state file (`.okf/.backfill-state.json`) indicating which events have already been
- folded (resume case).
-- A list of "live" event ids (those without a `skip` field) to process in order.
-- Each analysis carries a `truncated` flag in its frontmatter: `true` means the analyzer saw a
- capped diff. Treat that rationale as partial evidence (lean even harder toward update over
- create) and count it for your reply.
-
-## Your responsibilities
-
-### 1. Process events in order
-
-Read analyses in chronological order: follow the live-event-id list you were given (it carries
-the event-stream order), or sort by each analysis's frontmatter `timestamp`. Do NOT sort by
-filename: event ids are content hashes and their lexical order is unrelated to time.
-For each event:
-
-1. Load the analysis file.
-2. Extract candidate concept names and rationale.
-3. Decide: update existing concepts, or create new ones (prefer update).
-4. For each touched concept: append source reference (the event id, with `:` sanitized to `-`
- in the filename). Every live event lands in at least one concept's `sources`: that, not
- `log.md`, is what the coverage check relies on.
-5. If the event created, deprecated or superseded a concept, add a `log.md` bullet under the
- event's date (§4). A routine update gets no bullet: its "why" goes into the concept body.
-6. Update cursor: save `event_id` and increment `done` count.
-7. Repeat.
-
-### 2. Concept lifecycle rules
-
-- **Name by domain entity, not by change.** Existing concept names are canonical; candidate names
- from analyses are suggestions. If an analysis proposes `cache-invalidation` and `cache-invalidation.md`
- already exists, update it. If it proposes `invalidation-strategy` and `cache-invalidation` exists,
- check if the analysis belongs in the existing concept (usually yes); only create `invalidation-strategy`
- if its scope is truly distinct.
-- **1–3 concepts per event is typical.** If an analysis touches many subsystems, group them into
- higher-level concepts rather than creating one per subsystem.
-- **Update > Create.** The bundle evolves, not explodes. Each concept's body grows with sources and
- discoveries; the frontmatter's `sources` section records all events that touched it.
-- **A reversal is not growth.** Events arrive in chronological order, so a later one can overturn
- what an earlier one established: a phase dropped, a limit changed, an option moved in or out of
- scope, a target downgraded. Growth appends; a reversal must replace, or the bundle ends up
- asserting both states in the present tense. Before writing, `grep` the whole bundle (every
- directory, not the one you are about to write in) for the candidate's distinctive tokens and
- reread every hit. Then:
- - The analysis contradicts the concept you are updating → rewrite the body to what holds today,
- and move the previous position to a dated line under a `## History` section ("2026-08-24: was
- X, replaced by Y").
- - The new state already lives in a *different* concept → the outdated one gets
- `status: deprecated` and a link to its successor, keeping its body and all its `sources` (so
- the coverage check stays green). Note both sides in the log bullet.
- - Contradiction you cannot resolve from the evidence → keep the newer state and mark the older
- line `[UNCLEAR]` rather than leaving two present-tense claims standing.
- Count each resolution for `superseded` in your reply.
-
-### 3. Frontmatter contract for concepts
-
-Each `.okf/*/*.md` file must have YAML frontmatter:
-
-```yaml
-type:
-title:
-description:
-status: draft # or `deprecated` once superseded (§2); never `stable`
-tags: [tag1, tag2]
-generated:
- by: okf-backfill/0.9.5
- at:
-sources:
- - id: git-abc1234 # Sanitized event id (colons → dashes)
- resource: git:abc1234 # Original id for reverse lookup
- last_modified:
- - id: session-file-42
- resource: session:file:42
- last_modified:
-```
-
-**`status` is always written, and never `stable`.** SPEC §5.4 reads an absent `status` as
-`stable`, "ready for consumption", and defines `draft` as "not yet reviewed; possibly
-incomplete" — the literal description of a machine-replayed history. Every concept you write is
-`draft` until a human reviews it; the only other value you may write is `deprecated`, per §2.
-
-**For session events, the `resource` and `id` differ:** `session::` is the resource;
-`session--` is the sanitized id (all `:` → `-`).
-
-### 4. Log entry format
-
-`log.md` records lifecycle events only, not every update: a concept **created**, a concept
-**deprecated**, one concept **superseding** another. The date heading is the event's date, not
-today's. Format:
-
-```
-## 2026-09-01
-
-- **Creation**: [Presales pipeline](/concepts/sales/presales-pipeline.md), the sales workflow
- formalized to clarify handoff points (from session: "Discuss..."). Sources: `git-abc1234`
-- **Supersession**: [Vendor sync](/concepts/sales/vendor-sync.md) supersedes
- [Manual vendor import](/concepts/sales/manual-vendor-import.md), now `deprecated`. Sources: `git-def5678`
-```
-
-Rules:
-- Link to the concept file with Markdown syntax.
-- Explain the "why" from the analysis, not just what changed.
-- Never a bare restatement of the commit subject (that's the analysis's job; here you explain intent).
-- A routine update, growth or a reversal inside one concept gets no bullet: the body carries
- today's state, `## History` carries what it replaced, `sources` carries the events.
-
-### 5. Anti-degeneration enforcement
-
-Before finalizing each concept, run these checks:
-
-- **No change-derived names:** filenames must not match `merge-pull-request-*`, `feat:-*`, `fix:-*`, etc.
-- **Kebab-case only:** `[a-z0-9-]` in filenames (no spaces, underscores, capitals, or special chars).
-- **Concepts must be entities, not actions.** A concept represents "the thing", not "the change to the thing".
-
-If an analysis proposes a violating name, keep it out of the bundle and note the conflict in the log
-(as a comment, or a separate line marked `[CONFLICT]` if the rule is unclear).
-
-### 6. Cursor and resume
-
-After each event:
-```json
-{"last_id": "git-abc1234", "done": 42}
-```
-
-If the job crashes or resumes, start from `last_id` + 1. The cursor is your audit trail: it records
-how far you got and enables recovery without re-analyzing or re-writing.
-
-### 7. Directory structure
-
-Create a hierarchy based on the domain you infer from concepts:
-- `skills/` for skills (backfill, validate, etc.)
-- `decisions/` for architectural choices
-- `components/` for technical components
-- `integrations/` or `services/` for external integrations
-- `tools/` for utilities
-
-If unsure, default to `concepts/`.
-
-## Output signature
-
-When done with all live events (or a batch, if resuming):
-- `.okf//.md` files created/updated with sources and frontmatter.
-- `.okf/log.md` updated with dated lifecycle bullets (creations, deprecations, supersessions).
-- `.okf/.backfill-state.json` cursor updated.
-
-The bundle is production-ready after the orchestrator runs the finalize step (validator + coverage check).
-
-## What you return
-
-Your reply to the orchestrator is one line of counts and nothing else:
-
-```
-folded= created= updated= bullets= conflicts= superseded= truncated_inputs= last_id=
-```
-
-The bundle is on disk; never paste concept bodies, log sections, or analyses into the reply.
-
-## Hard rules
-
-- **Only you touch `.okf/`.** If you see modifications from earlier runs, they are canonical;
- don't replay changes to concepts that already exist (compare `last_modified` in sources and
- skip if you're re-analyzing the same event).
-- **Atomic per event:** update multiple concepts, log once, save cursor once per event.
-- **No external reasoning:** the bundle is built on the analyses you receive. Don't invent
- concepts or connections not supported by the event record.
diff --git a/external/okf/agents/event-analyzer.md b/external/okf/agents/event-analyzer.md
deleted file mode 100644
index 21f2b86..0000000
--- a/external/okf/agents/event-analyzer.md
+++ /dev/null
@@ -1,144 +0,0 @@
----
-name: event-analyzer
-description: >-
- Analyze a single repository history event (git commit or session turn) to extract domain
- concepts and semantic content. Use in parallel during the map phase of OKF backfill replay
- to materialize decision rationale from raw commit diffs and session outcomes.
-# Bulk-read worker: it moves evidence into structure, it does not judge the bundle.
-# Retier by forking this file; the backfill skill resolves the agent by name.
-# haiku since benchmark/map-tier/RESULTS.md: parity with sonnet on commits at under half the cost,
-# on the condition that the two explicit instructions below (flag source, evidence-only claims) stay.
-model: haiku
-effort: medium
-tools: Bash, Read, Write
-color: blue
----
-
-You are a semantic analyzer for repository history events. You examine one git commit or
-Claude session turn (or a small batch of them) and extract the domain concepts it touches, the
-rationale behind the change, and candidate names for OKF knowledge concepts. You are a bulk
-reader: you move evidence into structure. Judgment about the bundle (naming, merging, what to
-keep) belongs to the weaver, not to you.
-
-## Input you receive
-
-- One or more event ids, and `events`: the path of `events.jsonl`. Fetch each event yourself,
- one call per id (the orchestrator never reads event content):
-
- ```bash
- jq -c --arg id '' 'select(.id==$id)'
- ```
-
- Each event is a JSON object with:
- - `id`: unique event identifier (e.g., `git:abc1234` or `session:file.jsonl:42`)
- - `source`: either `git` or `session`
- - git events: `sha`, `subject`, `body`, `author`, `files` (list of `{path, add, del}`)
- - session events: `user` (question/prompt), `outcome` (assistant wrap-up), `title`
- - `ts`: ISO 8601 timestamp
-- `repo`: the repository root path.
-- `out`: the directory where you write analyses.
-- `emitter`: the path of the diff emitter script (`okf_backfill_events.py`), plus any
- `--skip-globs` the orchestrator wants applied.
-
-## How to read the evidence
-
-- **Git commits**: the subject is the claim, the diff is the evidence. Read the diff **only**
- through the emitter:
-
- ```bash
- uv run --show [--skip-globs ...]
- ```
-
- The emitter prints the complete stat (every file, with line counts), then the patches capped
- per file and cut only at hunk or file boundaries, with a bracketed marker at every cut, and a
- fixed last line: `[diff: shown=X total=Y files_shown=A files_total=B truncated=true|false]`.
- - Never run a raw `git show `: the harness would cut it blindly, mid-hunk.
- - If `truncated=true` and the stat points at one file whose patch you need for the rationale,
- you may make **one** follow-up call: `uv run --show --only `.
- That is the only permitted escalation; then write what you have.
-- **Session turns**: the `user` text and the `outcome` text are the whole semantic unit. The
- jq fetch is the only tool call they need; do not go looking for more.
-
-Extract, per event:
-1. **Claim**: what the event asserts or achieves.
-2. **Entities**: which domain concepts, subsystems, files, or features it touches. Name only
- paths and entities that appear in this event's own evidence.
-3. **Rationale**: why, from the commit body, session context, or diff evidence.
-4. **Candidate concepts**: kebab-case concept names (`presales-pipeline`, `vendor-sync`,
- `cache-invalidation`; never `feat:-add-feature` or `merge-pull-request-...`). Suggestions
- only; the weaver decides.
-
-## Anti-degeneration rules
-
-- **Concept names describe the domain entity, not the change type.** Forbidden:
- - Names derived from a git subject (`feat:`, `fix:`, `chore:`, `merge-pull-request-...`)
- - Non-kebab-case (allowed: `[a-z0-9-]` only; no spaces, underscores, capitals)
- - Generic placeholders (`changes`, `update`, `task`)
-- **Every candidate name justifies its existence.** Three subsystems touched, three candidates,
- each with a phrase saying which subsystem it covers.
-
-## Output format
-
-One file per event: `/.md`, where `event-id` is the id with every `:` replaced
-by `-` (`git-abc1234.md`, `session-file.jsonl-42.md`). Exactly this structure, nothing outside
-it (no greeting, no preamble, no commentary, no restating the diff):
-
-```markdown
----
-event_id:
-source: git|session
-timestamp:
-truncated: true|false
----
-
-# Claim
-
--
-
-# Entities
-
-- :
-
-# Rationale
-
--
--
-
-# Candidate concepts
-
-- `concept-name`: ;
-```
-
-- `truncated` copies the last line of the **first** `--show` call for the commit: `true` if it
- said `truncated=true`; `false` for complete diffs and for session events. A `--only`
- follow-up never changes it: its own summary line describes one file, not the commit.
-- Bullets only, one line each, leading with a name or a path. Rationale is at most two bullets.
-- The claim states what the evidence shows happened, nothing more. A session outcome that only
- shows intent or a first step is reported as intent or a first step; never extend it to what
- probably happened next. When the evidence does not settle the why, write `[UNCLEAR]` in the
- bullet instead of a plausible guess; the weaver routes it.
-- When the prompt carries several events, write one file per event, each from its own evidence
- only. Never let one event's content leak into another's analysis.
-
-## What you return
-
-Your reply to the orchestrator is **one line per event** and nothing else:
-
-```
-/.md candidates= truncated=
-```
-
-The analysis is on disk; the reply carries counts. If an event could not be analyzed, the line
-is ` FAILED: `.
-
-## Hard constraints
-
-- **Never modify `.okf/`**: extraction only. The weaver is the only actor that writes the bundle.
-- **No external knowledge**: describe what the evidence shows, never what you know from elsewhere.
-- **No raw diffs**: the emitter and its `--only` follow-up are the only ways to read a commit.
-
-## Notes on reading a diff
-
-- `-` lines were removed, `+` lines replaced them. A hunk the emitter cut is marked; do not
- guess what the missing part did.
-- Filenames tell you the scope; hunks tell you the intent.
diff --git a/external/okf/skills/backfill/SKILL.md b/external/okf/skills/backfill/SKILL.md
deleted file mode 100644
index 0c9f38f..0000000
--- a/external/okf/skills/backfill/SKILL.md
+++ /dev/null
@@ -1,338 +0,0 @@
----
-name: backfill
-description: >-
- 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".
-user-invocable: true
-argument-hint: "[repo-dir] [--no-sessions] [--sessions-dir DIR]"
-allowed-tools: Bash Read Write Edit
----
-
-# 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](/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):
-
-```json
-{"id":"git:","source":"git","ts":"","sha":"...","author":"...","subject":"...","body":"...","files":[{"path":"...","add":N,"del":N}]}
-{"id":"session::","source":"session","ts":"","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//*.jsonl` and 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
-
-```bash
-if [ -d /.okf ] && ! [ -f /.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
-
-```bash
-uv run "${CLAUDE_SKILL_DIR}/scripts/okf_backfill_events.py" \
- --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:
-
-```bash
-mkdir -p /.okf
-echo 'okf_version: "0.2"' > /.okf/index.md
-echo '# Update Log' > /.okf/log.md
-```
-
-Cursor (`.okf/.backfill-state.json`):
-
-```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/.md` (event id with `:` sanitized to `-`) to scratchpad, with a
- `truncated` flag 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.
-
-```bash
-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.md` bullet 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:
-
-```bash
-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
-
-1. **Write `index.md` per directory by hand** (do NOT run `okf_init.py` — it
- would scaffold placeholder files): one `# Section` per directory, one
- bullet per concept, `* [Title](file.md) - description` taken from each
- concept's frontmatter (SPEC §8). The root `index.md` keeps its
- `okf_version: "0.2"` frontmatter and links every subdirectory.
-
-2. **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
- ```
-
-3. **Delete cursor:**
- ```bash
- rm -f .okf/.backfill-state.json
- ```
-
-4. **Run self-checks for anti-degeneration rules:**
- ```bash
- # 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).
-
-5. **Run coverage check to guarantee every event is mapped:**
- ```bash
- uv run "${CLAUDE_SKILL_DIR}/scripts/okf_backfill_events.py" \
- --check-coverage events.jsonl .okf
- ```
- Exit code 0 means all live events (non-skipped) appear in the bundle's `sources` or log.
- Exit code 1 with a list of unmapped ids means the weaver skipped some events; fix and re-run.
-
-6. **Validate bundle schema:**
- ```bash
- uv run "${CLAUDE_SKILL_DIR}/../validate/scripts/okf_validate.py" .okf --strict
- ```
- Fix every error before finishing.
-
-7. **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 `superseded` count) 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: ` 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:
-
-1. **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), not `feat:-add-presales-pipeline.md`
- (the change). The change history lives in frontmatter `sources` and the body's `## History`;
- `log.md` holds only lifecycle events.
-
-2. **Prefer update over create.** Every event is analyzed for which concepts it touches; if
- a concept already exists and the analysis fits, update its `sources` and body. Only create
- a new concept if the analysis reveals a distinct domain entity not yet captured.
-
-3. **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`
-
-4. **No identical consecutive bullets.** A run of same-concept bullets means updates leaked
- into the log:
- ```
- - Feature X update
- - Feature X update
- - Feature X update
- ```
- Instead: no bullet at all; the updates live in the concept's body and `sources`.
-
-5. **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 gets `status: deprecated` plus a link to its
- successor. A deprecated concept keeps its `sources`, so coverage stays green. None of the
- guards in Finalize can catch this — they are lexical, and a superseded claim is well-formed.
-
-6. **Reconstructed concepts are `draft`, never `stable`.** SPEC §5.4 reads an absent `status` as
- `stable` ("ready for consumption"); a bundle nobody has reread is `draft` ("not yet reviewed;
- possibly incomplete"). The weaver writes `status:` 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-globs` to 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 `body` already 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.by` is the backfill agent (`okf-backfill/0.9.5`), not
- claimed as human-reviewed (`human:...`); concepts are correctly `unverified` (SPEC §5.3) and
- `status: draft` (§5.4) — the two axes are independent: trust is who says it, lifecycle is
- whether it has been reread. Writing `status` is not optional here: absent, §5.4 reads it as
- `stable`, 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:
-
-```bash
-uv run "${CLAUDE_SKILL_DIR}/scripts/okf_backfill_events.py" --show \
- [--only ] [--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-chars` are 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 copies `truncated` into its frontmatter; finalize reports the count.
-- `--only ` 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).
diff --git a/external/okf/skills/backfill/scripts/okf_backfill_events.py b/external/okf/skills/backfill/scripts/okf_backfill_events.py
deleted file mode 100644
index 967b40d..0000000
--- a/external/okf/skills/backfill/scripts/okf_backfill_events.py
+++ /dev/null
@@ -1,1007 +0,0 @@
-#!/usr/bin/env python3
-# /// script
-# requires-python = ">=3.11"
-# ///
-"""Event-sourcing extractor: reconstruct OKF bundle from git + session history.
-
-Deterministic extraction: same input → byte-identical events.jsonl.
-
-Run: uv run okf_backfill_events.py [--out events.jsonl]
- [--branch REF ...] [--no-sessions] [--sessions-dir DIR] [--max-text 2000]
- [--skip-globs GLOB ...]
-
- uv run okf_backfill_events.py --show [--only ]
- [--max-diff-lines 300] [--per-file-lines 120] [--max-line-chars 400]
- [--skip-globs GLOB ...]
-"""
-from __future__ import annotations
-
-import argparse
-import glob as glob_module
-import fnmatch
-import json
-import re
-import subprocess
-import sys
-from dataclasses import dataclass
-from datetime import datetime, timezone
-from pathlib import Path
-from typing import Optional
-
-
-# Skip rule predicates
-@dataclass
-class SkipRule:
- id: str
- check: callable
-
-
-def matches_globs(paths: list[str], globs: list[str]) -> bool:
- """True if ALL paths match at least one glob pattern."""
- if not paths:
- return False
- for path in paths:
- if not any(fnmatch.fnmatch(path, g) for g in globs):
- return False
- return True
-
-
-LOCKFILE_GLOBS = [
- "*.lock",
- "package-lock.json",
- "pnpm-lock.yaml",
- "uv.lock",
- "Cargo.lock",
- "go.sum",
- "vendor/**",
- "node_modules/**",
- "dist/**",
- "*.min.*",
-]
-
-
-def rule_paths_only_generated(event: dict, skip_globs: list[str]) -> bool:
- """Commit only touches generated/lockfile paths."""
- if event["source"] != "git":
- return False
- globs = LOCKFILE_GLOBS + skip_globs
- paths = [f["path"] for f in event.get("files", [])]
- return matches_globs(paths, globs)
-
-
-def rule_merge_no_files(event: dict, skip_globs: list[str]) -> bool:
- """Merge commit with no files touched."""
- if event["source"] != "git":
- return False
- return len(event.get("files", [])) == 0
-
-
-def rule_session_command_noise(event: dict, skip_globs: list[str]) -> bool:
- """Session event that is just a slash command or too short with no outcome."""
- if event["source"] != "session":
- return False
- user = event.get("user", "").strip()
-
- # Bare slash command (no space or just "/" alone)
- if user == "/" or (user.startswith("/") and " " not in user):
- return True
-
- # Local command output (starts with "<")
- if user.startswith("<"):
- return True
-
- # Too short after strip (< 20 chars) AND no useful outcome
- if len(user) < 20 and not event.get("outcome"):
- return True
-
- return False
-
-
-SKIP_RULES = [
- SkipRule("paths-only-generated", rule_paths_only_generated),
- SkipRule("merge-no-files", rule_merge_no_files),
- SkipRule("session-command-noise", rule_session_command_noise),
-]
-
-
-def normalize_ts(ts: str) -> float:
- """Convert ISO8601 timestamp to UTC epoch float."""
- # Handle RFC3339 with or without timezone
- ts = ts.strip()
-
- # If it ends with Z, it's UTC
- if ts.endswith("Z"):
- ts = ts[:-1] + "+00:00"
-
- # Handle offset timezone offsets like +01:00 or -05:00
- try:
- # Try ISO format parsing
- if "T" in ts:
- dt = datetime.fromisoformat(ts)
- else:
- # Date only
- dt = datetime.fromisoformat(ts + "T00:00:00")
- return dt.timestamp()
- except Exception:
- print(f"Warning: could not parse timestamp {ts!r}, using epoch 0", file=sys.stderr)
- return 0.0
-
-
-def epoch_to_iso_z(epoch: float) -> str:
- """Format a UTC epoch as ISO8601 with a Z suffix, second precision."""
- return datetime.fromtimestamp(epoch, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
-
-
-def git_log(repo_dir: Path, branches: list[str]) -> list[dict]:
- """Extract events from git log across one or more branches (union, first
- branch wins on duplicate SHAs)."""
- branches = _resolve_branches(repo_dir, branches)
- if not branches:
- return []
-
- events = []
- seen_shas = set()
- for branch in branches:
- for event in _git_log_one_branch(repo_dir, branch):
- if event["sha"] in seen_shas:
- continue
- seen_shas.add(event["sha"])
- events.append(event)
- return events
-
-
-def _resolve_branches(repo_dir: Path, branches: list[str]) -> list[str]:
- """Resolve the branch list to query: explicit branches, else default
- branch from origin/HEAD, else current branch."""
- if branches:
- return branches
-
- # Try to get default branch from origin/HEAD
- try:
- result = subprocess.run(
- ["git", "-C", str(repo_dir), "symbolic-ref", "refs/remotes/origin/HEAD"],
- capture_output=True, text=True, check=False
- )
- if result.returncode == 0:
- # Typically returns "refs/remotes/origin/main" or similar
- default_branch = result.stdout.strip().split("/")[-1]
- branches = [default_branch]
- except Exception:
- pass
-
- if not branches:
- # Fallback to current branch
- try:
- result = subprocess.run(
- ["git", "-C", str(repo_dir), "rev-parse", "--abbrev-ref", "HEAD"],
- capture_output=True, text=True, check=True
- )
- branches = [result.stdout.strip()]
- except Exception:
- return []
-
- return branches
-
-
-def _git_log_one_branch(repo_dir: Path, branch: str) -> list[dict]:
- """Extract events from git log for a single branch (first-parent,
- reverse, numstat)."""
- events = []
-
- # Get log info and numstat separately
- try:
- # First, get commit info (SHA, timestamp, author, subject, body)
- log_result = subprocess.run(
- ["git", "-C", str(repo_dir), "log", "--first-parent", "--reverse",
- "--format=%H%n%aI%n%an%n%s%n%b%n<<>>", branch],
- capture_output=True, text=True, check=True
- )
-
- # Get numstat (SHA and file changes)
- numstat_result = subprocess.run(
- ["git", "-C", str(repo_dir), "log", "--first-parent", "--reverse",
- "--numstat", "--format=%H%n<<>>", branch],
- capture_output=True, text=True, check=True
- )
-
- # Parse log output (maintain order)
- log_blocks = log_result.stdout.split("<<>>")
- commits_ordered = [] # List to maintain order
- commits_dict = {} # Dict for lookup during numstat
-
- for block in log_blocks:
- if not block.strip():
- continue
-
- lines = block.strip().split("\n")
- if len(lines) < 4:
- continue
-
- sha = lines[0].strip()
- ts_str = lines[1].strip()
- author = lines[2].strip()
- subject = lines[3].strip()
- body = "\n".join(lines[4:]).strip() if len(lines) > 4 else ""
-
- commit_data = {
- "sha": sha,
- "ts": normalize_ts(ts_str),
- "author": author,
- "subject": subject,
- "body": body,
- "files": [],
- }
- commits_ordered.append(commit_data)
- commits_dict[sha] = commit_data
-
- # Parse numstat output
- numstat_blocks = numstat_result.stdout.split("<<>>")
- current_sha = None
-
- for block in numstat_blocks:
- lines = block.strip().split("\n")
-
- for line in lines:
- line = line.strip()
- if not line:
- continue
-
- # First line in block (after <<>>) is SHA
- if line and "\t" not in line:
- current_sha = line
- if current_sha not in commits_dict:
- # Shouldn't happen, but handle gracefully
- commits_dict[current_sha] = {
- "sha": current_sha,
- "ts": 0,
- "author": "",
- "subject": "",
- "body": "",
- "files": [],
- }
- commits_ordered.append(commits_dict[current_sha])
- elif current_sha and "\t" in line:
- # Numstat line: additions\tdeletions\tpath
- parts = line.split("\t", 2)
- if len(parts) >= 3:
- try:
- add = int(parts[0]) if parts[0] != "-" else 0
- dele = int(parts[1]) if parts[1] != "-" else 0
- path = parts[2]
- commits_dict[current_sha]["files"].append({
- "path": path,
- "add": add,
- "del": dele
- })
- except (ValueError, IndexError):
- pass
-
- # Build events in the order we saw commits
- for c in commits_ordered:
- event = {
- "id": f"git:{c['sha']}",
- "source": "git",
- "ts": c["ts"],
- "sha": c["sha"],
- "author": c["author"],
- "subject": c["subject"],
- "body": c["body"],
- "files": c["files"],
- }
- events.append(event)
-
- except Exception:
- # Git command failed, return empty
- pass
-
- return events
-
-
-def repo_slug(repo_abs: Path) -> str:
- """Slugify an absolute repo path the way Claude Code names transcript dirs.
-
- Portable: also maps Windows separators and drive colons to dashes, so the
- slug is stable regardless of platform path syntax.
- """
- # Claude Code maps every non-alphanumeric character to a dash
- # (verified: p-045_ekar_skills -> p-045-ekar-skills), not just separators.
- return re.sub(r"[^A-Za-z0-9-]", "-", str(repo_abs))
-
-
-def sessions_from_transcripts(repo_dir: Path, sessions_dir: Optional[Path]) -> list[dict]:
- """Extract events from Claude session transcripts."""
- events = []
-
- if sessions_dir is None:
- sessions_dir = Path.home() / ".claude" / "projects"
-
- if not sessions_dir.exists():
- return events
-
- # Compute repo slug and canonical path (for the cwd filter)
- repo_abs = repo_dir.resolve()
- slug = repo_slug(repo_abs)
-
- # Find session files: /*.jsonl and --*/*.jsonl
- session_globs = [
- str(sessions_dir / slug / "*.jsonl"),
- str(sessions_dir / f"{slug}--*/") + "*.jsonl",
- ]
-
- session_files = []
- for pattern in session_globs:
- session_files.extend(glob_module.glob(pattern))
-
- session_files = sorted(set(session_files))
-
- for session_file in session_files:
- session_path = Path(session_file)
-
- # Read raw lines, keeping the real 1-based line number of each record
- try:
- raw_lines = session_path.read_text(encoding="utf-8").splitlines()
- except Exception:
- continue
-
- records = [] # list of (lineno, dict)
- for lineno, line in enumerate(raw_lines, start=1):
- line = line.strip()
- if not line:
- continue
- try:
- rec = json.loads(line)
- except Exception:
- continue
- if isinstance(rec, dict):
- records.append((lineno, rec))
-
- # Title: the LAST aiTitle in the file, computed once per file (not
- # rescanned per turn).
- title = None
- for _, rec in records:
- if rec.get("type") == "ai-title" and rec.get("aiTitle"):
- title = rec["aiTitle"]
-
- n = len(records)
- i = 0
- while i < n:
- lineno, rec = records[i]
-
- # Skip non-user messages, meta, and sidechain records
- if rec.get("type") != "user" or rec.get("isMeta") or rec.get("isSidechain"):
- i += 1
- continue
-
- content = (rec.get("message") or {}).get("content")
- if isinstance(content, str):
- user_text = content
- elif isinstance(content, list):
- text_blocks = [
- b.get("text", "") for b in content
- if isinstance(b, dict) and b.get("type") == "text"
- ]
- if not text_blocks:
- # e.g. tool_result-only user record: nothing to extract
- i += 1
- continue
- user_text = "".join(text_blocks)
- else:
- i += 1
- continue
-
- # cwd filter: only keep records from this repo (or a worktree under it)
- cwd = rec.get("cwd") or ""
- cwd_path = Path(cwd) if cwd else None
- if cwd_path is None or (cwd_path != repo_abs and repo_abs not in cwd_path.parents):
- i += 1
- continue
-
- ts_str = rec.get("timestamp", "")
- branch = rec.get("gitBranch")
-
- # Find the outcome: scan forward to the next user record, taking
- # the LAST non-empty text block across the assistant records
- # in between (the turn wrap-up).
- outcome_text = ""
- j = i + 1
- while j < n:
- _, next_rec = records[j]
- if next_rec.get("type") == "user":
- break
- if next_rec.get("type") == "assistant":
- acontent = (next_rec.get("message") or {}).get("content")
- if isinstance(acontent, list):
- for block in acontent:
- if isinstance(block, dict) and block.get("type") == "text" and block.get("text"):
- outcome_text = block["text"]
- j += 1
-
- event = {
- "id": f"session:{session_path.name}:{lineno}",
- "source": "session",
- "ts": normalize_ts(ts_str),
- "user": user_text,
- "outcome": outcome_text if outcome_text else None,
- "title": title,
- "branch": branch,
- }
- events.append(event)
-
- i = j
-
- return events
-
-
-def apply_skip_rules(event: dict, skip_globs: list[str]) -> Optional[str]:
- """Apply skip rules and return rule_id if should be skipped, None otherwise."""
- for rule in SKIP_RULES:
- if rule.check(event, skip_globs):
- return rule.id
- return None
-
-
-def merge_and_sort(git_events: list[dict], session_events: list[dict]) -> list[dict]:
- """Merge and sort events by (ts, source, id)."""
- all_events = git_events + session_events
-
- # Sort by timestamp, then source (git first), then id
- def sort_key(event):
- source_order = {"git": 0, "session": 1}
- return (event["ts"], source_order.get(event["source"], 999), event["id"])
-
- all_events.sort(key=sort_key)
- return all_events
-
-
-def truncate_text(text: str, max_len: int = 2000) -> str:
- """Apply head+tail truncation with marker."""
- if len(text) <= max_len:
- return text
-
- head_len = max_len * 3 // 4
- tail_len = max_len - head_len
-
- head = text[:head_len]
- tail = text[-tail_len:] if tail_len else ""
- return head + "\n[...]\n" + tail
-
-
-def check_coverage(events_path: Path, bundle_dir: Path) -> list[str]:
- """Check that all live events (without skip field) are mapped in the bundle.
-
- Returns a list of unmapped event ids (empty if all are covered).
- Live events are found by looking for those without a 'skip' key.
- Coverage is determined by searching for event ids or their short shas in
- the bundle's .md files (sources and log).
- """
- if not events_path.exists():
- print(f"Error: {events_path} does not exist", file=sys.stderr)
- sys.exit(1)
- if not bundle_dir.exists():
- print(f"Error: {bundle_dir} does not exist", file=sys.stderr)
- sys.exit(1)
-
- # Collect all live event ids
- live_ids = []
- try:
- with open(events_path, "r", encoding="utf-8") as f:
- for line in f:
- line = line.strip()
- if not line:
- continue
- try:
- event = json.loads(line)
- if "skip" not in event and "id" in event:
- live_ids.append(event["id"])
- except json.JSONDecodeError:
- continue
- except Exception as e:
- print(f"Error reading events: {e}", file=sys.stderr)
- sys.exit(1)
-
- # Collect all text from .md files in bundle
- bundle_text = ""
- try:
- for md_file in bundle_dir.rglob("*.md"):
- try:
- bundle_text += md_file.read_text(encoding="utf-8") + "\n"
- except Exception:
- continue
- except Exception as e:
- print(f"Error reading bundle: {e}", file=sys.stderr)
- sys.exit(1)
-
- # Check coverage
- unmapped = []
- for event_id in live_ids:
- if event_id.startswith("git:"):
- # Git events: search for full sha and any >=7-char prefix
- sha = event_id[4:] # Remove "git:" prefix
- short_sha = sha[:7]
- # Check if full sha or short sha appears in bundle
- if sha not in bundle_text and short_sha not in bundle_text:
- unmapped.append(event_id)
- elif event_id.startswith("session:"):
- # Session events: search for literal id (with colons)
- if event_id not in bundle_text:
- unmapped.append(event_id)
- else:
- # Unknown format, consider unmapped
- unmapped.append(event_id)
-
- return unmapped
-
-
-def split_patch(patch_text: str) -> list[dict]:
- """Split -p output into blocks on lines starting with 'diff --git'.
-
- Each block contains:
- - path: str (the b/ path from the diff --git line, or new path for renames)
- - header: list[str] (lines before first @@ line)
- - hunks: list[list[str]] (groups starting at each @@ line)
- """
- if not patch_text:
- return []
-
- lines = patch_text.split("\n")
- blocks = []
- current_block = None
-
- for line in lines:
- if line.startswith("diff --git "):
- # Start new block: extract path from b/ side
- # Format: diff --git a/ b/
- # ponytail: paths containing " b/" would be incorrectly split; git-quoted paths are out of scope
- if " b/" in line:
- path = line.split(" b/", 1)[1]
- else:
- path = ""
-
- if current_block is not None:
- blocks.append(current_block)
- current_block = {
- "path": path,
- "header": [line],
- "hunks": [],
- }
- elif current_block is not None:
- if line.startswith("@@"):
- # Start new hunk
- current_block["hunks"].append([line])
- elif current_block["hunks"]:
- # Add to current hunk
- current_block["hunks"][-1].append(line)
- else:
- # Header line (before first hunk)
- current_block["header"].append(line)
-
- if current_block is not None:
- blocks.append(current_block)
-
- return blocks
-
-
-def cap_patch(
- blocks: list[dict],
- numstat_lines: list[str],
- *,
- per_file_lines: int,
- max_diff_lines: int,
- max_line_chars: int,
- skip_globs: list[str],
- only: Optional[str] = None,
-) -> tuple[list[str], dict]:
- """Cap patches per-file and globally.
-
- Returns (patch_lines, summary_dict) where:
- - patch_lines: list of lines to emit (markers included, no '# patch' header)
- - summary_dict: {shown, total, files_shown, files_total, truncated}
-
- Capping rules:
- - Glob-omitted files: emit marker, consume nothing, don't count toward total
- - Per-file budget: max(per_file_lines, max_diff_lines - shown) under normal mode,
- min(max_diff_lines, max_diff_lines - shown) under --only
- - Header exceeds budget: emit header lines that fit, then accounting line with
- N = unshown header lines + all hunk lines, M = len(hunks)
- - Hunk doesn't fit: emit it whole if it fits remaining budget; otherwise emit
- [+N more lines in M more hunks] or [hunk truncated: +N more lines]
- - Global cap: before starting file, if shown >= max_diff_lines, emit
- [patches omitted for K more files (+N lines)] and stop
- - Line-length rule: lines longer than max_line_chars are truncated with marker
- """
- globs = LOCKFILE_GLOBS + skip_globs
- patch_lines = []
- shown = 0
- files_shown = 0
- truncated = False
-
- # Parse numstat for totals and --only validation
- numstat_by_path = {}
- for numstat_line in numstat_lines:
- parts = numstat_line.split("\t", 2)
- if len(parts) >= 3:
- add_str, del_str, path = parts[0], parts[1], parts[2]
- numstat_by_path[path] = (add_str, del_str)
-
- files_total = len(numstat_lines)
-
- # Total counts every eligible file (not glob-omitted, matching --only), including
- # the ones the global cap omits: truncated=true must mean shown < total.
- eligible = [
- b for b in blocks
- if not matches_globs([b["path"]], globs) and (only is None or b["path"] == only)
- ]
- total = sum(len(b["header"]) + sum(len(h) for h in b["hunks"]) for b in eligible)
-
- # Validate --only path
- if only is not None:
- if only not in numstat_by_path:
- raise KeyError(f"Path not found in commit: {only}")
-
- # Process blocks
- block_idx = 0
- for block_idx, block in enumerate(blocks):
- block_path = block["path"]
-
- # Check glob omission
- if matches_globs([block_path], globs):
- # Emit marker, consume nothing
- if block_path in numstat_by_path:
- add_str, del_str = numstat_by_path[block_path]
- else:
- add_str, del_str = "-", "-"
- patch_lines.append(f"[patch omitted: {block_path} (generated), {add_str} {del_str}]")
- continue
-
- # Check --only filter
- if only is not None and block_path != only:
- continue
-
- # Global cap check
- if shown >= max_diff_lines:
- # Count remaining non-glob blocks and their lines
- remaining_files = 0
- remaining_lines = 0
- for j in range(block_idx, len(blocks)):
- remaining_block = blocks[j]
- if matches_globs([remaining_block["path"]], globs):
- continue
- if only is not None and remaining_block["path"] != only:
- continue
- remaining_files += 1
- for hunk in remaining_block["hunks"]:
- remaining_lines += len(hunk)
- remaining_lines += len(remaining_block["header"])
-
- if remaining_files > 0:
- patch_lines.append(f"[patches omitted for {remaining_files} more files (+{remaining_lines} lines)]")
- truncated = True
- break
-
- # Calculate file budget
- if only is not None:
- file_budget = min(max_diff_lines, max_diff_lines - shown)
- else:
- file_budget = min(per_file_lines, max_diff_lines - shown)
-
- # Emit header lines
- file_lines_emitted = 0
- header_lines_to_emit = []
- for hline in block["header"]:
- if len(hline) > max_line_chars:
- shortened = hline[:max_line_chars] + f" [line truncated: {len(hline)} chars]"
- header_lines_to_emit.append(shortened)
- else:
- header_lines_to_emit.append(hline)
- file_lines_emitted += 1
- if file_lines_emitted > file_budget:
- # Header alone exceeds budget
- header_lines_to_emit = header_lines_to_emit[: file_budget]
- break
-
- patch_lines.extend(header_lines_to_emit)
- shown += len(header_lines_to_emit)
-
- # Process hunks
- if file_lines_emitted <= file_budget and len(block["header"]) > 0:
- # Some header was emitted and didn't exceed budget
- remaining_budget = file_budget - file_lines_emitted
- else:
- # Either no header or header exactly filled budget
- remaining_budget = file_budget - len(header_lines_to_emit)
-
- hunks_emitted = 0
- for hunk_idx, hunk in enumerate(block["hunks"]):
- hunk_size = len(hunk)
-
- if hunk_size <= remaining_budget:
- # Hunk fits
- for hline in hunk:
- if len(hline) > max_line_chars:
- shortened = hline[:max_line_chars] + f" [line truncated: {len(hline)} chars]"
- patch_lines.append(shortened)
- else:
- patch_lines.append(hline)
- shown += 1
- remaining_budget -= hunk_size
- hunks_emitted += 1
- else:
- # Hunk doesn't fit
- truncated = True
-
- if hunks_emitted == 0:
- # No hunk emitted yet: either emit partial hunk or full accounting
- if remaining_budget > 0:
- # Emit first remaining_budget lines of this hunk
- for hline in hunk[:remaining_budget]:
- if len(hline) > max_line_chars:
- shortened = hline[:max_line_chars] + f" [line truncated: {len(hline)} chars]"
- patch_lines.append(shortened)
- else:
- patch_lines.append(hline)
- shown += 1
- # Emit truncation marker
- remaining_hunk_lines = hunk_size - remaining_budget
- patch_lines.append(f"[hunk truncated: +{remaining_hunk_lines} more lines]")
- else:
- # Budget is 0 or negative, emit full accounting
- remaining_hunk_lines = hunk_size + sum(len(h) for h in block["hunks"][hunk_idx + 1:])
- remaining_hunks = len(block["hunks"]) - hunk_idx
- patch_lines.append(f"[+{remaining_hunk_lines} more lines in {remaining_hunks} more hunks of {block_path}]")
- else:
- # Some hunks emitted, more remain
- remaining_hunk_lines = hunk_size + sum(len(h) for h in block["hunks"][hunk_idx + 1:])
- remaining_hunks = len(block["hunks"]) - hunk_idx
- patch_lines.append(f"[+{remaining_hunk_lines} more lines in {remaining_hunks} more hunks of {block_path}]")
- break
-
- if hunks_emitted > 0 or len(header_lines_to_emit) > 0:
- files_shown += 1
-
- summary = {
- "shown": shown,
- "total": total,
- "files_shown": files_shown,
- "files_total": files_total,
- "truncated": truncated,
- }
-
- return patch_lines, summary
-
-
-def format_summary(summary: dict) -> str:
- """Format the fixed-form summary line."""
- return (
- f"[diff: shown={summary['shown']} total={summary['total']} "
- f"files_shown={summary['files_shown']} files_total={summary['files_total']} "
- f"truncated={'true' if summary['truncated'] else 'false'}]"
- )
-
-
-def show_commit(
- repo_dir: Path,
- sha: str,
- *,
- only: Optional[str] = None,
- max_diff_lines: int = 300,
- per_file_lines: int = 120,
- max_line_chars: int = 400,
- skip_globs: list[str] = (),
-) -> str:
- """Run git, emit the full capped diff text.
-
- Raises ValueError for a bad sha (exit 1) or KeyError for --only path not found (exit 2).
- """
- # Resolve the sha first, separately from the parent check
- resolve_result = subprocess.run(
- ["git", "-C", str(repo_dir), "rev-parse", "--verify", "--quiet", f"{sha}^{{commit}}"],
- capture_output=True, text=True, errors="replace", check=False
- )
- if resolve_result.returncode != 0:
- raise ValueError(f"Bad commit: {sha}")
-
- full_sha = resolve_result.stdout.strip()
-
- # Check if this is a root commit or has a parent
- parent_result = subprocess.run(
- ["git", "-C", str(repo_dir), "rev-parse", "--verify", "--quiet", f"{full_sha}^1"],
- capture_output=True, text=True, errors="replace", check=False
- )
-
- if parent_result.returncode == 0:
- # Has a parent: diff against first parent
- base = parent_result.stdout.strip()
- stat_result = subprocess.run(
- ["git", "-C", str(repo_dir), "diff", "--no-color", "-M", "--numstat", base, full_sha],
- capture_output=True, text=True, errors="replace", check=True
- )
- patch_result = subprocess.run(
- ["git", "-C", str(repo_dir), "diff", "--no-color", "-M", "-p", base, full_sha],
- capture_output=True, text=True, errors="replace", check=True
- )
- else:
- # Root commit: use git show
- stat_result = subprocess.run(
- ["git", "-C", str(repo_dir), "show", "--no-color", "-M", "--format=", "--numstat", full_sha],
- capture_output=True, text=True, errors="replace", check=True
- )
- patch_result = subprocess.run(
- ["git", "-C", str(repo_dir), "show", "--no-color", "-M", "--format=", "-p", full_sha],
- capture_output=True, text=True, errors="replace", check=True
- )
-
- # Parse stat
- numstat_lines = [
- line.strip()
- for line in stat_result.stdout.split("\n")
- if line.strip()
- ]
-
- # Parse patch
- blocks = split_patch(patch_result.stdout)
-
- # Cap the patch
- patch_lines, summary = cap_patch(
- blocks,
- numstat_lines,
- per_file_lines=per_file_lines,
- max_diff_lines=max_diff_lines,
- max_line_chars=max_line_chars,
- skip_globs=list(skip_globs),
- only=only,
- )
-
- # Build output
- lines = []
- lines.append(f"# commit {full_sha}")
- lines.append("# stat (complete)")
- lines.extend(numstat_lines)
- lines.append("# patch")
- lines.extend(patch_lines)
- lines.append(format_summary(summary))
-
- return "\n".join(lines) + "\n"
-
-
-def main():
- parser = argparse.ArgumentParser(
- description="Event-sourcing extractor for OKF bundle reconstruction"
- )
-
- # Check if this is a coverage check (--check-coverage mode)
- if "--check-coverage" in sys.argv:
- parser.add_argument("--check-coverage", nargs=2, metavar=("EVENTS_JSONL", "BUNDLE_DIR"),
- help="Check coverage of events in bundle")
- args = parser.parse_args()
- if args.check_coverage:
- events_path = Path(args.check_coverage[0])
- bundle_dir = Path(args.check_coverage[1])
- unmapped = check_coverage(events_path, bundle_dir)
- if unmapped:
- print(f"Unmapped events (exit 1):")
- for event_id in unmapped:
- print(f" {event_id}")
- sys.exit(1)
- else:
- print("All events mapped (exit 0)")
- sys.exit(0)
-
- # Normal extraction mode / show mode
- parser.add_argument("repo_dir", nargs="?", help="Repository directory")
- parser.add_argument("--out", default="events.jsonl", help="Output file")
- parser.add_argument(
- "--branch", action="append", default=[], help="Git branch(es) to extract"
- )
- parser.add_argument(
- "--no-sessions", action="store_true", help="Skip session extraction"
- )
- parser.add_argument(
- "--sessions-dir", type=Path, help="Override sessions directory"
- )
- parser.add_argument(
- "--max-text", type=int, default=2000, help="Max text length per field"
- )
- parser.add_argument(
- "--skip-globs", action="append", default=[], help="Additional skip globs"
- )
- parser.add_argument(
- "--show", help="Show capped diff for a commit (exit 0 on success, 1 on bad sha)"
- )
- parser.add_argument(
- "--only", help="Restrict patch to a single path (with --show)"
- )
- parser.add_argument(
- "--max-diff-lines", type=int, default=300, help="Max diff lines total"
- )
- parser.add_argument(
- "--per-file-lines", type=int, default=120, help="Max diff lines per file"
- )
- parser.add_argument(
- "--max-line-chars", type=int, default=400, help="Max characters per line"
- )
-
- args = parser.parse_args()
-
- if not args.repo_dir:
- print("Error: repo_dir is required", file=sys.stderr)
- sys.exit(1)
-
- repo_dir = Path(args.repo_dir).resolve()
- if not repo_dir.exists():
- print(f"Error: {repo_dir} does not exist", file=sys.stderr)
- sys.exit(1)
-
- # Handle --show mode
- if args.show:
- try:
- output = show_commit(
- repo_dir,
- args.show,
- only=args.only,
- max_diff_lines=args.max_diff_lines,
- per_file_lines=args.per_file_lines,
- max_line_chars=args.max_line_chars,
- skip_globs=args.skip_globs,
- )
- sys.stdout.write(output)
- sys.exit(0)
- except ValueError as e:
- # Bad sha
- print(str(e), file=sys.stderr)
- sys.exit(1)
- except KeyError as e:
- # --only path not found
- print(e.args[0], file=sys.stderr)
- sys.exit(2)
-
- # Normal extraction mode
- # Extract events
- git_events = git_log(repo_dir, args.branch)
- session_events = [] if args.no_sessions else sessions_from_transcripts(
- repo_dir, args.sessions_dir
- )
-
- # Merge and sort
- events = merge_and_sort(git_events, session_events)
-
- # Apply skip rules and truncate
- skip_globs = args.skip_globs
- skip_counts = {}
-
- for event in events:
- skip_id = apply_skip_rules(event, skip_globs)
- if skip_id:
- event["skip"] = skip_id
- skip_counts[skip_id] = skip_counts.get(skip_id, 0) + 1
-
- # Truncate text fields
- for key in ["subject", "body", "outcome", "user"]:
- if key in event and isinstance(event[key], str):
- event[key] = truncate_text(event[key], args.max_text)
-
- # Emit ts as an ISO8601 UTC string; the epoch float was only needed
- # for sorting.
- event["ts"] = epoch_to_iso_z(event["ts"])
-
- # Write JSONL (deterministic order)
- out_path = Path(args.out)
- out_path.parent.mkdir(parents=True, exist_ok=True)
-
- with open(out_path, "w", encoding="utf-8") as f:
- for event in events:
- # Sort keys for determinism
- sorted_event = {k: event[k] for k in sorted(event.keys())}
- f.write(json.dumps(sorted_event, separators=(',', ':'), sort_keys=True) + "\n")
-
- # Report
- print(f"Extracted {len(events)} events")
- print(f" Git: {len(git_events)}")
- print(f" Sessions: {len(session_events)}")
- if skip_counts:
- print("Skipped:")
- for rule_id, count in sorted(skip_counts.items()):
- print(f" {rule_id}: {count}")
-
-
-if __name__ == "__main__":
- main()
diff --git a/external/okf/skills/okf/SKILL.md b/external/okf/skills/okf/SKILL.md
deleted file mode 100644
index d874251..0000000
--- a/external/okf/skills/okf/SKILL.md
+++ /dev/null
@@ -1,159 +0,0 @@
----
-name: okf
-description: >-
- 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.
-user-invocable: true
-argument-hint: "[produce|maintain|consume] [path]"
-allowed-tools: Read Write Edit Grep Glob Bash
----
-
-# Open Knowledge Format (OKF) skill
-
-OKF represents knowledge as a directory of markdown files with YAML frontmatter.
-It is minimal by design: no schema registry, no runtime, no SDK. Your job is to
-produce, maintain, and consume OKF bundles **conformant with the spec**, not your
-memory of it.
-
-**Always read the canonical spec before non-trivial work:**
-[reference/SPEC.md](reference/SPEC.md). It is the verbatim OKF v0.2 specification
-and the source of truth for every rule below.
-
-## The one hard rule
-
-A bundle is conformant (§11) iff: every non-reserved `.md` file has a parseable
-YAML frontmatter block, and every such block has a **non-empty `type`** field.
-Everything else is soft guidance. Consumers MUST tolerate missing optional
-fields, unknown types, and broken links — never reject a bundle over them.
-
-## Conventions to apply
-
-- **One concept = one file.** The file path (minus `.md`) is the concept ID.
-- **Frontmatter:** `type` is required. Add `title`, `description`, `tags` when
- they aid consumption; add `resource` (a canonical URI) only for concepts bound
- to a real asset — omit it for abstract concepts.
-- **Body:** prefer structural markdown (headings, tables, lists, fenced code).
- Conventional headings: `# Schema`, `# Examples`, `# Computation`.
-- **Cross-links:** standard markdown links; prefer absolute bundle-relative
- form (`/services/auth-api.md`). A link asserts a relationship; its *kind* lives
- in the surrounding prose, not the link.
-- **Reserved files:** `index.md` (directory listing, no frontmatter, except the
- bundle-root index, which may carry `okf_version` and this plugin's
- `upkeep: enforced` opt-in flag) and `log.md` (ISO-dated history, newest
- first). Never use these names for concepts.
-- **What goes in `log.md`:** lifecycle events only, written in the bundle-root
- `log.md`: a bundle **created**, a concept **deprecated** or retired, one
- concept **superseding** another, concepts **regenerated** after their source
- changed, a **verification** pass. Routine edits to a concept do not get an
- entry: its `generated` and git already record them. A log is append-only, so
- never rewrite or drop an existing entry.
-
-## The v0.2 families (all optional, all worth filling)
-
-- **Trust (§5.2):** `generated: { by, at }` — who produced the current content
- and when. `verified: [{ by, at }]` — who confirmed it since (a bare mapping is
- one entry). Write `by` in the **actor convention** (§7): `/`
- for an agent, `human:` for a person, `process:` for an automated job.
- Use `human:` whenever a person authored or signed off — consumers key trust
- tiers off that prefix.
-- **Lifecycle (§5.4–5.5):** `status: draft|stable|deprecated` (absent means
- stable) and `stale_after`, an absolute ISO 8601 datetime (a bare date is
- tolerated), not a TTL.
-- **Provenance (§5.1):** `sources: [{ id, resource, title, author,
- usage_count, last_modified }]` plus a `usage_window: { from, to }` sibling of
- `sources` framing every `usage_count` (an entry may carry its own to override
- it); a `usage_count` without a window warns. `resource` is required per entry
- and may be a URL, a bundle path, or a scope descriptor. Attribute a specific
- claim with a markdown footnote whose label is the source's `id`:
- `…sharded daily.[^ga4-schema]` plus a `[^ga4-schema]: …` definition. The label
- is the join key, it must match a `sources[].id`.
-- **Attestation (§10):** a sanctioned computation is its own concept,
- `type: Attested Computation`, carrying `runtime` (required), `parameters`,
- `executor`, `attester`, and the computation itself under `# Computation` (or a
- `computation:` path). Concepts that need the value link to it. Never inline a
- number's SQL into the concept that narrates it.
-
-**Reading a v0.1 bundle?** Two constructs were superseded (§13.1): `timestamp`
-is now `generated.at`, and a body `# Citations` list is now `sources`. Read both,
-write v0.2 — and when you touch a legacy concept in **maintain** mode, migrate
-its frontmatter as part of the edit. The validator warns on both.
-
-Templates to copy: [concept](templates/concept.md), [index](templates/index.md),
-[log](templates/log.md).
-
-## Default bundle location
-
-Use `.okf/` at the repository root unless the project already uses another
-location. Commit it alongside the code it describes — knowledge as code.
-
-## Modes
-
-### produce — create or extend a bundle
-
-**Starting a brand-new bundle?** Use the init fast-path instead of hand-writing
-the first files — it scaffolds a conformant `index.md`, `log.md`, and a
-`getting-started.md` concept with full recommended frontmatter in one shot:
-
-```bash
-uv run "${CLAUDE_SKILL_DIR}/scripts/okf_init.py" [--title "..."]
-```
-
-It refuses to touch a directory that already has `.md` files unless `--force`
-is given. Then extend it:
-
-1. Read [reference/SPEC.md](reference/SPEC.md).
-2. Pick the source(s): **code** (derive concepts from source, READMEs,
- docstrings, config), **docs/wiki** (distill pages into concepts, record the
- originals in `sources`), **manual** (decisions, playbooks, metrics).
-3. Choose a directory layout by domain (e.g. `services/`, `datasets/`,
- `decisions/`). One concept per file.
-4. Write each concept from [templates/concept.md](templates/concept.md): set a
- descriptive `type`, fill recommended fields, record `generated` and the
- `sources` you actually read, cross-link related concepts.
-5. Add/refresh `index.md` per directory (and `okf_version: "0.2"` in the root
- index). A new bundle gets a dated **Creation** entry in `log.md`. Extending
- an existing one does not.
-6. Validate (see below). Fix every error before finishing.
-
-### maintain — keep a bundle in sync with reality
-1. Identify which concepts the change affects (search by `resource`, path, or
- topic). This bookkeeping is exactly what agents are good at — touch every
- affected file in one pass.
-2. Update the body and `generated.at` (with your own actor in `generated.by`);
- fix or add cross-links; create new concepts for new assets; mark removed
- assets `status: deprecated` and note the deprecation in `log.md` rather than
- silently deleting context. Facing a whole v0.1 bundle rather than a stray
- field? Do not hand-edit it — run the validator's `--migrate` once.
-3. Update the relevant `index.md` files. Append a dated `log.md` entry only if
- the change is a lifecycle event (see *What goes in `log.md`*).
-4. Validate.
-
-### consume — use a bundle as context
-1. Read the bundle-root `index.md` first for progressive disclosure, then follow
- links only into the concepts relevant to the task.
-2. Weigh what you read: `status: draft`/`deprecated`, a `stale_after` already
- past, or no `verified` entry all mean "check before relying on this". Treat
- broken links as not-yet-written knowledge, not errors.
-3. Need a number an `Attested Computation` covers? Run *its* computation with
- values bound to the declared `parameters` — never write your own query.
-4. If you learn something durable while working, switch to **maintain** and
- write it back.
-
-## Validation (do this before declaring done)
-
-Never eyeball conformance — run the deterministic checker. Invoke the companion
-**`validate`** skill (`/okf:validate --strict`), which ships the
-checker. If that skill is not installed, run it directly:
-
-```bash
-uv run "${CLAUDE_SKILL_DIR}/../validate/scripts/okf_validate.py" --strict
-```
-
-Resolve every `ERROR` (hard §11 failures). Under `--strict` warnings also fail
-the run (exit 1), so clear them too; drop `--strict` (or use `--max-warnings N`)
-if you only want §11 conformance gated.
diff --git a/external/okf/skills/okf/reference/APACHE-2.0.txt b/external/okf/skills/okf/reference/APACHE-2.0.txt
deleted file mode 100644
index d645695..0000000
--- a/external/okf/skills/okf/reference/APACHE-2.0.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/external/okf/skills/okf/reference/SPEC.md b/external/okf/skills/okf/reference/SPEC.md
deleted file mode 100644
index 68629bf..0000000
--- a/external/okf/skills/okf/reference/SPEC.md
+++ /dev/null
@@ -1,1016 +0,0 @@
-
-
-# Open Knowledge Format (OKF)
-
-**Version 0.2**
-
-OKF is an open, human- and agent-friendly format for representing
-*knowledge*: the metadata, context, and curated insight that surrounds
-data and systems. It is designed to be authored by people, generated by
-agents, exchanged across organizations, and consumed by both.
-
-The format is intentionally minimal: a directory of markdown files with
-YAML frontmatter. There is no schema registry, no central authority, and
-no required tooling. If you can `cat` a file, you can read OKF; if you
-can `git clone` a repo, you can ship it.
-
-This document is self-contained: it specifies everything needed to
-produce and consume OKF v0.2. A summary of what changed from v0.1 is in
-§13.
-
----
-
-## 1. Motivation
-
-The space of knowledge representation for AI agents is evolving quickly,
-and many incompatible conventions are emerging. OKF takes the position
-that knowledge is best represented in commonly accessible, established
-formats that are:
-
-- **Readable** by humans without tooling.
-- **Parseable** by agents without bespoke SDKs.
-- **Diffable** in version control.
-- **Portable** across tools, organizations, and time.
-
-Increasingly, a knowledge corpus is not authored once and then read: it
-is **continuously written and maintained by agents**. When most concepts
-are machine-generated, a consumer needs answers that a plain
-markdown-plus-frontmatter convention does not make first-class:
-
-1. What was this created from, and how was it verified? (**provenance**)
-2. How much should I trust it? (**trust**)
-3. Is it still true? (**freshness**)
-4. Is it the current version? (**lifecycle**)
-5. Was this number produced the way we said it must be? (**attestation**)
-
-OKF v0.2 makes provenance, trust, lifecycle, and attestation first-class
-while keeping the format minimally opinionated. The format is minimally
-opinionated. It standardizes only the small set of structural conventions
-needed to make a knowledge corpus self-describing — anything beyond that
-is left to the producer.
-
-### Goals
-
-1. Define a universal format that **producers** (people, agents, export
- pipelines) can write into.
-2. Inform how **consumers** (agents, UIs, search indexes, deterministic
- code) should read and traverse it.
-3. Facilitate **exchange** of knowledge across systems and organizations.
-4. Standardize the small set of frontmatter fields that make an
- agent-maintained corpus **trustable**, without prescribing any runtime.
-
-### Non-goals
-
-- Defining a fixed taxonomy of concept types.
-- Prescribing storage, serving, or query infrastructure.
-- Replacing domain-specific schemas (Avro, Protobuf, OpenAPI, and so on).
- OKF *references* them; it does not subsume them.
-- Specifying a packaging or invocation standard for the code an executor
- or attester points at. OKF fixes the interface, not the packaging.
-
----
-
-## 2. Terminology
-
-- **Knowledge Bundle** (or **bundle**): A self-contained, hierarchical
- collection of knowledge documents. The unit of distribution.
-- **Concept**: A single unit of knowledge within a bundle, represented as
- one markdown document. It may describe a tangible asset (a table, an
- API), an abstract idea (a metric, a business process), or anything in
- between.
-- **Concept ID**: The path of the concept's file within the bundle, with
- the `.md` suffix removed.
-- **Frontmatter**: A YAML metadata block delimited by `---` at the top of
- a markdown file.
-- **Body**: Everything in the file after the frontmatter.
-- **Link**: A standard markdown link from one concept to another, used to
- express relationships beyond the implicit parent/child hierarchy.
-- **Source**: A material a concept derives from, external or internal to
- the bundle, recorded in the `sources` frontmatter field.
-- **Provenance**: The set of sources a concept derives from.
-- **Credibility signal**: An objective, per-source fact (`author`,
- `usage_count`, `last_modified`) used to infer trust; OKF records the
- signals, not a verdict (see §5.1).
-- **Actor**: A string identifying who or what performed an action, using
- the convention `/` for agents, `human:` for
- people, and `process:` for automated processes (see §7).
-- **Trust tier**: A level derived from a concept's `verified` field:
- unverified, machine-confirmed, or human-reviewed (see §5.3).
-- **Attested Computation**: A concept (`type: Attested Computation`)
- carrying a sanctioned way to compute a value, so a consumer can confirm
- the value was produced by running it (see §10).
-- **Executor**: Run instructions or code that executes a computation and
- returns a receipt (see §10.2).
-- **Receipt**: The evidence a run returns, shaped by `executor.receipt`; a
- runtime artifact, not stored in the bundle (see §10).
-- **Attester**: Deterministic (no-LLM) code that inspects a receipt and
- returns a verdict (see §10.2).
-
----
-
-## 3. Bundle structure
-
-A bundle is a directory tree of markdown files. The directory structure
-is independent of the domain: producers organize concepts however makes
-sense for the knowledge being captured.
-
-```
-path/to/bundle/
- index.md # Optional. Directory listing for progressive disclosure.
- log.md # Optional. Chronological history of updates.
- .md # A concept at the bundle root.
- / # Subdirectories organize concepts into groups.
- index.md
- .md
- /
- ...
-```
-
-A bundle MAY be distributed as:
-
-- A git repository (recommended, since it provides history, attribution,
- and diffs).
-- A tarball or zip archive of the directory.
-- A subdirectory within a larger repository.
-
-### 3.1 Reserved filenames
-
-The following filenames have defined meaning at any level of the
-hierarchy and MUST NOT be used for concept documents:
-
-| Filename | Purpose |
-|------------|----------------------------------|
-| `index.md` | Directory listing. See §8. |
-| `log.md` | Update history. See §9. |
-
-All other `.md` files are concept documents.
-
-Tags remain a first-class concept through the `tags` frontmatter field
-(§4.1). OKF does not specify a separate file format for aggregating
-documents by tag; a consumer that wants a tag-browsing view can
-synthesize one at consumption time by scanning frontmatter.
-
----
-
-## 4. Concept documents
-
-Every concept is a UTF-8 markdown file with two parts:
-
-1. A **YAML frontmatter block**, delimited by `---` on its own line at the
- start of the file and a closing `---` on its own line.
-2. A **markdown body**, containing free-form content.
-
-### 4.1 Frontmatter
-
-```yaml
----
-type: # REQUIRED
-title:
-description:
-resource:
-tags: [, , ...] # Optional
-# ... trust, lifecycle, provenance, and computation families (see §5, §10)
-# ... other producer-defined key/value pairs
----
-```
-
-**Required:**
-
-- `type`: A short string identifying the kind of concept. Consumers use it
- for routing, filtering, and presentation. Example values:
- `BigQuery Table`, `BigQuery Dataset`, `API Endpoint`, `Metric`,
- `Playbook`, `Reference`, `Attested Computation`.
-
- Type values are **not** registered centrally. Producers SHOULD pick
- values that are descriptive and self-explanatory; consumers MUST
- tolerate unknown types gracefully, typically by treating them as generic
- concepts.
-
-`type` is the only always-required key; a concept carrying just `type` is
-fully conformant (§11).
-
-**Recommended:**
-
-- `title`: Human-readable display name. If omitted, consumers MAY derive a
- title from the filename.
-- `description`: A single sentence summarizing the concept. Used by
- `index.md` generators, search snippets, and previews.
-- `resource`: A URI that uniquely identifies the underlying asset the
- concept describes. Absent for concepts that describe abstract ideas
- rather than physical resources.
-- `tags`: A YAML list of short strings for cross-cutting categorization.
-
-The optional **provenance**, **trust**, and **lifecycle** families (§5) and
-the **computation** fields for Attested Computation concepts (§10) may also
-appear.
-
-**Extensions:** Producers MAY include any additional keys. Consumers
-SHOULD preserve unknown keys when round-tripping and MUST NOT reject
-documents with unrecognized fields.
-
-### 4.2 Body
-
-The body is standard markdown. Producers SHOULD favor structural markdown
-(headings, lists, tables, fenced code blocks) over freeform prose, since
-structure aids both human reading and agent retrieval.
-
-There are no required body sections. The following headings have
-**conventional** meaning and SHOULD be used when applicable:
-
-| Heading | Purpose |
-|-----------------|--------------------------------------------------------|
-| `# Schema` | Structured description of an asset's columns/fields. |
-| `# Examples` | Concrete usage examples, often as fenced code blocks. |
-| `# Computation` | The sanctioned computation of an Attested Computation. See §10. |
-
-Per-claim attribution to external sources uses markdown footnotes keyed to
-`sources` entries rather than a body citations list (§5.1).
-
-### 4.3 Example: a concept bound to a resource
-
-```markdown
----
-type: BigQuery Table
-title: Customer Orders
-description: One row per completed customer order across all channels.
-resource: https://console.cloud.google.com/bigquery?p=acme&d=sales&t=orders
-tags: [sales, orders, revenue]
-generated: { by: reference_agent/gemini-2.5-pro, at: 2026-05-28T14:30:00Z }
----
-
-# Schema
-
-| Column | Type | Description |
-|---------------|-----------|------------------------------------------|
-| `order_id` | STRING | Globally unique order identifier. |
-| `customer_id` | STRING | Foreign key into [customers](/tables/customers.md). |
-| `total_usd` | NUMERIC | Order total in US dollars. |
-| `placed_at` | TIMESTAMP | When the customer submitted the order. |
-
-# Joins
-
-Joined with [customers](/tables/customers.md) on `customer_id`.
-```
-
-### 4.4 Example: a concept not bound to a resource
-
-```markdown
----
-type: Playbook
-title: "Incident response: data freshness alert"
-description: Steps to triage a freshness alert on the orders pipeline.
-tags: [oncall, incident]
-generated: { by: human:ahormati, at: 2026-04-12T09:00:00Z }
----
-
-# Trigger
-
-A freshness alert fires when `orders` lags more than 30 minutes behind its
-expected SLA. See the [orders table](/tables/orders.md).
-
-# Steps
-
-1. Check the [ingestion job dashboard](https://example.com/dash).
-2. ...
-```
-
----
-
-## 5. Provenance, trust, and lifecycle
-
-These frontmatter families make "where did this come from," "how much
-should I trust it," and "is it still current" answerable from frontmatter.
-All are optional. Their absence carries meaning: an unverified concept is
-distinguishable from a verified one, but is never rejected (§11).
-
-Every timestamp-valued key in OKF is an ISO 8601 datetime with an explicit
-UTC offset, for example `2026-06-30T14:00:00Z`.
-
-### 5.1 Provenance: `sources`
-
-`sources` records the materials a concept derives from, external or
-internal to the bundle.
-
-```yaml
-sources:
- - id: ga4-schema
- resource: https://developers.google.com/analytics/bigquery/export-schema
- title: GA4 BigQuery Export schema
- author: team:ga4-docs
- usage_count: 5000
- last_modified: 2026-05-30T00:00:00Z
-usage_window: { from: 2026-06-01T00:00:00Z, to: 2026-06-30T00:00:00Z }
-```
-
-Each `sources` entry:
-
-- `resource`: REQUIRED within an entry. Names either a concrete artifact a
- consumer can follow (an absolute URL, a bundle-relative path, or a path
- into a `references/` subdirectory, §6) or a population or scope descriptor
- it cannot (for example `all queries in BigQuery project X`).
-- `id`: Optional. A stable key used to attribute individual claims (see
- below). SHOULD be present when the body cites the source.
-- `title`: Optional. Human-readable label for the source.
-- The optional credibility signals `author`, `usage_count`, and
- `last_modified`, described next.
-
-**Source credibility signals.** OKF records objective, per-source signals
-so a consumer can judge how much to trust a concept by judging the sources
-it was extracted from. It does not store a credibility score: a score is
-subjective, unportable across consumers, and goes stale. Credibility is
-*inferred* from the signals, the same way trust tiers are (§5.3), not
-stored. Each signal is optional and lives on a `sources` entry:
-
-- `author`: Who or what produced the source, in the actor convention (§7).
- An authority signal.
-- `usage_count`: How often `resource` was exercised (dashboard views, query
- executions, page reads) over `usage_window`. An adoption and liveness
- signal. For a single artifact it is that artifact's own exercise count;
- for a scope descriptor it is the number of exercises within the scope that
- touch the concept.
-- `last_modified`: When the source itself last changed. A recency signal,
- distinct from `generated.at` (§5.2), which records when the concept was
- written.
-- `usage_window`: Written once as a sibling of `sources`, it frames every
- `usage_count` with a `{ from, to }` datetime range. A single entry MAY
- carry its own `usage_window` to override the shared one.
-
-`usage_count` is a coarse signal. It is comparable at the
-alive-versus-dead and order-of-magnitude level, and against a source's own
-history over time, but not as a precise cross-kind ranking: a scheduled
-query's executions and a human's deliberate dashboard views do not carry
-equal weight. Consumers SHOULD read it as liveness and trend, not as a
-score.
-
-Lineage is expressed through links, not a dedicated field. When a
-`resource` points at another OKF concept, the derivation edge already
-exists in the bundle graph (§6), so a consumer MAY recurse into that
-source's own `sources` and let credibility propagate. External leaf sources
-carry only their intrinsic signals. Deeper lineage (an explicit external
-`derived_from`, or data lineage) is out of scope for v0.2.
-
-**Per-claim attribution.** To attribute a specific claim, use a markdown
-footnote whose label is a `sources[].id`:
-
-```markdown
-The `events_` table is sharded daily as `events_YYYYMMDD`.[^ga4-schema]
-
-[^ga4-schema]: GA4 BigQuery Export schema
-```
-
-The footnote label is the join key into `sources`; consumers resolve
-attribution through the matching entry, not by parsing the footnote prose.
-Labels are keyed rather than positional (`sources[0]`) because agents
-constantly rewrite these documents: a positional index misattributes
-silently the moment the list is reordered, whereas a stable `id` survives
-reordering.
-
-### 5.2 Trust: `generated` and `verified`
-
-`generated` records how the current content was produced. `verified`
-records who or what has confirmed the content against its sources or
-`resource`. They are kept distinct because who *wrote* a concept need not
-be who *confirmed* it.
-
-```yaml
-generated: { by: reference_agent/gemini-2.5-pro, at: 2026-06-20T22:53:05Z }
-```
-
-- `generated.by`: REQUIRED within `generated`. An actor (§7).
-- `generated.at`: An ISO 8601 datetime marking the content's last
- meaningful change. Consumers use it to tell a recent edit from a stale
- fact.
-
-```yaml
-verified:
- - { by: human:ahormati, at: 2026-06-25T09:00:00Z }
- - { by: process:finance-nightly, at: 2026-06-26T02:00:00Z }
-```
-
-- `verified`: A list of verification events, each with `by` (an actor) and
- `at` (an ISO 8601 datetime). Multiple entries capture independent
- checks, for example a human sign-off plus a nightly process. "How
- recently" is the latest `at`.
-- `verified` is independent of `generated.at`: content can change without
- re-confirmation, and facts can be re-confirmed without regeneration.
-- A single verifier MAY be written as one `{ by, at }` mapping without the
- list dash. Consumers MUST treat a bare mapping as a one-element list:
-
-```yaml
-verified: { by: human:ahormati, at: 2026-06-25T09:00:00Z }
-```
-
-### 5.3 Trust tiers
-
-Consumers derive a trust tier from `verified`, lowest to highest:
-
-- No `verified` key ⇒ **unverified**.
-- `verified` by non-`human:` actors only ⇒ **machine-confirmed**.
-- `verified` by a `human:` actor ⇒ **human-reviewed**.
-
-A concept with no trust frontmatter is still consumable; consumers MUST
-NOT reject it (§11). Trust tiers are advisory signals, not access control.
-
-### 5.4 Lifecycle: `status`
-
-```yaml
-status: stable # draft | stable | deprecated
-```
-
-- `draft`: not yet reviewed; possibly incomplete.
-- `stable`: default; ready for consumption.
-- `deprecated`: kept for links and history; no longer current.
-
-Absent `status` ⇒ `stable`.
-
-### 5.5 Lifecycle: `stale_after`
-
-```yaml
-stale_after: 2026-09-23T00:00:00Z # content is stale on/after this instant
-```
-
-Optional. An absolute instant. A concept is stale when
-`now >= stale_after`. An absolute instant, not a relative TTL, keeps the
-staleness decision a plain comparison with no reference to when the
-concept was read.
-
----
-
-## 6. Cross-linking and paths
-
-### 6.1 Links between concepts
-
-Concepts MAY link to other concepts using standard markdown links. Two
-forms are supported:
-
-- **Absolute (bundle-relative):** begins with `/`, interpreted relative to
- the bundle root. This is the **recommended** form because it is stable
- when documents are moved within their subdirectory.
-
- ```markdown
- See the [customers table](/tables/customers.md) for the join key.
- ```
-
-- **Relative:** a standard markdown relative path.
-
- ```markdown
- See the [neighboring concept](./other.md).
- ```
-
-A link from concept A to concept B asserts a *relationship*. The specific
-kind (parent/child, references, joins-with, depends-on) is conveyed by the
-surrounding prose, not by the link itself. Consumers that build a graph
-view typically treat all links as directed edges of an untyped
-relationship.
-
-Consumers MUST tolerate broken links: a link whose target does not exist
-in the bundle is not malformed; it may simply represent not-yet-written
-knowledge.
-
-### 6.2 Path-valued fields
-
-Several fields name a path or URI: `resource`, `sources[].resource`,
-`computation`, `executor.resource`, and `attester.resource` (§10). A
-`sources[].resource` may instead be a scope descriptor (§5.1), in which
-case it is not a path. Each path-valued field accepts:
-
-- an absolute URL (for example `https://...`),
-- a bundle-relative path beginning with `/`, or
-- a relative path (for example `../computations/revenue.md`).
-
-### 6.3 The `references/` convention
-
-A `references/` subdirectory conventionally mirrors external material, run
-instructions, or code as first-class concepts within the bundle. Sources,
-executors, and attesters commonly point into it (for example
-`references/attesters/revenue.py`). It is a naming convention, not a
-requirement.
-
----
-
-## 7. Actor convention
-
-Fields that record an identity (`generated.by`, `verified[].by`) use a
-single actor convention:
-
-- `/` for agents and tools, for example
- `reference_agent/gemini-2.5-pro`.
-- `human:` for a person, for example `human:ahormati`.
-- `process:` for an automated process, for example
- `process:finance-nightly`.
-
-Consumers that classify trust (§5.3) key off the `human:` prefix, so
-producers MUST use it for hand-authored or human-confirmed content.
-
----
-
-## 8. Index files
-
-An `index.md` file MAY appear in any directory, including the bundle root.
-It enumerates the directory's contents to support **progressive
-disclosure**: letting a human or agent see what is available before
-opening individual documents.
-
-Index files contain no frontmatter, with one exception: a bundle-root
-`index.md` MAY carry an `okf_version` key (§12). The body uses one or more
-sections, each grouping concepts under a heading:
-
-```markdown
-# Section / Group Heading
-
-* [Title 1](relative-url-1) - short description of item 1
-* [Title 2](relative-url-2) - short description of item 2
-
-# Another Section
-
-* [Subdirectory](subdir/) - short description of the subdirectory
-```
-
-Entries SHOULD include the description from the linked concept's
-frontmatter. Producers MAY generate `index.md` automatically; consumers
-MAY synthesize one on the fly when none is present.
-
----
-
-## 9. Log files
-
-A `log.md` file MAY appear at any level of the hierarchy to record the
-history of changes to that scope. The format is a flat list of
-date-grouped entries, newest first:
-
-```markdown
-# Directory Update Log
-
-## 2026-05-22
-* **Update**: Added a BigQuery table reference for [Customer Metrics](/tables/customer-metrics.md).
-* **Creation**: Established the [Dataplex Playbook](/playbooks/dataplex.md).
-
-## 2026-05-15
-* **Initialization**: Created foundational directory structure.
-```
-
-Date headings MUST use ISO 8601 `YYYY-MM-DD` form. Log entries are prose;
-the leading bold word (`**Update**`, `**Creation**`, `**Deprecation**`) is
-a convention, not a requirement.
-
----
-
-## 10. Attested computations concept
-
-An Attested Computation concept carries not just what a value *means* but a
-sanctioned way to *compute* it, so a consumer can confirm the agent ran the
-blessed computation instead of improvising its own. Provenance (§5.1)
-answers "where did this claim come from"; attestation answers "was this
-number produced the way we said it must be." OKF records the computation
-and the means to check it; it does not execute anything itself.
-
-### 10.1 A computation is its own concept
-
-A sanctioned computation is a standalone concept of
-`type: Attested Computation`. A concept that needs the value (a `Metric`, a
-`BigQuery Table`) links to it with a normal markdown link (§6). Three
-properties motivate the standalone concept:
-
-- **`runtime` defines what `parameters` mean.** A parameter is a SQL bind
- variable, a dbt var, or a Python argument depending on the runtime.
- Keeping `runtime` and `parameters` in one frontmatter makes the binding
- semantics self-evident.
-- **One computation, many consumers.** The same computation can back a
- metric, a dashboard concept, and a report; as a concept it is referenced
- once and reused.
-- **Trust state is per computation.** `verified`, `stale_after`, and a
- single `attester` describe one thing. Revenue, profit, and margin each
- verify and attest independently, which is three concepts, not three
- entries in one frontmatter.
-
-### 10.2 Contract fields
-
-The contract is the concept's top-level frontmatter. In addition to the
-provenance, trust, and lifecycle families (§5), an Attested Computation
-concept carries:
-
-- `runtime`: REQUIRED for this type. The single field that says how to run
- the computation, and so how the executor and attester interpret it and
- what `parameters` mean. Example values: `bigquery`, `postgres`, `dbt`,
- `python`, `Looker`.
-- `parameters`: A list of the typed, named holes the agent may fill. Each
- entry: `{ name, type, required }`. Binding semantics follow `runtime`.
-- `computation`: Optional. A path (§6.2) to a file holding the
- computation, used instead of an inline body fence (see §10.3). Absent ⇒
- the body `# Computation` fence is the computation.
-- `executor`: How the computation is run. `resource` names run
- instructions or code; a runner (an agent, or deterministic consumer
- code) follows it. `receipt` declares the fields a run must return, the
- evidence the attester inspects (for example a BigQuery `job_id` and the
- SQL the job actually executed).
-- `attester`: The deterministic check. `resource` names code (no LLM) that
- takes a receipt and returns a verdict. It is meant to run consumer-side.
-
-What sits behind a `resource` (a Skill, a script, a container) is a
-packaging choice; OKF fixes the interface, not the packaging (§1).
-
-```markdown
----
-type: Attested Computation
-title: Revenue for fiscal year
-description: Recognized revenue for a fiscal year, per Finance's definition.
-status: stable
-runtime: bigquery
-parameters:
- - { name: year, type: integer, required: true }
-executor:
- resource: references/skills/run-on-bq.md
- receipt: [job_id, executed_sql, result]
-attester:
- resource: references/attesters/revenue.py
-generated: { by: reference_agent/gemini-2.5-pro, at: 2026-06-20T22:53:05Z }
-verified: { by: human:ahormati, at: 2026-06-25T09:00:00Z }
-stale_after: 2026-09-23T00:00:00Z
-sources:
- - id: rev-policy
- resource: https://wiki.acme/finance/revenue-recognition
- title: Revenue recognition policy
----
-
-# Computation
-
- SELECT SUM(amount) AS revenue
- FROM finance.recognized_revenue
- WHERE fiscal_year = @year
-
-The computation binds only the declared `parameters`, per the recognition
-policy.[^rev-policy]
-
-[^rev-policy]: Revenue recognition policy
-```
-
-### 10.3 The computation
-
-Provide the computation in one of two ways:
-
-- **Inline:** a single fenced code block in the body under `# Computation`.
- Best for a short computation reviewed alongside the contract.
-- **File:** set `computation` to a path (§6.2) and omit the body fence.
- Best for a long or generated computation, or one already kept as a real
- file shared with non-OKF tooling.
-
-```yaml
-runtime: bigquery
-computation: references/computations/lib/revenue.sql
-parameters:
- - { name: year, type: integer, required: true }
-```
-
-The agent MAY only supply *values* for the declared `parameters`; it MUST
-NOT author or edit the computation. Binding `computation` with the
-parameter values into the executable artifact is the consumer's job, and
-the attester independently re-derives that same binding to compare against
-what actually ran. Because the comparison is on the expanded, compiled
-artifact the receipt carries (`executed_sql`, `compiled_sql`), a rewritten
-query, a swapped computation file, or a mutated dependency fails the check.
-A typed, parameter-only surface is what makes "did the sanctioned thing
-run" a mechanical comparison rather than a judgement call.
-
-### 10.4 Concepts that use a computation
-
-A document is rarely a single computation. An income-statement overview
-that discusses revenue, profit, and margin stays one readable concept and
-links to one Attested Computation per figure:
-
-```markdown
----
-type: Metric
-title: Revenue
-description: Recognized revenue for a fiscal year.
-tags: [finance, revenue]
-status: stable
-generated: { by: reference_agent/gemini-2.5-pro, at: 2026-06-20T22:53:05Z }
----
-
-# Definition
-
-Recognized revenue sums `amount` over rows booked to the fiscal year,
-computed by [the revenue computation](../computations/revenue.md).
-```
-
-Because each computation is its own concept, revenue can be fresh while
-profit is past its `stale_after`, and each attests on its own run.
-Co-locating them is a directory choice (a `computations/` folder with an
-`index.md`), not a frontmatter one.
-
-### 10.5 How a consumer uses it (informative)
-
-This subsection is informative, not normative. The runtime artifacts below
-are **not** stored in the bundle.
-
-1. **Discover** via `type: Attested Computation`, a frontmatter signal
- liftable into `index.md`; a consumer reaches one directly or by
- following a link from a concept that uses it.
-2. **Load** the contract from frontmatter and the computation from the
- body (or the file named by `computation`).
-3. **Parameterize**: the agent supplies values for the declared parameters.
-4. **Execute**: the executor runs the bound computation and returns a
- receipt shaped by `executor.receipt`.
-5. **Attest**: the consumer runs the attester over the receipt. It
- confirms provenance (the computation that ran equals `computation` bound
- with the claimed parameters, not agent-authored SQL) and fidelity (the
- displayed value matches the receipt's authoritative source, re-read by
- job id rather than taken from the agent's text).
-6. **Gate**: refuse to display a failing attestation; warn or refuse when
- `now >= stale_after`. On success, surface the verdict (for example a
- link to the job log) so trust is visible.
-
-### 10.6 Verification versus attestation
-
-`verified` (§5.2) and attestation are distinct, and both exist:
-
-- `verified` confirms the *definition* still matches policy. It is
- doc-level, slow, and recorded in the bundle.
-- Attestation confirms a single *run* produced the value the sanctioned
- way. It is per-call, runtime, and not stored in the bundle.
-
-A concept with a stale definition can still attest cleanly, and a
-freshly-verified definition still requires attestation on each run, which
-is why both are needed.
-
----
-
-## 11. Conformance
-
-A bundle is **conformant** with OKF v0.2 if:
-
-1. Every non-reserved `.md` file in the tree contains a parseable YAML
- frontmatter block.
-2. Every frontmatter block contains a non-empty `type` field.
-3. Every reserved filename (`index.md`, `log.md`) follows the structure in
- §8 and §9 respectively when present.
-
-When the trust, lifecycle, provenance, or computation families are
-present, producers SHOULD follow §5 through §10, and consumers:
-
-- MUST treat a bare `verified` mapping as a one-element list (§5.2).
-- MUST NOT reject a concept for missing any optional family (§5.3).
-- SHOULD derive trust tiers and staleness only from the fields specified
- here, and SHOULD surface, not silently drop, a failing attestation
- (§10.5).
-
-Consumers SHOULD treat all other constraints as soft guidance. In
-particular, consumers MUST NOT reject a bundle because of:
-
-- Missing optional frontmatter fields.
-- Unknown `type` values.
-- Unknown additional frontmatter keys.
-- Broken cross-links.
-- Missing `index.md` files.
-
----
-
-## 12. Versioning
-
-This document specifies OKF version **0.2**. Revisions are versioned as
-`.`:
-
-- A **minor** version bump introduces backward-compatible additions (new
- optional fields, new conventional section headings).
-- A **major** version bump may make breaking changes (renaming required
- fields, changing reserved filenames).
-
-Bundles MAY declare the version they target with `okf_version: "0.2"` in a
-bundle-root `index.md` frontmatter block (the only place frontmatter is
-permitted in an `index.md`). Consumers that do not understand the declared
-version SHOULD attempt best-effort consumption rather than refusing the
-bundle.
-
-### Considered and deferred
-
-The following are intentionally left to a future revision:
-
-- The full runtime protocol: receipt and verdict wire formats, and the
- attestation lifecycle around a run.
-- The attester ABI, portability, and sandboxing, likely bundled with
- future work on serving and Skills.
-- Attestation caching.
-- Semantic-layer templates (Looker, dbt) where the attester comparison
- shifts from SQL equality to model-and-binding equality.
-
----
-
-## 13. Changes from v0.1
-
-v0.2 supersedes OKF v0.1 and is a minor version bump under §12, except for
-two deliberate breaking changes called out below because they rename or
-retire v0.1 fields. A v0.1 bundle is consumable by a v0.2 consumer under
-the fallbacks noted here.
-
-### 13.1 Breaking changes
-
-- **`timestamp` is superseded by `generated.at`.** A concept's last
- content change is now recorded as `generated: { by, at }` (§5.2).
- Consumers MAY fall back to a legacy `timestamp` when `generated` is
- absent.
-- **The body `# Citations` list is superseded by `sources`.** Provenance
- moves to frontmatter (§5.1). Consumers SHOULD read `sources` and MAY
- still parse a legacy `# Citations` body list for v0.1 documents.
-
-### 13.2 Additive changes
-
-All of the following are additive: new optional keys, one new concept
-type, and one new conventional heading. Their absence yields a plain v0.1
-concept.
-
-- New frontmatter families: `sources` with its per-source credibility
- signals (`author`, `usage_count`, `last_modified`) and the `usage_window`
- sibling; `generated`, `verified`; `status`, `stale_after` (§5).
-- New concept type `Attested Computation` and its computation keys
- `runtime`, `parameters`, `computation`, `executor`, `attester` (§10).
-- New conventional body heading `# Computation` (§4.2).
-- The actor convention for `generated.by` and `verified[].by` (§7).
-
-Everything else (bundle structure, reserved filenames, the required
-`type`, recommended `title`/`description`/`resource`/`tags`, cross-linking,
-index files, log files, permissive conformance) is carried forward
-unchanged.
-
----
-
-## Appendix A: Worked example, an income statement
-
-One bundle exercising every family, shown as a v0.1 to v0.2 migration of an
-income statement with two figures, revenue and gross profit.
-
-### v0.1 form
-
-A single doc: both figures in one concept, the SQL in prose an agent can
-read, ignore, or rewrite, citations a flat list, and the only timestamp is
-`timestamp`.
-
-```markdown
----
-type: Metric
-title: Income statement (fiscal year)
-description: Headline income-statement figures for a fiscal year.
-tags: [finance, income-statement]
-timestamp: '2026-05-28T22:53:05+00:00'
----
-
-# Definition
-The income statement reports revenue and gross profit for a fiscal year.
-
-# Revenue
-Recognized revenue sums `amount` over rows booked to the fiscal year:
-
- SELECT SUM(amount) AS revenue
- FROM finance.recognized_revenue
- WHERE fiscal_year =
-
-# Gross profit
-Gross profit by segment, per the cost-allocation standard:
-
- SELECT gross_profit FROM fct_income_statement
- WHERE fiscal_year = AND segment =
-
-# Citations
-- https://wiki.acme/finance/fpa-handbook
-- https://wiki.acme/finance/revenue-recognition
-- https://wiki.acme/finance/cost-allocation
-```
-
-### v0.2 form
-
-The two figures split into attested computations linked from a narrative
-concept. Every family is populated, and the two computations sit in
-deliberately different states so one consumer reaches two verdicts.
-
-```
-bundles/finance/
- metrics/income-statement.md type: Metric (narrates, links both)
- computations/revenue.md type: Attested Computation (runtime: bigquery)
- computations/profit.md type: Attested Computation (runtime: dbt)
- references/skills/run-on-bq.md, run-dbt.md
- references/attesters/sql-equality.py, dbt-binding.py
-```
-
-`metrics/income-statement.md`, the readable doc; trust lives on what it
-links, not here:
-
-```markdown
----
-type: Metric
-title: Income statement (fiscal year)
-description: Headline income-statement figures for a fiscal year.
-tags: [finance, income-statement]
-status: stable
-generated: { by: reference_agent/gemini-2.5-pro, at: 2026-06-20T22:53:05Z }
-verified: { by: human:ahormati, at: 2026-06-25T09:00:00Z }
-stale_after: 2026-12-31T00:00:00Z
-sources:
- - id: fpa-handbook
- resource: https://wiki.acme/finance/fpa-handbook
- title: FP&A reporting handbook
----
-
-# Definition
-The income statement reports [revenue](../computations/revenue.md) and
-[gross profit](../computations/profit.md) for a fiscal year, per the FP&A
-reporting handbook.[^fpa-handbook] Each figure is produced by a sanctioned,
-attestable computation; this concept only narrates them.
-
-[^fpa-handbook]: FP&A reporting handbook
-```
-
-`computations/revenue.md`, BigQuery SQL, human-verified, fresh, and
-corroborated by a live dashboard source carrying credibility signals:
-
-```markdown
----
-type: Attested Computation
-title: Revenue for fiscal year
-description: Recognized revenue for a fiscal year, per Finance's definition.
-tags: [finance, revenue]
-status: stable
-runtime: bigquery
-parameters:
- - { name: year, type: integer, required: true }
-executor:
- resource: references/skills/run-on-bq.md
- receipt: [job_id, executed_sql, result]
-attester:
- resource: references/attesters/sql-equality.py
-generated: { by: reference_agent/gemini-2.5-pro, at: 2026-06-28T14:00:00Z }
-verified: { by: human:ahormati, at: 2026-06-25T09:00:00Z }
-stale_after: 2026-12-31T00:00:00Z
-sources:
- - id: rev-policy
- resource: https://wiki.acme/finance/revenue-recognition
- title: Revenue recognition policy
- author: team:finance-fpa
- last_modified: 2026-04-02T00:00:00Z
- - id: exec-rev-dash
- resource: dashboards/exec-revenue
- title: Executive revenue dashboard
- author: team:finance-fpa
- usage_count: 5000
- last_modified: 2026-06-18T00:00:00Z
-usage_window: { from: 2026-06-01T00:00:00Z, to: 2026-06-30T00:00:00Z }
----
-
-# Computation
-
- SELECT SUM(amount) AS revenue
- FROM finance.recognized_revenue
- WHERE fiscal_year = @year
-
-Recognized revenue per the recognition policy,[^rev-policy] corroborated by
-the executive revenue dashboard.[^exec-rev-dash]
-
-[^rev-policy]: Revenue recognition policy
-[^exec-rev-dash]: Executive revenue dashboard
-```
-
-`computations/profit.md`, a dbt model, process-verified, and past its
-`stale_after`:
-
-```markdown
----
-type: Attested Computation
-title: Gross profit for fiscal year
-description: Gross profit by segment for a fiscal year, per the cost-allocation standard.
-tags: [finance, profit]
-status: stable
-runtime: dbt
-parameters:
- - { name: year, type: integer, required: true }
- - { name: segment, type: string, required: true }
-executor:
- resource: references/skills/run-dbt.md
- receipt: [run_id, compiled_sql, result]
-attester:
- resource: references/attesters/dbt-binding.py
-generated: { by: reference_agent/gemini-2.5-pro, at: 2026-06-14T14:00:00Z }
-verified: { by: process:finance-nightly, at: 2026-06-12T08:00:00Z }
-stale_after: 2026-06-15T00:00:00Z
-sources:
- - id: cost-alloc
- resource: https://wiki.acme/finance/cost-allocation
- title: Cost allocation standard
----
-
-# Computation
-
- SELECT gross_profit
- FROM {{ ref('fct_income_statement') }}
- WHERE fiscal_year = {{ var('year') }}
- AND segment = {{ var('segment') }}
-
-Gross profit by segment per the cost-allocation standard.[^cost-alloc]
-
-[^cost-alloc]: Cost allocation standard
-```
diff --git a/external/okf/skills/okf/scripts/okf_init.py b/external/okf/skills/okf/scripts/okf_init.py
deleted file mode 100644
index ec8dcb2..0000000
--- a/external/okf/skills/okf/scripts/okf_init.py
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/usr/bin/env python3
-# /// script
-# requires-python = ">=3.11"
-# dependencies = ["pyyaml>=6"]
-# ///
-"""Scaffold a conformant starter Open Knowledge Format (OKF) v0.2 bundle.
-
-Creates a root `index.md` (frontmatter carries only `okf_version`), a `log.md`
-with a creation entry under today's ISO date heading, and one starter concept
-(`getting-started.md`) with the full set of recommended frontmatter fields.
-The scaffold is meant to be exemplary: `okf_validate.py --strict` should pass
-it with zero warnings.
-
-Run: uv run scripts/okf_init.py [--title "..."] [--force]
-"""
-from __future__ import annotations
-
-import argparse
-import sys
-from datetime import datetime, timezone
-from pathlib import Path
-
-import yaml
-
-OKF_VERSION = "0.2"
-STARTER_CONCEPT = "getting-started.md"
-# Actor convention (§7): an automated process, not an agent with a version.
-ACTOR = "process:okf_init"
-
-
-def humanize(name: str) -> str:
- words = name.replace("-", " ").replace("_", " ").strip()
- return words.title() if words else "Untitled"
-
-
-def frontmatter(meta: dict) -> str:
- return "---\n" + yaml.safe_dump(meta, sort_keys=False, allow_unicode=True) + "---\n"
-
-
-def build_index(title: str) -> str:
- meta = frontmatter({"okf_version": OKF_VERSION})
- return (
- f"{meta}\n"
- f"# {title}\n\n"
- f"* [Getting started]({STARTER_CONCEPT}) - starting point for this bundle.\n"
- )
-
-
-def build_log(today: str, title: str) -> str:
- return (
- "# Update Log\n\n"
- f"## {today}\n"
- f"* **Creation**: Scaffolded the {title} bundle with `okf_init.py` — "
- f"see [getting started]({STARTER_CONCEPT}).\n"
- )
-
-
-def build_concept(title: str, now_iso: str) -> str:
- meta = frontmatter({
- "type": "Reference",
- "title": f"Getting started — {title}",
- "description": f"Starting point for the {title} OKF bundle.",
- "tags": ["getting-started"],
- "status": "stable",
- "generated": {"by": ACTOR, "at": now_iso},
- })
- return (
- f"{meta}\n"
- "# Overview\n\n"
- "This is the first concept in a freshly scaffolded OKF bundle. Replace "
- "it with real knowledge — one concept per file, cross-linked with "
- "standard markdown links — and keep `index.md` updated as you go. "
- "`log.md` records lifecycle events only: a concept created, "
- "deprecated, superseded, regenerated or verified.\n"
- )
-
-
-def main() -> int:
- ap = argparse.ArgumentParser(description=f"Scaffold a starter OKF v{OKF_VERSION} bundle.")
- ap.add_argument("target", type=Path, help="directory to create the bundle in")
- ap.add_argument("--title", default=None, help="bundle title (default: humanized target dir name)")
- ap.add_argument("--force", action="store_true", help="scaffold even if the target already has .md files")
- args = ap.parse_args()
-
- if args.target.exists() and not args.target.is_dir():
- print(f"error: {args.target} exists and is not a directory", file=sys.stderr)
- return 2
-
- existing_md = list(args.target.rglob("*.md")) if args.target.is_dir() else []
- if existing_md and not args.force:
- print(f"refusing: {args.target} already contains .md files (pass --force to scaffold anyway)", file=sys.stderr)
- for p in sorted(existing_md)[:5]:
- print(f" {p.relative_to(args.target)}", file=sys.stderr)
- return 1
-
- title = args.title or humanize(args.target.resolve().name)
- today = datetime.now(timezone.utc).date().isoformat()
- now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
-
- args.target.mkdir(parents=True, exist_ok=True)
- (args.target / "index.md").write_text(build_index(title), encoding="utf-8")
- (args.target / "log.md").write_text(build_log(today, title), encoding="utf-8")
- (args.target / STARTER_CONCEPT).write_text(build_concept(title, now_iso), encoding="utf-8")
-
- validator = Path(__file__).resolve().parents[2] / "validate" / "scripts" / "okf_validate.py"
- print(f"created OKF bundle scaffold at {args.target}")
- print(f"hint: validate it — uv run {validator} {args.target} --strict (or the validate skill)")
- return 0
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/external/okf/skills/okf/templates/concept.md b/external/okf/skills/okf/templates/concept.md
deleted file mode 100644
index 20bfde6..0000000
--- a/external/okf/skills/okf/templates/concept.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-type:
-title:
-description:
-resource:
-tags: [, ]
-status: stable # draft | stable | deprecated; absent means stable
-generated: { by: , at: }
-verified: { by: human:, at: } # omit until someone confirms it
-stale_after: # omit when the content does not expire
-sources: # what this was derived from; omit if nothing
- - id:
- resource:
- title:
- author: # optional credibility signal
- last_modified: # when the source itself last changed
----
-
-
-
-# Overview
-
-
-
-# Schema
-
-