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:
1 parent
938cea9447
commit
b18a481d8c
17 files changed
+120
-115
No files matched your search
@@ -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>`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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,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.
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user