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
@@ -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