runboth 0.1.0__tar.gz

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 (40) hide show
  1. runboth-0.1.0/LICENSE.md +93 -0
  2. runboth-0.1.0/PKG-INFO +163 -0
  3. runboth-0.1.0/README.md +146 -0
  4. runboth-0.1.0/pyproject.toml +42 -0
  5. runboth-0.1.0/runboth/__init__.py +30 -0
  6. runboth-0.1.0/runboth/adjudicate.py +841 -0
  7. runboth-0.1.0/runboth/agent_hook.py +180 -0
  8. runboth-0.1.0/runboth/blast.py +241 -0
  9. runboth-0.1.0/runboth/cli.py +337 -0
  10. runboth-0.1.0/runboth/control_constructors.py +49 -0
  11. runboth-0.1.0/runboth/control_dotted_imports.py +43 -0
  12. runboth-0.1.0/runboth/control_overblock.py +69 -0
  13. runboth-0.1.0/runboth/determinism.py +302 -0
  14. runboth-0.1.0/runboth/engine.py +593 -0
  15. runboth-0.1.0/runboth/fixtures.py +326 -0
  16. runboth-0.1.0/runboth/hook.py +153 -0
  17. runboth-0.1.0/runboth/install_agent.py +97 -0
  18. runboth-0.1.0/runboth/install_hook.py +217 -0
  19. runboth-0.1.0/runboth/interagent.py +229 -0
  20. runboth-0.1.0/runboth/isolate.py +181 -0
  21. runboth-0.1.0/runboth/mcp_server.py +181 -0
  22. runboth-0.1.0/runboth/mergecheck.py +224 -0
  23. runboth-0.1.0/runboth/methods.py +445 -0
  24. runboth-0.1.0/runboth/pr_report.py +262 -0
  25. runboth-0.1.0/runboth/precommit.py +591 -0
  26. runboth-0.1.0/runboth/sandbox.py +1246 -0
  27. runboth-0.1.0/runboth/versions.py +220 -0
  28. runboth-0.1.0/runboth.egg-info/PKG-INFO +163 -0
  29. runboth-0.1.0/runboth.egg-info/SOURCES.txt +38 -0
  30. runboth-0.1.0/runboth.egg-info/dependency_links.txt +1 -0
  31. runboth-0.1.0/runboth.egg-info/entry_points.txt +2 -0
  32. runboth-0.1.0/runboth.egg-info/top_level.txt +1 -0
  33. runboth-0.1.0/setup.cfg +4 -0
  34. runboth-0.1.0/tests/test_change_coverage.py +103 -0
  35. runboth-0.1.0/tests/test_controls.py +91 -0
  36. runboth-0.1.0/tests/test_gate_end_to_end.py +178 -0
  37. runboth-0.1.0/tests/test_measurement_artifacts.py +205 -0
  38. runboth-0.1.0/tests/test_report_shaping.py +136 -0
  39. runboth-0.1.0/tests/test_versions.py +103 -0
  40. runboth-0.1.0/tests/test_witness_quality.py +113 -0
@@ -0,0 +1,93 @@
1
+ # Functional Source License, Version 1.1, Apache 2.0 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-Apache-2.0
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Kyle Clouthier, Clouthier Simulation Labs
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under these Terms and
20
+ Conditions, as indicated by our inclusion of these Terms and Conditions with the Software.
21
+
22
+ ### License Grant
23
+
24
+ Subject to your compliance with this License Grant and the Patents, Redistribution and Trademark
25
+ clauses below, we hereby grant you the right to use, copy, modify, create derivative works,
26
+ publicly perform, publicly display and redistribute the Software for any Permitted Purpose
27
+ identified below.
28
+
29
+ ### Permitted Purpose
30
+
31
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use means making the
32
+ Software available to others in a commercial product or service that:
33
+
34
+ 1. substitutes for the Software;
35
+ 2. substitutes for any other product or service we offer using the Software that exists as of the
36
+ date we make the Software available; or
37
+ 3. offers the same or substantially similar functionality as the Software.
38
+
39
+ Permitted Purposes specifically include using the Software:
40
+
41
+ 1. for your internal use and access;
42
+ 2. for non-commercial education;
43
+ 3. for non-commercial research; and
44
+ 4. in connection with professional services that you provide to a licensee using the Software in
45
+ accordance with these Terms and Conditions.
46
+
47
+ ### Patents
48
+
49
+ To the extent your use for a Permitted Purpose would necessarily infringe our patents, the license
50
+ grant above includes a license under our patents. If you make a claim against any party that the
51
+ Software infringes or contributes to the infringement of any patent, then your patent license to
52
+ the Software ends immediately.
53
+
54
+ ### Redistribution
55
+
56
+ The Terms and Conditions apply to all copies, modifications and derivatives of the Software.
57
+
58
+ If you redistribute any copies, modifications or derivatives of the Software, you must include a
59
+ copy of or a link to these Terms and Conditions and not remove any copyright notices provided in
60
+ or with the Software.
61
+
62
+ ### Disclaimer
63
+
64
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
65
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
66
+ NONINFRINGEMENT.
67
+
68
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE SOFTWARE,
69
+ INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, EVEN IF WE HAVE BEEN INFORMED OF
70
+ THEIR POSSIBILITY IN ADVANCE.
71
+
72
+ ### Trademarks
73
+
74
+ Except for displaying the License Details and identifying us as the origin of the Software, you
75
+ have no right under these Terms and Conditions to use our trademarks, trade names, service marks
76
+ or product names.
77
+
78
+ ## Grant of Future License
79
+
80
+ We hereby irrevocably grant you an additional license to use the Software under the Apache License,
81
+ Version 2.0 that is effective on the second anniversary of the date we make the Software available.
82
+ On or after that date, you may use the Software under the Apache License, Version 2.0, in which
83
+ case the following will apply:
84
+
85
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this Software
86
+ except in compliance with the License.
87
+
88
+ You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
89
+
90
+ Unless required by applicable law or agreed to in writing, software distributed under the License
91
+ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
92
+ or implied. See the License for the specific language governing permissions and limitations under
93
+ the License.
runboth-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: runboth
3
+ Version: 0.1.0
4
+ Summary: An AI changed your code. RunBoth runs both versions and tells you what actually behaves differently, including the functions nobody touched.
5
+ Author-email: Kyle Clouthier <kyle@simgen.dev>
6
+ License: FSL-1.1-Apache-2.0
7
+ Project-URL: Homepage, https://runboth.dev
8
+ Keywords: differential testing,behaviour,refactoring,AI agents,code review
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Software Development :: Testing
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE.md
16
+ Dynamic: license-file
17
+
18
+ <p align="center">
19
+ <img src="brand/mark-128.png" width="72" alt="">
20
+ </p>
21
+
22
+ <h1 align="center">RunBoth</h1>
23
+
24
+ <p align="center">
25
+ <b>An AI changed your code. RunBoth runs both versions and tells you what actually behaves
26
+ differently, including the functions nobody touched.</b>
27
+ </p>
28
+
29
+ <p align="center">
30
+ <a href="https://runboth.dev">runboth.dev</a> ·
31
+ <a href="HOW_IT_WORKS.md">How it works</a> ·
32
+ <a href="RED_TEAM_2026-09-12.md">Red team results</a>
33
+ </p>
34
+
35
+ ---
36
+
37
+ ```
38
+ $ git commit -m "refactor: tidy up the rates module"
39
+
40
+ BLOCKED: the behaviour changed and your message does not say so.
41
+
42
+ rate(100)
43
+ used to: return 0.1
44
+ now: return 0.0
45
+
46
+ AND 1 function you did NOT touch now behaves differently,
47
+ because it calls what you changed:
48
+
49
+ total(2.5, 100) pkg/invoice.py
50
+ used to: return 225.0
51
+ now: return 250.0
52
+ ```
53
+
54
+ ## What it does
55
+
56
+ It checks out both versions of your code, generates inputs for every changed function from its
57
+ signature and from the constants mined out of its own bytecode, runs both versions in separate
58
+ sandboxed subprocesses, and compares seven observation channels. When they disagree it hands you
59
+ the exact input that separates them.
60
+
61
+ No test suite required. No network calls. No AI model. No dependencies.
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ pip install runboth # once the first release is on PyPI
67
+ pip install git+https://github.com/runboth/runboth # works today
68
+ runboth install-hook # a commit-msg gate, silent unless behaviour moved
69
+ ```
70
+
71
+ As a GitHub Action, running on your own runners:
72
+
73
+ ```yaml
74
+ - uses: runboth/runboth@v0.1.0
75
+ with:
76
+ budget: 60
77
+ ```
78
+
79
+ ## Three verdicts, never two
80
+
81
+ | verdict | meaning |
82
+ |---|---|
83
+ | `changed` | with a witness: the arguments, the old result, the new result |
84
+ | `no_change at budget N` | N generated inputs found no difference across seven channels |
85
+ | `abstained` | it could not be checked, and here is the reason |
86
+
87
+ "Cannot tell" and "no difference" are different claims, and collapsing them into a green check is
88
+ how tools end up lying. **RunBoth never says safe.**
89
+
90
+ ## The seven channels
91
+
92
+ Return value · exception raised · warnings · stdout · stderr · argument mutation · object state.
93
+
94
+ A narrow definition of behaviour does not under-report, it lies, because whatever sits outside the
95
+ definition comes back as `no_change`.
96
+
97
+ ## What it is not
98
+
99
+ **Not a model checker.** Kani and CBMC translate code into logic, let inputs be unconstrained
100
+ symbols, and ask a solver whether a bad state is reachable within a bound. They return a proof.
101
+ RunBoth executes real code on concrete values. It finds differences and reproduces them; it
102
+ cannot prove absence, and never claims to.
103
+
104
+ **Not mutation testing.** Mutation testing damages your code to score your test suite. RunBoth
105
+ damages nothing; both versions come from your git history, and no test suite is needed.
106
+
107
+ ## Measured
108
+
109
+ Red-teamed against eight public repositories it had never been tuned on, with an automated oracle
110
+ built to catch the tool lying. **2,548 functions, zero false positives.** Full method and numbers
111
+ in [RED_TEAM_2026-09-12.md](RED_TEAM_2026-09-12.md).
112
+
113
+ | repo | layout | functions | abstained | median/commit |
114
+ |---|---|---|---|---|
115
+ | boltons | flat | 268 | 0.0% | 7.9s |
116
+ | sqlparse | flat | 230 | 0.9% | 14.3s |
117
+ | arrow | flat | 283 | 1.4% | 118s |
118
+ | cachetools | src/ | 325 | 1.8% | 60.9s |
119
+ | more-itertools | flat | 843 | 4.4% | 102.5s |
120
+ | packaging | src/ | 78 | 5.1% | 0.2s |
121
+ | pluggy | src/ | 157 | 6.4% | 33.5s |
122
+ | tenacity | async | 364 | 14.3% | 192.8s |
123
+
124
+ An adversarial corpus of 22 functions written specifically to induce false positives (object
125
+ addresses in default `repr`, `datetime.now`, unseeded `random`, `uuid4`, `os.getpid`, set
126
+ iteration order, mutable defaults, generators, `__file__` paths) produced none.
127
+
128
+ ## Environment variables
129
+
130
+ | variable | effect |
131
+ |---|---|
132
+ | `RUNBOTH_SKIP=1` | let a commit through without checking it |
133
+ | `RUNBOTH_BUDGET` | generated inputs per function (gate default 80) |
134
+ | `RUNBOTH_WORKERS` | parallel adjudications, default 4 |
135
+ | `RUNBOTH_ALL_PATHS=1` | also check tests, benchmarks, docs and task runners |
136
+ | `RUNBOTH_ENGINE` | engine directory, if the hook cannot resolve it |
137
+
138
+ `git commit --no-verify` also bypasses the gate, and the gate says so itself when it blocks.
139
+
140
+ ## Honest limits
141
+
142
+ - Function-level checking is **Python only**. Changed files in other languages are named
143
+ explicitly rather than passed over quietly.
144
+ - Sampling finds differences; it cannot prove their absence.
145
+ - Nondeterministic, too-slow, or unconstructible functions abstain **with a reason**, and are
146
+ never counted as passing.
147
+ - The sandbox contains accidents: resource limits, network blocked, filesystem writes blocked. It
148
+ is **not** a security boundary against hostile code, and no pure-Python sandbox is.
149
+
150
+ ## Development
151
+
152
+ ```bash
153
+ pip install -e .
154
+ pytest tests/ -q
155
+ runboth selftest # the control suites, half of which must fail
156
+ ```
157
+
158
+ ## Licence
159
+
160
+ [FSL-1.1-Apache-2.0](LICENSE.md). Free for every use except building a competing product, and it
161
+ converts to plain Apache 2.0 two years after each release.
162
+
163
+ Built by Kyle Clouthier at Clouthier Simulation Labs.
@@ -0,0 +1,146 @@
1
+ <p align="center">
2
+ <img src="brand/mark-128.png" width="72" alt="">
3
+ </p>
4
+
5
+ <h1 align="center">RunBoth</h1>
6
+
7
+ <p align="center">
8
+ <b>An AI changed your code. RunBoth runs both versions and tells you what actually behaves
9
+ differently, including the functions nobody touched.</b>
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://runboth.dev">runboth.dev</a> ·
14
+ <a href="HOW_IT_WORKS.md">How it works</a> ·
15
+ <a href="RED_TEAM_2026-09-12.md">Red team results</a>
16
+ </p>
17
+
18
+ ---
19
+
20
+ ```
21
+ $ git commit -m "refactor: tidy up the rates module"
22
+
23
+ BLOCKED: the behaviour changed and your message does not say so.
24
+
25
+ rate(100)
26
+ used to: return 0.1
27
+ now: return 0.0
28
+
29
+ AND 1 function you did NOT touch now behaves differently,
30
+ because it calls what you changed:
31
+
32
+ total(2.5, 100) pkg/invoice.py
33
+ used to: return 225.0
34
+ now: return 250.0
35
+ ```
36
+
37
+ ## What it does
38
+
39
+ It checks out both versions of your code, generates inputs for every changed function from its
40
+ signature and from the constants mined out of its own bytecode, runs both versions in separate
41
+ sandboxed subprocesses, and compares seven observation channels. When they disagree it hands you
42
+ the exact input that separates them.
43
+
44
+ No test suite required. No network calls. No AI model. No dependencies.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install runboth # once the first release is on PyPI
50
+ pip install git+https://github.com/runboth/runboth # works today
51
+ runboth install-hook # a commit-msg gate, silent unless behaviour moved
52
+ ```
53
+
54
+ As a GitHub Action, running on your own runners:
55
+
56
+ ```yaml
57
+ - uses: runboth/runboth@v0.1.0
58
+ with:
59
+ budget: 60
60
+ ```
61
+
62
+ ## Three verdicts, never two
63
+
64
+ | verdict | meaning |
65
+ |---|---|
66
+ | `changed` | with a witness: the arguments, the old result, the new result |
67
+ | `no_change at budget N` | N generated inputs found no difference across seven channels |
68
+ | `abstained` | it could not be checked, and here is the reason |
69
+
70
+ "Cannot tell" and "no difference" are different claims, and collapsing them into a green check is
71
+ how tools end up lying. **RunBoth never says safe.**
72
+
73
+ ## The seven channels
74
+
75
+ Return value · exception raised · warnings · stdout · stderr · argument mutation · object state.
76
+
77
+ A narrow definition of behaviour does not under-report, it lies, because whatever sits outside the
78
+ definition comes back as `no_change`.
79
+
80
+ ## What it is not
81
+
82
+ **Not a model checker.** Kani and CBMC translate code into logic, let inputs be unconstrained
83
+ symbols, and ask a solver whether a bad state is reachable within a bound. They return a proof.
84
+ RunBoth executes real code on concrete values. It finds differences and reproduces them; it
85
+ cannot prove absence, and never claims to.
86
+
87
+ **Not mutation testing.** Mutation testing damages your code to score your test suite. RunBoth
88
+ damages nothing; both versions come from your git history, and no test suite is needed.
89
+
90
+ ## Measured
91
+
92
+ Red-teamed against eight public repositories it had never been tuned on, with an automated oracle
93
+ built to catch the tool lying. **2,548 functions, zero false positives.** Full method and numbers
94
+ in [RED_TEAM_2026-09-12.md](RED_TEAM_2026-09-12.md).
95
+
96
+ | repo | layout | functions | abstained | median/commit |
97
+ |---|---|---|---|---|
98
+ | boltons | flat | 268 | 0.0% | 7.9s |
99
+ | sqlparse | flat | 230 | 0.9% | 14.3s |
100
+ | arrow | flat | 283 | 1.4% | 118s |
101
+ | cachetools | src/ | 325 | 1.8% | 60.9s |
102
+ | more-itertools | flat | 843 | 4.4% | 102.5s |
103
+ | packaging | src/ | 78 | 5.1% | 0.2s |
104
+ | pluggy | src/ | 157 | 6.4% | 33.5s |
105
+ | tenacity | async | 364 | 14.3% | 192.8s |
106
+
107
+ An adversarial corpus of 22 functions written specifically to induce false positives (object
108
+ addresses in default `repr`, `datetime.now`, unseeded `random`, `uuid4`, `os.getpid`, set
109
+ iteration order, mutable defaults, generators, `__file__` paths) produced none.
110
+
111
+ ## Environment variables
112
+
113
+ | variable | effect |
114
+ |---|---|
115
+ | `RUNBOTH_SKIP=1` | let a commit through without checking it |
116
+ | `RUNBOTH_BUDGET` | generated inputs per function (gate default 80) |
117
+ | `RUNBOTH_WORKERS` | parallel adjudications, default 4 |
118
+ | `RUNBOTH_ALL_PATHS=1` | also check tests, benchmarks, docs and task runners |
119
+ | `RUNBOTH_ENGINE` | engine directory, if the hook cannot resolve it |
120
+
121
+ `git commit --no-verify` also bypasses the gate, and the gate says so itself when it blocks.
122
+
123
+ ## Honest limits
124
+
125
+ - Function-level checking is **Python only**. Changed files in other languages are named
126
+ explicitly rather than passed over quietly.
127
+ - Sampling finds differences; it cannot prove their absence.
128
+ - Nondeterministic, too-slow, or unconstructible functions abstain **with a reason**, and are
129
+ never counted as passing.
130
+ - The sandbox contains accidents: resource limits, network blocked, filesystem writes blocked. It
131
+ is **not** a security boundary against hostile code, and no pure-Python sandbox is.
132
+
133
+ ## Development
134
+
135
+ ```bash
136
+ pip install -e .
137
+ pytest tests/ -q
138
+ runboth selftest # the control suites, half of which must fail
139
+ ```
140
+
141
+ ## Licence
142
+
143
+ [FSL-1.1-Apache-2.0](LICENSE.md). Free for every use except building a competing product, and it
144
+ converts to plain Apache 2.0 two years after each release.
145
+
146
+ Built by Kyle Clouthier at Clouthier Simulation Labs.
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "runboth"
7
+ version = "0.1.0"
8
+ description = "An AI changed your code. RunBoth runs both versions and tells you what actually behaves differently, including the functions nobody touched."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ # FSL-1.1-Apache-2.0: source-available, free for every use except building a competing
12
+ # product, and it converts to plain Apache 2.0 automatically two years after each release.
13
+ # Chosen 2026-09-12 over the MIT OR Apache-2.0 that was declared here before, because nothing
14
+ # is published yet so the change costs nothing, and because a permissive licence on a product
15
+ # whose whole value is one narrow mechanism invites exactly one outcome. Reversible: relicense
16
+ # is the author's to make right up until the first public release.
17
+ license = { text = "FSL-1.1-Apache-2.0" }
18
+ authors = [{ name = "Kyle Clouthier", email = "kyle@simgen.dev" }]
19
+ keywords = ["differential testing", "behaviour", "refactoring", "AI agents", "code review"]
20
+ classifiers = [
21
+ "Development Status :: 3 - Alpha",
22
+ "Intended Audience :: Developers",
23
+ "Programming Language :: Python :: 3",
24
+ "Topic :: Software Development :: Testing",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://runboth.dev"
29
+
30
+ [project.scripts]
31
+ runboth = "runboth.cli:main"
32
+
33
+ # The engine is a flat directory of modules that import each other by bare name
34
+ # (`import sandbox`), and every entry point already puts its own directory on sys.path.
35
+ # Installing that directory AS the `runboth` package, with an __init__ that does the same
36
+ # sys.path insert, keeps those imports working unchanged whether the tree is run from a
37
+ # checkout or from site-packages.
38
+ [tool.setuptools]
39
+ packages = ["runboth"]
40
+
41
+ [tool.setuptools.package-dir]
42
+ runboth = "runboth"
@@ -0,0 +1,30 @@
1
+ """runboth: behaviour adjudication for AI-written code.
2
+
3
+ This directory is a flat set of modules that import one another by bare name. When it is
4
+ installed as the `runboth` package, this file puts the package directory itself on sys.path so
5
+ those imports resolve to the installed copies. It is the same thing `cli.py` already does when
6
+ run from a checkout, made unconditional so `from runboth.cli import main` works too.
7
+ """
8
+ import sys as _sys
9
+ from pathlib import Path as _Path
10
+
11
+ _here = str(_Path(__file__).resolve().parent)
12
+ if _here not in _sys.path:
13
+ _sys.path.insert(0, _here)
14
+
15
+ __all__ = ["main", "engine_dir"]
16
+
17
+
18
+ def engine_dir():
19
+ """Where the engine modules live, wherever this package was installed or checked out.
20
+
21
+ The git hook uses this to locate `precommit.py` at RUN time instead of having an absolute
22
+ path baked into it at INSTALL time. Moving the project used to silently break every hook
23
+ already installed, which is exactly the class of failure this tool exists to catch.
24
+ """
25
+ return _here
26
+
27
+
28
+ def main():
29
+ from cli import main as _main
30
+ return _main()