uncoded 0.5.0__tar.gz → 0.7.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 (77) hide show
  1. {uncoded-0.5.0 → uncoded-0.7.0}/.agents/skills/coherence-review/SKILL.md +31 -30
  2. {uncoded-0.5.0 → uncoded-0.7.0}/.claude/skills/coherence-review/SKILL.md +31 -30
  3. {uncoded-0.5.0 → uncoded-0.7.0}/.serena/project.yml +1 -0
  4. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/namespace.yaml +57 -34
  5. uncoded-0.7.0/.uncoded/stubs/src/uncoded/cli.pyi +19 -0
  6. uncoded-0.7.0/.uncoded/stubs/src/uncoded/config.pyi +17 -0
  7. uncoded-0.7.0/.uncoded/stubs/src/uncoded/extract.pyi +33 -0
  8. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/src/uncoded/instruction_files.pyi +1 -7
  9. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/src/uncoded/namespace_map.pyi +0 -5
  10. uncoded-0.7.0/.uncoded/stubs/src/uncoded/serena_setup.pyi +26 -0
  11. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/src/uncoded/skill.pyi +1 -2
  12. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/src/uncoded/stubs.pyi +7 -35
  13. uncoded-0.7.0/.uncoded/stubs/src/uncoded/sync.pyi +9 -0
  14. uncoded-0.7.0/.uncoded/stubs/tests/test_cli.pyi +80 -0
  15. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/tests/test_config.pyi +15 -9
  16. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/tests/test_extract.pyi +16 -8
  17. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/tests/test_instruction_files.pyi +10 -5
  18. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/tests/test_namespace_map.pyi +0 -8
  19. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/tests/test_serena_setup.pyi +11 -22
  20. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/tests/test_skill.pyi +7 -2
  21. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/tests/test_stubs.pyi +28 -17
  22. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/tests/test_sync.pyi +15 -2
  23. uncoded-0.7.0/AGENTS.md +170 -0
  24. uncoded-0.7.0/CLAUDE.md +1 -0
  25. {uncoded-0.5.0 → uncoded-0.7.0}/PKG-INFO +53 -12
  26. {uncoded-0.5.0 → uncoded-0.7.0}/README.md +51 -10
  27. {uncoded-0.5.0 → uncoded-0.7.0}/pyproject.toml +1 -1
  28. uncoded-0.7.0/src/uncoded/cli.py +177 -0
  29. uncoded-0.7.0/src/uncoded/config.py +87 -0
  30. {uncoded-0.5.0 → uncoded-0.7.0}/src/uncoded/extract.py +34 -26
  31. uncoded-0.7.0/src/uncoded/instruction_files.py +176 -0
  32. {uncoded-0.5.0 → uncoded-0.7.0}/src/uncoded/namespace_map.py +1 -2
  33. {uncoded-0.5.0 → uncoded-0.7.0}/src/uncoded/serena_setup.py +64 -51
  34. {uncoded-0.5.0 → uncoded-0.7.0}/src/uncoded/skill.py +46 -34
  35. {uncoded-0.5.0 → uncoded-0.7.0}/src/uncoded/stubs.py +106 -63
  36. {uncoded-0.5.0 → uncoded-0.7.0}/src/uncoded/sync.py +32 -9
  37. uncoded-0.7.0/tests/test_cli.py +406 -0
  38. uncoded-0.7.0/tests/test_config.py +92 -0
  39. {uncoded-0.5.0 → uncoded-0.7.0}/tests/test_extract.py +59 -19
  40. uncoded-0.7.0/tests/test_instruction_files.py +122 -0
  41. {uncoded-0.5.0 → uncoded-0.7.0}/tests/test_namespace_map.py +0 -31
  42. {uncoded-0.5.0 → uncoded-0.7.0}/tests/test_serena_setup.py +71 -42
  43. {uncoded-0.5.0 → uncoded-0.7.0}/tests/test_skill.py +46 -24
  44. {uncoded-0.5.0 → uncoded-0.7.0}/tests/test_stubs.py +159 -58
  45. uncoded-0.7.0/tests/test_sync.py +139 -0
  46. uncoded-0.5.0/.uncoded/stubs/src/uncoded/cli.pyi +0 -23
  47. uncoded-0.5.0/.uncoded/stubs/src/uncoded/config.pyi +0 -17
  48. uncoded-0.5.0/.uncoded/stubs/src/uncoded/extract.pyi +0 -41
  49. uncoded-0.5.0/.uncoded/stubs/src/uncoded/serena_setup.pyi +0 -32
  50. uncoded-0.5.0/.uncoded/stubs/src/uncoded/sync.pyi +0 -11
  51. uncoded-0.5.0/.uncoded/stubs/tests/test_cli.pyi +0 -66
  52. uncoded-0.5.0/AGENTS.md +0 -124
  53. uncoded-0.5.0/CLAUDE.md +0 -124
  54. uncoded-0.5.0/MAINTENANCE.md +0 -26
  55. uncoded-0.5.0/SYSTEM.md +0 -53
  56. uncoded-0.5.0/src/uncoded/cli.py +0 -103
  57. uncoded-0.5.0/src/uncoded/config.py +0 -62
  58. uncoded-0.5.0/src/uncoded/instruction_files.py +0 -133
  59. uncoded-0.5.0/tests/test_cli.py +0 -199
  60. uncoded-0.5.0/tests/test_config.py +0 -83
  61. uncoded-0.5.0/tests/test_instruction_files.py +0 -92
  62. uncoded-0.5.0/tests/test_sync.py +0 -82
  63. {uncoded-0.5.0 → uncoded-0.7.0}/.claude/settings.json +0 -0
  64. {uncoded-0.5.0 → uncoded-0.7.0}/.github/workflows/ci.yml +0 -0
  65. {uncoded-0.5.0 → uncoded-0.7.0}/.github/workflows/publish.yml +0 -0
  66. {uncoded-0.5.0 → uncoded-0.7.0}/.gitignore +0 -0
  67. {uncoded-0.5.0 → uncoded-0.7.0}/.mcp.json +0 -0
  68. {uncoded-0.5.0 → uncoded-0.7.0}/.pre-commit-config.yaml +0 -0
  69. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/reviews/2026-04-25-001215.md +0 -0
  70. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/src/uncoded/__init__.pyi +0 -0
  71. {uncoded-0.5.0 → uncoded-0.7.0}/.uncoded/stubs/tests/test_uncoded.pyi +0 -0
  72. {uncoded-0.5.0 → uncoded-0.7.0}/LICENSE +0 -0
  73. {uncoded-0.5.0 → uncoded-0.7.0}/resources/lsp-research.md +0 -0
  74. {uncoded-0.5.0 → uncoded-0.7.0}/src/uncoded/__init__.py +0 -0
  75. {uncoded-0.5.0 → uncoded-0.7.0}/tests/__init__.py +0 -0
  76. {uncoded-0.5.0 → uncoded-0.7.0}/tests/test_uncoded.py +0 -0
  77. {uncoded-0.5.0 → uncoded-0.7.0}/uv.lock +0 -0
