uncoded 2.0.0__tar.gz → 2.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 (91) hide show
  1. uncoded-2.1.0/.agents/skills/uncoded-code-navigation/SKILL.md +106 -0
  2. {uncoded-2.0.0/.claude → uncoded-2.1.0/.agents}/skills/uncoded-coherence-review/SKILL.md +123 -112
  3. uncoded-2.1.0/.agents/skills/uncoded-doc-navigation/SKILL.md +24 -0
  4. uncoded-2.1.0/.claude/skills/uncoded-code-navigation/SKILL.md +106 -0
  5. {uncoded-2.0.0/.agents → uncoded-2.1.0/.claude}/skills/uncoded-coherence-review/SKILL.md +123 -112
  6. uncoded-2.1.0/.claude/skills/uncoded-doc-navigation/SKILL.md +24 -0
  7. uncoded-2.1.0/.markdownlint-cli2.yaml +5 -0
  8. {uncoded-2.0.0 → uncoded-2.1.0}/.pre-commit-config.yaml +11 -0
  9. uncoded-2.1.0/.prettierrc +4 -0
  10. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/docs.yaml +2 -1
  11. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/namespace.yaml +13 -2
  12. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/body.pyi +1 -0
  13. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/cli.pyi +1 -1
  14. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/docs_map.pyi +2 -1
  15. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/extract.pyi +1 -1
  16. uncoded-2.1.0/.uncoded/stubs/src/uncoded/markers.pyi +3 -0
  17. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/namespace_map.pyi +1 -0
  18. uncoded-2.1.0/.uncoded/stubs/src/uncoded/read_helpers.pyi +14 -0
  19. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/refs.pyi +1 -0
  20. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/resolver.pyi +1 -0
  21. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/skill.pyi +1 -0
  22. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_body.pyi +3 -0
  23. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_cli.pyi +12 -0
  24. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_extract.pyi +4 -1
  25. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_skill.pyi +4 -0
  26. uncoded-2.1.0/AGENTS.md +80 -0
  27. uncoded-2.1.0/PKG-INFO +379 -0
  28. uncoded-2.1.0/README.md +358 -0
  29. {uncoded-2.0.0 → uncoded-2.1.0}/pyproject.toml +16 -6
  30. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/body.py +4 -4
  31. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/cli.py +68 -72
  32. uncoded-2.1.0/src/uncoded/code_navigation.md +99 -0
  33. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/coherence_review.md +120 -111
  34. uncoded-2.1.0/src/uncoded/doc_navigation.md +17 -0
  35. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/docs_map.py +6 -5
  36. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/extract.py +8 -8
  37. uncoded-2.1.0/src/uncoded/markers.py +3 -0
  38. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/namespace_map.py +3 -2
  39. uncoded-2.1.0/src/uncoded/read_helpers.py +62 -0
  40. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/refs.py +4 -3
  41. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/resolver.py +7 -6
  42. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/skill.py +16 -10
  43. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/sync.py +3 -3
  44. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_body.py +10 -0
  45. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_cli.py +43 -3
  46. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_extract.py +17 -3
  47. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_skill.py +21 -4
  48. uncoded-2.0.0/.agents/skills/uncoded-code-navigation/SKILL.md +0 -109
  49. uncoded-2.0.0/.agents/skills/uncoded-doc-navigation/SKILL.md +0 -22
  50. uncoded-2.0.0/.claude/skills/uncoded-code-navigation/SKILL.md +0 -109
  51. uncoded-2.0.0/.claude/skills/uncoded-doc-navigation/SKILL.md +0 -22
  52. uncoded-2.0.0/.markdownlint-cli2.yaml +0 -5
  53. uncoded-2.0.0/.uncoded/stubs/src/uncoded/read_helpers.pyi +0 -7
  54. uncoded-2.0.0/AGENTS.md +0 -70
  55. uncoded-2.0.0/PKG-INFO +0 -367
  56. uncoded-2.0.0/README.md +0 -346
  57. uncoded-2.0.0/src/uncoded/code_navigation.md +0 -104
  58. uncoded-2.0.0/src/uncoded/doc_navigation.md +0 -17
  59. uncoded-2.0.0/src/uncoded/read_helpers.py +0 -25
  60. {uncoded-2.0.0 → uncoded-2.1.0}/.github/workflows/ci.yml +0 -0
  61. {uncoded-2.0.0 → uncoded-2.1.0}/.github/workflows/publish.yml +0 -0
  62. {uncoded-2.0.0 → uncoded-2.1.0}/.gitignore +0 -0
  63. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/__init__.pyi +0 -0
  64. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/ast_helpers.pyi +0 -0
  65. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/config.pyi +0 -0
  66. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/stubs.pyi +0 -0
  67. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/sync.pyi +0 -0
  68. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/src/uncoded/yaml_tree.pyi +0 -0
  69. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_config.pyi +0 -0
  70. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_docs_map.pyi +0 -0
  71. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_namespace_map.pyi +0 -0
  72. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_refs.pyi +0 -0
  73. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_stubs.pyi +0 -0
  74. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_sync.pyi +0 -0
  75. {uncoded-2.0.0 → uncoded-2.1.0}/.uncoded/stubs/tests/test_uncoded.pyi +0 -0
  76. {uncoded-2.0.0 → uncoded-2.1.0}/CLAUDE.md +0 -0
  77. {uncoded-2.0.0 → uncoded-2.1.0}/LICENSE +0 -0
  78. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/__init__.py +0 -0
  79. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/ast_helpers.py +0 -0
  80. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/config.py +0 -0
  81. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/stubs.py +0 -0
  82. {uncoded-2.0.0 → uncoded-2.1.0}/src/uncoded/yaml_tree.py +0 -0
  83. {uncoded-2.0.0 → uncoded-2.1.0}/tests/__init__.py +0 -0
  84. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_config.py +0 -0
  85. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_docs_map.py +0 -0
  86. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_namespace_map.py +0 -0
  87. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_refs.py +0 -0
  88. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_stubs.py +0 -0
  89. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_sync.py +0 -0
  90. {uncoded-2.0.0 → uncoded-2.1.0}/tests/test_uncoded.py +0 -0
  91. {uncoded-2.0.0 → uncoded-2.1.0}/uv.lock +0 -0
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: uncoded-code-navigation
3
+ description: Use before searching, reading, or editing Python source in a codebase indexed by uncoded. This covers locating a symbol, reading a definition, or checking references before you refactor, rename, or delete.
4
+ ---
5
+
6
+ <!-- Generated by uncoded — do not edit; regeneration overwrites. -->
7
+
8
+ # Code Navigation
9
+
10
+ This codebase uses [uncoded](https://github.com/alimanfoo/uncoded) to maintain a
11
+ symbol index over its source code, with two associated CLI tools: `uncoded body`
12
+ for reading a symbol's body and `uncoded refs` for finding references.
13
+
14
+ ## The dispatch rule
15
+
16
+ **If your search term is the name of a Python symbol, use the index. Python
17
+ symbols are classes, functions, methods, attributes, and module-level constants.
18
+ If it's a pattern, regex, or free-text phrase, use grep.**
19
+
20
+ This applies to every tool call where you find code, not just the first in the
21
+ session. The pretrained reflex for "find X" is grep, and that reflex is wrong
22
+ here. Reaching for `grep -rn 'def parse_config'` to read a function's body is a
23
+ case for `uncoded body`. Reaching for `grep -rn 'validate_input'` to check
24
+ callers before a refactor is a case for `uncoded refs`. Reaching for `grep` then
25
+ `Edit` to delete dead code is a case for `uncoded refs` to confirm the code is
26
+ dead, then `Edit`. The grep version of any of these is noisier and less
27
+ reliable. Grep matches comments, strings, and unrelated attributes. Grep misses
28
+ re-exports, so caller and delete checks come back incomplete. Grep forces offset
29
+ arithmetic to slice a body. The indexed tools don't.
30
+
31
+ ## How to execute the rule
32
+
33
+ The index has two parts (a namespace map and per-file stubs) and three steps
34
+ (orient, understand, act).
35
+
36
+ **Step 1: Orient. Read the namespace map first.** Before answering the user,
37
+ before any other tool call:
38
+
39
+ ```text
40
+ Read .uncoded/namespace.yaml
41
+ ```
42
+
43
+ This lists every symbol in the codebase: directories, files, classes, methods,
44
+ functions. Without it loaded, "find X" answers come from pretrained guesses.
45
+ They reflect what a project like this probably contains, not what is actually
46
+ here. Read it once, in full, at session start.
47
+
48
+ **Step 2: Understand. Read the `.pyi` stub before any `.py` source.** Stub paths
49
+ mirror source paths under `.uncoded/stubs/`:
50
+
51
+ ```text
52
+ src/foo/bar.py → .uncoded/stubs/src/foo/bar.pyi
53
+ tests/test_foo.py → .uncoded/stubs/tests/test_foo.pyi
54
+ ```
55
+
56
+ Read the stub for every file you intend to touch or reference, including tests.
57
+ The stub contains imports, every signature with types, module-level assignments,
58
+ and class attributes. That is enough for most navigation. Skipping straight to
59
+ source means reading many lines to learn what the stub would have told you in
60
+ one. If no stub exists at the expected path, the file has no symbols indexed. In
61
+ that narrow case, read source directly.
62
+
63
+ **Step 3: Act.** Use `uncoded body` to read a symbol's body. Use `uncoded refs`
64
+ to find every reference to a symbol. Use `Edit` (with `uncoded body`'s output as
65
+ `old_string`) to change a symbol. With the map and stub loaded, you have the
66
+ exact `relative_path` and `name_path` each tool needs. Use `ClassName/method`
67
+ for a method and `function_name` for a top-level function. Per task:
68
+
69
+ - **Read a symbol's body.** `uvx uncoded body <name_path> --in <relative_path>`
70
+ prints the symbol's source text to stdout, byte-identical to disk. Returns
71
+ exactly the symbol. No offset arithmetic, no risk of reading too much. Its
72
+ output has every byte `Edit` needs as `old_string`. You need no extra `Read`
73
+ for partial edits. Stay on stubs for a wider sweep.
74
+
75
+ - **Find every reference to a symbol.**
76
+ `uvx uncoded refs <name_path> --in <relative_path>`. Prints one reference per
77
+ line as `file:line:col`, sorted. Grep on the name misses re-exports and adds
78
+ false positives from comments, strings, and attribute lookups on other types.
79
+ If the next move depends on the answer being complete, grep cannot give you
80
+ that.
81
+
82
+ - **Edit a symbol.** `uvx uncoded body <name_path> --in <relative_path>` gives
83
+ the exact `old_string`; then `Edit` to apply the change.
84
+
85
+ - **Rename.** `uvx uncoded refs <name_path> --in <relative_path>` enumerates
86
+ every site; then `Edit` at each.
87
+
88
+ - **Safely delete.** `uvx uncoded refs <name_path> --in <relative_path>` must
89
+ return empty; then `Edit` to remove.
90
+
91
+ ## Where Read, Edit, and grep are still the right tools
92
+
93
+ The rule is about source navigation by symbol name. Outside that, Read, Edit,
94
+ and grep stay correct:
95
+
96
+ - Free-text or pattern search outside source: Markdown, YAML, TOML, configs,
97
+ commit messages, notebook JSON, fixture data.
98
+ - Pattern search across signatures (regex over type annotations, decorator
99
+ usage, alias declarations). These are not symbol-name lookups, even though
100
+ they sit inside source.
101
+ - Partial-line edits inside a symbol body, once you have retrieved it via
102
+ `uncoded body`.
103
+ - The rare stub-less Python file that needs exploratory reading.
104
+
105
+ The dispatch rule turns on the search term. A symbol name goes to the index. A
106
+ regex or free-text phrase goes to grep.
@@ -1,8 +1,10 @@
1
1
  ---
2
2
  name: uncoded-coherence-review
3
- description: 'Perform a coherence review of a Python codebase: a diagnostic sweep for semantic drift, naming inconsistency, promissory mismatch, and structural incoherence. Produces a Markdown report of findings with verbatim evidence and confidence levels, for human investigation. Assumes uncoded is installed (.uncoded/namespace.yaml and .uncoded/stubs/ present).'
3
+ description: Review a Python codebase for coherence. It sweeps for semantic drift, naming inconsistency, mismatch between a symbol's name, signature, and docstring, and structural incoherence. It produces a Markdown report of findings, with verbatim evidence and confidence levels, for human investigation. It assumes uncoded is installed (.uncoded/namespace.yaml and .uncoded/stubs/ present).
4
4
  ---
5
5
 
6
+ <!-- Generated by uncoded — do not edit; regeneration overwrites. -->
7
+
6
8
  # Coherence Review
7
9
 
8
10
  A diagnostic sweep of a Python codebase for specific, observable symptoms of
@@ -13,67 +15,77 @@ human to investigate. Not a bug hunt, not a lint pass, not a refactor.
13
15
  ## Why coherence, and what you are looking for
14
16
 
15
17
  A codebase accumulates corruption differently from how it accumulates bugs. Bugs
16
- announce themselves — tests fail, users complain, exceptions raise. Corruption
17
- passes every integrity check. It is the slow divergence between what the code
18
- claims to mean and what it actually does, between how one part of the codebase
19
- names a concept and how another names the same concept, between an architecture
20
- as declared and an architecture as practised. Each local decision that
21
- contributed to it was reasonable. The accumulation is not.
18
+ announce themselves. Tests fail, users complain, exceptions raise. Corruption
19
+ passes every integrity check. It is a slow divergence between things that should
20
+ match:
21
+
22
+ - what the code claims to mean and what it actually does
23
+ - how one part of the codebase names a concept and how another names the same
24
+ concept
25
+ - an architecture as declared and an architecture as practised
26
+
27
+ Each local decision that contributed to it was reasonable. The accumulation is
28
+ not.
22
29
 
23
30
  Corruption's one observable signature is **internal inconsistency**. Not
24
- absolute wrongness — that requires a reference outside the code, which is not
31
+ absolute wrongness. That requires a reference outside the code, which is not
25
32
  available here. Just pairwise disagreement between things that ought to agree:
26
- a name and its behaviour, two names for the same concept, a docstring and the
27
- signature it sits on, a declared architecture and the actual import graph. Every
28
- symptom in the sweeps below is a form of inconsistency. The review's job is to
29
- find these disagreements — not to diagnose root cause and not to fix them.
33
+
34
+ - a name and its behaviour
35
+ - two names for the same concept
36
+ - a docstring and the signature it sits on
37
+ - a declared architecture and the actual import graph
38
+
39
+ Every symptom in the sweeps below is a form of inconsistency. The review's job
40
+ is to find these disagreements. It does not diagnose root cause and does not fix
41
+ them.
30
42
 
31
43
  ## What this skill is not
32
44
 
33
45
  - **Not a bug-finder.** Bugs are the job of testing and code review. A coherence
34
46
  review can run on code that passes every test and still find plenty.
35
- - **Not a style pass.** Tabs versus spaces, docstring format, import ordering —
47
+ - **Not a style pass.** Tabs versus spaces, docstring format, import ordering:
36
48
  irrelevant. Linters exist.
37
49
  - **Not a refactor.** No proposing fixes, no suggesting renames, no rewriting
38
50
  code. The output is findings. The human decides what to do with them.
39
- - **Not a general code review.** Performance, security, correctness — out of
51
+ - **Not a general code review.** Performance, security, correctness: out of
40
52
  scope unless they happen to manifest as a coherence symptom.
41
53
 
42
54
  ## Prerequisites
43
55
 
44
- Verify by reading `.uncoded/namespace.yaml` — if it exists and is non-empty,
45
- proceed. If not, stop and tell the user to run `uvx uncoded sync` first; the review
46
- depends on the index.
56
+ Verify by reading `.uncoded/namespace.yaml`. If it exists and is non-empty,
57
+ proceed. If not, stop and tell the user to run `uvx uncoded sync` first. The
58
+ review depends on the index.
47
59
 
48
- The structural sweep uses `uncoded refs` for cross-file reference checks —
49
- it ships with uncoded itself and is always available when the index is present.
60
+ The structural sweep uses `uncoded refs` for cross-file reference checks. It
61
+ ships with uncoded itself and is always available when the index is present.
50
62
 
51
63
  ## Workflow
52
64
 
53
65
  The review proceeds in four sweeps, each building on the previous:
54
66
 
55
- 1. **Orient** — load the navigation index and form a mental map.
56
- 2. **Lexical sweep** — read the namespace, look for naming-level inconsistency.
57
- 3. **Promissory sweep** — check each symbol's name / signature / docstring
58
- for internal disagreement; names and signatures from the stub, docstrings
59
- via `uncoded body`.
60
- 4. **Structural sweep** — combine namespace and imports to find boundary and
67
+ 1. **Orient**: load the navigation index and form a mental map.
68
+ 2. **Lexical sweep**: read the namespace, look for naming-level inconsistency.
69
+ 3. **Promissory sweep**: check each symbol's name / signature / docstring for
70
+ internal disagreement. Names and signatures come from the stub. Docstrings
71
+ come via `uncoded body`.
72
+ 4. **Structural sweep**: combine namespace and imports to find boundary and
61
73
  shape symptoms.
62
74
 
63
- Do the sweeps in order. Write findings as you go — do not hold them in memory
75
+ Do the sweeps in order. Write findings as you go. Do not hold them in memory
64
76
  until the end.
65
77
 
66
78
  ## Step 1: Orient
67
79
 
68
- Read `.uncoded/namespace.yaml` in full. This is the map — directories, files,
69
- classes, methods, functions. Do not skim. Every public symbol in the codebase
70
- is listed here, and the shape of the namespace itself is evidence.
80
+ Read `.uncoded/namespace.yaml` in full. This is the map: directories, files,
81
+ classes, methods, functions. Do not skim. Every public symbol in the codebase is
82
+ listed here, and the shape of the namespace itself is evidence.
71
83
 
72
84
  While reading, note:
73
85
 
74
86
  - The vocabulary the codebase uses for its core concepts
75
87
  - The organisational logic (domain-driven? layered? feature-based? ad hoc?)
76
- - Anything that surprises you — odd names, asymmetric organisation, suspicious
88
+ - Anything that surprises you: odd names, asymmetric organisation, suspicious
77
89
  clusters
78
90
 
79
91
  Also read `CLAUDE.md` if present, and follow any repo-specific navigation
@@ -91,18 +103,18 @@ symptom.
91
103
  different names in different parts of the codebase. Examples: `fetch_user`,
92
104
  `get_user`, `load_user`, `retrieve_user` all appearing as separate functions
93
105
  doing substantively the same thing. `compute_*`, `calculate_*`, `derive_*` used
94
- interchangeably. Two classes called `UserRecord` and `AccountProfile` that
95
- model the same entity.
106
+ interchangeably. Two classes called `UserRecord` and `AccountProfile` that model
107
+ the same entity.
96
108
 
97
109
  Detection: scan the namespace for symbol clusters with verb or noun overlap.
98
- Where suspicion arises, check the stub signatures and the source docstrings
99
- (via `uncoded body`) to confirm the candidates overlap in meaning.
110
+ Where suspicion arises, check the stub signatures and the source docstrings (via
111
+ `uncoded body`) to confirm the candidates overlap in meaning.
100
112
 
101
- **Qualifier accretion.** Names carrying modifiers that are fossils of
102
- iteration: `_new`, `_v2`, `_updated`, `_legacy`, `_real`, `_proper`, `_final`,
103
- `_fixed`. Also prefix forms: `new_`, `old_`, `real_`. These are almost always
104
- worth flagging — someone needed to distinguish a new thing from an old thing
105
- and the distinction was never resolved.
113
+ **Qualifier accretion.** Names carrying modifiers that are fossils of iteration:
114
+ `_new`, `_v2`, `_updated`, `_legacy`, `_real`, `_proper`, `_final`, `_fixed`.
115
+ Also prefix forms: `new_`, `old_`, `real_`. These are almost always worth
116
+ flagging. Someone needed to distinguish a new thing from an old thing, and the
117
+ distinction was never resolved.
106
118
 
107
119
  Detection: scan the namespace for these qualifier patterns.
108
120
 
@@ -114,11 +126,11 @@ without looking outward.
114
126
  Detection: look for directories whose namespace entries share few word-roots
115
127
  with the rest of the codebase.
116
128
 
117
- **Collision with drift.** The same name appearing in multiple places with
118
- subtly different meanings — visible as different signatures, different docstring
129
+ **Collision with drift.** The same name appearing in multiple places with subtly
130
+ different meanings, visible as different signatures, different docstring
119
131
  content, or different domain associations.
120
132
 
121
- Detection: identify name collisions in the namespace, then compare signatures
133
+ Detection: identify name collisions in the namespace. Then compare signatures
122
134
  (from the stubs) and docstrings (via `uncoded body`) to see whether the uses
123
135
  agree.
124
136
 
@@ -126,11 +138,10 @@ agree.
126
138
 
127
139
  Examine each public symbol's name / signature / docstring triple for internal
128
140
  disagreement. Load each source file's stub once for the names and signatures
129
- across that file's symbols. Then, for each non-trivial public symbol (skip
130
- trivial one-liners and `__init__` with no meaningful body), run
141
+ across that file's symbols. Then, for each non-trivial public symbol, run
131
142
  `uvx uncoded body <name_path> --in <relative_path>` to read the symbol's source.
