Skip to content

Keel impact score — a derived, evidence-first measure of Keel's per-session leverage#53

Merged
dbudnikau-personal merged 7 commits into
mainfrom
claude/keel-impact-scoring-5uuenl
Jul 8, 2026
Merged

Keel impact score — a derived, evidence-first measure of Keel's per-session leverage#53
dbudnikau-personal merged 7 commits into
mainfrom
claude/keel-impact-scoring-5uuenl

Conversation

@dbudnikau-personal

Copy link
Copy Markdown
Collaborator

What & why

An optional way to answer "is Keel earning its keep?" — quantify how much Keel actually shaped a session, then aggregate it across projects. Built around one rule that keeps it honest: the score is derived, not asserted.

The design deliberately avoids the trap of a self-congratulatory number: the model that used Keel does not pick its own score. It enumerates counted, cited events; a Bash tool computes the number by a fixed formula. So the "marketing" score is a pure function of the evidence and can't be inflated by a good mood.

How it works

1. Derived score (/keel-scorecommands/keel-score.md + tools/keel-impact.sh). The model reports typed events, each owing a concrete artifact from the session (a rule id, a diff line, a guardrail block). The tool derives:

HELP  = 3·guard + 2·fire + 1·hit
COST  = 2·miss  + 2·friction
score = round(100·HELP / (HELP+COST))     ("—" when there are no events — nothing to measure, not a fake 0)

Guardrail fires (objective blocks) weigh most; retrieval misses and friction pull it down. Each row also carries a conf tier (a score behind one event is visibly weak) and a silent-rules count (demote candidates), kept out of the score.

2. The objective signal is collected in the shell, at zero token cost. A guardrail actually firing is the most objective input, so it's pushed out of the model's hands: secret-guard, pre-pr-gate, and public-audit each record a metadata-only event (never the matched secret) to a per-repo event log that add auto-ingests. Deterministic, portable (a git hook + a text file), and free of tokens — the model can't over- or under-state it.

3. Works out of the box, per project, no env. Recording is gated on a repo-local .keel/ marker (resolved from the git top level), so the loop needs no exported variable and works in any commit context. init-project enables it by default (--no-impact to skip); keel-impact.sh enable [dir] opts an existing repo in; $KEEL_IMPACT_LOG / $KEEL_IMPACT_LEDGER remain explicit overrides.

4. Cross-project view. keel-impact.sh rollup --registry <INSTANCE.md> sweeps every project in the Projects table (same parser as doctor --registry) and reports each one's mean score from its own .keel/ledger.md, plus a grand total and the cumulative guardrail-fire / retrieval-miss signals.

5. One narrow doctor check. doctor gains a WARN (not a GAP) when a .keel/ marker exists but isn't gitignored — mirroring its existing unignored-private-context check so an enabled event log can't leak into history. It deliberately does not gain an "impact tracking on/off" line: that would be false drift for an optional feature, and doctor stays a baseline audit.

