uncoded 0.7.0__tar.gz → 0.7.1__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.
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/namespace.yaml +16 -6
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/stubs.pyi +1 -1
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_cli.pyi +3 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_extract.pyi +11 -2
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_serena_setup.pyi +4 -1
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_stubs.pyi +19 -4
- {uncoded-0.7.0 → uncoded-0.7.1}/AGENTS.md +4 -1
- {uncoded-0.7.0 → uncoded-0.7.1}/PKG-INFO +11 -3
- {uncoded-0.7.0 → uncoded-0.7.1}/README.md +9 -2
- {uncoded-0.7.0 → uncoded-0.7.1}/pyproject.toml +10 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/extract.py +1 -1
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/stubs.py +7 -8
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_cli.py +26 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_extract.py +33 -2
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_serena_setup.py +21 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_stubs.py +238 -46
- uncoded-0.7.1/uv.lock +329 -0
- uncoded-0.7.0/uv.lock +0 -229
- {uncoded-0.7.0 → uncoded-0.7.1}/.agents/skills/coherence-review/SKILL.md +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.claude/settings.json +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.claude/skills/coherence-review/SKILL.md +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.github/workflows/ci.yml +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.github/workflows/publish.yml +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.gitignore +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.mcp.json +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.pre-commit-config.yaml +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.serena/project.yml +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/reviews/2026-04-25-001215.md +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/__init__.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/cli.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/config.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/extract.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/instruction_files.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/namespace_map.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/serena_setup.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/skill.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/src/uncoded/sync.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_config.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_instruction_files.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_namespace_map.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_skill.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_sync.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/.uncoded/stubs/tests/test_uncoded.pyi +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/CLAUDE.md +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/LICENSE +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/resources/lsp-research.md +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/__init__.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/cli.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/config.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/instruction_files.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/namespace_map.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/serena_setup.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/skill.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/src/uncoded/sync.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/__init__.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_config.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_instruction_files.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_namespace_map.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_skill.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_sync.py +0 -0
- {uncoded-0.7.0 → uncoded-0.7.1}/tests/test_uncoded.py +0 -0
|
@@ -78,7 +78,7 @@ src/:
|
|
|
78
78
|
name:
|
|
79
79
|
annotation:
|
|
80
80
|
value_source:
|
|
81
|
-
|
|
81
|
+
is_pep695_alias:
|
|
82
82
|
StubClass:
|
|
83
83
|
name:
|
|
84
84
|
bases:
|
|
@@ -114,6 +114,7 @@ tests/:
|
|
|
114
114
|
test_writes_namespace_map_stubs_and_instruction_file:
|
|
115
115
|
test_idempotent_second_run:
|
|
116
116
|
test_dedupes_when_claude_md_is_symlink_to_agents_md:
|
|
117
|
+
test_instruction_file_outside_project_uses_absolute_path:
|
|
117
118
|
test_error_when_no_pyproject_toml:
|
|
118
119
|
test_error_when_source_root_missing:
|
|
119
120
|
test_error_when_uncoded_section_missing:
|
|
@@ -155,7 +156,7 @@ tests/:
|
|
|
155
156
|
test_reads_configured_list:
|
|
156
157
|
test_empty_list_is_respected:
|
|
157
158
|
test_extract.py:
|
|
158
|
-
|
|
159
|
+
TestExtractModuleFromSource:
|
|
159
160
|
test_classes_and_functions:
|
|
160
161
|
test_async_functions_and_methods:
|
|
161
162
|
test_empty_module:
|
|
@@ -163,10 +164,13 @@ tests/:
|
|
|
163
164
|
test_type_alias_classic:
|
|
164
165
|
test_type_alias_pep695:
|
|
165
166
|
test_tuple_unpacking_skipped:
|
|
167
|
+
test_chained_assignment_skipped:
|
|
166
168
|
test_unannotated_class_variable:
|
|
169
|
+
test_class_tuple_unpacking_skipped:
|
|
167
170
|
test_annotated_attributes:
|
|
168
171
|
test_property_classified_as_attribute:
|
|
169
172
|
test_property_setter_and_deleter_suppressed:
|
|
173
|
+
test_non_property_decorator_classified_as_method:
|
|
170
174
|
test_preserves_source_order:
|
|
171
175
|
TestIterAndExtract:
|
|
172
176
|
test_basic_walk:
|
|
@@ -174,7 +178,7 @@ tests/:
|
|
|
174
178
|
test_includes_init_with_symbols:
|
|
175
179
|
test_skips_empty_init:
|
|
176
180
|
test_skips_syntax_errors:
|
|
177
|
-
|
|
181
|
+
TestExtractModulesFromFiles:
|
|
178
182
|
test_returns_module_info_per_parseable_file:
|
|
179
183
|
test_preserves_source_order:
|
|
180
184
|
test_module_with_only_constants_is_kept:
|
|
@@ -232,6 +236,7 @@ tests/:
|
|
|
232
236
|
test_merges_into_existing_claude_settings:
|
|
233
237
|
test_does_not_overwrite_existing_serena_project_yml:
|
|
234
238
|
test_does_not_duplicate_on_second_merge:
|
|
239
|
+
test_appends_missing_allowed_tool_to_existing_settings:
|
|
235
240
|
test_setup_uses_root_name_when_no_pyproject:
|
|
236
241
|
test_setup_reads_name_from_pyproject:
|
|
237
242
|
TestRepoDogfooding:
|
|
@@ -263,7 +268,7 @@ tests/:
|
|
|
263
268
|
test_class_with_attributes_and_methods:
|
|
264
269
|
test_class_with_bases:
|
|
265
270
|
test_class_no_bases:
|
|
266
|
-
|
|
271
|
+
test_extract_params_covers_input_kind:
|
|
267
272
|
test_imports_collected:
|
|
268
273
|
test_syntax_error_raises:
|
|
269
274
|
test_source_order_preserved:
|
|
@@ -275,6 +280,8 @@ tests/:
|
|
|
275
280
|
test_type_alias_classic:
|
|
276
281
|
test_type_alias_pep695:
|
|
277
282
|
test_tuple_unpacking_skipped:
|
|
283
|
+
test_ann_assign_non_name_target_skipped:
|
|
284
|
+
test_class_tuple_unpacking_skipped:
|
|
278
285
|
test_class_with_unannotated_attribute:
|
|
279
286
|
test_property_rendered_as_attribute:
|
|
280
287
|
test_property_without_return_annotation:
|
|
@@ -284,9 +291,9 @@ tests/:
|
|
|
284
291
|
test_imports_rendered:
|
|
285
292
|
test_rendered_stub_has_no_line_range_comments:
|
|
286
293
|
test_async_function_prefix:
|
|
287
|
-
|
|
294
|
+
test_render_param_covers_input_kind:
|
|
295
|
+
test_return_annotation_rendered:
|
|
288
296
|
test_class_with_bases:
|
|
289
|
-
test_class_no_bases:
|
|
290
297
|
test_class_with_no_members_renders_body:
|
|
291
298
|
test_attribute_with_annotation:
|
|
292
299
|
test_method_indented:
|
|
@@ -298,6 +305,7 @@ tests/:
|
|
|
298
305
|
test_constant_bare_annotation_rendered:
|
|
299
306
|
test_type_alias_pep695_rendered:
|
|
300
307
|
test_unannotated_class_attribute_rendered:
|
|
308
|
+
test_renders_valid_python_for_representative_source:
|
|
301
309
|
TestBuildStubs:
|
|
302
310
|
_setup:
|
|
303
311
|
_build:
|
|
@@ -309,6 +317,7 @@ tests/:
|
|
|
309
317
|
test_no_op_when_clean:
|
|
310
318
|
test_reports_count_on_first_build:
|
|
311
319
|
test_reports_zero_when_clean:
|
|
320
|
+
test_skips_module_with_no_symbols:
|
|
312
321
|
TestBuildStubsCheckMode:
|
|
313
322
|
_setup:
|
|
314
323
|
_build:
|
|
@@ -322,6 +331,7 @@ tests/:
|
|
|
322
331
|
test_prunes_orphan_stubs:
|
|
323
332
|
test_project_root_anchors_writes_independent_of_cwd:
|
|
324
333
|
test_project_root_anchors_orphan_pruning_independent_of_cwd:
|
|
334
|
+
test_source_root_outside_project_root_skips_cleanup:
|
|
325
335
|
test_sync.py:
|
|
326
336
|
TestSyncFile:
|
|
327
337
|
test_creates_missing_file:
|
|
@@ -20,6 +20,9 @@ class TestSyncApplyMode:
|
|
|
20
20
|
def test_dedupes_when_claude_md_is_symlink_to_agents_md(self, tmp_path, monkeypatch, capsys):
|
|
21
21
|
...
|
|
22
22
|
|
|
23
|
+
def test_instruction_file_outside_project_uses_absolute_path(self, tmp_path, monkeypatch):
|
|
24
|
+
...
|
|
25
|
+
|
|
23
26
|
def test_error_when_no_pyproject_toml(self, tmp_path, monkeypatch, capsys):
|
|
24
27
|
...
|
|
25
28
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import textwrap
|
|
4
4
|
from uncoded.extract import extract_module, extract_modules, iter_source_files
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class TestExtractModuleFromSource:
|
|
7
7
|
def test_classes_and_functions(self):
|
|
8
8
|
...
|
|
9
9
|
|
|
@@ -25,9 +25,15 @@ class TestExtractModule:
|
|
|
25
25
|
def test_tuple_unpacking_skipped(self):
|
|
26
26
|
...
|
|
27
27
|
|
|
28
|
+
def test_chained_assignment_skipped(self):
|
|
29
|
+
...
|
|
30
|
+
|
|
28
31
|
def test_unannotated_class_variable(self):
|
|
29
32
|
...
|
|
30
33
|
|
|
34
|
+
def test_class_tuple_unpacking_skipped(self):
|
|
35
|
+
...
|
|
36
|
+
|
|
31
37
|
def test_annotated_attributes(self):
|
|
32
38
|
...
|
|
33
39
|
|
|
@@ -37,6 +43,9 @@ class TestExtractModule:
|
|
|
37
43
|
def test_property_setter_and_deleter_suppressed(self):
|
|
38
44
|
...
|
|
39
45
|
|
|
46
|
+
def test_non_property_decorator_classified_as_method(self):
|
|
47
|
+
...
|
|
48
|
+
|
|
40
49
|
def test_preserves_source_order(self):
|
|
41
50
|
...
|
|
42
51
|
|
|
@@ -56,7 +65,7 @@ class TestIterAndExtract:
|
|
|
56
65
|
def test_skips_syntax_errors(self, tmp_path, capsys):
|
|
57
66
|
...
|
|
58
67
|
|
|
59
|
-
class
|
|
68
|
+
class TestExtractModulesFromFiles:
|
|
60
69
|
def test_returns_module_info_per_parseable_file(self):
|
|
61
70
|
...
|
|
62
71
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import yaml
|
|
6
|
-
from uncoded.serena_setup import SERENA_ALLOWED_TOOLS, SERENA_VERSION, setup
|
|
6
|
+
from uncoded.serena_setup import MCP_SERVER_SERENA, SERENA_ALLOWED_TOOLS, SERENA_VERSION, setup
|
|
7
7
|
|
|
8
8
|
REPO_ROOT = Path(__file__).parent.parent
|
|
9
9
|
EXPECTED_EXCLUDED_TOOLS = ...
|
|
@@ -46,6 +46,9 @@ class TestSetup:
|
|
|
46
46
|
def test_does_not_duplicate_on_second_merge(self, tmp_path):
|
|
47
47
|
...
|
|
48
48
|
|
|
49
|
+
def test_appends_missing_allowed_tool_to_existing_settings(self, tmp_path):
|
|
50
|
+
...
|
|
51
|
+
|
|
49
52
|
def test_setup_uses_root_name_when_no_pyproject(self, tmp_path):
|
|
50
53
|
...
|
|
51
54
|
|
|
@@ -28,7 +28,7 @@ class TestExtractStub:
|
|
|
28
28
|
def test_class_no_bases(self):
|
|
29
29
|
...
|
|
30
30
|
|
|
31
|
-
def
|
|
31
|
+
def test_extract_params_covers_input_kind(self, source, expected):
|
|
32
32
|
...
|
|
33
33
|
|
|
34
34
|
def test_imports_collected(self):
|
|
@@ -64,6 +64,12 @@ class TestExtractStub:
|
|
|
64
64
|
def test_tuple_unpacking_skipped(self):
|
|
65
65
|
...
|
|
66
66
|
|
|
67
|
+
def test_ann_assign_non_name_target_skipped(self):
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def test_class_tuple_unpacking_skipped(self):
|
|
71
|
+
...
|
|
72
|
+
|
|
67
73
|
def test_class_with_unannotated_attribute(self):
|
|
68
74
|
...
|
|
69
75
|
|
|
@@ -89,13 +95,13 @@ class TestRenderStub:
|
|
|
89
95
|
def test_async_function_prefix(self):
|
|
90
96
|
...
|
|
91
97
|
|
|
92
|
-
def
|
|
98
|
+
def test_render_param_covers_input_kind(self, params, expected):
|
|
93
99
|
...
|
|
94
100
|
|
|
95
|
-
def
|
|
101
|
+
def test_return_annotation_rendered(self):
|
|
96
102
|
...
|
|
97
103
|
|
|
98
|
-
def
|
|
104
|
+
def test_class_with_bases(self):
|
|
99
105
|
...
|
|
100
106
|
|
|
101
107
|
def test_class_with_no_members_renders_body(self):
|
|
@@ -131,6 +137,9 @@ class TestRenderStub:
|
|
|
131
137
|
def test_unannotated_class_attribute_rendered(self):
|
|
132
138
|
...
|
|
133
139
|
|
|
140
|
+
def test_renders_valid_python_for_representative_source(self):
|
|
141
|
+
...
|
|
142
|
+
|
|
134
143
|
class TestBuildStubs:
|
|
135
144
|
def _setup(self, tmp_path):
|
|
136
145
|
...
|
|
@@ -162,6 +171,9 @@ class TestBuildStubs:
|
|
|
162
171
|
def test_reports_zero_when_clean(self, tmp_path):
|
|
163
172
|
...
|
|
164
173
|
|
|
174
|
+
def test_skips_module_with_no_symbols(self, tmp_path):
|
|
175
|
+
...
|
|
176
|
+
|
|
165
177
|
class TestBuildStubsCheckMode:
|
|
166
178
|
def _setup(self, tmp_path):
|
|
167
179
|
...
|
|
@@ -196,3 +208,6 @@ class TestWriteStubs:
|
|
|
196
208
|
|
|
197
209
|
def test_project_root_anchors_orphan_pruning_independent_of_cwd(self, tmp_path, monkeypatch):
|
|
198
210
|
...
|
|
211
|
+
|
|
212
|
+
def test_source_root_outside_project_root_skips_cleanup(self, tmp_path):
|
|
213
|
+
...
|
|
@@ -47,8 +47,11 @@ uv run uncoded check
|
|
|
47
47
|
# Generate Serena + ty MCP and Claude Code configuration
|
|
48
48
|
uv run uncoded setup
|
|
49
49
|
|
|
50
|
-
# Run tests
|
|
50
|
+
# Run tests (branch coverage enforced; see [tool.coverage.report] in pyproject.toml)
|
|
51
51
|
uv run pytest
|
|
52
|
+
|
|
53
|
+
# Run a subset of tests without the coverage gate
|
|
54
|
+
uv run pytest tests/test_stubs.py --no-cov
|
|
52
55
|
```
|
|
53
56
|
|
|
54
57
|
<!-- uncoded:start -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: uncoded
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Symbol indexes and language-server setup for AI-assisted code navigation
|
|
5
5
|
Project-URL: Homepage, https://github.com/alimanfoo/uncoded
|
|
6
6
|
Project-URL: Repository, https://github.com/alimanfoo/uncoded
|
|
@@ -14,6 +14,7 @@ Requires-Python: >=3.12
|
|
|
14
14
|
Requires-Dist: pyyaml>=6.0
|
|
15
15
|
Provides-Extra: dev
|
|
16
16
|
Requires-Dist: pre-commit>=3.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
|
|
17
18
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
|
|
@@ -229,13 +230,20 @@ uv sync --extra dev
|
|
|
229
230
|
uv run pre-commit install
|
|
230
231
|
```
|
|
231
232
|
|
|
232
|
-
Run the tests
|
|
233
|
+
Run the tests (branch coverage is enforced; see `[tool.coverage.report]` in
|
|
234
|
+
`pyproject.toml` for the threshold):
|
|
233
235
|
|
|
234
236
|
```
|
|
235
237
|
uv run pytest
|
|
236
238
|
```
|
|
237
239
|
|
|
238
|
-
|
|
240
|
+
To run a subset of tests without the coverage gate:
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
uv run pytest tests/test_stubs.py --no-cov
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Run the same checks CI's lint job runs:
|
|
239
247
|
|
|
240
248
|
```
|
|
241
249
|
uv run pre-commit run --all-files
|
|
@@ -210,13 +210,20 @@ uv sync --extra dev
|
|
|
210
210
|
uv run pre-commit install
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
-
Run the tests
|
|
213
|
+
Run the tests (branch coverage is enforced; see `[tool.coverage.report]` in
|
|
214
|
+
`pyproject.toml` for the threshold):
|
|
214
215
|
|
|
215
216
|
```
|
|
216
217
|
uv run pytest
|
|
217
218
|
```
|
|
218
219
|
|
|
219
|
-
|
|
220
|
+
To run a subset of tests without the coverage gate:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
uv run pytest tests/test_stubs.py --no-cov
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Run the same checks CI's lint job runs:
|
|
220
227
|
|
|
221
228
|
```
|
|
222
229
|
uv run pre-commit run --all-files
|
|
@@ -25,6 +25,7 @@ classifiers = [
|
|
|
25
25
|
dev = [
|
|
26
26
|
"pre-commit>=3.0",
|
|
27
27
|
"pytest>=8.0",
|
|
28
|
+
"pytest-cov>=6.0",
|
|
28
29
|
]
|
|
29
30
|
|
|
30
31
|
[project.scripts]
|
|
@@ -42,6 +43,15 @@ source-roots = ["src", "tests"]
|
|
|
42
43
|
|
|
43
44
|
[tool.pytest.ini_options]
|
|
44
45
|
testpaths = ["tests"]
|
|
46
|
+
addopts = "--cov"
|
|
47
|
+
|
|
48
|
+
[tool.coverage.run]
|
|
49
|
+
source = ["src"]
|
|
50
|
+
branch = true
|
|
51
|
+
|
|
52
|
+
[tool.coverage.report]
|
|
53
|
+
show_missing = true
|
|
54
|
+
fail_under = 100
|
|
45
55
|
|
|
46
56
|
[tool.ruff]
|
|
47
57
|
src = ["src"]
|
|
@@ -87,7 +87,7 @@ def extract_module(source: str, rel_path: str) -> ModuleInfo:
|
|
|
87
87
|
name = _assign_target_name(node)
|
|
88
88
|
if name:
|
|
89
89
|
constants.append(name)
|
|
90
|
-
elif isinstance(node, ast.TypeAlias)
|
|
90
|
+
elif isinstance(node, ast.TypeAlias):
|
|
91
91
|
constants.append(node.name.id)
|
|
92
92
|
|
|
93
93
|
return ModuleInfo(
|
|
@@ -43,7 +43,7 @@ class StubAssignment:
|
|
|
43
43
|
name: str
|
|
44
44
|
annotation: str | None = None
|
|
45
45
|
value_source: str | None = None
|
|
46
|
-
|
|
46
|
+
is_pep695_alias: bool = False
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@dataclass
|
|
@@ -119,12 +119,10 @@ def _extract_assignment(
|
|
|
119
119
|
unpacking or attribute assignment), which we can't represent cleanly.
|
|
120
120
|
"""
|
|
121
121
|
if isinstance(node, ast.TypeAlias):
|
|
122
|
-
if not isinstance(node.name, ast.Name):
|
|
123
|
-
return None
|
|
124
122
|
return StubAssignment(
|
|
125
123
|
name=node.name.id,
|
|
126
124
|
value_source=_render_value(node.value),
|
|
127
|
-
|
|
125
|
+
is_pep695_alias=True,
|
|
128
126
|
)
|
|
129
127
|
|
|
130
128
|
if isinstance(node, ast.AnnAssign):
|
|
@@ -166,8 +164,6 @@ def _property_attribute(
|
|
|
166
164
|
return StubAssignment(
|
|
167
165
|
name=node.name,
|
|
168
166
|
annotation=ast.unparse(node.returns) if node.returns else None,
|
|
169
|
-
value_source=None,
|
|
170
|
-
is_type_alias=False,
|
|
171
167
|
)
|
|
172
168
|
|
|
173
169
|
|
|
@@ -250,7 +246,7 @@ def _render_function(func: StubFunction, indent: str = "") -> list[str]:
|
|
|
250
246
|
|
|
251
247
|
def _format_assignment_body(a: StubAssignment) -> str:
|
|
252
248
|
"""Render the 'name [: type] [= value]' portion of an assignment."""
|
|
253
|
-
if a.
|
|
249
|
+
if a.is_pep695_alias:
|
|
254
250
|
return f"type {a.name} = {a.value_source}"
|
|
255
251
|
head = f"{a.name}: {a.annotation}" if a.annotation else a.name
|
|
256
252
|
if a.value_source is None:
|
|
@@ -375,7 +371,10 @@ def _write_stubs(
|
|
|
375
371
|
if existing.resolve() in expected:
|
|
376
372
|
continue
|
|
377
373
|
display = existing.relative_to(project_root)
|
|
378
|
-
|
|
374
|
+
# .pyi may have been removed between rglob and remove_file
|
|
375
|
+
if remove_file( # pragma: no branch
|
|
376
|
+
display, project_root=project_root, check=check
|
|
377
|
+
):
|
|
379
378
|
changes += 1
|
|
380
379
|
|
|
381
380
|
if check:
|
|
@@ -103,6 +103,32 @@ class TestSyncApplyMode:
|
|
|
103
103
|
]
|
|
104
104
|
assert instruction_lines == ["Updated AGENTS.md"]
|
|
105
105
|
|
|
106
|
+
def test_instruction_file_outside_project_uses_absolute_path(
|
|
107
|
+
self, tmp_path, monkeypatch
|
|
108
|
+
):
|
|
109
|
+
project = tmp_path / "project"
|
|
110
|
+
project.mkdir()
|
|
111
|
+
(project / "src").mkdir()
|
|
112
|
+
(project / "pyproject.toml").write_text(
|
|
113
|
+
textwrap.dedent(
|
|
114
|
+
"""\
|
|
115
|
+
[project]
|
|
116
|
+
name = "demo"
|
|
117
|
+
|
|
118
|
+
[tool.uncoded]
|
|
119
|
+
source-roots = ["src"]
|
|
120
|
+
instruction-files = ["../outside.md"]
|
|
121
|
+
"""
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
monkeypatch.chdir(project)
|
|
125
|
+
|
|
126
|
+
assert cli._sync() == 0
|
|
127
|
+
|
|
128
|
+
outside_file = tmp_path / "outside.md"
|
|
129
|
+
assert outside_file.exists()
|
|
130
|
+
assert "<!-- uncoded:start -->" in outside_file.read_text()
|
|
131
|
+
|
|
106
132
|
def test_error_when_no_pyproject_toml(self, tmp_path, monkeypatch, capsys):
|
|
107
133
|
# Pins the problem statement, the recovery hint pointing at
|
|
108
134
|
# [tool.uncoded] source-roots, and the absence of any absolute
|
|
@@ -3,7 +3,7 @@ import textwrap
|
|
|
3
3
|
from uncoded.extract import extract_module, extract_modules, iter_source_files
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class TestExtractModuleFromSource:
|
|
7
7
|
def test_classes_and_functions(self):
|
|
8
8
|
source = textwrap.dedent("""\
|
|
9
9
|
class MyClass:
|
|
@@ -99,6 +99,13 @@ class TestExtractModule:
|
|
|
99
99
|
|
|
100
100
|
assert result.constants == []
|
|
101
101
|
|
|
102
|
+
def test_chained_assignment_skipped(self):
|
|
103
|
+
source = "x = y = 1\n"
|
|
104
|
+
|
|
105
|
+
result = extract_module(source, "chain.py")
|
|
106
|
+
|
|
107
|
+
assert result.constants == []
|
|
108
|
+
|
|
102
109
|
def test_unannotated_class_variable(self):
|
|
103
110
|
source = textwrap.dedent("""\
|
|
104
111
|
class Registry:
|
|
@@ -112,6 +119,16 @@ class TestExtractModule:
|
|
|
112
119
|
cls = result.classes[0]
|
|
113
120
|
assert cls.attributes == ["items", "_cache", "count"]
|
|
114
121
|
|
|
122
|
+
def test_class_tuple_unpacking_skipped(self):
|
|
123
|
+
source = textwrap.dedent("""\
|
|
124
|
+
class C:
|
|
125
|
+
a, b = 1, 2
|
|
126
|
+
""")
|
|
127
|
+
|
|
128
|
+
result = extract_module(source, "c.py")
|
|
129
|
+
|
|
130
|
+
assert result.classes[0].attributes == []
|
|
131
|
+
|
|
115
132
|
def test_annotated_attributes(self):
|
|
116
133
|
source = textwrap.dedent("""\
|
|
117
134
|
from dataclasses import dataclass
|
|
@@ -172,6 +189,20 @@ class TestExtractModule:
|
|
|
172
189
|
assert cls.attributes == ["path"]
|
|
173
190
|
assert cls.methods == []
|
|
174
191
|
|
|
192
|
+
def test_non_property_decorator_classified_as_method(self):
|
|
193
|
+
source = textwrap.dedent("""\
|
|
194
|
+
class Util:
|
|
195
|
+
@staticmethod
|
|
196
|
+
def helper():
|
|
197
|
+
pass
|
|
198
|
+
""")
|
|
199
|
+
|
|
200
|
+
result = extract_module(source, "util.py")
|
|
201
|
+
|
|
202
|
+
cls = result.classes[0]
|
|
203
|
+
assert cls.methods == ["helper"]
|
|
204
|
+
assert cls.attributes == []
|
|
205
|
+
|
|
175
206
|
def test_preserves_source_order(self):
|
|
176
207
|
source = textwrap.dedent("""\
|
|
177
208
|
def zebra():
|
|
@@ -284,7 +315,7 @@ class TestIterAndExtract:
|
|
|
284
315
|
assert "SyntaxError" in err
|
|
285
316
|
|
|
286
317
|
|
|
287
|
-
class
|
|
318
|
+
class TestExtractModulesFromFiles:
|
|
288
319
|
def test_returns_module_info_per_parseable_file(self):
|
|
289
320
|
files = [
|
|
290
321
|
("def foo(): pass\n", "src/a.py"),
|
|
@@ -4,6 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
import yaml
|
|
5
5
|
|
|
6
6
|
from uncoded.serena_setup import (
|
|
7
|
+
MCP_SERVER_SERENA,
|
|
7
8
|
SERENA_ALLOWED_TOOLS,
|
|
8
9
|
SERENA_VERSION,
|
|
9
10
|
setup,
|
|
@@ -173,6 +174,26 @@ class TestSetup:
|
|
|
173
174
|
for tool in SERENA_ALLOWED_TOOLS:
|
|
174
175
|
assert claude["permissions"]["allow"].count(tool) == 1
|
|
175
176
|
|
|
177
|
+
def test_appends_missing_allowed_tool_to_existing_settings(self, tmp_path):
|
|
178
|
+
(tmp_path / ".mcp.json").write_text(
|
|
179
|
+
json.dumps({"mcpServers": {"serena": MCP_SERVER_SERENA}})
|
|
180
|
+
)
|
|
181
|
+
claude_path = tmp_path / ".claude" / "settings.json"
|
|
182
|
+
claude_path.parent.mkdir()
|
|
183
|
+
missing_tool = SERENA_ALLOWED_TOOLS[-1]
|
|
184
|
+
claude_path.write_text(
|
|
185
|
+
json.dumps(
|
|
186
|
+
{
|
|
187
|
+
"enabledMcpjsonServers": ["serena"],
|
|
188
|
+
"permissions": {"allow": list(SERENA_ALLOWED_TOOLS[:-1])},
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
self._run(tmp_path)
|
|
193
|
+
data = json.loads(claude_path.read_text())
|
|
194
|
+
assert missing_tool in data["permissions"]["allow"]
|
|
195
|
+
assert set(data["permissions"]["allow"]) >= set(SERENA_ALLOWED_TOOLS)
|
|
196
|
+
|
|
176
197
|
def test_setup_uses_root_name_when_no_pyproject(self, tmp_path):
|
|
177
198
|
# No ``pyproject.toml`` anywhere from ``tmp_path`` up to the
|
|
178
199
|
# filesystem root, so ``read_project_name(start=tmp_path)`` falls
|