132
- The docstring is at the top; the rest of the body is available for any
133
- finding that needs it.
143
+ Skip trivial one-liners and `__init__` with no meaningful body. The docstring is
144
+ at the top. The rest of the body is available for any finding that needs it.
134
145
 
135
146
  **Name–signature mismatch.** Does the name's verb fit the signature's return? A
136
147
  function called `validate_*` that returns the validated object rather than
@@ -147,79 +158,80 @@ more specific, more general, or simply different from what the name advertises?
147
158
  "Normalises and validates the record" on a function called `check_record`.
148
159
 
149
160
  **Defensive docstrings.** Docstrings that warn about the function rather than
150
- describe it. "Note: this does not actually X despite the name." "Do not use
151
- this for Y; use Z instead." These are confessions — someone noticed drift and
161
+ describe it. "Note: this does not actually X despite the name." "Do not use this
162
+ for Y; use Z instead." These are confessions. Someone noticed drift and
152
163
  documented it rather than fixing it.
153
164
 
154
- Quote evidence verbatim. The stub excerpt is the evidence for name and
155
- signature findings; the docstring read via `uncoded body` is the evidence
156
- for any docstring-related finding.
165
+ Quote evidence verbatim. The stub excerpt is the evidence for name and signature
166
+ findings. The docstring read via `uncoded body` is the evidence for any
167
+ docstring-related finding.
157
168
 