Design choices worth flagging

  • Portable, not Claude-only. A Markdown command + a POSIX-ish Bash tool, not a Skill — it works under any AI assistant, like the rest of Keel. awk uses no interval expressions (busybox/Alpine parity).
  • Opt-in guardrail instrumentation. Each hook writes only when tracking is enabled, so default behaviour is byte-identical and the security hooks are untouched in the common case. pre-pr-gate writes to the log file only, never stdout, so its JSON decision stays intact.
  • Honest about limits. The only real counterfactual — an occasional A/B (same task with Keel vs cold) — is documented as the ground truth these self-reported scores merely estimate; it is intentionally not automated (it can't be zero-cost). Non-guardrail events (fire/hit/miss/friction) remain model-judged by design.

Tests

Full suite green — 337 assertions across 11 files, including new coverage in test_keel_impact.sh (derivation, confidence tiers, handling, event/ingest/no-double-count, enable, registry rollup), and every guardrail test now exercises all three enable paths (override / marker-only / neither → nothing written) plus no-secret-leak and stdout-integrity. The test harness pins the impact log/ledger into its sandbox so a suite run never records into a real .keel/.

Files

  • New: commands/keel-score.md, tools/keel-impact.sh, docs/keel-impact.md, tests/test_keel_impact.sh
  • Instrumented: tools/secret-guard/secret-scan.sh, tools/pre-pr-gate.sh, tools/public-audit.sh
  • Wiring: tools/init-project.sh, tools/doctor.sh, install.sh, .gitignore, commands/wrap.md (optional step 7)
  • Docs/tests: docs/loading-and-cost.md, CHANGELOG.md, tests/lib.sh, and the four instrumented tools' tests

🤖 Generated with Claude Code

https://claude.ai/code/session_01Qghzk5C4mWTAcLCvXofYdN


Generated by Claude Code

claude added 7 commits July 8, 2026 16:23
An optional, evidence-first mechanism to score how much Keel actually
shaped a session, turning the wrap-time promote/demote gut check into a
tracked number plus the evidence behind it.

- commands/keel-score.md: 0-100 five-axis rubric (convention adherence,
  retrieval hit-miss, rework avoided, guardrails fired, command leverage)
  under three honesty rules — no point without a citation, score the
  counterfactual delta, subtract a friction penalty (help - friction).
- tools/keel-impact.sh: the deterministic half — date stamp, row append,
  rolling trend — over an append-only ledger. Model judges, script counts
  (same split as doctor). Interval-free awk regex for busybox parity.
- docs/keel-impact.md: the ledger, seeded with its schema/header.
- commands/wrap.md: optional step 7 wiring it into the session-wrap ritual.
- tests/test_keel_impact.sh: 21 assertions (append, parse, rollup, range
  validation, table-safety escaping).
- docs/loading-and-cost.md: honest command-size band bumped 1,070 -> 1,200
  to match the new max; CHANGELOG entry.

Portable by design (Markdown command + POSIX-ish Bash, not a Claude-only
skill) so it works under any AI assistant, like the rest of Keel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qghzk5C4mWTAcLCvXofYdN
Reground the mechanism on honesty: the model no longer picks a 0-100
number. It enumerates counted, cited EVENTS and the tool derives the
score by a fixed formula, so the marketing number is a pure function of
the evidence and cannot be inflated by vibe.

- tools/keel-impact.sh: `add` now takes event counts
  (--guard/--fire/--hit/--miss/--friction/--silent), not --score, and
  computes score = round(100*HELP/(HELP+COST)), HELP=3*guard+2*fire+hit,
  COST=2*miss+2*friction. No events -> "—" (nothing to measure), never a
  fake 0. Records a confidence tier from the event count; keeps `silent`
  (unused always-loaded rules = demote candidates) out of the score.
  Rollup skips "—" from the mean and reports the honest cumulative
  signals (total guardrail fires, total retrieval misses).
- commands/keel-score.md: rewritten around "you do not pick the score" —
  gather typed events each owing a concrete artifact; documents the only
  real counterfactual (an occasional A/B, Keel vs cold) as the ground
  truth these self-reported scores estimate.
- docs/keel-impact.md: new derived-score schema + formula in the header.
- commands/wrap.md: step 7 reworded (count events, tool derives).
- tests/test_keel_impact.sh: 29 assertions pinning the derivation,
  confidence tiers, "—" handling, rollup aggregates, and validation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qghzk5C4mWTAcLCvXofYdN
Push the most objective input to the score — a guardrail actually firing —
out of the model's hands and into a deterministic shell event log, so it
is portable (a git hook + a text file), deterministic (a line count), and
free of token cost.

- tools/secret-guard/secret-scan.sh: on a block, append a metadata-only
  event (guard/secret-guard/blocked — NEVER the matched secret) to
  $KEEL_IMPACT_LOG. Strictly opt-in: writes only when the env var is set,
  so default hook behaviour is byte-identical.
- tools/keel-impact.sh: new `event TYPE [source] [detail]` producer entry
  point; `add` now auto-ingests any logged events into the counts, then
  truncates the log so nothing is double-counted (--no-ingest opts out).
  Log defaults to .keel/impact-events.log (gitignored), $KEEL_IMPACT_LOG
  overrides. So a secret-guard block reaches the derived score without the
  model counting it — and cannot be over- or under-stated by it.
- commands/keel-score.md + docs/keel-impact.md: document that guard is
  collected for you; leave --guard at 0 unless a fire wasn't logged.
- tests: test_keel_impact.sh (40) covers event/ingest/no-ingest/no-double-
  count and is now hermetic against an ambient log; test_secret_guard.sh
  (60) covers the opt-in, metadata-only, no-leak, off-by-default emission.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qghzk5C4mWTAcLCvXofYdN
Extend the deterministic, zero-token guardrail-fire capture from
secret-guard to the other two guardrails, so every guardrail fire reaches
the impact score without the model counting it.

- tools/pre-pr-gate.sh: on a deny, append a metadata-only guard event
  (in deny(), covering both deny sites). Writes to the log file only,
  never stdout, so the hook's JSON decision payload stays intact.
- tools/public-audit.sh: on a GAP (exit 1 — a real publication blocker
  caught), append a guard event. Not on a clean run or advisory WARNs.
- Both are opt-in: they write only when $KEEL_IMPACT_LOG is set, so
  default behaviour is byte-identical (existing tests untouched).
- Docs (keel-score.md, ledger header) now name all three guardrails as
  the producers; CHANGELOG generalized.
- tests: test_pre_pr_gate.sh (+5) and test_public_audit.sh (+5) cover the
  opt-in, metadata-only, no-leak, off-by-default, stdout-integrity, and
  (public-audit) clean-run-records-nothing cases.
- docs/loading-and-cost.md: command-size band ceiling 1,200 -> 1,250 for
  headroom (keel-score.md sits at ~1,201 after the guardrail-list edit).

Verified end-to-end: all three guardrails write to one log and a single
`add` auto-ingests all of them (guard col = 3, model passed --guard 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qghzk5C4mWTAcLCvXofYdN
Make the guardrail-fire loop turn-key: enable it once per repo and it
works in any commit context (git hook, IDE, CI) with no exported
variable — the objective signal reaches the score with zero config.

- Guardrails now resolve where to record a fire by: $KEEL_IMPACT_LOG
  override, else a repo-local .keel/ marker (found from the git top
  level). With neither, nothing is written (behaviour unchanged). This
  replaces the env-only gate, which couldn't survive GUI/IDE commits or
  be scoped per project. Applied to secret-guard, pre-pr-gate (via the
  hook's cwd), and public-audit (via the audited dir).
- tools/keel-impact.sh: new `enable [dir]` opts a repo in — creates the
  gitignored .keel/ marker the hooks look for. Idempotent.
- tools/init-project.sh: new projects are born tracked (.keel/ created +
  gitignored); --no-impact opts out.
- install.sh: Done message points to `keel-impact.sh enable` for existing
  repos.
- tests/lib.sh: pin KEEL_IMPACT_LOG into the sandbox so a suite run never
  records into a maintainer's real .keel/. Every guardrail test now
  covers all three enable paths (override / marker-only / neither), plus
  enable-subcommand and init-project born-tracked / --no-impact cases.
- Marker resolution written as explicit `if` (not an && chain) so it is
  unambiguously set -e safe in the security-adjacent hooks.
- Docs (keel-score.md, ledger header, CHANGELOG) updated for the marker.

Verified end-to-end with all env unset: enable a repo -> a secret-guard
block records into .keel/ -> `add` auto-ingests it -> score derived.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qghzk5C4mWTAcLCvXofYdN
Two additions at the right altitude: the cross-project "usefulness of
Keel" view lives in the impact tool, and doctor gains only a hygiene
check — it does not become a feature-status dashboard.

- tools/keel-impact.sh:
  - `rollup --registry FILE` sweeps every project in an INSTANCE.md
    Projects table (same parser as doctor --registry) and reports each
    one's mean score from its own .keel/ledger.md, a grand total, and the
    cumulative guardrail-fire / retrieval-miss signals.
  - Resolve the ledger (and event log) from the tracked repo's .keel/
    marker, mirroring the guardrails — so a project's sessions score into
    .keel/ledger.md with no env, and the registry sweep has a canonical
    per-project path. Falls back to Keel's docs/keel-impact.md (ledger)
    when a repo has no marker, preserving Keel's own dogfooding default.
- tools/doctor.sh: WARN (not GAP) when a .keel/ marker exists but isn't
  gitignored — its event log could leak into history. Mirrors the
  existing unignored-private-context check; it never nags a project to
  enable tracking (optional feature != baseline drift).
- tests/lib.sh: also pin KEEL_IMPACT_LEDGER into the sandbox so a stray
  `add` in a test never writes into Keel's real docs/keel-impact.md.
- tests: registry sweep (scored / zero / unscored projects + grand total
  + missing-registry error) in test_keel_impact.sh; the .keel/ hygiene
  WARN (and its absence once ignored) in test_doctor.sh.

Verified end-to-end with all env unset: two enabled repos score into
their .keel/ledger.md and `rollup --registry` reports both plus a total.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qghzk5C4mWTAcLCvXofYdN
The ledger is the durable deliverable (per-session score history) — the
whole point of "know Keel's usefulness over time". Ignoring all of .keel/
made that history local-only, lost on a fresh clone / CI and unshareable.
Split the two: ignore only the ephemeral event log; let .keel/ledger.md
be committed.

- .gitignore / enable / init-project: ignore /.keel/impact-events.log
  (not the whole /.keel/). The ledger beside it stays trackable — a repo
  can `git add .keel/ledger.md` for a shareable, cross-clone record.
- doctor: the hygiene WARN now targets the log specifically
  (check-ignore .keel/impact-events.log), so it never flags the
  committable ledger. Still a WARN, never a GAP, never nags to enable.
- tests: assert the split directly (log ignored, ledger trackable via
  git check-ignore) and update the doctor / init-project expectations.

Verified: after enable+score, `git add -A -n` stages .keel/ledger.md but
not .keel/impact-events.log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qghzk5C4mWTAcLCvXofYdN
@dbudnikau-personal dbudnikau-personal merged commit ac8a52b into main Jul 8, 2026
4 checks passed
@dbudnikau-personal dbudnikau-personal deleted the claude/keel-impact-scoring-5uuenl branch July 8, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants