myverdict 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 (42) hide show
  1. myverdict-0.1.0/PKG-INFO +72 -0
  2. myverdict-0.1.0/README.md +40 -0
  3. myverdict-0.1.0/myverdict.egg-info/PKG-INFO +72 -0
  4. myverdict-0.1.0/myverdict.egg-info/SOURCES.txt +40 -0
  5. myverdict-0.1.0/myverdict.egg-info/dependency_links.txt +1 -0
  6. myverdict-0.1.0/myverdict.egg-info/entry_points.txt +3 -0
  7. myverdict-0.1.0/myverdict.egg-info/requires.txt +9 -0
  8. myverdict-0.1.0/myverdict.egg-info/top_level.txt +2 -0
  9. myverdict-0.1.0/pyproject.toml +49 -0
  10. myverdict-0.1.0/setup.cfg +4 -0
  11. myverdict-0.1.0/tests/test_ast_utils.py +260 -0
  12. myverdict-0.1.0/tests/test_bob_integration.py +264 -0
  13. myverdict-0.1.0/tests/test_bob_mode_install.py +106 -0
  14. myverdict-0.1.0/tests/test_cli.py +309 -0
  15. myverdict-0.1.0/tests/test_dead_functions.py +548 -0
  16. myverdict-0.1.0/tests/test_diff.py +112 -0
  17. myverdict-0.1.0/tests/test_hallucinated_api.py +390 -0
  18. myverdict-0.1.0/tests/test_mcp_server.py +325 -0
  19. myverdict-0.1.0/tests/test_report.py +491 -0
  20. myverdict-0.1.0/tests/test_suppressed_exc.py +325 -0
  21. myverdict-0.1.0/tests/test_trace.py +402 -0
  22. myverdict-0.1.0/tests/test_vacuous_tests.py +206 -0
  23. myverdict-0.1.0/verdict/__init__.py +0 -0
  24. myverdict-0.1.0/verdict/_coverage_plugin.py +137 -0
  25. myverdict-0.1.0/verdict/_tracer_plugin.py +138 -0
  26. myverdict-0.1.0/verdict/ast_utils.py +117 -0
  27. myverdict-0.1.0/verdict/bob_integration/__init__.py +7 -0
  28. myverdict-0.1.0/verdict/bob_integration/slash_commands/__init__.py +3 -0
  29. myverdict-0.1.0/verdict/checks/__init__.py +3 -0
  30. myverdict-0.1.0/verdict/checks/coverage_delta.py +224 -0
  31. myverdict-0.1.0/verdict/checks/dead_functions.py +198 -0
  32. myverdict-0.1.0/verdict/checks/hallucinated_api.py +507 -0
  33. myverdict-0.1.0/verdict/checks/phantom_files.py +137 -0
  34. myverdict-0.1.0/verdict/checks/suppressed_exc.py +244 -0
  35. myverdict-0.1.0/verdict/checks/trace.py +217 -0
  36. myverdict-0.1.0/verdict/checks/vacuous_tests.py +310 -0
  37. myverdict-0.1.0/verdict/cli.py +574 -0
  38. myverdict-0.1.0/verdict/dashboard_cmd.py +330 -0
  39. myverdict-0.1.0/verdict/diff.py +75 -0
  40. myverdict-0.1.0/verdict/mcp_server.py +350 -0
  41. myverdict-0.1.0/verdict/report.py +197 -0
  42. myverdict-0.1.0/verdict/types.py +49 -0
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: myverdict
3
+ Version: 0.1.0
4
+ Summary: A lie detector for AI coding agents: audits diffs and traces what actually runs.
5
+ Author: verdict contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nshah271/verdict
8
+ Project-URL: Repository, https://github.com/nshah271/verdict
9
+ Project-URL: Issues, https://github.com/nshah271/verdict/issues
10
+ Keywords: ai,code-review,static-analysis,lie-detector,agents,claude,bob,mcp
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Quality Assurance
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Operating System :: OS Independent
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: mcp>=1.0.0
25
+ Requires-Dist: click>=8.1
26
+ Requires-Dist: jedi>=0.19
27
+ Provides-Extra: dev
28
+ Requires-Dist: ruff; extra == "dev"
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: pytest-asyncio; extra == "dev"
31
+ Requires-Dist: pre-commit; extra == "dev"
32
+
33
+ # verdict
34
+
35
+ A lie detector for AI coding agents. Audits an AI-generated diff statically and traces what actually runs when the tests execute, then returns a single scorecard: `PASS`, `SUSPICIOUS`, or `LIED`.
36
+
37
+ Built for the IBM Bob hackathon (May 2026). Repo bootstrap in progress; the real README lands as part of P3.5.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ pip install myverdict
43
+ ```
44
+
45
+ The PyPI package is named `myverdict` (the shorter `verdict-ai` slot was taken by an unrelated project before we shipped). The Python import is still `import verdict` and the CLI is still `verdict`.
46
+
47
+ ## Quick start with IBM Bob
48
+
49
+ ```bash
50
+ # Install verdict's MCP server and Custom Mode globally so Bob sees them
51
+ # in every project on this machine:
52
+ verdict mcp-install --global
53
+ verdict bob-mode-install --global
54
+
55
+ # Restart Bob, then in any project switch to "Verifier" mode after a
56
+ # coding session or type /verify for a one-shot audit.
57
+ ```
58
+
59
+ Drop `--global` to install per-project under `.bob/` instead.
60
+
61
+ ## Status
62
+
63
+ | Priority | Owner | Status |
64
+ |----------|-------|--------|
65
+ | P0.1 foundation (diff + AST) | Neel | not started |
66
+ | P0.2 dead function detection | Jacob | not started |
67
+ | P0.3 vacuous test detection | Alexie | not started |
68
+ | P0.4 CLI + scorecard | Neel | not started |
69
+ | P1.1 execution tracer | Neel | not started |
70
+ | P1.2 MCP server | Ben | not started |
71
+
72
+ See `verdict-spec.md` and `TEAM.md` (local to the team, not in this repo) for the full plan.
@@ -0,0 +1,40 @@
1
+ # verdict
2
+
3
+ A lie detector for AI coding agents. Audits an AI-generated diff statically and traces what actually runs when the tests execute, then returns a single scorecard: `PASS`, `SUSPICIOUS`, or `LIED`.
4
+
5
+ Built for the IBM Bob hackathon (May 2026). Repo bootstrap in progress; the real README lands as part of P3.5.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install myverdict
11
+ ```
12
+
13
+ The PyPI package is named `myverdict` (the shorter `verdict-ai` slot was taken by an unrelated project before we shipped). The Python import is still `import verdict` and the CLI is still `verdict`.
14
+
15
+ ## Quick start with IBM Bob
16
+
17
+ ```bash
18
+ # Install verdict's MCP server and Custom Mode globally so Bob sees them
19
+ # in every project on this machine:
20
+ verdict mcp-install --global
21
+ verdict bob-mode-install --global
22
+
23
+ # Restart Bob, then in any project switch to "Verifier" mode after a
24
+ # coding session or type /verify for a one-shot audit.
25
+ ```
26
+
27
+ Drop `--global` to install per-project under `.bob/` instead.
28
+
29
+ ## Status
30
+
31
+ | Priority | Owner | Status |
32
+ |----------|-------|--------|
33
+ | P0.1 foundation (diff + AST) | Neel | not started |
34
+ | P0.2 dead function detection | Jacob | not started |
35
+ | P0.3 vacuous test detection | Alexie | not started |
36
+ | P0.4 CLI + scorecard | Neel | not started |
37
+ | P1.1 execution tracer | Neel | not started |
38
+ | P1.2 MCP server | Ben | not started |
39
+
40
+ See `verdict-spec.md` and `TEAM.md` (local to the team, not in this repo) for the full plan.
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: myverdict
3
+ Version: 0.1.0
4
+ Summary: A lie detector for AI coding agents: audits diffs and traces what actually runs.
5
+ Author: verdict contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nshah271/verdict
8
+ Project-URL: Repository, https://github.com/nshah271/verdict
9
+ Project-URL: Issues, https://github.com/nshah271/verdict/issues
10
+ Keywords: ai,code-review,static-analysis,lie-detector,agents,claude,bob,mcp
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Quality Assurance
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Operating System :: OS Independent
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: mcp>=1.0.0
25
+ Requires-Dist: click>=8.1
26
+ Requires-Dist: jedi>=0.19
27
+ Provides-Extra: dev
28
+ Requires-Dist: ruff; extra == "dev"
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: pytest-asyncio; extra == "dev"
31
+ Requires-Dist: pre-commit; extra == "dev"
32
+
33
+ # verdict
34
+
35
+ A lie detector for AI coding agents. Audits an AI-generated diff statically and traces what actually runs when the tests execute, then returns a single scorecard: `PASS`, `SUSPICIOUS`, or `LIED`.
36
+
37
+ Built for the IBM Bob hackathon (May 2026). Repo bootstrap in progress; the real README lands as part of P3.5.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ pip install myverdict
43
+ ```
44
+
45
+ The PyPI package is named `myverdict` (the shorter `verdict-ai` slot was taken by an unrelated project before we shipped). The Python import is still `import verdict` and the CLI is still `verdict`.
46
+
47
+ ## Quick start with IBM Bob
48
+
49
+ ```bash
50
+ # Install verdict's MCP server and Custom Mode globally so Bob sees them
51
+ # in every project on this machine:
52
+ verdict mcp-install --global
53
+ verdict bob-mode-install --global
54
+
55
+ # Restart Bob, then in any project switch to "Verifier" mode after a
56
+ # coding session or type /verify for a one-shot audit.
57
+ ```
58
+
59
+ Drop `--global` to install per-project under `.bob/` instead.
60
+
61
+ ## Status
62
+
63
+ | Priority | Owner | Status |
64
+ |----------|-------|--------|
65
+ | P0.1 foundation (diff + AST) | Neel | not started |
66
+ | P0.2 dead function detection | Jacob | not started |
67
+ | P0.3 vacuous test detection | Alexie | not started |
68
+ | P0.4 CLI + scorecard | Neel | not started |
69
+ | P1.1 execution tracer | Neel | not started |
70
+ | P1.2 MCP server | Ben | not started |
71
+
72
+ See `verdict-spec.md` and `TEAM.md` (local to the team, not in this repo) for the full plan.
@@ -0,0 +1,40 @@
1
+ README.md
2
+ pyproject.toml
3
+ myverdict.egg-info/PKG-INFO
4
+ myverdict.egg-info/SOURCES.txt
5
+ myverdict.egg-info/dependency_links.txt
6
+ myverdict.egg-info/entry_points.txt
7
+ myverdict.egg-info/requires.txt
8
+ myverdict.egg-info/top_level.txt
9
+ tests/test_ast_utils.py
10
+ tests/test_bob_integration.py
11
+ tests/test_bob_mode_install.py
12
+ tests/test_cli.py
13
+ tests/test_dead_functions.py
14
+ tests/test_diff.py
15
+ tests/test_hallucinated_api.py
16
+ tests/test_mcp_server.py
17
+ tests/test_report.py
18
+ tests/test_suppressed_exc.py
19
+ tests/test_trace.py
20
+ tests/test_vacuous_tests.py
21
+ verdict/__init__.py
22
+ verdict/_coverage_plugin.py
23
+ verdict/_tracer_plugin.py
24
+ verdict/ast_utils.py
25
+ verdict/cli.py
26
+ verdict/dashboard_cmd.py
27
+ verdict/diff.py
28
+ verdict/mcp_server.py
29
+ verdict/report.py
30
+ verdict/types.py
31
+ verdict/bob_integration/__init__.py
32
+ verdict/bob_integration/slash_commands/__init__.py
33
+ verdict/checks/__init__.py
34
+ verdict/checks/coverage_delta.py
35
+ verdict/checks/dead_functions.py
36
+ verdict/checks/hallucinated_api.py
37
+ verdict/checks/phantom_files.py
38
+ verdict/checks/suppressed_exc.py
39
+ verdict/checks/trace.py
40
+ verdict/checks/vacuous_tests.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ verdict = verdict.cli:cli
3
+ verdict-mcp = verdict.mcp_server:entrypoint
@@ -0,0 +1,9 @@
1
+ mcp>=1.0.0
2
+ click>=8.1
3
+ jedi>=0.19
4
+
5
+ [dev]
6
+ ruff
7
+ pytest
8
+ pytest-asyncio
9
+ pre-commit
@@ -0,0 +1,2 @@
1
+ verdict
2
+ verdict-vscode
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "myverdict"
7
+ version = "0.1.0"
8
+ description = "A lie detector for AI coding agents: audits diffs and traces what actually runs."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "verdict contributors" }]
13
+ keywords = ["ai", "code-review", "static-analysis", "lie-detector", "agents", "claude", "bob", "mcp"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Topic :: Software Development :: Quality Assurance",
18
+ "Topic :: Software Development :: Testing",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Operating System :: OS Independent",
26
+ ]
27
+ dependencies = ["mcp>=1.0.0", "click>=8.1", "jedi>=0.19"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/nshah271/verdict"
31
+ Repository = "https://github.com/nshah271/verdict"
32
+ Issues = "https://github.com/nshah271/verdict/issues"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["ruff", "pytest", "pytest-asyncio", "pre-commit"]
36
+
37
+ [project.scripts]
38
+ verdict = "verdict.cli:cli"
39
+ verdict-mcp = "verdict.mcp_server:entrypoint"
40
+
41
+ [tool.setuptools.packages.find]
42
+ include = ["verdict*"]
43
+
44
+ [tool.ruff]
45
+ line-length = 100
46
+ target-version = "py310"
47
+
48
+ [tool.ruff.lint]
49
+ select = ["E", "F", "I", "B", "UP"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,260 @@
1
+ """Tests for AST analysis functionality."""
2
+
3
+ import tempfile
4
+ from pathlib import Path
5
+
6
+ from verdict.ast_utils import get_added_functions, get_added_tests
7
+ from verdict.types import ChangedFile
8
+
9
+
10
+ def test_get_added_functions_basic():
11
+ """Test extracting added functions from a Python file."""
12
+ changed_file: ChangedFile = {
13
+ "path": "tests/fixtures/sample_module.py",
14
+ "added_lines": [8, 14, 21, 48, 51, 55], # regular_function, async_function, simple_fixture, SampleClass, __init__, method_one
15
+ "removed_lines": [],
16
+ }
17
+
18
+ added_functions = get_added_functions([changed_file], repo_root=".")
19
+
20
+ # Convert to dict for easier lookup
21
+ funcs_by_name = {f["name"]: f for f in added_functions}
22
+
23
+ # Check regular function
24
+ assert "regular_function" in funcs_by_name
25
+ regular = funcs_by_name["regular_function"]
26
+ assert regular["file"] == "tests/fixtures/sample_module.py"
27
+ assert regular["line"] == 8
28
+ assert regular["end_line"] == 10
29
+ assert regular["is_test"] is True # In tests/ directory
30
+ assert regular["decorators"] == []
31
+
32
+ # Check async function
33
+ assert "async_function" in funcs_by_name
34
+ async_func = funcs_by_name["async_function"]
35
+ assert async_func["line"] == 14
36
+ assert async_func["is_test"] is True # In tests/ directory
37
+
38
+ # Check function with decorator
39
+ assert "simple_fixture" in funcs_by_name
40
+ fixture = funcs_by_name["simple_fixture"]
41
+ assert fixture["line"] == 21
42
+ assert fixture["decorators"] == ["pytest.fixture"]
43
+
44
+ # Check class
45
+ assert "SampleClass" in funcs_by_name
46
+ cls = funcs_by_name["SampleClass"]
47
+ assert cls["line"] == 48
48
+ assert cls["is_test"] is True # In tests/ directory
49
+
50
+ # Check method
51
+ assert "method_one" in funcs_by_name
52
+ method = funcs_by_name["method_one"]
53
+ assert method["line"] == 55
54
+ assert method["is_test"] is True # In tests/ directory
55
+
56
+
57
+ def test_decorator_with_call_args():
58
+ """Test that decorators with call args are extracted correctly."""
59
+ changed_file: ChangedFile = {
60
+ "path": "tests/fixtures/sample_module.py",
61
+ "added_lines": [28], # complex_fixture function def line
62
+ "removed_lines": [],
63
+ }
64
+
65
+ added_functions = get_added_functions([changed_file], repo_root=".")
66
+
67
+ funcs_by_name = {f["name"]: f for f in added_functions}
68
+ assert "complex_fixture" in funcs_by_name
69
+ fixture = funcs_by_name["complex_fixture"]
70
+ # Should extract just the decorator name, not the call args
71
+ assert fixture["decorators"] == ["pytest.fixture"]
72
+
73
+
74
+ def test_multiple_decorators():
75
+ """Test function with multiple decorators."""
76
+ changed_file: ChangedFile = {
77
+ "path": "tests/fixtures/sample_module.py",
78
+ "added_lines": [36], # test_with_multiple_decorators function def line
79
+ "removed_lines": [],
80
+ }
81
+
82
+ added_functions = get_added_functions([changed_file], repo_root=".")
83
+
84
+ funcs_by_name = {f["name"]: f for f in added_functions}
85
+ assert "test_with_multiple_decorators" in funcs_by_name
86
+ test_func = funcs_by_name["test_with_multiple_decorators"]
87
+ assert test_func["is_test"] is True # name starts with test_
88
+ assert "pytest.mark.parametrize" in test_func["decorators"]
89
+ assert "pytest.mark.slow" in test_func["decorators"]
90
+
91
+
92
+ def test_is_test_detection_by_name():
93
+ """Test that functions starting with test_ are marked as tests."""
94
+ changed_file: ChangedFile = {
95
+ "path": "tests/fixtures/sample_module.py",
96
+ "added_lines": [42], # test_simple_case
97
+ "removed_lines": [],
98
+ }
99
+
100
+ added_functions = get_added_functions([changed_file], repo_root=".")
101
+
102
+ funcs_by_name = {f["name"]: f for f in added_functions}
103
+ assert "test_simple_case" in funcs_by_name
104
+ test_func = funcs_by_name["test_simple_case"]
105
+ assert test_func["is_test"] is True
106
+
107
+
108
+ def test_is_test_detection_by_path():
109
+ """Test that functions in test files are marked as tests."""
110
+ changed_file: ChangedFile = {
111
+ "path": "tests/fixtures/sample_module.py", # Contains "tests/"
112
+ "added_lines": [91], # helper_function (not named test_*)
113
+ "removed_lines": [],
114
+ }
115
+
116
+ added_functions = get_added_functions([changed_file], repo_root=".")
117
+
118
+ funcs_by_name = {f["name"]: f for f in added_functions}
119
+ assert "helper_function" in funcs_by_name
120
+ helper = funcs_by_name["helper_function"]
121
+ # Should be marked as test because path contains "tests/"
122
+ assert helper["is_test"] is True
123
+
124
+
125
+ def test_class_and_methods_both_appear():
126
+ """Test that both class and its methods appear as separate entries."""
127
+ changed_file: ChangedFile = {
128
+ "path": "tests/fixtures/sample_module.py",
129
+ "added_lines": [48, 51, 55, 59], # SampleClass, __init__, method_one, async_method
130
+ "removed_lines": [],
131
+ }
132
+
133
+ added_functions = get_added_functions([changed_file], repo_root=".")
134
+
135
+ funcs_by_name = {f["name"]: f for f in added_functions}
136
+
137
+ # Class should appear
138
+ assert "SampleClass" in funcs_by_name
139
+
140
+ # Methods should also appear as separate entries
141
+ assert "__init__" in funcs_by_name
142
+ assert "method_one" in funcs_by_name
143
+ assert "async_method" in funcs_by_name
144
+
145
+
146
+ def test_async_function_detected():
147
+ """Test that async functions are properly detected."""
148
+ changed_file: ChangedFile = {
149
+ "path": "tests/fixtures/sample_module.py",
150
+ "added_lines": [14, 59, 84], # async_function, async_method, test_async_with_decorators
151
+ "removed_lines": [],
152
+ }
153
+
154
+ added_functions = get_added_functions([changed_file], repo_root=".")
155
+
156
+ funcs_by_name = {f["name"]: f for f in added_functions}
157
+
158
+ # All three async functions should be detected
159
+ assert "async_function" in funcs_by_name
160
+ assert "async_method" in funcs_by_name
161
+ assert "test_async_with_decorators" in funcs_by_name
162
+
163
+
164
+ def test_syntax_error_skipped():
165
+ """Test that files with syntax errors are skipped without crashing."""
166
+ # Create a temporary file with invalid Python syntax
167
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
168
+ f.write("def broken_function(\n # Missing closing paren and body\n")
169
+ temp_path = f.name
170
+
171
+ try:
172
+ changed_file: ChangedFile = {
173
+ "path": temp_path,
174
+ "added_lines": [1],
175
+ "removed_lines": [],
176
+ }
177
+
178
+ # Should not crash, just skip the file
179
+ added_functions = get_added_functions([changed_file], repo_root=".")
180
+ assert added_functions == []
181
+ finally:
182
+ Path(temp_path).unlink()
183
+
184
+
185
+ def test_non_python_file_skipped():
186
+ """Test that non-Python files are skipped."""
187
+ changed_file: ChangedFile = {
188
+ "path": "README.md",
189
+ "added_lines": [1, 2, 3],
190
+ "removed_lines": [],
191
+ }
192
+
193
+ added_functions = get_added_functions([changed_file], repo_root=".")
194
+ assert added_functions == []
195
+
196
+
197
+ def test_empty_added_lines():
198
+ """Test that files with no added lines return no functions."""
199
+ changed_file: ChangedFile = {
200
+ "path": "tests/fixtures/sample_module.py",
201
+ "added_lines": [], # No added lines
202
+ "removed_lines": [1, 2, 3],
203
+ }
204
+
205
+ added_functions = get_added_functions([changed_file], repo_root=".")
206
+ assert added_functions == []
207
+
208
+
209
+ def test_get_added_tests_filter():
210
+ """Test that get_added_tests correctly filters test functions."""
211
+ changed_file: ChangedFile = {
212
+ "path": "tests/fixtures/sample_module.py",
213
+ "added_lines": [8, 42, 91], # regular_function, test_simple_case, helper_function
214
+ "removed_lines": [],
215
+ }
216
+
217
+ added_functions = get_added_functions([changed_file], repo_root=".")
218
+ test_functions = get_added_tests(added_functions)
219
+
220
+ # Should only include test functions
221
+ test_names = {f["name"] for f in test_functions}
222
+ assert "test_simple_case" in test_names
223
+ assert "helper_function" in test_names # In tests/ directory
224
+ assert "regular_function" in test_names # Also a test because in tests/ directory
225
+
226
+
227
+ def test_nested_class():
228
+ """Test that nested classes are detected."""
229
+ changed_file: ChangedFile = {
230
+ "path": "tests/fixtures/sample_module.py",
231
+ "added_lines": [70, 73, 76], # OuterClass, InnerClass, inner_method
232
+ "removed_lines": [],
233
+ }
234
+
235
+ added_functions = get_added_functions([changed_file], repo_root=".")
236
+
237
+ funcs_by_name = {f["name"]: f for f in added_functions}
238
+
239
+ # Both outer and inner classes should appear
240
+ assert "OuterClass" in funcs_by_name
241
+ assert "InnerClass" in funcs_by_name
242
+ assert "inner_method" in funcs_by_name
243
+
244
+
245
+ def test_property_decorator():
246
+ """Test that property decorators are extracted correctly."""
247
+ changed_file: ChangedFile = {
248
+ "path": "tests/fixtures/sample_module.py",
249
+ "added_lines": [64], # computed_property with @property
250
+ "removed_lines": [],
251
+ }
252
+
253
+ added_functions = get_added_functions([changed_file], repo_root=".")
254
+
255
+ funcs_by_name = {f["name"]: f for f in added_functions}
256
+ assert "computed_property" in funcs_by_name
257
+ prop = funcs_by_name["computed_property"]
258
+ assert "property" in prop["decorators"]
259
+
260
+ # Made with Bob