158
- **When confidence needs the body.** Consult the implementation in the body,
159
- not just the docstring, when a finding's confidence needs it: a
160
- 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.
169
+ **When confidence needs the body.** Consult the implementation in the body when
170
+ a finding's confidence needs it. Two cases call for this. The first is a
171
+ name-behaviour mismatch the docstring alone does not settle. The second is a
172
+ defensive docstring you want to verify against the body. It targets the symbol
173
+ directly, with no offset arithmetic and no risk of over-reading. Never read a
174
+ whole source file during this sweep.
164
175
 
165
176
  ## Step 4: Structural sweep
166
177
 
167
- Combine the namespace with the import graph and `uncoded refs` for
168
- cross-file reference resolution.
178
+ Combine the namespace with the import graph and `uncoded refs` for cross-file
179
+ reference resolution.
169
180
 
170
181
  **Overgrown public surfaces / god modules.** A module or class whose public
171
182
  namespace is much larger than its siblings, or spans obviously different
172
- concerns. Look for outlier symbol counts: a file with forty public symbols where
173
- its neighbours have five; a class with thirty methods covering multiple domains.
183
+ concerns. Look for outlier symbol counts. For example, a file with forty public
184
+ symbols where its neighbours have five, or a class with thirty methods covering
185
+ multiple domains.
174
186
 
175
187
  **Boundary violations.** One module importing private symbols (leading
176
188
  underscore) from another. Scan `from module import _thing` patterns across
177
189
  stubs' import sections. Each instance is a finding.
178
190
 
179
191
  **Cross-vocabulary imports.** Imports that cross domain boundaries in suspicious
180
- directions — a `core/` or `utils/` module importing from a specific business
181
- domain; a module in domain A importing from domain B when those domains appear
182
- meant to be independent. Flag candidates and note the direction; let the human
183
- decide.
192
+ directions. For example, a `core/` or `utils/` module importing from a specific
193
+ business domain. Another is a module in domain A importing from domain B when
194
+ those domains appear meant to be independent. Flag candidates and note the
195
+ direction. Let the human decide.
184
196
 
185
- **Zero-reference public symbols.** A public symbol (no leading underscore) with no
186
- references anywhere in the codebase. Either dead code or an unused API surface.
197
+ **Zero-reference public symbols.** A public symbol (no leading underscore) with
198
+ no references anywhere in the codebase. Either dead code or an unused API
199
+ surface.
187
200
 
188
201
  Check systematically, not by spot-check:
189
202
 
190
203
  1. From the namespace map, list all public symbols in each source module.
191
- 2. Cross-reference with stub import sections — any symbol imported by another
192
- source module is live; remove it from the candidate list. This culls the
204
+ 2. Cross-reference with stub import sections. Any symbol imported by another
205
+ source module is live. Remove it from the candidate list. This culls the
193
206
  obvious cases cheaply.
194
- 3. For remaining candidates, use `uvx uncoded refs <name_path> --in <relative_path>`
195
- to verify. Empty output confirms no references.
207
+ 3. For remaining candidates, use
208
+ `uvx uncoded refs <name_path> --in <relative_path>` to verify. Empty output
209
+ confirms no references.
196
210
  4. Distinguish two sub-cases when reporting:
197
- - *No references anywhere* — dead code; highest priority.
198
- - *References only in tests* — the symbol is tested but not used in source;
211
+ - _No references anywhere._ Dead code. Highest priority.
212
+ - _References only in tests._ The symbol is tested but not used in source. It
199
213
  may be an exposed internal that should be private.
200
214
 
201
215
  **Redundant public surface.** A public constant and a public parameterless
202
- function in the same module where the function's sole body is `return
203
- <constant>`. Both symbols being public exposes an implementation detail
204
- unnecessarily — only one needs to be public. Detection: use the stubs to find
205
- public parameterless functions near public constants, then verify each
206
- candidate body with `uvx uncoded body` before reporting.
216
+ function in the same module where the function's sole body is
217
+ `return <constant>`. Both symbols being public exposes an implementation detail
218
+ unnecessarily. Only one needs to be public. Detection: use the stubs to find
219
+ public parameterless functions near public constants. Verify each candidate body
220
+ with `uvx uncoded body` before reporting.
207
221
 
208
222
  ## Report format
209
223
 
210
224
  Save the report as `.uncoded/reviews/YYYY-MM-DD-HHMMSS.md`, using today's date
211
- and current time (timestamped to preserve multiple runs on the same day).
212
- Create the directory if it does not exist.
225
+ and current time. The timestamp preserves multiple runs on the same day. Create
226
+ the directory if it does not exist.
213
227
 
214
228
  Use this structure:
215
229
 
216
230
  ```markdown