@@ -55,8 +55,9 @@ The review proceeds in four sweeps, each building on the previous:
55
55
 
56
56
  1. **Orient** — load the navigation index and form a mental map.
57
57
  2. **Lexical sweep** — read the namespace, look for naming-level inconsistency.
58
- 3. **Promissory sweep** — read stubs, check each symbol's name / signature /
59
- docstring for internal disagreement.
58
+ 3. **Promissory sweep** — check each symbol's name / signature / docstring
59
+ for internal disagreement; names and signatures from the stub, docstrings
60
+ via `find_symbol(include_body=True)`.
60
61
  4. **Structural sweep** — combine namespace and imports to find boundary and
61
62
  shape symptoms.
62
63
 
@@ -66,9 +67,8 @@ until the end.
66
67
  ## Step 1: Orient
67
68
 
68
69
  Read `.uncoded/namespace.yaml` in full. This is the map — directories, files,
69
- classes, methods, functions — in source order. Do not skim. Every public symbol
70
- in the codebase is listed here, and the shape of the namespace itself is
71
- evidence.
70
+ classes, methods, functions. Do not skim. Every public symbol in the codebase
71
+ is listed here, and the shape of the namespace itself is evidence.
72
72
 
73
73
  While reading, note:
74
74
 
@@ -96,8 +96,9 @@ interchangeably. Two classes called `UserRecord` and `AccountProfile` that
96
96
  model the same entity.
97
97
 
98
98
  Detection: scan the namespace for symbol clusters with verb or noun overlap.
99
- Where suspicion arises, spot-check the relevant stubs to confirm they overlap
100
- in meaning.
99
+ Where suspicion arises, check the stub signatures and the source docstrings
100
+ (via Serena's `find_symbol` with `include_body=True`) to confirm the
101
+ candidates overlap in meaning.
101
102
 
102
103
  **Qualifier accretion.** Names carrying modifiers that are fossils of
103
104
  iteration: `_new`, `_v2`, `_updated`, `_legacy`, `_real`, `_proper`, `_final`,
@@ -119,16 +120,18 @@ with the rest of the codebase.
119
120
  subtly different meanings — visible as different signatures, different docstring
120
121
  content, or different domain associations.
121
122
 
122
- Detection: identify name collisions in the namespace, then examine the stubs to
123
- see whether the uses agree.
123
+ Detection: identify name collisions in the namespace, then compare signatures
124
+ (from the stubs) and docstrings (via Serena's `find_symbol` with
125
+ `include_body=True`) to see whether the uses agree.
124
126
 
125
127
  ## Step 3: Promissory sweep
126
128
 
127
- Working from stubs, examine each public symbol's name / signature / docstring
128
- triple for internal disagreement.
129
-
130
- For each non-trivial public symbol (skip trivial one-liners and `__init__` with
131
- no meaningful body):
129
+ Examine each public symbol's name / signature / docstring triple for internal
130
+ disagreement. Load each source file's stub once for the names and signatures
131
+ across that file's symbols. Then, for each non-trivial public symbol (skip
132
+ trivial one-liners and `__init__` with no meaningful body), call Serena's
133
+ `find_symbol` with `include_body=True` to read the docstring — the call also
134
+ returns the body, available if a finding needs it.
132
135
 
133
136
  **Name–signature mismatch.** Does the name's verb fit the signature's return? A
134
137
  function called `validate_*` that returns the validated object rather than
@@ -149,18 +152,15 @@ describe it. "Note: this does not actually X despite the name." "Do not use
149
152
  this for Y; use Z instead." These are confessions — someone noticed drift and
150
153
  documented it rather than fixing it.
151
154
 
152
- The stub itself is the evidence. Quote the stub excerpt (name, signature,
153
- first-line docstring) verbatim in the finding.
155
+ Quote evidence verbatim. The stub excerpt is the evidence for name and
156
+ signature findings; the docstring returned by `find_symbol` with
157
+ `include_body=True` is the evidence for any docstring-related finding.
154
158
 
155
- **When to read source.** The stub is usually sufficient for discovery the
156
- inconsistency IS the mismatch between name, signature, and docstring, all of
157
- which the stub provides. Read the symbol body when a finding is already
158
- identified but confidence is genuinely uncertain: an undocumented parameter
159
- where significance depends on what it controls; a name–behaviour mismatch where
160
- the stub alone doesn't confirm it; a defensive docstring you want to verify is
161
- accurate. Use Serena's `find_symbol` with `include_body=True` — targeted to the
162
- symbol, no offset arithmetic, no risk of over-reading. Never read a whole source
163
- file during this sweep.
159
+ **When to read further.** Read the body when a finding's confidence needs it:
160
+ a name–behaviour mismatch the docstring alone doesn't settle, or a defensive
161
+ docstring you want to verify against the body. Targeted to the symbol, no
162
+ offset arithmetic, no risk of over-reading. Never read a whole source file
163
+ during this sweep.
164
164
 
165
165
  ## Step 4: Structural sweep
166
166
 
@@ -244,7 +244,8 @@ Regions with two or more findings — examine these first:
244
244
  **Confidence:** high | medium | low
245
245
 
246
246
  **Evidence:**
247
- > Verbatim quote from namespace.yaml, stub, or import statement.
247
+ > Verbatim quote from namespace.yaml, stub, source docstring (via
248
+ > `find_symbol`), or import statement.
248
249
 
249
250
  One or two sentences describing the inconsistency. Not a diagnosis. Not a fix.
250
251
 
@@ -261,14 +262,14 @@ clear evidence is useful — the human can filter. A dropped finding is not.
261
262
 
262
263
  **Confidence is part of the finding, not a gate.**
263
264
 
264
- - `high` — the inconsistency is explicit; evidence is directly in the stub or
265
- namespace
265
+ - `high` — the inconsistency is explicit; evidence is directly in the
266
+ namespace, the stub, or the source docstring
266
267
  - `medium` — strongly implied but depends on judgement about intent
267
268
  - `low` — pattern-based suspicion that needs human interpretation
268
269
 
269
270
  **Evidence must be verbatim.** Quote the relevant namespace line, stub excerpt,
270
- or import statement exactly. A finding the human cannot quickly verify is worse
271
- than no finding.
271
+ source docstring, or import statement exactly. A finding the human cannot
272
+ quickly verify is worse than no finding.
272
273
 
273
274
  **One finding per inconsistency.** If a single symbol has a name–signature
274
275
  mismatch and a docstring–name mismatch, that is two findings on the same
@@ -55,8 +55,9 @@ The review proceeds in four sweeps, each building on the previous:
55
55
 
56
56
  1. **Orient** — load the navigation index and form a mental map.
57
57
  2. **Lexical sweep** — read the namespace, look for naming-level inconsistency.
58
- 3. **Promissory sweep** — read stubs, check each symbol's name / signature /
59
- docstring for internal disagreement.
58
+ 3. **Promissory sweep** — check each symbol's name / signature / docstring
59
+ for internal disagreement; names and signatures from the stub, docstrings
60
+ via `find_symbol(include_body=True)`.
60
61
  4. **Structural sweep** — combine namespace and imports to find boundary and
61
62
  shape symptoms.
62
63
 
@@ -66,9 +67,8 @@ until the end.
66
67
  ## Step 1: Orient
67
68
 
68
69
  Read `.uncoded/namespace.yaml` in full. This is the map — directories, files,
