feat(score): cost and time labels, worktree path below the table

Efficiency and speed are after/before ratios where over 100 % is worse, so the labels now read 💸 cost and ⏱️ time in score.py, tokens.py, measure.py, the gate texts, the message block and every skill that quotes them; 💰 stays the value quotient and the --efficiency/--speed flags keep their names. --commit-table drops the worktree column, which took the table's width, and prints the path below it alone in a text block for copy and paste.

🎯 Fix: 94 %
💥 Break: 2 %
💸 Cost: 100 %
⏱️ Time: –
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 92
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-04 21:05:56 +01:00
1 parent 938cea9447
commit b18a481d8c
17 files changed
+120 -115

No files matched your search

+2 -2
View File
@@ -14,8 +14,8 @@ and never estimates one itself.
1. **Fix confidence** (percent): probability the change does what it claims.
**Break risk** (percent): probability it breaks anything else, from the
`kvb-check-break-risk` skill. Two numbers, never one blended figure.
2. **Score**: pass both, with efficiency from the `kvb-check-efficiency`
skill, speed from the `kvb-check-speed` skill and security and the
2. **Score**: pass both, with cost from the `kvb-check-efficiency`
skill, time from the `kvb-check-speed` skill and security and the
critical count from `security.py` of the `kvb-check-security` skill, to
`${CLAUDE_SKILL_DIR}/../kvb-check-score/scripts/score.py --fix <n> --break
<n> --efficiency <n|-> --speed <n|-> --security <n|~n> --critical <count>`
+6 -6
View File
@@ -4,7 +4,7 @@ description: >
Token cost of skill edits, CPU and RAM of code edits.
---
Efficiency is the share of its base's resources a change uses: under 100 %
💸 Cost is the share of the base's resources a change uses: under 100 %
it saves, over 100 % it costs more. Paste the script's lines, never a
paragraph about them. `<ref>` is the commit the change
starts from: `HEAD` for uncommitted work, the merge base with the default
@@ -20,7 +20,7 @@ It compares every changed `*.md` under `skills/`, untracked ones included,
at `<ref>` with the working tree, and the always-loaded listing: all
`description:` words, counted by the repository's `scripts/budget.py`.
Tokens are an estimate, characters / 4. Changed files without any base text
print `efficiency new`, which counts as over 100. Skills have no speed.
print `cost new`, which counts as over 100. Skills have no time.
A second line, `💰 value 1:<n>` (`<n>:1` when value < cost), weighs both
per day and never changes the exit code; pass its quotient or `?` to
@@ -45,15 +45,15 @@ python3 ${CLAUDE_SKILL_DIR}/scripts/measure.py --base <ref> -- <command...>
Start it in the directory the command expects. It checks `<ref>` out as a
detached worktree under `/tmp`, runs the command there and in the working
tree from the same relative directory, compares CPU seconds and peak RSS,
and removes the worktree. Efficiency is the mean of the CPU and the RAM
ratio. The same run measures wall time for `kvb-check-speed`.
and removes the worktree. Cost is the mean of the CPU and the RAM ratio.
It also measures wall time for `kvb-check-speed`.
- **Runs**: one per tree. Both values at most 100 end it; otherwise 4 more
per tree, alternating, and the line reports the mean of all 5
(`mean of 5`).
- **Other load**: a 1 s sample of system CPU busy before and after, and per
run the busy share beyond the command's own CPU time. Above 30 %, or
unreadable without `/proc/stat`, the result is an estimate: speed comes
unreadable without `/proc/stat`, the result is an estimate: time comes
from the CPU-time ratio, both values carry `~` and the line ends in
`(estimated: other load <x> %)`. Pass the numbers without `~` to
`kvb-check-score`; the exit code holds as for a measured result.
@@ -64,7 +64,7 @@ ratio. The same run measures wall time for `kvb-check-speed`.
`${CLAUDE_SKILL_DIR}/../kvb-org-queue/scripts/queue.py hold --kind <kind>
--task <register task> -- python3 <measure.py> ...`, in the background,
by the `kvb-org-queue` skill; a cancelled wait there also exits 3, but
prints no 🌱 line. A light one self-admits with
prints no 💸 line. A light one self-admits with
`watch.py admit --kind test-nostack` of `kvb-org-organize`.
- Report the run you made; never rerun to pick the best result.
@@ -9,26 +9,26 @@ Args:
relative to each tree's root; its output is discarded.
Runs:
One run per tree, base first. When efficiency and speed are both at most 100 that
One run per tree, base first. When cost and time are both at most 100 that
is the result; otherwise 4 more runs per tree, alternating, and the arithmetic mean
of all 5 per tree counts.
Other load:
A 1 s /proc/stat sample of system CPU busy before and after the runs, and per run
the busy jiffies minus the command's own CPU time, as a share of all jiffies. When
any share exceeds 30 % or cannot be read, the result is an estimate: speed comes
any share exceeds 30 % or cannot be read, the result is an estimate: time comes
from the CPU-time ratio instead of wall time.
Output:
`🌱 efficiency <p> % (cpu <b>→<a> s, ram <b>→<a> MB) · ⚡ speed <p> % (wall <b>→<a> s)`,
`💸 cost <p> % (cpu <b>→<a> s, ram <b>→<a> MB) · ⏱️ time <p> % (wall <b>→<a> s)`,
then ` · mean of 5` after the extra runs. An estimate marks both values with `~`,
shows `(cpu <b>→<a> s)` for speed and ends in `(estimated: other load <x> %)`, or
`unknown` for <x>. Efficiency is the mean of the CPU ratio (user plus system seconds
shows `(cpu <b>→<a> s)` for time and ends in `(estimated: other load <x> %)`, or
`unknown` for <x>. Cost is the mean of the CPU ratio (user plus system seconds
of the command and its waited-for children) and the peak RSS ratio; each ratio is
working tree over base, times 100.
Exit codes:
0 efficiency and speed at most 100, 2 usage error or a failing command
0 cost and time at most 100, 2 usage error or a failing command
(its tree, exit code and last stderr line on stderr), 3 either over 100.
"""
@@ -144,9 +144,9 @@ def line(found: dict) -> str:
basis = "cpu" if found["estimated"] else "wall"
before, after = found["base"], found["head"]
text = (
f"🌱 efficiency {mark}{found['efficiency']} % (cpu {before['cpu']:.2f}→{after['cpu']:.2f} s, "
f"💸 cost {mark}{found['efficiency']} % (cpu {before['cpu']:.2f}→{after['cpu']:.2f} s, "
f"ram {before['ram']:.1f}→{after['ram']:.1f} MB) · "
f"⚡ speed {mark}{found['speed']} % ({basis} {before[basis]:.2f}→{after[basis]:.2f} s)"
f"⏱️ time {mark}{found['speed']} % ({basis} {before[basis]:.2f}→{after[basis]:.2f} s)"
)
if found["runs"] > 1:
text += f" · mean of {found['runs']}"
@@ -15,8 +15,8 @@ Args:
shipped change instead.
Output:
Line 1: `🌱 efficiency <p> % (skills <b>→<a> tokens; listing <b>→<a> words)`, p = after / before * 100,
or `🌱 efficiency new (+<a> tokens; listing <b>→<a> words)` when the changed files hold no
Line 1: `💸 cost <p> % (skills <b>→<a> tokens; listing <b>→<a> words)`, p = after / before * 100,
or `💸 cost new (+<a> tokens; listing <b>→<a> words)` when the changed files hold no
base tokens. Changed files are the *.md under skills/ that `git diff <ref>` names plus untracked
ones; a file missing on a side counts 0. Tokens are an estimate: characters / 4. The listing
is the sum of every skills/*/SKILL.md description word.
@@ -298,9 +298,9 @@ def check(argv: list[str]) -> int:
else:
listing = f"listing {listing_before}→{listing_after} words"
if efficiency is None:
print(f"🌱 efficiency new (+{after} tokens; {listing})")
print(f"💸 cost new (+{after} tokens; {listing})")
else:
print(f"🌱 efficiency {efficiency} % (skills {before}→{after} tokens; {listing})")
print(f"💸 cost {efficiency} % (skills {before}→{after} tokens; {listing})")
print(money)
return 3 if efficiency is None or efficiency > LIMIT else 0
+1 -1
View File
@@ -6,7 +6,7 @@ description: >
---
The gate rule, stated here and nowhere else: nothing is declared 90/5 (fix
confidence ≥ 90 %, break risk ≤ 5 %, efficiency and speed ≤ 100 % in
confidence ≥ 90 %, break risk ≤ 5 %, cost and time ≤ 100 % in
`kvb-check-score`; over 100 %, ask the operator before the commit, naming
the cause) before every `make test*` target of the project, each Makefile target named `test` or starting with
`test-`, ran green on the exact head with a clean tracked tree. Until then
+14 -14
View File
@@ -1,7 +1,7 @@
---
name: kvb-check-score
description: >
Score fix, break, efficiency, speed and security; run wherever confidence is reported.
Score fix, break, cost, time and security; run wherever confidence is reported.
---
Run it wherever fix confidence and break risk are reported: every
@@ -16,17 +16,18 @@ python3 ${CLAUDE_SKILL_DIR}/scripts/score.py --fix <0-100> --break <0-100> --eff
| --- | --- | --- |
| 🎯 | fix confidence | `kvb-check-confidence` |
| 💥 | break risk | `kvb-check-break-risk` |
| 🌱 | efficiency | `tokens.py` (skills) or `measure.py` (code) of `kvb-check-efficiency` |
| ⚡ | speed | `measure.py`, see `kvb-check-speed` |
| 💸 | cost, `--efficiency` | `tokens.py` (skills) or `measure.py` (code) of `kvb-check-efficiency` |
| ⏱️ | time, `--speed` | `measure.py`, see `kvb-check-speed` |
| 🛡️ | security, critical count | `security.py` of `kvb-check-security`; `~<n>` from a review by hand |
| 💰 | cost:value quotient, tables and message block only | 💰 line of `tokens.py` (skills) of `kvb-check-efficiency` |
| 🏆 | score | this script |
`-` means not measured or not relevant (skills have no speed) and counts as
Cost and time: after over base in percent, over 100 worse. `-` means not
measured or not relevant (skills have no time) and counts as
100; it shows as `–`. `new` (changed skill text without a base) counts as 100
in the score, yet still asks. Never invent a value: measure it or pass `-`.
Score = `fix * (100 - break) / 100 * 100 / efficiency * 100 / speed * security / 100`,
Score = `fix * (100 - break) / 100 * 100 / cost * 100 / time * security / 100`,
capped to 0..100 and rounded.
## Gate
@@ -41,11 +42,11 @@ The second line is the verdict, first match wins:
in the block and commit only on the operator's word. Close the gap as the
last section of `kvb-check-break-risk` says.
- `⚠️ gate: <causes>: ask the operator before the commit` (exit 3), naming
each of `efficiency <e> % > 100`, `speed <s> % > 100` and
each of `cost <c> % > 100`, `time <t> % > 100` and
`security <n> % < 100`: ask with the block what the cost buys or why each
finding is acceptable; commit only on a yes, never carried over to
another commit.
- `✅ gate: 90/5, efficiency and speed <= 100, security 100 %` (exit 0):
- `✅ gate: 90/5, cost and time <= 100, security 100 %` (exit 0):
commit as usual. 90/5 still needs `gate: green` of `kvb-check-gate`.
- Exit 2: usage error.
@@ -53,12 +54,11 @@ The second line is the verdict, first match wins:
Each flag replaces the two lines; values, gate and exit code stay:
- `--table`: one markdown table, `| 🎯 Fix | 💥 Break | 🌱 Efficiency |
⚡ Speed | 🛡️ Security | 🏆 Score | Gate |`.
- `--commit-table <repo>`: that table led by `📁 Worktree | 🌿 Branch |
📊 Diff stat` from git (top level, branch or `detached at <sha>`, staged
shortstat as `<n> files +<added>/-<removed>`). Every commit block is this
table; after the commit, `--commit <sha>` shows that commit's diff stat
- `--table`: one markdown table, `| 🎯 Fix | 💥 Break | 💸 Cost |
⏱️ Time | 🛡️ Security | 🏆 Score | Gate |`.
- `--commit-table <repo>`: that table led by `🌿 Branch | 📊 Diff stat`
from git, 📁 worktree path below, alone in a code block. Every commit
block is this table; after the commit, `--commit <sha>` shows that commit's diff stat
and a `🔖 Commit` column.
- `--message-block`: one value per line, `🎯 Fix: <f> %` through
`🏆 Score: <n>` and `🚦 Gate: <verdict>`. Every commit message carries it
@@ -66,6 +66,6 @@ Each flag replaces the two lines; values, gate and exit code stay:
`-m` argument, so the approval prompt and git history show the values.
On a skill change add `--value <n:n|?>`, the quotient of the 💰 line (`?`
while it has no value): a `💰 Value` column after `🌱 Efficiency`, or line
while it has no value): a `💰 Value` column after `💸 Cost`, or line
after `🛡️ Security`. It changes neither score nor gate; alone it is a usage
error.
+29 -27
View File
@@ -1,38 +1,41 @@
#!/usr/bin/env python3
"""Fold fix confidence, break risk, efficiency, speed and security into one score and one gate verdict.
"""Fold fix confidence, break risk, cost, time and security into one score and one gate verdict.
Args:
--fix: fix confidence in percent, 0 to 100.
--break: break risk in percent, 0 to 100.
--efficiency: efficiency in percent above 0; `-` when not measured or not
relevant, `new` when the change has no base. Both count as 100 in the score.
--speed: speed in percent above 0, `-` or `new` as for --efficiency.
--efficiency: cost in percent above 0, from kvb-check-efficiency; `-` when not
measured or not relevant, `new` when the change has no base. Both count as 100
in the score.
--speed: time in percent above 0, from kvb-check-speed; `-` or `new` as for
--efficiency.
--security: security in percent, 0 to 100, from security.py of kvb-check-security;
`~<n>` for a value from a review by hand.
--critical: count of critical security findings.
--table: print one markdown table instead of the two lines.
--commit-table <repo>: the same table, led by the worktree, branch and staged
diff stat of that repository.
--commit-table <repo>: the same table, led by the branch and staged diff stat of
that repository, then a blank line, `📁 Worktree:` and the top level path
alone in a ```text block, so it copies in one go.
--commit <sha>: with --commit-table, the diff stat of that commit instead of
the staged one, plus a commit column.
--message-block: print the commit message block instead of the two lines, one
value per line: `🎯 Fix: <f> %`, `💥 Break: <b> %`, `🌱 Efficiency: <e>`,
`⚡ Speed: <s>`, `🛡️ Security: <n> %`, `💰 Value: <n:n>` with --value only,
value per line: `🎯 Fix: <f> %`, `💥 Break: <b> %`, `💸 Cost: <c>`,
`⏱️ Time: <t>`, `🛡️ Security: <n> %`, `💰 Value: <n:n>` with --value only,
`🏆 Score: <n>`, `🚦 Gate: <verdict>`; values and markers as in line 1.
--value <n:n|?>: with --table, --commit-table or --message-block, a value after
the efficiency one (--message-block: after security): the cost:value quotient of
the cost one (--message-block: after security): the cost:value quotient of
tokens.py of kvb-check-efficiency, `?` when unknown. It changes neither score nor gate.
Output:
Line 1: `🎯 fix <f> % · 💥 break <b> % · 🌱 efficiency <e> % · ⚡ speed <s> % ·
Line 1: `🎯 fix <f> % · 💥 break <b> % · 💸 cost <c> % · ⏱️ time <t> % ·
🛡️ security <n> % · 🏆 score <n>`, `–` in place of `<x> %` for `-`, `new` for `new`.
Score is fix * (100 - break) / 100 * 100 / efficiency * 100 / speed * security / 100,
Score is fix * (100 - break) / 100 * 100 / cost * 100 / time * security / 100,
capped to 0..100, rounded half up.
Line 2: the gate verdict, `✅ gate: ...`, `⛔ gate: ...` or `⚠️ gate: ...`.
Exit codes:
0 gate passed, 1 fix under 90 or break over 5, 2 usage error,
3 efficiency or speed over 100 or new, or security under 100: ask the operator
3 cost or time over 100 or new, or security under 100: ask the operator
before the commit, 4 a critical security finding: do not commit.
"""
@@ -51,10 +54,11 @@ SAFE = 100
DASH = "-"
NEW = "new"
ESTIMATE = "~"
SCORE_HEADER = ("🎯 Fix", "💥 Break", "🌱 Efficiency", "⚡ Speed", "🛡️ Security", "🏆 Score", "Gate")
COMMIT_HEADER = ("📁 Worktree", "🌿 Branch", "📊 Diff stat")
SCORE_HEADER = ("🎯 Fix", "💥 Break", "💸 Cost", "⏱️ Time", "🛡️ Security", "🏆 Score", "Gate")
COMMIT_HEADER = ("🌿 Branch", "📊 Diff stat")
WORKTREE = "📁 Worktree:"
VALUE_HEADER = "💰 Value"
VALUE_AT = SCORE_HEADER.index("🌱 Efficiency") + 1
VALUE_AT = SCORE_HEADER.index("💸 Cost") + 1
UNKNOWN = "?"
QUOTIENT = re.compile(r"\d+(?:\.\d+)?:\d+(?:\.\d+)?")
SHORTSTAT = re.compile(
@@ -143,13 +147,13 @@ def gate(args: argparse.Namespace) -> tuple[str, str, int]:
failed.append(f"break {number(args.risk)} % > {BREAK_MAX}")
value, mark = args.security
unsafe = f"security {mark}{number(value)} % < {SAFE}" if value < SAFE else None
asks = [text for text in (over("efficiency", args.efficiency), over("speed", args.speed), unsafe) if text]
asks = [text for text in (over("cost", args.efficiency), over("time", args.speed), unsafe) if text]
ask = f"{', '.join(asks)}: ask the operator before the commit" if asks else ""
if failed:
return "⛔", "; ".join(part for part in (", ".join(failed), ask) if part), 1
if asks:
return "⚠️", ask, 3
return "✅", f"{FIX_MIN}/{BREAK_MAX}, efficiency and speed <= {COST_MAX}, security {SAFE} %", 0
return "✅", f"{FIX_MIN}/{BREAK_MAX}, cost and time <= {COST_MAX}, security {SAFE} %", 0
def git(repo: str, *args: str) -> str:
@@ -173,13 +177,9 @@ def diffstat(shortstat: str) -> str:
def commit_cells(repo: str, commit: str | None) -> tuple[list[str], list[str]]:
branch = git(repo, "branch", "--show-current") or f"detached at {git(repo, 'rev-parse', '--short', 'HEAD')}"
if commit is None:
stat = git(repo, "diff", "--cached", "--shortstat")
return list(COMMIT_HEADER), [git(repo, "rev-parse", "--show-toplevel"), branch, diffstat(stat)]
return list(COMMIT_HEADER), [branch, diffstat(git(repo, "diff", "--cached", "--shortstat"))]
stat = git(repo, "show", "--shortstat", "--format=", commit)
return (
[*COMMIT_HEADER, "🔖 Commit"],
[git(repo, "rev-parse", "--show-toplevel"), branch, diffstat(stat), git(repo, "rev-parse", "--short", commit)],
)
return [*COMMIT_HEADER, "🔖 Commit"], [branch, diffstat(stat), git(repo, "rev-parse", "--short", commit)]
def cell(text: str) -> str:
@@ -229,20 +229,22 @@ def main() -> int:
row.insert(VALUE_AT, args.value)
if args.commit_table:
lead, cells = commit_cells(args.commit_table, args.commit)
top = git(args.commit_table, "rev-parse", "--show-toplevel")
print(table([*lead, *header], [*cells, *row]))
print(f"\n{WORKTREE}\n\n```text\n{top}\n```")
elif args.table:
print(table(header, row))
elif args.message_block:
worth_line = [f"💰 Value: {args.value}"] if args.value else []
print("\n".join((
f"🎯 Fix: {values[0]}", f"💥 Break: {values[1]}", f"🌱 Efficiency: {values[2]}",
f"⚡ Speed: {values[3]}", f"🛡️ Security: {values[4]}", *worth_line,
f"🎯 Fix: {values[0]}", f"💥 Break: {values[1]}", f"💸 Cost: {values[2]}",
f"⏱️ Time: {values[3]}", f"🛡️ Security: {values[4]}", *worth_line,
f"🏆 Score: {total}", f"🚦 Gate: {values[6]}",
)))
else:
print(
f"🎯 fix {values[0]} · 💥 break {values[1]} · 🌱 efficiency {values[2]} · "
f"⚡ speed {values[3]} · 🛡️ security {values[4]} · 🏆 score {total}"
f"🎯 fix {values[0]} · 💥 break {values[1]} · 💸 cost {values[2]} · "
f"⏱️ time {values[3]} · 🛡️ security {values[4]} · 🏆 score {total}"
)
print(f"{emoji} gate: {verdict}")
return code
+4 -4
View File
@@ -4,15 +4,15 @@ description: >
Wall-clock speed of changed code versus its base commit.
---
Speed is the share of its base's wall-clock time a change needs: under
100 % it is faster, over 100 % slower. It has no script of its own: one run
Its ⏱️ time is the share of its base's wall-clock time a change needs:
under 100 % it is faster, over 100 % slower. It has no script of its own: one run
of the `kvb-check-efficiency` measurement yields both values.
```bash
python3 ${CLAUDE_SKILL_DIR}/../kvb-check-efficiency/scripts/measure.py --base <ref> -- <command...>
```
Its `⚡ speed <p> % (wall <b>→<a> s)` compares wall seconds at `<ref>` and
Its `⏱️ time <p> % (wall <b>→<a> s)` compares wall seconds at `<ref>` and
in the working tree: one run each, or the mean of 5 each when a value
exceeds 100. The command, the runs, the `queue.py hold` wrapper for heavy
commands and the exit codes are those of the `kvb-check-efficiency` skill;
@@ -24,6 +24,6 @@ pasting its whole line reports both values at once.
renders) has no speed either: `--speed -`, and say why.
- **Other load**: wall time follows host load. Above 30 % other load the
script falls back to the CPU-time ratio and marks the estimate:
`⚡ speed ~<p> % (cpu <b>→<a> s)`, ending in
`⏱️ time ~<p> % (cpu <b>→<a> s)`, ending in
`(estimated: other load <x> %)`. Report the run you
made; never rerun to pick the best result.
+1 -1
View File
@@ -24,7 +24,7 @@ skill's Rules apply here too.
--task <register task> -- <command>`, which waits for the rule-based
grant and releases on exit. The fix enters the main line only
at the funnel's bar (the `kvb-org-split` skill's: fix confidence ≥ 90 %,
break risk ≤ 5 %, efficiency and speed ≤ 100 % in the `kvb-check-score`
break risk ≤ 5 %, cost and time ≤ 100 % in the `kvb-check-score`
block; over 100 %, ask the operator before the commit, naming the cause)
after its independent review; below the bar the cluster ends as a
proposal for the operator, never as a merge. Never mask a failure: no
+1 -1
View File
@@ -16,6 +16,6 @@ checklist (multi-select) with suggestions, free text allowed:
be changed (files, roles, services); whether new dependencies, toolchains
or languages may be introduced; whether infrastructure or CI configuration
may change; the merge bar (default: the `kvb-org-split` skill's bar of fix
confidence ≥ 90 %, break risk ≤ 5 %, efficiency and speed ≤ 100 %, over
confidence ≥ 90 %, break risk ≤ 5 %, cost and time ≤ 100 %, over
100 % only after asking the operator, naming the cause); a time cap per
delegated agent (default 60 minutes).
+3 -3
View File
@@ -12,14 +12,14 @@ description: >
2. Show in the message right before the `git commit` call, never only after
it, the full staged diff (`git diff --cached`), then the commit table of
the kvb-check-score skill (`score.py ... --commit-table <worktree>`):
📁 worktree, 🌿 branch, 📊 staged diff stat, then fix, break, efficiency,
speed, security, score and gate of the staged bundle, as the
📁 worktree, 🌿 branch, 📊 staged diff stat, then fix, break, cost,
time, security, score and gate of the staged bundle, as the
kvb-check-confidence skill produces them: break risk derived through the
kvb-check-break-risk skill, never estimated.
3. Approval-prompt duty: the commit call's approval prompt (Claude Code: the
Bash `description`; other runtimes: their prompt text) names the worktree
path, the branch, the diff stat and the score values (🎯 fix, 💥 break,
🌱 efficiency, ⚡ speed, 🛡️ security, 🏆 score). Missing any item: do not
💸 cost, ⏱️ time, 🛡️ security, 🏆 score). Missing any item: do not
send the call. Gate exit 4 (critical security finding) means no commit:
the call is never sent, not even on the operator's word; fix and rescan
first. Exit 1 (below 90/5) is shown, the operator's word decides; exit 3
+2 -2
View File
@@ -24,9 +24,9 @@ confirms it.
branch fixes, each verified as the `kvb-git-issue-close` skill does.
5. **Confirm** with one `AskUserQuestion`: title (Conventional Commits),
base, state and labels. The state follows the bar of the `kvb-org-split`
skill (fix confidence ≥ 90 %, break risk ≤ 5 %, efficiency and speed
skill (fix confidence ≥ 90 %, break risk ≤ 5 %, cost and time
≤ 100 %): ready at or above it, a draft with the residuals in the body
below it. An efficiency or speed over 100 % goes into this question with
below it. A cost or time over 100 % goes into this question with
its cause.
6. **Create**: `gh pr create --base <default> --head <branch> --title
"<title>" --body-file <file>`, plus `--draft` below the bar.
+2 -2
View File
@@ -12,7 +12,7 @@ holds:
failure on it, a red default-branch run or an issue that reproduces there;
name the run or issue.
- **`90/5`**: the pull request meets the bar of the `kvb-org-split` skill (fix
confidence ≥ 90 %, break risk ≤ 5 %, efficiency and speed ≤ 100 %; over
confidence ≥ 90 %, break risk ≤ 5 %, cost and time ≤ 100 %; over
100 %, ask the operator first, naming the cause), from the
`kvb-check-score` block in its body or freshly assessed, and only when
`gate.py status` of the `kvb-check-gate` skill prints `gate: green` with
@@ -27,7 +27,7 @@ holds:
3. A missing label is created once: `gh label create hotfix --color B60205
--description "Fixes a failure on the default branch"`, `gh label create
90/5 --color 0E8A16 --description
"Fix confidence >= 90 %, break risk <= 5 %, efficiency and speed <= 100 %"`.
"Fix confidence >= 90 %, break risk <= 5 %, cost and time <= 100 %"`.
4. Standalone, confirm the changes with one `AskUserQuestion`; from
`kvb-git-pr-create`, its confirmation covers them.
5. `gh pr edit <pr> --add-label <l>` or `--remove-label <l>`. A draft that
+2 -2
View File
@@ -38,8 +38,8 @@ One table, most-blocking first, one row per item:
- **State**: `open`, `running`, `blocked`, `done`. `done` means verified (a
passing run, a green check, a reproduction that now succeeds); work that
merely looks finished is `open`.
- **Score**: the `kvb-check-score` line (🎯 fix, 💥 break, 🌱 efficiency,
⚡ speed, 🏆 score) per the `kvb-check-confidence` skill, only for rows
- **Score**: the `kvb-check-score` line (🎯 fix, 💥 break, 💸 cost,
⏱️ time, 🏆 score) per the `kvb-check-confidence` skill, only for rows
that changed something; `-` for pure observation rows.
- **Evidence / next step**: for `done` the strongest proof line (what ran,
what it returned), otherwise the single next action.
+1 -1
View File
@@ -12,7 +12,7 @@ in a sidetree until it does, or leaves as a proposal.
## The bar
- **Fix confidence ≥ 90 %, break risk ≤ 5 %, efficiency and speed ≤ 100 %**,
- **Fix confidence ≥ 90 %, break risk ≤ 5 %, cost and time ≤ 100 %**,
all from the `kvb-check-score` block of `kvb-check-confidence`; over 100 %,
ask the operator before the commit, naming the cause.
- The operator may set a different bar. Never lower it to make a change fit.
+8 -8
View File
@@ -33,8 +33,8 @@ NEW_B = "💰 value ?:? (cost 0/day; pass --value with evidence; kvb-b new: all
CHEAP = "print('cheap')\n"
COSTLY = "data = list(range(2_000_000))\nprint(sum(data))\n"
LINE = re.compile(
r"^🌱 efficiency (?P<mark>~?)(?P<efficiency>\d+) % \(cpu \d+\.\d\d→\d+\.\d\d s, ram \d+\.\d→\d+\.\d MB\) · "
r"⚡ speed (?P<speed_mark>~?)(?P<speed>\d+) % \((?P<basis>wall|cpu) \d+\.\d\d→\d+\.\d\d s\)(?P<tail>.*)$"
r"^💸 cost (?P<mark>~?)(?P<efficiency>\d+) % \(cpu \d+\.\d\d→\d+\.\d\d s, ram \d+\.\d→\d+\.\d MB\) · "
r"⏱️ time (?P<speed_mark>~?)(?P<speed>\d+) % \((?P<basis>wall|cpu) \d+\.\d\d→\d+\.\d\d s\)(?P<tail>.*)$"
)
@@ -121,7 +121,7 @@ class TestTokens(GitRepo):
self.write("skills/kvb-a/scripts/tool.py", "print(1)\n" * 50)
result = self.tokens()
self.assertEqual(
result.stdout.splitlines(), ["🌱 efficiency 50 % (skills 100→50 tokens; listing 3→3 words)", NO_VALUE]
result.stdout.splitlines(), ["💸 cost 50 % (skills 100→50 tokens; listing 3→3 words)", NO_VALUE]
)
self.assertEqual(result.returncode, 0, result.stderr)
@@ -130,7 +130,7 @@ class TestTokens(GitRepo):
self.write("skills/kvb-b/SKILL.md", SKILL_B.ljust(200, "y"))
result = self.tokens()
self.assertEqual(
result.stdout.splitlines(), ["🌱 efficiency 200 % (skills 100→200 tokens; listing 3→5 words)", NEW_B]
result.stdout.splitlines(), ["💸 cost 200 % (skills 100→200 tokens; listing 3→5 words)", NEW_B]
)
self.assertEqual(result.returncode, 3)
result = self.tokens("--json")
@@ -157,7 +157,7 @@ class TestTokens(GitRepo):
def test_only_new_files_print_new(self):
self.write("skills/kvb-b/SKILL.md", SKILL_B.ljust(200, "y"))
result = self.tokens()
self.assertEqual(result.stdout.splitlines(), ["🌱 efficiency new (+50 tokens; listing 3→5 words)", NEW_B])
self.assertEqual(result.stdout.splitlines(), ["💸 cost new (+50 tokens; listing 3→5 words)", NEW_B])
self.assertEqual(result.returncode, 3)
self.assertIsNone(json.loads(self.tokens("--json").stdout)["efficiency"])
@@ -165,14 +165,14 @@ class TestTokens(GitRepo):
self.git("mv", "skills/kvb-a/notes.md", "skills/kvb-a/moved.md")
result = self.tokens()
self.assertEqual(
result.stdout.splitlines(), ["🌱 efficiency 100 % (skills 100→100 tokens; listing 3→3 words)", NO_VALUE]
result.stdout.splitlines(), ["💸 cost 100 % (skills 100→100 tokens; listing 3→3 words)", NO_VALUE]
)
self.assertEqual(result.returncode, 0)
def test_no_change_is_100(self):
result = self.tokens()
self.assertEqual(
result.stdout.splitlines(), ["🌱 efficiency 100 % (skills 0→0 tokens; listing 3→3 words)", NO_VALUE]
result.stdout.splitlines(), ["💸 cost 100 % (skills 0→0 tokens; listing 3→3 words)", NO_VALUE]
)
self.assertEqual(result.returncode, 0)
@@ -193,7 +193,7 @@ class TestTokens(GitRepo):
self.assertEqual(
result.stdout.splitlines(),
[
"🌱 efficiency 869 % (skills 13→113 tokens; listing 3→5 words)",
"💸 cost 869 % (skills 13→113 tokens; listing 3→5 words)",
"💰 value ?:? (cost 203/day; pass --value with evidence)",
],
)
+32 -29
View File
@@ -12,9 +12,9 @@ from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SCORE = REPO_ROOT / "skills" / "kvb-check-score" / "scripts" / "score.py"
PASS = "✅ gate: 90/5, efficiency and speed <= 100, security 100 %"
PASS = "✅ gate: 90/5, cost and time <= 100, security 100 %"
BASE = {"fix": "95", "break": "2", "efficiency": "100", "speed": "100", "security": "100", "critical": "0"}
HEADER = "| 🎯 Fix | 💥 Break | 🌱 Efficiency | ⚡ Speed | 🛡️ Security | 🏆 Score | Gate |"
HEADER = "| 🎯 Fix | 💥 Break | 💸 Cost | ⏱️ Time | 🛡️ Security | 🏆 Score | Gate |"
ASK = ": ask the operator before the commit"
ENV = {"PATH": os.environ["PATH"]}
@@ -37,12 +37,12 @@ class TestScore(unittest.TestCase):
def test_formula_and_line_format(self):
self.assertEqual(
self._score(fix="95", **{"break": "4"}),
(["🎯 fix 95 % · 💥 break 4 % · 🌱 efficiency 100 % · ⚡ speed 100 % · 🛡️ security 100 % · 🏆 score 91",
(["🎯 fix 95 % · 💥 break 4 % · 💸 cost 100 % · ⏱️ time 100 % · 🛡️ security 100 % · 🏆 score 91",
PASS], 0),
)
lines, code = self._score(efficiency="97", speed="-")
self.assertEqual(
lines[0], "🎯 fix 95 % · 💥 break 2 % · 🌱 efficiency 97 % · ⚡ speed – · 🛡️ security 100 % · 🏆 score 96"
lines[0], "🎯 fix 95 % · 💥 break 2 % · 💸 cost 97 % · ⏱️ time – · 🛡️ security 100 % · 🏆 score 96"
)
self.assertEqual(code, 0)
self.assertEqual(self._score(fix="92.5")[0][0].split(" · ")[0], "🎯 fix 92.5 %")
@@ -51,14 +51,14 @@ class TestScore(unittest.TestCase):
lines, code = self._score(fix="92", efficiency="-", speed="-", **{"break": "3"})
self.assertEqual(
(lines, code),
(["🎯 fix 92 % · 💥 break 3 % · 🌱 efficiency – · ⚡ speed – · 🛡️ security 100 % · 🏆 score 89", PASS], 0),
(["🎯 fix 92 % · 💥 break 3 % · 💸 cost – · ⏱️ time – · 🛡️ security 100 % · 🏆 score 89", PASS], 0),
)
def test_security_multiplies_and_asks_below_100(self):
lines, code = self._score(fix="100", efficiency="-", speed="-", security="75", **{"break": "0"})
self.assertEqual(
(lines, code),
(["🎯 fix 100 % · 💥 break 0 % · 🌱 efficiency – · ⚡ speed – · 🛡️ security 75 % · 🏆 score 75",
(["🎯 fix 100 % · 💥 break 0 % · 💸 cost – · ⏱️ time – · 🛡️ security 75 % · 🏆 score 75",
f"⚠️ gate: security 75 % < 100{ASK}"], 3),
)
lines, code = self._score(security="~80")
@@ -94,7 +94,7 @@ class TestScore(unittest.TestCase):
({"fix": "80", "break": "10"}, "⛔ gate: fix 80 % < 90, break 10 % > 5"),
(
{"fix": "85", "efficiency": "150", "speed": "new", "security": "50"},
f"⛔ gate: fix 85 % < 90; efficiency 150 % > 100, speed new, security 50 % < 100{ASK}",
f"⛔ gate: fix 85 % < 90; cost 150 % > 100, time new, security 50 % < 100{ASK}",
),
):
with self.subTest(values=values):
@@ -103,13 +103,13 @@ class TestScore(unittest.TestCase):
def test_costs_ask_the_operator_exit_3(self):
for values, verdict in (
({"efficiency": "120"}, f"⚠️ gate: efficiency 120 % > 100{ASK}"),
({"efficiency": "-", "speed": "130"}, f"⚠️ gate: speed 130 % > 100{ASK}"),
({"efficiency": "100.5", "speed": "-"}, f"⚠️ gate: efficiency 100.5 % > 100{ASK}"),
({"efficiency": "new", "speed": "-"}, f"⚠️ gate: efficiency new{ASK}"),
({"efficiency": "120"}, f"⚠️ gate: cost 120 % > 100{ASK}"),
({"efficiency": "-", "speed": "130"}, f"⚠️ gate: time 130 % > 100{ASK}"),
({"efficiency": "100.5", "speed": "-"}, f"⚠️ gate: cost 100.5 % > 100{ASK}"),
({"efficiency": "new", "speed": "-"}, f"⚠️ gate: cost new{ASK}"),
(
{"efficiency": "120", "speed": "130", "security": "90"},
f"⚠️ gate: efficiency 120 % > 100, speed 130 % > 100, security 90 % < 100{ASK}",
f"⚠️ gate: cost 120 % > 100, time 130 % > 100, security 90 % < 100{ASK}",
),
):
with self.subTest(values=values):
@@ -128,7 +128,7 @@ class TestScore(unittest.TestCase):
def test_new_counts_as_100_in_the_score(self):
lines, _ = self._score(efficiency="new", speed="-", **{"break": "0"})
self.assertEqual(
lines[0], "🎯 fix 95 % · 💥 break 0 % · 🌱 efficiency new · ⚡ speed – · 🛡️ security 100 % · 🏆 score 95"
lines[0], "🎯 fix 95 % · 💥 break 0 % · 💸 cost new · ⏱️ time – · 🛡️ security 100 % · 🏆 score 95"
)
def test_table(self):
@@ -138,7 +138,7 @@ class TestScore(unittest.TestCase):
[
HEADER,
"| --- | --- | --- | --- | --- | --- | --- |",
"| 95 % | 2 % | 97 % | – | 100 % | 96 | ✅ 90/5, efficiency and speed <= 100, security 100 % |",
"| 95 % | 2 % | 97 % | – | 100 % | 96 | ✅ 90/5, cost and time <= 100, security 100 % |",
],
)
self.assertEqual(result.returncode, 0)
@@ -152,8 +152,8 @@ class TestScore(unittest.TestCase):
(result.stdout.splitlines(), result.returncode),
(
[
"🎯 Fix: 95 %", "💥 Break: 2 %", "🌱 Efficiency: 97 %", "⚡ Speed: –", "🛡️ Security: 100 %",
"🏆 Score: 96", "🚦 Gate: ✅ 90/5, efficiency and speed <= 100, security 100 %",
"🎯 Fix: 95 %", "💥 Break: 2 %", "💸 Cost: 97 %", "⏱️ Time: –", "🛡️ Security: 100 %",
"🏆 Score: 96", "🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %",
],
0,
),
@@ -165,7 +165,7 @@ class TestScore(unittest.TestCase):
(result.stdout.splitlines(), result.returncode),
(
[
"🎯 Fix: 95 %", "💥 Break: 2 %", "🌱 Efficiency: 100 %", "⚡ Speed: 100 %", "🛡️ Security: ~80 %",
"🎯 Fix: 95 %", "💥 Break: 2 %", "💸 Cost: 100 %", "⏱️ Time: 100 %", "🛡️ Security: ~80 %",
"💰 Value: 1:20", "🏆 Score: 74", f"🚦 Gate: ⚠️ security ~80 % < 100{ASK}",
],
3,
@@ -188,9 +188,9 @@ class TestScore(unittest.TestCase):
def test_value_column_changes_neither_score_nor_gate(self):
for value, verdict, code in (
("1:20", "✅ 90/5, efficiency and speed <= 100, security 100 %", 0),
("4.1:1", "✅ 90/5, efficiency and speed <= 100, security 100 %", 0),
("?", "⚠️ efficiency 120 % > 100" + ASK, 3),
("1:20", "✅ 90/5, cost and time <= 100, security 100 %", 0),
("4.1:1", "✅ 90/5, cost and time <= 100, security 100 %", 0),
("?", "⚠️ cost 120 % > 100" + ASK, 3),
):
with self.subTest(value=value):
efficiency = "97" if code == 0 else "120"
@@ -201,7 +201,7 @@ class TestScore(unittest.TestCase):
self.assertEqual(
result.stdout.splitlines(),
[
HEADER.replace("🌱 Efficiency |", "🌱 Efficiency | 💰 Value |"),
HEADER.replace("💸 Cost |", "💸 Cost | 💰 Value |"),
"|" + " --- |" * 8,
f"| 95 % | 2 % | {efficiency} % | {value} | – | 100 % | {score} | {verdict} |",
],
@@ -235,8 +235,9 @@ class TestScore(unittest.TestCase):
(repo / "a.txt").write_text("two\n", encoding="utf-8")
(repo / "b.txt").write_text("three\nfour\n", encoding="utf-8")
git("add", "a.txt", "b.txt")
head = ["📁 Worktree", "🌿 Branch", "📊 Diff stat"]
score = "| 95 % | 2 % | 100 % | 100 % | 100 % | 93 | ✅ 90/5, efficiency and speed <= 100, security 100 % |"
head = ["🌿 Branch", "📊 Diff stat"]
score = "| 95 % | 2 % | 100 % | 100 % | 100 % | 93 | ✅ 90/5, cost and time <= 100, security 100 % |"
worktree = ["", "📁 Worktree:", "", "```text", str(repo.resolve()), "```"]
result = self._run(*flags(BASE), "--commit-table", str(repo), cwd=repo)
self.assertEqual(result.returncode, 0, result.stderr)
@@ -244,27 +245,29 @@ class TestScore(unittest.TestCase):
result.stdout.splitlines(),
[
"| " + " | ".join(head) + " " + HEADER,
"|" + " --- |" * 10,
f"| {repo.resolve()} | main | 2 files +3/-1 " + score,
"|" + " --- |" * 9,
"| main | 2 files +3/-1 " + score,
*worktree,
],
)
git("commit", "-qm", "change")
sha = git("rev-parse", "--short", "HEAD")
result = self._run(*flags(BASE), "--commit-table", str(repo), "--commit", "HEAD", cwd=repo)
self.assertEqual(result.stdout.splitlines()[0], "| " + " | ".join([*head, "🔖 Commit"]) + " " + HEADER)
self.assertEqual(result.stdout.splitlines()[2], f"| {repo.resolve()} | main | 2 files +3/-1 | {sha} " + score)
self.assertEqual(result.stdout.splitlines()[2], f"| main | 2 files +3/-1 | {sha} " + score)
self.assertEqual(result.stdout.splitlines()[3:], worktree)
result = self._run(*flags(BASE), "--commit-table", str(repo), "--commit", "HEAD", "--value", "?", cwd=repo)
self.assertEqual(
result.stdout.splitlines()[0],
"| " + " | ".join([*head, "🔖 Commit"]) + " " + HEADER.replace("🌱 Efficiency |", "🌱 Efficiency | 💰 Value |"),
"| " + " | ".join([*head, "🔖 Commit"]) + " " + HEADER.replace("💸 Cost |", "💸 Cost | 💰 Value |"),
)
self.assertEqual(
result.stdout.splitlines()[2],
f"| {repo.resolve()} | main | 2 files +3/-1 | {sha} " + score.replace("| 100 % |", "| 100 % | ? |", 1),
f"| main | 2 files +3/-1 | {sha} " + score.replace("| 100 % |", "| 100 % | ? |", 1),
)
git("checkout", "-q", "--detach", "HEAD")
result = self._run(*flags(BASE), "--commit-table", str(repo), cwd=repo)
self.assertEqual(result.stdout.splitlines()[2], f"| {repo.resolve()} | detached at {sha} | 0 files +0/-0 " + score)
self.assertEqual(result.stdout.splitlines()[2], f"| detached at {sha} | 0 files +0/-0 " + score)
self.assertEqual(self._run(*flags(BASE), "--commit-table", tmp.name, cwd=repo).returncode, 2)
def test_usage_errors_exit_2(self):