217
231
  # Coherence Review — <repo name>
218
232
 
219
- **Date:** YYYY-MM-DD
220
- **Symbols indexed:** N
221
- **Sweeps run:** lexical, promissory, structural
222
- **Findings:** N total (N lexical · N promissory · N structural)
233
+ **Date:** YYYY-MM-DD **Symbols indexed:** N **Sweeps run:** lexical, promissory,
234
+ structural **Findings:** N total (N lexical · N promissory · N structural)
223
235
 
224
236
  ## Priority regions
225
237
 
@@ -228,22 +240,21 @@ Regions with two or more findings — examine these first:
228
240
  - `path/to/file.py` — N findings
229
241
  - ...
230
242
 
231
- *(Omit this section if no region has more than one finding.)*
243
+ _(Omit this section if no region has more than one finding.)_
232
244
 
233
245
  ## Findings
234
246
 
235
247
  ### 1 · <symptom summary>
236
248
 
237
- **Category:** lexical | promissory | structural
238
- **Symptom:** concept-duplication | qualifier-accretion | vocabulary-island |
239
- collision-with-drift | name-signature-mismatch |
240
- docstring-signature-mismatch | docstring-name-mismatch |
241
- defensive-docstring | god-module |
242
- boundary-violation | cross-vocabulary-import | zero-reference
243
- **Location:** `path/to/file.py` · `ClassName/method_name`
244
- **Confidence:** high | medium | low
249
+ **Category:** lexical | promissory | structural **Symptom:** concept-duplication
250
+ | qualifier-accretion | vocabulary-island | collision-with-drift |
251
+ name-signature-mismatch | docstring-signature-mismatch | docstring-name-mismatch
252
+ | defensive-docstring | god-module | boundary-violation |
253
+ cross-vocabulary-import | zero-reference **Location:** `path/to/file.py` ·
254
+ `ClassName/method_name` **Confidence:** high | medium | low
245
255
 
246
256
  **Evidence:**
257
+
247
258
  > Verbatim quote from namespace.yaml, stub, source docstring (via
248
259
  > `uncoded body`), or import statement.
249
260
 
@@ -258,30 +269,30 @@ One or two sentences describing the inconsistency. Not a diagnosis. Not a fix.
258
269
 
259
270
  **Coverage, not filtering.** Report every finding at its confidence level. Do
260
271
  not silently drop findings judged low-severity. A low-confidence finding with
261
- clear evidence is useful — the human can filter. A dropped finding is not.
272
+ clear evidence is useful. The human can filter. A dropped finding is not.
262
273
 
263
274
  **Confidence is part of the finding, not a gate.**
264
275
 
265
- - `high` — the inconsistency is explicit; evidence is directly in the
266
- namespace, the stub, or the source docstring
267
- - `medium` — strongly implied but depends on judgement about intent
268
- - `low` — pattern-based suspicion that needs human interpretation
276
+ - `high`: the inconsistency is explicit. Evidence is directly in the namespace,
277
+ the stub, or the source docstring
278
+ - `medium`: strongly implied but depends on judgement about intent
279
+ - `low`: pattern-based suspicion that needs human interpretation
269
280
 
270
281
  **Evidence must be verbatim.** Quote the relevant namespace line, stub excerpt,
271
282
  source docstring, or import statement exactly. A finding the human cannot
272
283
  quickly verify is worse than no finding.
273
284
 
274
285
  **One finding per inconsistency.** If a single symbol has a name–signature