69
- classes, methods, functions — in source order. Do not skim. Every public symbol
70
- in the codebase is listed here, and the shape of the namespace itself is
71
- evidence.
70
+ classes, methods, functions. Do not skim. Every public symbol in the codebase
71
+ is listed here, and the shape of the namespace itself is evidence.
72
72
 
73
73
  While reading, note:
74
74
 
@@ -96,8 +96,9 @@ interchangeably. Two classes called `UserRecord` and `AccountProfile` that
96
96
  model the same entity.
97
97
 
98
98
  Detection: scan the namespace for symbol clusters with verb or noun overlap.
99
- Where suspicion arises, spot-check the relevant stubs to confirm they overlap
100
- in meaning.
99
+ Where suspicion arises, check the stub signatures and the source docstrings
100
+ (via Serena's `find_symbol` with `include_body=True`) to confirm the
101
+ candidates overlap in meaning.
101
102
 
102
103
  **Qualifier accretion.** Names carrying modifiers that are fossils of
103
104
  iteration: `_new`, `_v2`, `_updated`, `_legacy`, `_real`, `_proper`, `_final`,
@@ -119,16 +120,18 @@ with the rest of the codebase.
119
120
  subtly different meanings — visible as different signatures, different docstring
120
121
  content, or different domain associations.
121
122
 
122
- Detection: identify name collisions in the namespace, then examine the stubs to
123
- see whether the uses agree.
123
+ Detection: identify name collisions in the namespace, then compare signatures
124
+ (from the stubs) and docstrings (via Serena's `find_symbol` with
125
+ `include_body=True`) to see whether the uses agree.
124
126
 
125
127
  ## Step 3: Promissory sweep
126
128
 
127
- Working from stubs, examine each public symbol's name / signature / docstring
128
- triple for internal disagreement.
129
-
130
- For each non-trivial public symbol (skip trivial one-liners and `__init__` with
131
- no meaningful body):
129
+ Examine each public symbol's name / signature / docstring triple for internal
130
+ disagreement. Load each source file's stub once for the names and signatures
131
+ across that file's symbols. Then, for each non-trivial public symbol (skip
132
+ trivial one-liners and `__init__` with no meaningful body), call Serena's
133
+ `find_symbol` with `include_body=True` to read the docstring — the call also
134
+ returns the body, available if a finding needs it.
132
135
 
133
136
  **Name–signature mismatch.** Does the name's verb fit the signature's return? A
134
137
  function called `validate_*` that returns the validated object rather than
@@ -149,18 +152,15 @@ describe it. "Note: this does not actually X despite the name." "Do not use
149
152
  this for Y; use Z instead." These are confessions — someone noticed drift and
150
153
  documented it rather than fixing it.
151
154
 
152
- The stub itself is the evidence. Quote the stub excerpt (name, signature,
153
- first-line docstring) verbatim in the finding.
155
+ Quote evidence verbatim. The stub excerpt is the evidence for name and
156
+ signature findings; the docstring returned by `find_symbol` with
157
+ `include_body=True` is the evidence for any docstring-related finding.
154
158
 
155
- **When to read source.** The stub is usually sufficient for discovery the
156
- inconsistency IS the mismatch between name, signature, and docstring, all of
157
- which the stub provides. Read the symbol body when a finding is already
158
- identified but confidence is genuinely uncertain: an undocumented parameter
159
- where significance depends on what it controls; a name–behaviour mismatch where
160
- the stub alone doesn't confirm it; a defensive docstring you want to verify is
161
- accurate. Use Serena's `find_symbol` with `include_body=True` — targeted to the
162
- symbol, no offset arithmetic, no risk of over-reading. Never read a whole source
163
- file during this sweep.
159
+ **When to read further.** Read the body when a finding's confidence needs it:
160
+ a name–behaviour mismatch the docstring alone doesn't settle, or a defensive
161
+ docstring you want to verify against the body. Targeted to the symbol, no
162
+ offset arithmetic, no risk of over-reading. Never read a whole source file
163
+ during this sweep.
164
164
 
165
165
  ## Step 4: Structural sweep
166
166
 
@@ -244,7 +244,8 @@ Regions with two or more findings — examine these first:
244
244
  **Confidence:** high | medium | low
245
245
 
246
246
  **Evidence:**
247
- > Verbatim quote from namespace.yaml, stub, or import statement.
247
+ > Verbatim quote from namespace.yaml, stub, source docstring (via
248
+ > `find_symbol`), or import statement.
248
249
 
249
250
  One or two sentences describing the inconsistency. Not a diagnosis. Not a fix.
250
251
 
@@ -261,14 +262,14 @@ clear evidence is useful — the human can filter. A dropped finding is not.
261
262
 
262
263
  **Confidence is part of the finding, not a gate.**
263
264
 
264
- - `high` — the inconsistency is explicit; evidence is directly in the stub or
265
- namespace
265
+ - `high` — the inconsistency is explicit; evidence is directly in the
266
+ namespace, the stub, or the source docstring
266
267
  - `medium` — strongly implied but depends on judgement about intent
