bigpowers 2.91.0 → 2.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.agents/skills/guard-git/SKILL.md +7 -2
  2. package/.cline/skills/guard-git/SKILL.md +7 -2
  3. package/.codebuddy/skills/guard-git/SKILL.md +7 -2
  4. package/.codex/skills/guard-git/SKILL.md +7 -2
  5. package/.copilot/skills/guard-git/SKILL.md +7 -2
  6. package/.cursor/rules/guard-git.mdc +7 -2
  7. package/.gemini/extensions/bigpowers/commands/prompts/guard-git.md +7 -2
  8. package/.gemini/extensions/bigpowers/gemini-extension.json +1 -1
  9. package/.gemini/extensions/bigpowers/skills/guard-git/SKILL.md +7 -2
  10. package/.hermes/skills/guard-git/SKILL.md +7 -2
  11. package/.kilocode/rules/guard-git.md +7 -2
  12. package/.mimocode/skills/guard-git/SKILL.md +7 -2
  13. package/.opencode/skills/guard-git/SKILL.md +7 -2
  14. package/.pi/package.json +1 -1
  15. package/.pi/prompts/guard-git.md +7 -2
  16. package/.pi/skills/guard-git/SKILL.md +7 -2
  17. package/.qwen/skills/guard-git/SKILL.md +7 -2
  18. package/.trae/skills/guard-git/SKILL.md +7 -2
  19. package/.windsurf/rules/guard-git.md +7 -2
  20. package/.zcode/skills/guard-git/SKILL.md +7 -2
  21. package/CHANGELOG.md +15 -0
  22. package/extensions/omp-hooks.ts +197 -58
  23. package/hooks/pre/bash.sh +54 -7
  24. package/hooks/pre-tool-use.sh +49 -11
  25. package/package.json +1 -1
  26. package/scripts/ab-sandbox.sh +3 -3
  27. package/scripts/adapters/agy.sh +1 -1
  28. package/scripts/adapters/cursor.sh +1 -1
  29. package/scripts/adapters/gemini.sh +1 -1
  30. package/scripts/adapters/mimo.sh +1 -1
  31. package/scripts/adapters/pi.sh +1 -1
  32. package/scripts/adapters/zcode.sh +1 -1
  33. package/scripts/generate-context-bundle.sh +2 -1
  34. package/scripts/lib/__pycache__/atomic_write.cpython-310.pyc +0 -0
  35. package/scripts/lib/__pycache__/link_utils.cpython-310.pyc +0 -0
  36. package/scripts/lib/__pycache__/srp_engine_links.cpython-310.pyc +0 -0
  37. package/scripts/lib/__pycache__/srp_engine_okf.cpython-310.pyc +0 -0
  38. package/scripts/lib/ab-eval.py +19 -5
  39. package/scripts/lib/ab_lean.py +116 -0
  40. package/scripts/lib/ab_metrics.py +8 -0
  41. package/scripts/lib/ab_progress.py +3 -1
  42. package/scripts/lib/ab_report.py +15 -4
  43. package/scripts/lib/ab_vsm.py +122 -0
  44. package/scripts/lib/ab_waste.py +173 -0
  45. package/scripts/lib/git-argv.py +168 -0
  46. package/scripts/lib/golden-suite-gates.sh +2 -0
  47. package/scripts/omp-guard-probe.ts +46 -0
  48. package/scripts/test-adapter-render.sh +1 -1
  49. package/scripts/test-guard-git-normalize.sh +189 -0
  50. package/scripts/test-test-lock-hook.sh +7 -7
  51. package/scripts/verify-adversarial.sh +8 -1
  52. package/skills/guard-git/SKILL.md +7 -2
  53. package/skills-lock.json +1 -1
  54. package/tests/test_ab_eval.py +0 -44
  55. package/tests/test_ab_lean.py +81 -0
  56. package/tests/test_ab_score.py +12 -2
  57. package/tests/test_ab_tasks.py +75 -0
  58. package/tests/test_ab_vsm.py +109 -0
  59. package/tests/test_ab_waste.py +106 -0
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python3
2
+ # story: e58s08
3
+ """tests/test_ab_vsm.py — value stream map per run: stage rules, per-turn time, run records.
4
+
5
+ Run: python3 tests/test_ab_vsm.py
6
+ """
7
+ import json
8
+ import sys
9
+ import unittest
10
+ from pathlib import Path
11
+
12
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
13
+ from test_ab_eval import Sandbox, load_module # noqa: E402
14
+
15
+ vsm = load_module("ab_vsm", "ab_vsm.py")
16
+ STAGES = {"plan", "design", "build", "test", "deploy", "maintain", "wrap-up"}
17
+
18
+
19
+ def assistant(ts_s, cost=0.001, out=10, calls=()):
20
+ content = [{"type": "toolCall", "name": tool, "arguments": args} for tool, args in calls]
21
+ usage = {"input": 100, "output": out, "cacheRead": 0, "cacheWrite": 0, "cost": {"total": cost}}
22
+ return {"type": "message_end", "message": {"role": "assistant", "timestamp": int(ts_s * 1000),
23
+ "content": content, "usage": usage}}
24
+
25
+
26
+ def tool_result(ts_s):
27
+ return {"type": "message_end", "message": {"role": "toolResult", "timestamp": int(ts_s * 1000)}}
28
+
29
+
30
+ def transcript():
31
+ events = [
32
+ {"type": "session", "id": "s"},
33
+ {"type": "message_end", "message": {"role": "user", "timestamp": 0}},
34
+ assistant(0, calls=[("read", {"path": "/work/ISSUE.md"})]), tool_result(9),
35
+ assistant(10, calls=[("bash", {"command": "cat src/reports.js"})]), tool_result(24),
36
+ assistant(25, calls=[("edit", {"path": "/work/src/reports.js"})]), tool_result(39),
37
+ assistant(40, calls=[("bash", {"command": "npm test 2>&1 | tail"})]), tool_result(49),
38
+ assistant(50, calls=[("bash", {"command": "git commit -am fix"})]), tool_result(54),
39
+ assistant(55, out=40),
40
+ {"type": "agent_settled", "aborted": False},
41
+ ]
42
+ return "\n".join(json.dumps(e) for e in events)
43
+
44
+
45
+ class TestRules(unittest.TestCase):
46
+ def setUp(self):
47
+ self.rules = vsm.load_rules()
48
+
49
+ def test_rules_file_uses_known_stages(self):
50
+ self.assertTrue({r["stage"] for r in self.rules["rules"]} <= STAGES)
51
+ self.assertTrue(set(self.rules["turn_priority"]) <= STAGES)
52
+
53
+ def test_calls_are_classified(self):
54
+ cases = [
55
+ ("read", {"path": "/work/ISSUE.md"}, "plan"),
56
+ ("bash", {"command": "sed -n 1,80p .agents/skills/fix-bug/SKILL.md"}, "plan"),
57
+ ("read", {"path": "/work/src/reports.js"}, "design"),
58
+ ("edit", {"path": "/work/src/reports.js"}, "build"),
59
+ ("edit", {"path": "/work/test/ledger.test.js"}, "test"),
60
+ ("bash", {"command": "npm test 2>&1 | tail -20"}, "test"),
61
+ ("bash", {"command": "node --test test/"}, "test"),
62
+ ("bash", {"command": "git checkout -b fix/rounding"}, "deploy"),
63
+ ("bash", {"command": "git commit -am 'fix'"}, "deploy"),
64
+ ("write", {"path": "/work/specs/bugs/BUG-001.md"}, "maintain"),
65
+ ("edit", {"path": "/work/docs/LEDGER.md"}, "maintain"),
66
+ ("write", {"path": "/work/specs/plan.md"}, "plan"),
67
+ ]
68
+ for tool, args, expected in cases:
69
+ with self.subTest(tool=tool, args=args):
70
+ self.assertEqual(vsm.classify_call(tool, args, self.rules), expected)
71
+
72
+
73
+ class TestStageTime(unittest.TestCase):
74
+ def test_turn_time_goes_to_the_stage_of_its_action(self):
75
+ block = vsm.vsm_from("pi-json", transcript())
76
+ seconds = {stage: round(v["s"], 1) for stage, v in block["stages"].items()}
77
+ self.assertEqual(seconds, {"plan": 10.0, "design": 15.0, "build": 15.0, "test": 10.0,
78
+ "deploy": 5.0, "wrap-up": 0.0})
79
+ self.assertEqual(block["lead_time_s"], 55.0)
80
+ self.assertEqual(block["time_to_first_build_s"], 25.0)
81
+ self.assertEqual(block["switches"], 5)
82
+ self.assertEqual(block["sequence"], ["plan", "design", "build", "test", "deploy", "wrap-up"])
83
+ self.assertEqual(block["stages"]["wrap-up"]["output_tokens"], 40)
84
+ self.assertAlmostEqual(sum(v["cost_usd"] for v in block["stages"].values()), 0.006)
85
+
86
+ def test_build_wins_a_turn_with_several_calls(self):
87
+ events = [assistant(0, calls=[("read", {"path": "/work/ISSUE.md"}), ("edit", {"path": "/work/src/a.js"})]),
88
+ tool_result(5)]
89
+ block = vsm.vsm_from("pi-json", "\n".join(json.dumps(e) for e in events))
90
+ self.assertEqual(block["sequence"], ["build"])
91
+
92
+ def test_unsupported_or_empty_is_none(self):
93
+ self.assertIsNone(vsm.vsm_from("none", transcript()))
94
+ self.assertIsNone(vsm.vsm_from("pi-json", "not json"))
95
+
96
+
97
+ class TestRunRecord(Sandbox):
98
+ def test_record_carries_vsm(self):
99
+ script = self.tmp / "transcript.jsonl"
100
+ script.write_text(transcript() + "\n")
101
+ self.run_main("--adapter-cmd", f"cat {script}; echo 42 > answer.txt", "--parser", "pi-json",
102
+ "--arms", "vanilla")
103
+ rec = self.records()[0]
104
+ self.assertEqual(rec["vsm"]["stages"]["build"]["turns"], 1)
105
+ self.assertEqual(rec["vsm"]["lead_time_s"], 55.0)
106
+
107
+
108
+ if __name__ == "__main__":
109
+ unittest.main(verbosity=1)
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ # story: e58s09
3
+ """tests/test_ab_waste.py — lean waste metrics: relearning, rework, first-time-through, patch metrics.
4
+
5
+ Run: python3 tests/test_ab_waste.py
6
+ """
7
+ import gzip
8
+ import json
9
+ import sys
10
+ import unittest
11
+ from pathlib import Path
12
+
13
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
14
+ from test_ab_eval import Sandbox, load_module # noqa: E402
15
+
16
+ waste = load_module("ab_waste", "ab_waste.py")
17
+
18
+
19
+ class Stream:
20
+ """Builds a pi JSON transcript: assistant turns with one tool call each, plus tool results."""
21
+
22
+ def __init__(self):
23
+ self.events, self.t, self.n = [], 0, 0
24
+
25
+ def call(self, tool, args, result_text="ok", exit_code=0):
26
+ self.n += 1
27
+ cid = f"c{self.n}"
28
+ self.events.append({"type": "message_end", "message": {
29
+ "role": "assistant", "timestamp": self.t * 1000, "usage": {"output": 5, "cost": {"total": 0.001}},
30
+ "content": [{"type": "toolCall", "name": tool, "arguments": args}]}})
31
+ self.events.append({"type": "tool_execution_start", "toolCallId": cid, "toolName": tool, "args": args})
32
+ result = {"content": [{"type": "text", "text": result_text}], "details": {"exit_code": exit_code}}
33
+ self.events.append({"type": "tool_execution_end", "toolCallId": cid, "toolName": tool,
34
+ "result": result, "isError": False})
35
+ self.events.append({"type": "message_end", "message": {"role": "toolResult",
36
+ "timestamp": (self.t + 1) * 1000}})
37
+ self.t += 10
38
+ return self
39
+
40
+ def text(self):
41
+ return "\n".join(json.dumps(e) for e in self.events)
42
+
43
+
44
+ class TestTranscriptWaste(unittest.TestCase):
45
+ def test_relearning_and_rework_are_counted(self):
46
+ stream = (Stream()
47
+ .call("read", {"path": "/work/src/a.js"})
48
+ .call("read", {"path": "/work/src/a.js"})
49
+ .call("bash", {"command": "ls"})
50
+ .call("bash", {"command": "ls"})
51
+ .call("edit", {"path": "/work/src/a.js"})
52
+ .call("bash", {"command": "npm test 2>&1 | tail"}, "ℹ pass 3\nℹ fail 1\n")
53
+ .call("edit", {"path": "/work/src/a.js"})
54
+ .call("bash", {"command": "npm test 2>&1 | tail"}, "ℹ pass 4\nℹ fail 0\n"))
55
+ got = waste.transcript_waste(stream.text())
56
+ self.assertEqual((got["rereads"], got["repeated_commands"], got["test_runs"], got["rework_loops"],
57
+ got["reedits"], got["ftt"]), (1, 1, 2, 1, 1, False))
58
+
59
+ def test_non_zero_exit_is_a_failing_run(self):
60
+ stream = Stream().call("edit", {"path": "/work/src/a.js"}).call(
61
+ "bash", {"command": "node --test test/"}, "boom", exit_code=1)
62
+ self.assertEqual(waste.transcript_waste(stream.text())["rework_loops"], 1)
63
+
64
+ def test_clean_run_is_first_time_through(self):
65
+ stream = Stream().call("edit", {"path": "/work/src/a.js"}).call(
66
+ "bash", {"command": "npm test"}, "# pass 5\n# fail 0\n")
67
+ got = waste.transcript_waste(stream.text())
68
+ self.assertEqual((got["rework_loops"], got["ftt"]), (0, True))
69
+ self.assertGreater(got["pce"], 0)
70
+
71
+ def test_no_transcript_gives_none(self):
72
+ self.assertIsNone(waste.transcript_waste("not json"))
73
+
74
+
75
+ class TestPatchWaste(Sandbox):
76
+ AGENT = ("echo 42 > answer.txt; printf 'a\\nb\\n' > extra.js; "
77
+ "printf 'one two three\\n' > notes.md; echo x > debug.tmp.js")
78
+
79
+ def setUp(self):
80
+ super().setUp()
81
+ solution = self.tasks_dir / "t-01" / "solution"
82
+ solution.mkdir()
83
+ (solution / "answer.txt").write_text("42\n")
84
+
85
+ def test_patch_metrics_and_saved_patch(self):
86
+ self.run_main("--adapter-cmd", self.AGENT, "--arms", "vanilla")
87
+ got = self.records()[0]["waste"]
88
+ self.assertEqual(got["solution_lines"], 1)
89
+ self.assertIn("extra.js", got["extra_files"])
90
+ self.assertNotIn("answer.txt", got["extra_files"])
91
+ self.assertEqual(got["leftovers"], ["debug.tmp.js"])
92
+ self.assertEqual(got["doc_lines"], 1)
93
+ self.assertGreater(got["diff_ratio"], 1)
94
+ self.assertGreaterEqual(got["review_load"], got["product_lines"] + 3)
95
+ patch = self.out / "traces" / "t-01-vanilla-1.patch.gz"
96
+ self.assertIn("extra.js", gzip.decompress(patch.read_bytes()).decode())
97
+
98
+ def test_non_pi_adapter_has_patch_metrics_only(self):
99
+ self.run_main("--adapter-cmd", self.AGENT, "--arms", "vanilla")
100
+ got = self.records()[0]["waste"]
101
+ self.assertIsNone(got["rework_loops"])
102
+ self.assertIsNotNone(got["review_load"])
103
+
104
+
105
+ if __name__ == "__main__":
106
+ unittest.main(verbosity=1)