275
- mismatch and a docstring–name mismatch, that is two findings on the same
276
- symbol, not one combined finding. Let the report show the density.
286
+ mismatch and a docstring–name mismatch, that is two findings on the same symbol,
287
+ not one combined finding. Let the report show the density.
277
288
 
278
289
  **Do not propose fixes.** No renaming suggestions, no refactoring proposals, no
279
- "this should be moved to". The finding describes what is inconsistent. The
280
- human owns remediation.
290
+ "this should be moved to". The finding describes what is inconsistent. The human
291
+ owns remediation.
281
292
 
282
293
  **Do not flag style.** Docstring format, type annotation style, import ordering,
283
- naming conventions — out of scope. Coherence is about semantic consistency, not
284
- surface consistency.
294
+ and naming conventions are out of scope. Coherence is about semantic
295
+ consistency.
285
296
 
286
297
  **Do not fabricate.** Every finding must be anchored to code you actually
287
298
  examined. If a sweep suggests a pattern but you cannot find concrete instances,
@@ -292,9 +303,9 @@ do not include it.
292
303
  If the codebase has more than ~1000 public symbols:
293
304
 
294
305
  1. Complete the lexical sweep in full (the namespace is compact enough).
295
- 2. For the promissory sweep, prioritise: core domain modules (identified from
296
- the namespace structure), any module that appeared in a lexical finding, and
297
- a representative sample of the rest (~30% of remaining stubs).
306
+ 2. For the promissory sweep, prioritise core domain modules (identified from the
307
+ namespace structure) and any module that appeared in a lexical finding. Also
308
+ cover a representative sample of the rest, around 30% of remaining stubs.
298
309
  3. For the structural sweep, focus on the module and package level first,
299
310
  descending to individual symbols only where the higher-level scan raised
300
311
  flags.
@@ -314,7 +325,7 @@ quoted verbatim.</flag>
314
325
 
315
326
  <example>
316
327
  <scenario>A function `validate_user(user)` whose signature returns `User` rather
317
- than `bool` or `None`, and whose docstring says "Validates and returns the user
328
+ than `bool` or `None`. Its docstring says "Validates and returns the user
318
329
  if valid, raising UserValidationError otherwise."</scenario>
319
330
  <flag>Yes. Name–signature mismatch, medium confidence. The name reads as a
320
331
  predicate but the behaviour is a validator-filter. Stub quoted as
@@ -326,7 +337,7 @@ evidence.</flag>
326
337
  defining functions that wrap callables with LRU caching, with slightly different
327
338
  cache-size defaults.</scenario>
328
339
  <flag>Yes. Concept duplication, medium confidence. Both stubs quoted. Note the
329
- difference — the human may determine it is intentional.</flag>
340
+ difference. The human may determine it is intentional.</flag>
330
341
  </example>
331
342
 
332
343
  <example>
@@ -347,7 +358,7 @@ not indicate drift.</flag>
347
358
  <example>
348
359
  <scenario>A function is 80 lines of non-trivial logic.</scenario>
349
360
  <flag>No, not by itself. Complexity is not incoherence. Flag only if the
350
- complexity manifests as inconsistency — e.g. the function's behaviour has
361
+ complexity manifests as inconsistency. For example, the function's behaviour has
351
362
  drifted from what its name or docstring promise.</flag>
352
363
  </example>
353
364
 
@@ -355,6 +366,6 @@ drifted from what its name or docstring promise.</flag>
355
366
  <scenario>The codebase uses both `Optional[X]` and `X | None` in different
356
367
  files.</scenario>
357
368
  <flag>No. Both are valid Python and mean the same thing. Flag only if the