267
268
  - `low` — pattern-based suspicion that needs human interpretation
268
269
 
269
270
  **Evidence must be verbatim.** Quote the relevant namespace line, stub excerpt,
270
- or import statement exactly. A finding the human cannot quickly verify is worse
271
- than no finding.
271
+ source docstring, or import statement exactly. A finding the human cannot
272
+ quickly verify is worse than no finding.
272
273
 
273
274
  **One finding per inconsistency.** If a single symbol has a name–signature
274
275
  mismatch and a docstring–name mismatch, that is two findings on the same
@@ -85,6 +85,7 @@ excluded_tools:
85
85
  - edit_memory
86
86
  - delete_memory
87
87
  - rename_memory
88
+ - activate_project
88
89
  - onboarding
89
90
  - check_onboarding_performed
90
91
  - open_dashboard
@@ -2,8 +2,7 @@
2
2
  # Generated by uncoded — do not edit; regeneration overwrites.
3
3
  #
4
4
  # Pure key hierarchy (no lists, no values); indent to zoom in.
5
- # Directory keys end with "/". Entries within a file or class appear
6
- # in source order.
5
+ # Directory keys end with "/".
7
6
  #
8
7
  # For signatures and types, see stubs:
9
8
  # src/foo/bar.py → .uncoded/stubs/src/foo/bar.pyi
@@ -13,11 +12,11 @@ src/:
13
12
  __init__.py:
14
13
  __version__:
15
14
  cli.py:
16
- DEFAULT_MAP_OUTPUT:
17
15
  _sync:
18
16
  main:
19
17
  config.py:
20
18
  find_pyproject_toml:
19
+ read_project_name:
21
20
  read_source_roots:
22
21
  read_instruction_files:
23
22
  extract.py:
@@ -30,18 +29,17 @@ src/:
30
29
  constants:
31
30
  classes:
32
31
  functions:
33
- _property_kind:
32
+ property_kind:
34
33
  _assign_target_name:
35
34
  extract_module:
36
35
  iter_source_files:
37
- walk_source:
36
+ extract_modules:
38
37
  instruction_files.py:
39
38
  MARKER_START:
40
39
  MARKER_END:
41
40
  DEFAULT_INSTRUCTION_FILES:
42
41
  _SECTION_BODY:
43
42
  SECTION:
44
- generate_section:
45
43
  _replace_or_append:
46
44
  sync_instruction_file:
47
45
  namespace_map.py:
@@ -51,16 +49,16 @@ src/:
51
49
  build_map:
52
50
  render_map:
53
51
  serena_setup.py:
52
+ _Status:
54
53
  SERENA_VERSION:
55
54
  MCP_SERVER_SERENA:
56
- SERENA_PROJECT_YML:
55
+ SERENA_PROJECT_FIELDS:
57
56
  SERENA_ALLOWED_TOOLS:
58
57
  _STATUS_VERB:
59
- read_project_name:
60
58
  _sync_mcp_json:
61
- _sync_serena_project:
59
+ _write_serena_project_if_absent:
62
60
  _sync_claude_settings:
63
- setup_serena:
61
+ setup:
64
62
  skill.py:
65
63
  SKILL_OUTPUTS:
66
64
  LEGACY_SKILL_OUTPUTS:
@@ -68,7 +66,6 @@ src/:
68
66
  sync_skill:
69
67
  stubs.py:
70
68
  VALUE_WIDTH_CAP:
71
- DEFAULT_STUBS_OUTPUT:
72
69
  StubParam:
73
70
  name:
74
71
  annotation:
@@ -76,7 +73,6 @@ src/:
76
73
  name:
77
74
  params:
78
75
  return_annotation:
79
- docstring_excerpt:
80
76
  is_async:
81
77
  StubAssignment:
82
78
  name:
@@ -86,7 +82,6 @@ src/:
86
82
  StubClass:
87
83
  name:
88
84
  bases:
89
- docstring_excerpt:
90
85
  attributes:
91
86
  methods:
92
87
  StubModule:
@@ -95,7 +90,6 @@ src/:
95
90
  constants:
96
91
  classes:
97
92
  functions:
98
- _first_sentence:
99
93
  _extract_params:
100
94
  _render_value:
101
95
  _extract_assignment:
@@ -109,6 +103,7 @@ src/:
109
103
  _render_assignment:
110
104
  render_stub:
111
105
  _generate_stubs:
106
+ _write_stubs:
112
107
  build_stubs:
113
108
  sync.py:
114
109
  sync_file:
@@ -118,31 +113,41 @@ tests/:
118
113
  TestSyncApplyMode:
119
114
  test_writes_namespace_map_stubs_and_instruction_file:
120
115
  test_idempotent_second_run:
116
+ test_dedupes_when_claude_md_is_symlink_to_agents_md:
121
117
  test_error_when_no_pyproject_toml:
122
118
  test_error_when_source_root_missing:
119
+ test_error_when_uncoded_section_missing:
120
+ test_skip_warning_emitted_once_per_broken_file:
121
+ test_anchors_reads_and_writes_at_project_root_when_cwd_is_subdir:
122
+ test_artefacts_match_when_run_from_subdir_vs_project_root:
123
123
  TestSyncCheckMode:
124
124
  test_returns_one_and_does_not_write_on_empty_repo:
125
125
  test_returns_zero_when_index_is_up_to_date:
126
126
  test_returns_one_when_source_changes_after_sync:
127
127
  test_returns_one_when_source_file_deleted:
128
128
  test_returns_one_when_instruction_file_drifts:
129
+ test_dedupes_when_claude_md_is_symlink_to_agents_md:
129
130
  test_error_still_returns_one:
130
131
  TestMainDispatch:
131
132
  test_sync_subcommand_runs_in_apply_mode:
132
133
  test_check_subcommand_runs_in_check_mode:
133
134
  test_check_subcommand_returns_zero_on_fresh_index:
134
- test_setup_serena_subcommand:
135
+ test_setup_subcommand:
135
136
  test_no_subcommand_is_an_error:
136
137
  test_unknown_subcommand_is_an_error:
137
138
  _init_repo:
138
139
  test_config.py:
139
140
  TestFindPyprojectToml:
140
- test_finds_in_cwd:
141
- test_finds_in_parent:
141
+ test_finds_at_start:
142
+ test_finds_in_parent_of_start:
142
143
  test_returns_none_if_not_found:
144
+ TestReadProjectName:
145
+ test_reads_name_from_pyproject_toml:
146
+ test_falls_back_to_start_name_when_no_pyproject:
147
+ test_falls_back_to_start_name_when_no_project_section:
148
+ test_falls_back_to_start_name_when_name_missing:
143
149
  TestReadSourceRoots:
144
150
  test_reads_source_roots:
145
- test_raises_if_no_pyproject_toml:
146
151
  test_raises_if_no_uncoded_section:
147
152
  TestReadInstructionFiles:
148
153
  test_returns_default_when_no_pyproject_toml:
@@ -159,19 +164,23 @@ tests/:
159
164
  test_type_alias_pep695:
160
165
  test_tuple_unpacking_skipped:
161
166
  test_unannotated_class_variable:
162
- test_module_with_only_constants_is_kept:
163
167
  test_annotated_attributes:
164
168
  test_property_classified_as_attribute:
165
169
  test_property_setter_and_deleter_suppressed:
166
170
  test_preserves_source_order:
167
- TestWalkSource:
171
+ TestIterAndExtract:
168
172
  test_basic_walk:
169
173
  test_nested_subpackage:
170
174
  test_includes_init_with_symbols:
171
175
  test_skips_empty_init:
172
176
  test_skips_syntax_errors:
177
+ TestExtractModules:
178
+ test_returns_module_info_per_parseable_file:
179
+ test_preserves_source_order:
180
+ test_module_with_only_constants_is_kept:
181
+ test_skips_files_with_no_symbols:
173
182
  test_instruction_files.py:
174
- TestGenerateSection:
183
+ TestSection:
175
184
  test_contains_markers:
176
185
  test_markers_in_order:
177
186
  test_ends_with_newline:
@@ -187,6 +196,9 @@ tests/:
187
196
  test_does_not_create_file:
188
197
  test_does_not_update_existing_file:
189
198
  test_reports_no_change_when_clean:
199
+ TestSyncInstructionFileProjectRootAnchor:
200
+ test_project_root_anchors_create_independent_of_cwd:
201
+ test_project_root_anchors_update_of_existing_file:
190
202
  test_namespace_map.py:
191
203
  TestBuildMap:
192
204
  test_single_file:
@@ -195,9 +207,7 @@ tests/:
195
207
  test_class_with_attributes_and_methods:
196
208
  test_function_is_none:
197
209
  test_class_with_no_members:
198
- test_source_order_preserved:
199
210
  test_module_level_constants:
200
- test_constants_precede_classes_and_functions:
201
211
  TestRenderMap:
202
212
  test_roundtrips_through_yaml:
203
213
  test_preserves_insertion_order:
@@ -208,16 +218,13 @@ tests/:
208
218
  test_serena_setup.py:
209
219
  REPO_ROOT:
210
220
  EXPECTED_EXCLUDED_TOOLS:
211
- TestReadProjectName:
212
- test_reads_name_from_pyproject_toml:
213
- test_falls_back_to_cwd_name_when_no_pyproject:
214
- test_falls_back_to_cwd_name_when_no_project_section:
215
- test_falls_back_to_cwd_name_when_name_missing:
216
- TestSetupSerena:
221
+ EXPECTED_MCP_ARGS:
222
+ TestSetup:
217
223
  _run:
