uncoded 1.0.1__tar.gz → 1.2.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.
- uncoded-1.2.0/.uncoded/docs.yaml +34 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/namespace.yaml +160 -25
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/cli.pyi +5 -7
- uncoded-1.2.0/.uncoded/stubs/src/uncoded/config.pyi +27 -0
- uncoded-1.2.0/.uncoded/stubs/src/uncoded/docs_map.pyi +25 -0
- uncoded-1.2.0/.uncoded/stubs/src/uncoded/instruction_files.pyi +25 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/namespace_map.pyi +1 -5
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/skill.pyi +1 -1
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/stubs.pyi +3 -0
- uncoded-1.2.0/.uncoded/stubs/src/uncoded/yaml_tree.pyi +10 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/tests/test_cli.pyi +70 -2
- uncoded-1.2.0/.uncoded/stubs/tests/test_config.pyi +71 -0
- uncoded-1.2.0/.uncoded/stubs/tests/test_docs_map.pyi +144 -0
- uncoded-1.2.0/.uncoded/stubs/tests/test_instruction_files.pyi +109 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/tests/test_stubs.pyi +26 -1
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/tests/test_sync.pyi +0 -3
- {uncoded-1.0.1 → uncoded-1.2.0}/AGENTS.md +17 -4
- {uncoded-1.0.1 → uncoded-1.2.0}/PKG-INFO +42 -9
- {uncoded-1.0.1 → uncoded-1.2.0}/README.md +41 -8
- {uncoded-1.0.1 → uncoded-1.2.0}/pyproject.toml +1 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/cli.py +152 -84
- uncoded-1.2.0/src/uncoded/config.py +133 -0
- uncoded-1.2.0/src/uncoded/docs_map.py +154 -0
- uncoded-1.2.0/src/uncoded/docs_rule.md +5 -0
- uncoded-1.2.0/src/uncoded/instruction_files.py +135 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/namespace_map.py +2 -24
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/skill.py +16 -9
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/stubs.py +37 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/sync.py +2 -4
- uncoded-1.2.0/src/uncoded/yaml_tree.py +29 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/test_cli.py +412 -29
- uncoded-1.2.0/tests/test_config.py +204 -0
- uncoded-1.2.0/tests/test_docs_map.py +240 -0
- uncoded-1.2.0/tests/test_instruction_files.py +380 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/test_skill.py +15 -15
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/test_stubs.py +70 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/test_sync.py +0 -9
- uncoded-1.0.1/.uncoded/stubs/src/uncoded/config.pyi +0 -14
- uncoded-1.0.1/.uncoded/stubs/src/uncoded/instruction_files.pyi +0 -17
- uncoded-1.0.1/.uncoded/stubs/tests/test_config.pyi +0 -36
- uncoded-1.0.1/.uncoded/stubs/tests/test_instruction_files.pyi +0 -53
- uncoded-1.0.1/src/uncoded/config.py +0 -64
- uncoded-1.0.1/src/uncoded/instruction_files.py +0 -64
- uncoded-1.0.1/tests/test_config.py +0 -74
- uncoded-1.0.1/tests/test_instruction_files.py +0 -122
- {uncoded-1.0.1 → uncoded-1.2.0}/.agents/skills/coherence-review/SKILL.md +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.claude/skills/coherence-review/SKILL.md +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.github/workflows/ci.yml +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.github/workflows/publish.yml +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.gitignore +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.markdownlint-cli2.yaml +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.pre-commit-config.yaml +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/__init__.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/ast_helpers.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/body.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/extract.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/refs.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/resolver.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/src/uncoded/sync.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/tests/test_body.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/tests/test_extract.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/tests/test_namespace_map.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/tests/test_refs.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/tests/test_skill.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/.uncoded/stubs/tests/test_uncoded.pyi +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/CLAUDE.md +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/LICENSE +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/__init__.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/ast_helpers.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/body.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/coherence_review.md +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/dispatch_rule.md +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/extract.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/refs.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/src/uncoded/resolver.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/__init__.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/test_body.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/test_extract.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/test_namespace_map.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/test_refs.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/tests/test_uncoded.py +0 -0
- {uncoded-1.0.1 → uncoded-1.2.0}/uv.lock +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Documentation index of this codebase, for agent orientation.
|
|
2
|
+
# Generated by uncoded — do not edit; regeneration overwrites.
|
|
3
|
+
#
|
|
4
|
+
# Pure key hierarchy (no lists, no values); indent to zoom in.
|
|
5
|
+
# Directory keys end with "/". Leaf headings map to null.
|
|
6
|
+
#
|
|
7
|
+
# A " (N)" suffix marks the Nth occurrence of a repeated heading at that level.
|
|
8
|
+
# To navigate to it, grep the base heading text and take the Nth match.
|
|
9
|
+
|
|
10
|
+
README.md:
|
|
11
|
+
uncoded:
|
|
12
|
+
What it generates:
|
|
13
|
+
Install uv:
|
|
14
|
+
Configure:
|
|
15
|
+
Use:
|
|
16
|
+
Keep it current with pre-commit:
|
|
17
|
+
Verify the index is fresh:
|
|
18
|
+
Retrieve a symbol body:
|
|
19
|
+
Find references to a symbol:
|
|
20
|
+
How agents use it:
|
|
21
|
+
Coherence review:
|
|
22
|
+
Dev setup:
|
|
23
|
+
Note for Windows contributors:
|
|
24
|
+
Releasing:
|
|
25
|
+
AGENTS.md:
|
|
26
|
+
uncoded:
|
|
27
|
+
Problem:
|
|
28
|
+
Approach:
|
|
29
|
+
Commands:
|
|
30
|
+
How to read and edit code in this codebase:
|
|
31
|
+
The dispatch rule:
|
|
32
|
+
How to execute the rule:
|
|
33
|
+
Where Read, Edit, and grep are still the right tools:
|
|
34
|
+
How to read documentation in this codebase:
|
|
@@ -18,15 +18,29 @@ src/:
|
|
|
18
18
|
resolve_body:
|
|
19
19
|
_extract_body:
|
|
20
20
|
cli.py:
|
|
21
|
-
_find_project_root:
|
|
22
21
|
_sync:
|
|
23
22
|
_body:
|
|
24
23
|
_refs:
|
|
25
24
|
main:
|
|
26
25
|
config.py:
|
|
26
|
+
ConfigError:
|
|
27
|
+
Config:
|
|
28
|
+
project_root:
|
|
29
|
+
source_roots:
|
|
30
|
+
doc_roots:
|
|
31
|
+
instruction_files:
|
|
27
32
|
find_pyproject_toml:
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
_has_uncoded_section:
|
|
34
|
+
_find_config_file:
|
|
35
|
+
read_config:
|
|
36
|
+
docs_map.py:
|
|
37
|
+
DOCS_HEADER:
|
|
38
|
+
extract_headings:
|
|
39
|
+
_unique_key:
|
|
40
|
+
_collapse_empty:
|
|
41
|
+
iter_doc_files:
|
|
42
|
+
build_docs_map:
|
|
43
|
+
render_docs_map:
|
|
30
44
|
extract.py:
|
|
31
45
|
ClassInfo:
|
|
32
46
|
name:
|
|
@@ -41,17 +55,21 @@ src/:
|
|
|
41
55
|
iter_source_files:
|
|
42
56
|
extract_modules:
|
|
43
57
|
instruction_files.py:
|
|
44
|
-
MARKER_START:
|
|
45
58
|
MARKER_END:
|
|
59
|
+
MARKER_DOCS_END:
|
|
60
|
+
MARKER_START_PREFIX:
|
|
61
|
+
MARKER_DOCS_START_PREFIX:
|
|
46
62
|
DEFAULT_INSTRUCTION_FILES:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
_CODE_SECTION_BODY:
|
|
64
|
+
MARKER_START:
|
|
65
|
+
SECTION_CODE:
|
|
66
|
+
_DOCS_SECTION_BODY:
|
|
67
|
+
MARKER_DOCS_START:
|
|
68
|
+
SECTION_DOCS:
|
|
69
|
+
_apply_section:
|
|
50
70
|
sync_instruction_file:
|
|
51
71
|
namespace_map.py:
|
|
52
72
|
HEADER:
|
|
53
|
-
_CleanDumper:
|
|
54
|
-
increase_indent:
|
|
55
73
|
build_map:
|
|
56
74
|
render_map:
|
|
57
75
|
refs.py:
|
|
@@ -131,10 +149,15 @@ src/:
|
|
|
131
149
|
render_stub:
|
|
132
150
|
_generate_stubs:
|
|
133
151
|
_write_stubs:
|
|
152
|
+
remove_all_stubs:
|
|
134
153
|
build_stubs:
|
|
135
154
|
sync.py:
|
|
136
155
|
sync_file:
|
|
137
156
|
remove_file:
|
|
157
|
+
yaml_tree.py:
|
|
158
|
+
_CleanDumper:
|
|
159
|
+
increase_indent:
|
|
160
|
+
render_yaml_tree:
|
|
138
161
|
tests/:
|
|
139
162
|
test_body.py:
|
|
140
163
|
TestResolveBodyTopLevel:
|
|
@@ -192,10 +215,12 @@ tests/:
|
|
|
192
215
|
test_writes_namespace_map_stubs_and_instruction_file:
|
|
193
216
|
test_idempotent_second_run:
|
|
194
217
|
test_dedupes_when_claude_md_is_symlink_to_agents_md:
|
|
195
|
-
|
|
196
|
-
|
|
218
|
+
test_error_when_instruction_file_outside_project_root:
|
|
219
|
+
test_error_when_no_config_file:
|
|
220
|
+
test_error_when_source_root_outside_project_root:
|
|
197
221
|
test_error_when_source_root_missing:
|
|
198
222
|
test_error_when_uncoded_section_missing:
|
|
223
|
+
test_error_when_both_config_files_configure_uncoded:
|
|
199
224
|
test_skip_warning_emitted_once_per_broken_file:
|
|
200
225
|
test_anchors_reads_and_writes_at_project_root_when_cwd_is_subdir:
|
|
201
226
|
test_artefacts_match_when_run_from_subdir_vs_project_root:
|
|
@@ -237,20 +262,102 @@ tests/:
|
|
|
237
262
|
test_zero_references_exits_zero_with_empty_stdout:
|
|
238
263
|
test_multiple_references_prints_sorted:
|
|
239
264
|
test_lsp_failure_exits_one:
|
|
265
|
+
TestSyncDocRoots:
|
|
266
|
+
test_doc_only_writes_docs_yaml:
|
|
267
|
+
test_doc_only_does_not_write_namespace_yaml:
|
|
268
|
+
test_doc_only_does_not_write_stubs:
|
|
269
|
+
test_doc_only_instruction_file_has_docs_section_only:
|
|
270
|
+
test_doc_root_single_md_file:
|
|
271
|
+
test_error_when_doc_root_missing:
|
|
272
|
+
test_error_when_doc_root_is_non_md_file:
|
|
273
|
+
test_error_when_doc_root_outside_project_root:
|
|
274
|
+
test_both_roots_writes_both_artefacts:
|
|
275
|
+
test_both_roots_instruction_file_has_both_sections:
|
|
276
|
+
test_source_root_removal_cleans_code_artefacts:
|
|
277
|
+
test_doc_root_removal_cleans_docs_yaml:
|
|
278
|
+
test_check_returns_one_when_docs_yaml_stale:
|
|
279
|
+
test_check_returns_one_when_stubs_should_be_removed:
|
|
280
|
+
test_check_returns_one_when_docs_yaml_should_be_removed:
|
|
281
|
+
test_doc_only_does_not_write_skill:
|
|
282
|
+
test_doc_only_removes_preexisting_skill:
|
|
283
|
+
test_check_returns_one_when_skill_should_be_removed:
|
|
284
|
+
test_idempotent_doc_only:
|
|
240
285
|
_init_repo:
|
|
286
|
+
_init_doc_repo:
|
|
241
287
|
test_config.py:
|
|
242
288
|
TestFindPyprojectToml:
|
|
243
289
|
test_finds_at_start:
|
|
244
290
|
test_finds_in_parent_of_start:
|
|
245
291
|
test_returns_none_if_not_found:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
292
|
+
TestReadConfig:
|
|
293
|
+
test_returns_none_when_no_config_file:
|
|
294
|
+
test_source_roots_read_from_pyproject:
|
|
295
|
+
test_doc_roots_read_from_pyproject:
|
|
296
|
+
test_both_roots_empty_when_section_absent:
|
|
297
|
+
test_project_root_is_config_file_parent:
|
|
298
|
+
test_finds_pyproject_in_parent_directory:
|
|
299
|
+
test_instruction_files_default_when_key_absent:
|
|
300
|
+
test_instruction_files_configured:
|
|
301
|
+
test_instruction_files_empty_list_respected:
|
|
302
|
+
test_returns_frozen_dataclass:
|
|
303
|
+
test_finds_uncoded_toml_when_no_pyproject:
|
|
304
|
+
test_uncoded_toml_top_level_keys:
|
|
305
|
+
test_error_when_both_configure_uncoded_in_same_directory:
|
|
306
|
+
test_uncoded_toml_wins_over_bare_pyproject:
|
|
307
|
+
test_uncoded_toml_wins_when_nearer_than_pyproject:
|
|
308
|
+
test_pyproject_wins_when_nearer_than_uncoded_toml:
|
|
309
|
+
test_uncoded_pyproject_and_uncoded_toml_in_different_dirs_no_error:
|
|
310
|
+
test_skips_uncoded_directory_as_config_home:
|
|
311
|
+
test_docs_map.py:
|
|
312
|
+
TestExtractHeadings:
|
|
313
|
+
test_empty_text:
|
|
314
|
+
test_h1_through_h6:
|
|
315
|
+
test_trailing_hashes_stripped:
|
|
316
|
+
test_trailing_hashes_with_surrounding_spaces_stripped:
|
|
317
|
+
test_trailing_hashes_multiple_stripped:
|
|
318
|
+
test_hash_attached_to_word_preserved:
|
|
319
|
+
test_multiple_hashes_attached_to_words_preserved:
|
|
320
|
+
test_hash_attached_to_word_with_closing_sequence:
|
|
321
|
+
test_seven_hashes_not_a_heading:
|
|
322
|
+
test_no_space_after_hash_not_a_heading:
|
|
323
|
+
test_hash_only_not_a_heading:
|
|
324
|
+
test_empty_title_not_a_heading:
|
|
325
|
+
test_setext_underline_equals_not_a_heading:
|
|
326
|
+
test_setext_underline_dashes_not_a_heading:
|
|
327
|
+
test_backtick_fence_suppresses_headings:
|
|
328
|
+
test_tilde_fence_suppresses_headings:
|
|
329
|
+
test_fence_with_info_string:
|
|
330
|
+
test_unclosed_fence_suppresses_rest_of_file:
|
|
331
|
+
test_tilde_does_not_close_backtick_fence:
|
|
332
|
+
test_backtick_does_not_close_tilde_fence:
|
|
333
|
+
test_preserves_order:
|
|
334
|
+
test_title_with_special_chars:
|
|
335
|
+
test_leading_spaces_not_a_heading:
|
|
336
|
+
TestIterDocFiles:
|
|
337
|
+
test_single_file_root:
|
|
338
|
+
test_directory_walk_yields_md_files:
|
|
339
|
+
test_directory_walk_sorted:
|
|
340
|
+
test_directory_excludes_non_md:
|
|
341
|
+
test_directory_nested_subdir:
|
|
342
|
+
test_empty_directory_yields_nothing:
|
|
343
|
+
test_rel_path_is_relative_to_project_root:
|
|
344
|
+
TestBuildDocsMap:
|
|
345
|
+
test_empty_files:
|
|
346
|
+
test_headingless_file_maps_to_null:
|
|
347
|
+
test_single_heading:
|
|
348
|
+
test_nesting_parent_child:
|
|
349
|
+
test_level_skip_attaches_to_nearest_shallower:
|
|
350
|
+
test_heading_returns_to_same_level:
|
|
351
|
+
test_heading_not_starting_at_h1:
|
|
352
|
+
test_duplicate_sibling_disambiguation:
|
|
353
|
+
test_duplicate_increments_past_existing_suffix:
|
|
354
|
+
test_directory_structure_dir_key_has_trailing_slash:
|
|
355
|
+
test_two_files_in_same_directory:
|
|
356
|
+
test_multiple_files_flat:
|
|
357
|
+
TestRenderDocsMap:
|
|
358
|
+
test_header_present:
|
|
359
|
+
test_yaml_parseable:
|
|
360
|
+
test_null_renders_clean:
|
|
254
361
|
test_extract.py:
|
|
255
362
|
TestExtractModuleFromSource:
|
|
256
363
|
test_classes_and_functions:
|
|
@@ -280,18 +387,32 @@ tests/:
|
|
|
280
387
|
test_module_with_only_constants_is_kept:
|
|
281
388
|
test_skips_files_with_no_symbols:
|
|
282
389
|
test_instruction_files.py:
|
|
283
|
-
|
|
390
|
+
TestCodeSection:
|
|
391
|
+
test_contains_markers:
|
|
392
|
+
test_markers_in_order:
|
|
393
|
+
test_ends_with_newline:
|
|
394
|
+
TestDocsSection:
|
|
284
395
|
test_contains_markers:
|
|
285
396
|
test_markers_in_order:
|
|
286
397
|
test_ends_with_newline:
|
|
398
|
+
test_mentions_docs_yaml:
|
|
287
399
|
TestSyncInstructionFile:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
400
|
+
test_creates_file_with_code_section:
|
|
401
|
+
test_appends_code_section_to_existing_file:
|
|
402
|
+
test_replaces_existing_code_section:
|
|
403
|
+
test_preserves_content_after_code_section:
|
|
404
|
+
test_removes_code_section_on_none:
|
|
405
|
+
test_appends_docs_section:
|
|
406
|
+
test_removes_docs_section_on_none:
|
|
407
|
+
test_both_sections_appended_in_order:
|
|
408
|
+
test_both_sections_replaced_independently:
|
|
409
|
+
test_code_only_does_not_write_docs_markers:
|
|
410
|
+
test_docs_only_does_not_write_code_markers:
|
|
292
411
|
test_idempotent:
|
|
293
412
|
test_returns_true_on_first_write:
|
|
294
413
|
test_returns_false_when_clean:
|
|
414
|
+
test_returns_false_when_both_none_and_file_absent:
|
|
415
|
+
test_remove_section_absent_is_noop:
|
|
295
416
|
TestSyncInstructionFileCheckMode:
|
|
296
417
|
test_does_not_create_file:
|
|
297
418
|
test_does_not_update_existing_file:
|
|
@@ -299,6 +420,12 @@ tests/:
|
|
|
299
420
|
TestSyncInstructionFileProjectRootAnchor:
|
|
300
421
|
test_project_root_anchors_create_independent_of_cwd:
|
|
301
422
|
test_project_root_anchors_update_of_existing_file:
|
|
423
|
+
TestSyncInstructionFileFingerprint:
|
|
424
|
+
test_reflowed_body_survives_sync:
|
|
425
|
+
test_reflowed_body_passes_check:
|
|
426
|
+
test_different_fingerprint_refreshes_section:
|
|
427
|
+
test_plain_marker_refreshes_once_then_stable:
|
|
428
|
+
test_prose_mention_of_prefix_before_section_is_ignored:
|
|
302
429
|
test_namespace_map.py:
|
|
303
430
|
TestBuildMap:
|
|
304
431
|
test_single_file:
|
|
@@ -432,6 +559,15 @@ tests/:
|
|
|
432
559
|
test_project_root_anchors_writes_independent_of_cwd:
|
|
433
560
|
test_project_root_anchors_orphan_pruning_independent_of_cwd:
|
|
434
561
|
test_source_root_outside_project_root_skips_cleanup:
|
|
562
|
+
TestRemoveAllStubs:
|
|
563
|
+
_make_stubs:
|
|
564
|
+
test_noop_when_output_dir_absent:
|
|
565
|
+
test_removes_pyi_file_and_directory:
|
|
566
|
+
test_removes_nested_structure:
|
|
567
|
+
test_empty_directory_removed:
|
|
568
|
+
test_check_mode_reports_without_removing:
|
|
569
|
+
test_check_mode_noop_when_absent:
|
|
570
|
+
test_returns_count_of_pyi_files:
|
|
435
571
|
_setup:
|
|
436
572
|
_build:
|
|
437
573
|
test_sync.py:
|
|
@@ -452,7 +588,6 @@ tests/:
|
|
|
452
588
|
TestSyncFileProjectRootAnchor:
|
|
453
589
|
test_project_root_anchors_write_independent_of_cwd:
|
|
454
590
|
test_project_root_preserves_relative_path_in_message:
|
|
455
|
-
test_absolute_path_makes_project_root_a_no_op:
|
|
456
591
|
TestRemoveFileProjectRootAnchor:
|
|
457
592
|
test_project_root_anchors_removal_independent_of_cwd:
|
|
458
593
|
test_uncoded.py:
|
|
@@ -4,18 +4,16 @@ import argparse
|
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from uncoded.body import resolve_body
|
|
7
|
-
from uncoded.config import
|
|
7
|
+
from uncoded.config import ConfigError, read_config
|
|
8
|
+
from uncoded.docs_map import build_docs_map, iter_doc_files, render_docs_map
|
|
8
9
|
from uncoded.extract import extract_modules, iter_source_files
|
|
9
|
-
from uncoded.instruction_files import sync_instruction_file
|
|
10
|
+
from uncoded.instruction_files import SECTION_CODE, SECTION_DOCS, sync_instruction_file
|
|
10
11
|
from uncoded.namespace_map import build_map, render_map
|
|
11
12
|
from uncoded.refs import find_refs
|
|
12
13
|
from uncoded.resolver import NamePath, SymbolNotFound, UnsupportedNamePath
|
|
13
14
|
from uncoded.skill import sync_skill
|
|
14
|
-
from uncoded.stubs import build_stubs
|
|
15
|
-
from uncoded.sync import sync_file
|
|
16
|
-
|
|
17
|
-
def _find_project_root(*, start: Path) -> Path | None:
|
|
18
|
-
...
|
|
15
|
+
from uncoded.stubs import build_stubs, remove_all_stubs
|
|
16
|
+
from uncoded.sync import remove_file, sync_file
|
|
19
17
|
|
|
20
18
|
def _sync(*, start: Path | None, check: bool) -> int:
|
|
21
19
|
...
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# src/uncoded/config.py
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from uncoded.instruction_files import DEFAULT_INSTRUCTION_FILES
|
|
7
|
+
|
|
8
|
+
def find_pyproject_toml(start: Path) -> Path | None:
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
def _has_uncoded_section(*, pyproject_path: Path) -> bool:
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
def _find_config_file(*, start: Path) -> Path | None:
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
def read_config(start: Path) -> Config | None:
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
class ConfigError(Exception):
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
class Config:
|
|
24
|
+
project_root: Path
|
|
25
|
+
source_roots: list[Path]
|
|
26
|
+
doc_roots: list[Path]
|
|
27
|
+
instruction_files: list[Path]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# src/uncoded/docs_map.py
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable, Iterator
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from uncoded.yaml_tree import render_yaml_tree
|
|
6
|
+
|
|
7
|
+
DOCS_HEADER = ...
|
|
8
|
+
|
|
9
|
+
def extract_headings(text: str) -> list[tuple[int, str]]:
|
|
10
|
+
...
|
|
11
|
+
|
|
12
|
+
def _unique_key(*, parent: dict, title: str) -> str:
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
def _collapse_empty(*, mapping: dict) -> dict | None:
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
def iter_doc_files(doc_root: Path, project_root: Path) -> Iterator[tuple[str, Path]]:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def build_docs_map(files: Iterable[tuple[str, Path]]) -> dict:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def render_docs_map(mapping: dict) -> str:
|
|
25
|
+
...
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# src/uncoded/instruction_files.py
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import re
|
|
5
|
+
from importlib.resources import files
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from uncoded.sync import sync_file
|
|
8
|
+
|
|
9
|
+
MARKER_END = '<!-- uncoded:end -->'
|
|
10
|
+
MARKER_DOCS_END = '<!-- uncoded:docs:end -->'
|
|
11
|
+
MARKER_START_PREFIX = '<!-- uncoded:start'
|
|
12
|
+
MARKER_DOCS_START_PREFIX = '<!-- uncoded:docs:start'
|
|
13
|
+
DEFAULT_INSTRUCTION_FILES = [Path('CLAUDE.md'), Path('AGENTS.md')]
|
|
14
|
+
_CODE_SECTION_BODY = (files('uncoded') / 'dispatch_rule.md').read_text(encoding='utf-8').rstrip('\n')
|
|
15
|
+
MARKER_START = ...
|
|
16
|
+
SECTION_CODE = f'{MARKER_START}\n{_CODE_SECTION_BODY}\n{MARKER_END}\n'
|
|
17
|
+
_DOCS_SECTION_BODY = (files('uncoded') / 'docs_rule.md').read_text(encoding='utf-8').rstrip('\n')
|
|
18
|
+
MARKER_DOCS_START = ...
|
|
19
|
+
SECTION_DOCS = f'{MARKER_DOCS_START}\n{_DOCS_SECTION_BODY}\n{MARKER_DOCS_END}\n'
|
|
20
|
+
|
|
21
|
+
def _apply_section(text: str, start: str, end: str, body: str | None, *, prefix: str) -> str:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def sync_instruction_file(path: Path, *, code_section: str | None, docs_section: str | None, project_root: Path, check: bool) -> bool:
|
|
25
|
+
...
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# src/uncoded/namespace_map.py
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
import yaml
|
|
5
4
|
from uncoded.extract import ModuleInfo
|
|
5
|
+
from uncoded.yaml_tree import render_yaml_tree
|
|
6
6
|
|
|
7
7
|
HEADER = ...
|
|
8
8
|
|
|
@@ -11,7 +11,3 @@ def build_map(modules: list[ModuleInfo]) -> dict:
|
|
|
11
11
|
|
|
12
12
|
def render_map(namespace: dict) -> str:
|
|
13
13
|
...
|
|
14
|
-
|
|
15
|
-
class _CleanDumper(yaml.SafeDumper):
|
|
16
|
-
def increase_indent(self, flow, indentless):
|
|
17
|
-
...
|
|
@@ -8,5 +8,5 @@ SKILL_OUTPUTS = ...
|
|
|
8
8
|
LEGACY_SKILL_OUTPUTS = ...
|
|
9
9
|
_SKILL_CONTENT = (files('uncoded') / 'coherence_review.md').read_text(encoding='utf-8')
|
|
10
10
|
|
|
11
|
-
def sync_skill(*, project_root: Path, check: bool) -> bool:
|
|
11
|
+
def sync_skill(*, project_root: Path, check: bool, build: bool) -> bool:
|
|
12
12
|
...
|
|
@@ -51,6 +51,9 @@ def _generate_stubs(files: Iterable[tuple[str, str]]) -> dict[Path, str]:
|
|
|
51
51
|
def _write_stubs(*, stubs: dict[Path, str], source_root: Path, output_dir: Path, project_root: Path, check: bool) -> int:
|
|
52
52
|
...
|
|
53
53
|
|
|
54
|
+
def remove_all_stubs(output_dir: Path, *, project_root: Path, check: bool) -> int:
|
|
55
|
+
...
|
|
56
|
+
|
|
54
57
|
def build_stubs(*, files: Iterable[tuple[str, str]], source_root: Path, output_dir: Path, project_root: Path, check: bool) -> int:
|
|
55
58
|
...
|
|
56
59
|
|
|
@@ -6,11 +6,15 @@ from pathlib import Path
|
|
|
6
6
|
from unittest import mock
|
|
7
7
|
import pytest
|
|
8
8
|
from uncoded import cli
|
|
9
|
+
from uncoded.instruction_files import MARKER_START
|
|
9
10
|
from uncoded.skill import SKILL_OUTPUTS
|
|
10
11
|
|
|
11
12
|
def _init_repo(tmp_path, monkeypatch, source_roots):
|
|
12
13
|
...
|
|
13
14
|
|
|
15
|
+
def _init_doc_repo(tmp_path, monkeypatch, doc_roots):
|
|
16
|
+
...
|
|
17
|
+
|
|
14
18
|
class TestSyncApplyMode:
|
|
15
19
|
def test_writes_namespace_map_stubs_and_instruction_file(self, tmp_path, monkeypatch):
|
|
16
20
|
...
|
|
@@ -21,10 +25,13 @@ class TestSyncApplyMode:
|
|
|
21
25
|
def test_dedupes_when_claude_md_is_symlink_to_agents_md(self, tmp_path, monkeypatch, capsys):
|
|
22
26
|
...
|
|
23
27
|
|
|
24
|
-
def
|
|
28
|
+
def test_error_when_instruction_file_outside_project_root(self, tmp_path, monkeypatch, capsys):
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def test_error_when_no_config_file(self, tmp_path, monkeypatch, capsys):
|
|
25
32
|
...
|
|
26
33
|
|
|
27
|
-
def
|
|
34
|
+
def test_error_when_source_root_outside_project_root(self, tmp_path, monkeypatch, capsys):
|
|
28
35
|
...
|
|
29
36
|
|
|
30
37
|
def test_error_when_source_root_missing(self, tmp_path, monkeypatch, capsys):
|
|
@@ -33,6 +40,9 @@ class TestSyncApplyMode:
|
|
|
33
40
|
def test_error_when_uncoded_section_missing(self, tmp_path, monkeypatch, capsys):
|
|
34
41
|
...
|
|
35
42
|
|
|
43
|
+
def test_error_when_both_config_files_configure_uncoded(self, tmp_path, monkeypatch, capsys):
|
|
44
|
+
...
|
|
45
|
+
|
|
36
46
|
def test_skip_warning_emitted_once_per_broken_file(self, tmp_path, monkeypatch, capsys):
|
|
37
47
|
...
|
|
38
48
|
|
|
@@ -147,3 +157,61 @@ class TestRefsCommand:
|
|
|
147
157
|
|
|
148
158
|
def test_lsp_failure_exits_one(self, tmp_path, monkeypatch, capsys):
|
|
149
159
|
...
|
|
160
|
+
|
|
161
|
+
class TestSyncDocRoots:
|
|
162
|
+
def test_doc_only_writes_docs_yaml(self, tmp_path, monkeypatch):
|
|
163
|
+
...
|
|
164
|
+
|
|
165
|
+
def test_doc_only_does_not_write_namespace_yaml(self, tmp_path, monkeypatch):
|
|
166
|
+
...
|
|
167
|
+
|
|
168
|
+
def test_doc_only_does_not_write_stubs(self, tmp_path, monkeypatch):
|
|
169
|
+
...
|
|
170
|
+
|
|
171
|
+
def test_doc_only_instruction_file_has_docs_section_only(self, tmp_path, monkeypatch):
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
def test_doc_root_single_md_file(self, tmp_path, monkeypatch):
|
|
175
|
+
...
|
|
176
|
+
|
|
177
|
+
def test_error_when_doc_root_missing(self, tmp_path, monkeypatch, capsys):
|
|
178
|
+
...
|
|
179
|
+
|
|
180
|
+
def test_error_when_doc_root_is_non_md_file(self, tmp_path, monkeypatch, capsys):
|
|
181
|
+
...
|
|
182
|
+
|
|
183
|
+
def test_error_when_doc_root_outside_project_root(self, tmp_path, monkeypatch, capsys):
|
|
184
|
+
...
|
|
185
|
+
|
|
186
|
+
def test_both_roots_writes_both_artefacts(self, tmp_path, monkeypatch):
|
|
187
|
+
...
|
|
188
|
+
|
|
189
|
+
def test_both_roots_instruction_file_has_both_sections(self, tmp_path, monkeypatch):
|
|
190
|
+
...
|
|
191
|
+
|
|
192
|
+
def test_source_root_removal_cleans_code_artefacts(self, tmp_path, monkeypatch):
|
|
193
|
+
...
|
|
194
|
+
|
|
195
|
+
def test_doc_root_removal_cleans_docs_yaml(self, tmp_path, monkeypatch):
|
|
196
|
+
...
|
|
197
|
+
|
|
198
|
+
def test_check_returns_one_when_docs_yaml_stale(self, tmp_path, monkeypatch):
|
|
199
|
+
...
|
|
200
|
+
|
|
201
|
+
def test_check_returns_one_when_stubs_should_be_removed(self, tmp_path, monkeypatch):
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
def test_check_returns_one_when_docs_yaml_should_be_removed(self, tmp_path, monkeypatch):
|
|
205
|
+
...
|
|
206
|
+
|
|
207
|
+
def test_doc_only_does_not_write_skill(self, tmp_path, monkeypatch):
|
|
208
|
+
...
|
|
209
|
+
|
|
210
|
+
def test_doc_only_removes_preexisting_skill(self, tmp_path, monkeypatch):
|
|
211
|
+
...
|
|
212
|
+
|
|
213
|
+
def test_check_returns_one_when_skill_should_be_removed(self, tmp_path, monkeypatch):
|
|
214
|
+
...
|
|
215
|
+
|
|
216
|
+
def test_idempotent_doc_only(self, tmp_path, monkeypatch):
|
|
217
|
+
...
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# tests/test_config.py
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import pytest
|
|
5
|
+
from uncoded.config import Config, ConfigError, find_pyproject_toml, read_config
|
|
6
|
+
from uncoded.instruction_files import DEFAULT_INSTRUCTION_FILES
|
|
7
|
+
|
|
8
|
+
class TestFindPyprojectToml:
|
|
9
|
+
def test_finds_at_start(self, tmp_path):
|
|
10
|
+
...
|
|
11
|
+
|
|
12
|
+
def test_finds_in_parent_of_start(self, tmp_path):
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
def test_returns_none_if_not_found(self, tmp_path):
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
class TestReadConfig:
|
|
19
|
+
def test_returns_none_when_no_config_file(self, tmp_path):
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def test_source_roots_read_from_pyproject(self, tmp_path):
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def test_doc_roots_read_from_pyproject(self, tmp_path):
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def test_both_roots_empty_when_section_absent(self, tmp_path):
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def test_project_root_is_config_file_parent(self, tmp_path):
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
def test_finds_pyproject_in_parent_directory(self, tmp_path):
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def test_instruction_files_default_when_key_absent(self, tmp_path):
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
def test_instruction_files_configured(self, tmp_path):
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
def test_instruction_files_empty_list_respected(self, tmp_path):
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
def test_returns_frozen_dataclass(self, tmp_path):
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def test_finds_uncoded_toml_when_no_pyproject(self, tmp_path):
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def test_uncoded_toml_top_level_keys(self, tmp_path):
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
def test_error_when_both_configure_uncoded_in_same_directory(self, tmp_path):
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
def test_uncoded_toml_wins_over_bare_pyproject(self, tmp_path):
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
def test_uncoded_toml_wins_when_nearer_than_pyproject(self, tmp_path):
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
def test_pyproject_wins_when_nearer_than_uncoded_toml(self, tmp_path):
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def test_uncoded_pyproject_and_uncoded_toml_in_different_dirs_no_error(self, tmp_path):
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def test_skips_uncoded_directory_as_config_home(self, tmp_path):
|
|
71
|
+
...
|