358
- semantics of absence differ — e.g. some functions return None on failure while
359
- others raise, for the same kind of operation.</flag>
369
+ semantics of absence differ. For example, some functions return None on failure
370
+ while others raise, for the same kind of operation.</flag>
360
371
  </example>
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: uncoded-doc-navigation
3
+ description: Use before searching or reading a codebase's Markdown documentation indexed by uncoded. This covers locating which file and section cover a topic, or orienting to what documentation exists.
4
+ ---
5
+
6
+ <!-- Generated by uncoded — do not edit; regeneration overwrites. -->
7
+
8
+ # Documentation Navigation
9
+
10
+ This codebase uses [uncoded](https://github.com/alimanfoo/uncoded) to maintain a
11
+ documentation index.
12
+
13
+ **Step 1: Orient. Read the docs map first.** Before answering the user, before
14
+ any other tool call:
15
+
16
+ ```text
17
+ Read .uncoded/docs.yaml
18
+ ```
19
+
20
+ `.uncoded/docs.yaml` is an orientation outline: it lists every Markdown file and
21
+ its heading hierarchy. Read it in full, now.
22
+
23
+ **Step 2: Navigate.** Headings in the map are literal text. Use `Read` or `grep`
24
+ to navigate to a specific section identified in the map.
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: uncoded-code-navigation
3
+ description: Use before searching, reading, or editing Python source in a codebase indexed by uncoded. This covers locating a symbol, reading a definition, or checking references before you refactor, rename, or delete.
4
+ ---
5
+
6
+ <!-- Generated by uncoded — do not edit; regeneration overwrites. -->
7
+
8
+ # Code Navigation
9
+
10
+ This codebase uses [uncoded](https://github.com/alimanfoo/uncoded) to maintain a
11
+ symbol index over its source code, with two associated CLI tools: `uncoded body`
12
+ for reading a symbol's body and `uncoded refs` for finding references.
13
+
14
+ ## The dispatch rule
15
+
16
+ **If your search term is the name of a Python symbol, use the index. Python
17
+ symbols are classes, functions, methods, attributes, and module-level constants.
18
+ If it's a pattern, regex, or free-text phrase, use grep.**
19
+
20
+ This applies to every tool call where you find code, not just the first in the
21
+ session. The pretrained reflex for "find X" is grep, and that reflex is wrong
22
+ here. Reaching for `grep -rn 'def parse_config'` to read a function's body is a
23
+ case for `uncoded body`. Reaching for `grep -rn 'validate_input'` to check
24
+ callers before a refactor is a case for `uncoded refs`. Reaching for `grep` then
25
+ `Edit` to delete dead code is a case for `uncoded refs` to confirm the code is
26
+ dead, then `Edit`. The grep version of any of these is noisier and less
27
+ reliable. Grep matches comments, strings, and unrelated attributes. Grep misses
28
+ re-exports, so caller and delete checks come back incomplete. Grep forces offset
29
+ arithmetic to slice a body. The indexed tools don't.
30
+
31
+ ## How to execute the rule
32
+
33
+ The index has two parts (a namespace map and per-file stubs) and three steps
34
+ (orient, understand, act).
35
+
36
+ **Step 1: Orient. Read the namespace map first.** Before answering the user,
37
+ before any other tool call:
38
+
39
+ ```text
40
+ Read .uncoded/namespace.yaml
41
+ ```
42
+
43
+ This lists every symbol in the codebase: directories, files, classes, methods,
44
+ functions. Without it loaded, "find X" answers come from pretrained guesses.
45
+ They reflect what a project like this probably contains, not what is actually
46
+ here. Read it once, in full, at session start.
47
+
48
+ **Step 2: Understand. Read the `.pyi` stub before any `.py` source.** Stub paths
49
+ mirror source paths under `.uncoded/stubs/`:
50
+
51
+ ```text
52
+ src/foo/bar.py → .uncoded/stubs/src/foo/bar.pyi
53
+ tests/test_foo.py → .uncoded/stubs/tests/test_foo.pyi
54
+ ```
55
+
56
+ Read the stub for every file you intend to touch or reference, including tests.
57
+ The stub contains imports, every signature with types, module-level assignments,
58
+ and class attributes. That is enough for most navigation. Skipping straight to
59
+ source means reading many lines to learn what the stub would have told you in
60
+ one. If no stub exists at the expected path, the file has no symbols indexed. In
61
+ that narrow case, read source directly.
62
+
63
+ **Step 3: Act.** Use `uncoded body` to read a symbol's body. Use `uncoded refs`
64
+ to find every reference to a symbol. Use `Edit` (with `uncoded body`'s output as
65
+ `old_string`) to change a symbol. With the map and stub loaded, you have the
66
+ exact `relative_path` and `name_path` each tool needs. Use `ClassName/method`
67
+ for a method and `function_name` for a top-level function. Per task:
68
+
69
+ - **Read a symbol's body.** `uvx uncoded body <name_path> --in <relative_path>`
70
+ prints the symbol's source text to stdout, byte-identical to disk. Returns
71
+ exactly the symbol. No offset arithmetic, no risk of reading too much. Its
72
+ output has every byte `Edit` needs as `old_string`. You need no extra `Read`
73
+ for partial edits. Stay on stubs for a wider sweep.
74
+
75
+ - **Find every reference to a symbol.**
76
+ `uvx uncoded refs <name_path> --in <relative_path>`. Prints one reference per
77
+ line as `file:line:col`, sorted. Grep on the name misses re-exports and adds
78
+ false positives from comments, strings, and attribute lookups on other types.
79
+ If the next move depends on the answer being complete, grep cannot give you
80
+ that.
81
+
82
+ - **Edit a symbol.** `uvx uncoded body <name_path> --in <relative_path>` gives
83
+ the exact `old_string`; then `Edit` to apply the change.
84
+
85
+ - **Rename.** `uvx uncoded refs <name_path> --in <relative_path>` enumerates
86
+ every site; then `Edit` at each.
87
+
88
+ - **Safely delete.** `uvx uncoded refs <name_path> --in <relative_path>` must
89
+ return empty; then `Edit` to remove.
90
+
91
+ ## Where Read, Edit, and grep are still the right tools
92
+
93
+ The rule is about source navigation by symbol name. Outside that, Read, Edit,
94
+ and grep stay correct:
95
+
96
+ - Free-text or pattern search outside source: Markdown, YAML, TOML, configs,
97
+ commit messages, notebook JSON, fixture data.
98
+ - Pattern search across signatures (regex over type annotations, decorator
99
+ usage, alias declarations). These are not symbol-name lookups, even though
100
+ they sit inside source.
101
+ - Partial-line edits inside a symbol body, once you have retrieved it via
102
+ `uncoded body`.
103
+ - The rare stub-less Python file that needs exploratory reading.
104
+
105
+ The dispatch rule turns on the search term. A symbol name goes to the index. A
106
+ regex or free-text phrase goes to grep.