218
224
  test_creates_all_three_files:
219
225
  test_mcp_json_is_valid_and_pins_version:
220
226
  test_serena_project_yml_uses_ty_and_ignores_uncoded:
227
+ test_serena_project_yml_escapes_yaml_special_chars_in_name:
221
228
  test_claude_settings_enables_serena_and_allowlists_tools:
222
229
  test_idempotent:
223
230
  test_merges_into_existing_mcp_json:
@@ -225,9 +232,10 @@ tests/:
225
232
  test_merges_into_existing_claude_settings:
226
233
  test_does_not_overwrite_existing_serena_project_yml:
227
234
  test_does_not_duplicate_on_second_merge:
228
- test_falls_back_to_cwd_name_when_no_pyproject:
235
+ test_setup_uses_root_name_when_no_pyproject:
236
+ test_setup_reads_name_from_pyproject:
229
237
  TestRepoDogfooding:
230
- test_repo_mcp_json_pins_same_serena_version:
238
+ test_repo_mcp_json_matches_template_contract:
231
239
  test_repo_claude_settings_allowlists_every_serena_tool:
232
240
  test_repo_serena_project_yml_matches_template_contract:
233
241
  test_skill.py:
@@ -243,6 +251,9 @@ tests/:
243
251
  test_check_mode_reports_no_change_when_in_sync:
244
252
  test_removes_legacy_skill_files:
245
253
  test_check_mode_reports_legacy_skill_files_without_removing:
254
+ TestSyncSkillProjectRootAnchor:
255
+ test_project_root_anchors_writes_independent_of_cwd:
256
+ test_project_root_anchors_legacy_removal_independent_of_cwd:
246
257
  test_stubs.py:
247
258
  TestExtractStub:
248
259
  test_simple_function:
@@ -252,8 +263,6 @@ tests/:
252
263
  test_class_with_attributes_and_methods:
253
264
  test_class_with_bases:
254
265
  test_class_no_bases:
255
- test_docstring_first_sentence_only:
256
- test_no_docstring:
257
266
  test_kwargs_and_varargs:
258
267
  test_imports_collected:
259
268
  test_syntax_error_raises:
@@ -276,9 +285,9 @@ tests/:
276
285
  test_rendered_stub_has_no_line_range_comments:
277
286
  test_async_function_prefix:
278
287
  test_function_with_annotations:
279
- test_docstring_excerpt_rendered:
280
288
  test_class_with_bases:
281
289
  test_class_no_bases:
290
+ test_class_with_no_members_renders_body:
282
291
  test_attribute_with_annotation:
283
292
  test_method_indented:
284
293
  test_ends_with_newline:
@@ -291,6 +300,7 @@ tests/:
291
300
  test_unannotated_class_attribute_rendered:
292
301
  TestBuildStubs:
293
302
  _setup:
303
+ _build:
294
304
  test_writes_expected_stubs:
295
305
  test_removes_orphan_stub_when_source_deleted:
296
306
  test_removes_orphan_stub_when_source_renamed:
@@ -301,10 +311,17 @@ tests/:
301
311
  test_reports_zero_when_clean:
302
312
  TestBuildStubsCheckMode:
303
313
  _setup:
314
+ _build:
304
315
  test_does_not_write_stub_in_check_mode:
305
316
  test_zero_changes_when_clean:
306
317
  test_detects_stale_stub_content:
307
318
  test_detects_orphan_stub_without_removing_it:
319
+ TestWriteStubs:
320
+ test_writes_stubs:
321
+ test_check_mode_does_not_write:
322
+ test_prunes_orphan_stubs:
323
+ test_project_root_anchors_writes_independent_of_cwd:
324
+ test_project_root_anchors_orphan_pruning_independent_of_cwd:
308
325
  test_sync.py:
309
326
  TestSyncFile:
310
327
  test_creates_missing_file:
@@ -320,6 +337,12 @@ tests/:
320
337
  test_noop_when_absent:
321
338
  test_check_mode_does_not_remove:
322
339
  test_check_mode_reports_noop_when_absent:
340
+ TestSyncFileProjectRootAnchor:
341
+ test_project_root_anchors_write_independent_of_cwd:
342
+ test_project_root_preserves_relative_path_in_message:
343
+ test_absolute_path_makes_project_root_a_no_op:
344
+ TestRemoveFileProjectRootAnchor:
345
+ test_project_root_anchors_removal_independent_of_cwd:
323
346
  test_uncoded.py:
324
347
  test_import:
325
348
  test_version_is_exposed:
@@ -0,0 +1,19 @@
1
+ # src/uncoded/cli.py
2
+
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+ from uncoded.config import find_pyproject_toml, read_instruction_files, read_source_roots
7
+ from uncoded.extract import extract_modules, iter_source_files
8
+ from uncoded.instruction_files import sync_instruction_file
9
+ from uncoded.namespace_map import build_map, render_map
10
+ from uncoded.serena_setup import setup
11
+ from uncoded.skill import sync_skill
12
+ from uncoded.stubs import build_stubs
13
+ from uncoded.sync import sync_file
14
+
15
+ def _sync(*, start: Path | None, check: bool) -> int:
16
+ ...
17
+
18
+ def main() -> int:
19
+ ...
@@ -0,0 +1,17 @@
1
+ # src/uncoded/config.py
2
+
3
+ import tomllib
4
+ from pathlib import Path
5
+ from uncoded.instruction_files import DEFAULT_INSTRUCTION_FILES
6
+
7
+ def find_pyproject_toml(start: Path) -> Path | None:
8
+ ...
9
+
10
+ def read_project_name(start: Path) -> str:
11
+ ...
12
+
13
+ def read_source_roots(pyproject_path: Path) -> list[Path]:
14
+ ...
15
+
16
+ def read_instruction_files(start: Path) -> list[Path]:
17
+ ...
@@ -0,0 +1,33 @@
1
+ # src/uncoded/extract.py
2
+
3
+ import ast
4
+ import sys
5
+ from collections.abc import Iterable, Iterator
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+
9
+ def property_kind(node: ast.FunctionDef | ast.AsyncFunctionDef) -> str | None:
10
+ ...
11
+
12
+ def _assign_target_name(node: ast.Assign | ast.AnnAssign) -> str | None:
13
+ ...
14
+
15
+ def extract_module(source: str, rel_path: str) -> ModuleInfo:
16
+ ...
17
+
18
+ def iter_source_files(source_root: Path, project_root: Path) -> Iterator[tuple[str, str]]:
19
+ ...
20
+
21
+ def extract_modules(files: Iterable[tuple[str, str]]) -> list[ModuleInfo]:
22
+ ...
23
+
24
+ class ClassInfo:
25
+ name: str
26
+ attributes: list[str] = field(default_factory=list)
27
+ methods: list[str] = field(default_factory=list)
28
+
29
+ class ModuleInfo:
30
+ rel_path: str
31
+ constants: list[str] = field(default_factory=list)
32
+ classes: list[ClassInfo] = field(default_factory=list)
33
+ functions: list[str] = field(default_factory=list)
@@ -9,14 +9,8 @@ DEFAULT_INSTRUCTION_FILES = [Path('CLAUDE.md'), Path('AGENTS.md')]
9
9
  _SECTION_BODY = ...
10
10
  SECTION = f'{MARKER_START}\n{_SECTION_BODY}\n{MARKER_END}\n'
11
11
 
12
- def generate_section() -> str:
13
- """Return the full delimited uncoded section for an instruction file."""
14
- ...
15
-
16
12
  def _replace_or_append(existing: str, section: str) -> str:
17
- """Replace the delimited section in existing text, or append it if absent."""
18
13
  ...
19
14
 
20
- def sync_instruction_file(path: Path, *, check: bool) -> bool:
21
- """Write or update the uncoded navigation section in an instruction file."""
15
+ def sync_instruction_file(path: Path, *, project_root: Path, check: bool) -> bool:
22
16
  ...
@@ -7,16 +7,11 @@ from uncoded.extract import ModuleInfo
7
7
  HEADER = ...
8
8
 
9
9
  def build_map(modules: list[ModuleInfo]) -> dict:
10
- """Build a nested dict representing the namespace."""
11
10
  ...
12
11
 
13
12
  def render_map(namespace: dict) -> str:
14
- """Render a namespace map dict as a YAML string with an explanatory header."""
15
13
  ...
16
14
 
17
15
  class _CleanDumper(yaml.SafeDumper):
18
- """YAML dumper that indents list items and suppresses 'null' values."""
19
-
20
16
  def increase_indent(self, flow, indentless):
21
- """Force list items to be indented relative to their parent key."""
22
17
  ...
@@ -0,0 +1,26 @@
1
+ # src/uncoded/serena_setup.py
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Literal
6
+ import yaml
7
+ from uncoded.config import read_project_name
8
+
9
+ type _Status = Literal['wrote', 'updated', 'unchanged']
10
+ SERENA_VERSION = '1.1.2'
11
+ MCP_SERVER_SERENA = ...
12
+ SERENA_PROJECT_FIELDS = ...
13
+ SERENA_ALLOWED_TOOLS = ...
14
+ _STATUS_VERB: dict[_Status, str] = {'wrote': 'Wrote', 'updated': 'Updated', 'unchanged': 'Unchanged'}
15
+
16
+ def _sync_mcp_json(path: Path) -> _Status:
17
+ ...
18
+
19
+ def _write_serena_project_if_absent(path: Path, project_name: str) -> _Status:
20
+ ...
21
+
22
+ def _sync_claude_settings(path: Path) -> _Status:
23
+ ...
24
+
25
+ def setup(root: Path | None) -> int:
26
+ ...