uncoded 1.2.0__tar.gz → 2.0.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 (85) hide show
  1. uncoded-1.2.0/AGENTS.md → uncoded-2.0.0/.agents/skills/uncoded-code-navigation/SKILL.md +15 -86
  2. {uncoded-1.2.0/.agents/skills/coherence-review → uncoded-2.0.0/.agents/skills/uncoded-coherence-review}/SKILL.md +2 -2
  3. uncoded-2.0.0/.agents/skills/uncoded-doc-navigation/SKILL.md +22 -0
  4. uncoded-2.0.0/.claude/skills/uncoded-code-navigation/SKILL.md +109 -0
  5. {uncoded-1.2.0/.claude/skills/coherence-review → uncoded-2.0.0/.claude/skills/uncoded-coherence-review}/SKILL.md +2 -2
  6. uncoded-2.0.0/.claude/skills/uncoded-doc-navigation/SKILL.md +22 -0
  7. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/docs.yaml +2 -5
  8. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/namespace.yaml +44 -75
  9. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/cli.pyi +4 -2
  10. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/config.pyi +0 -2
  11. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/docs_map.pyi +1 -0
  12. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/extract.pyi +1 -0
  13. uncoded-2.0.0/.uncoded/stubs/src/uncoded/read_helpers.pyi +7 -0
  14. uncoded-2.0.0/.uncoded/stubs/src/uncoded/skill.pyi +30 -0
  15. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_cli.pyi +6 -21
  16. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_config.pyi +0 -10
  17. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_docs_map.pyi +6 -0
  18. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_extract.pyi +3 -0
  19. uncoded-2.0.0/.uncoded/stubs/tests/test_skill.pyi +95 -0
  20. uncoded-2.0.0/AGENTS.md +70 -0
  21. {uncoded-1.2.0 → uncoded-2.0.0}/PKG-INFO +83 -20
  22. {uncoded-1.2.0 → uncoded-2.0.0}/README.md +81 -19
  23. {uncoded-1.2.0 → uncoded-2.0.0}/pyproject.toml +1 -0
  24. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/cli.py +83 -94
  25. uncoded-1.2.0/src/uncoded/dispatch_rule.md → uncoded-2.0.0/src/uncoded/code_navigation.md +11 -12
  26. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/coherence_review.md +0 -5
  27. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/config.py +0 -10
  28. uncoded-2.0.0/src/uncoded/doc_navigation.md +17 -0
  29. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/docs_map.py +13 -2
  30. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/extract.py +11 -6
  31. uncoded-2.0.0/src/uncoded/read_helpers.py +25 -0
  32. uncoded-2.0.0/src/uncoded/skill.py +138 -0
  33. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/sync.py +2 -2
  34. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_cli.py +31 -158
  35. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_config.py +2 -35
  36. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_docs_map.py +21 -0
  37. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_extract.py +15 -0
  38. uncoded-2.0.0/tests/test_skill.py +313 -0
  39. {uncoded-1.2.0 → uncoded-2.0.0}/uv.lock +23 -0
  40. uncoded-1.2.0/.uncoded/stubs/src/uncoded/instruction_files.pyi +0 -25
  41. uncoded-1.2.0/.uncoded/stubs/src/uncoded/skill.pyi +0 -12
  42. uncoded-1.2.0/.uncoded/stubs/tests/test_instruction_files.pyi +0 -109
  43. uncoded-1.2.0/.uncoded/stubs/tests/test_skill.pyi +0 -45
  44. uncoded-1.2.0/src/uncoded/docs_rule.md +0 -5
  45. uncoded-1.2.0/src/uncoded/instruction_files.py +0 -135
  46. uncoded-1.2.0/src/uncoded/skill.py +0 -43
  47. uncoded-1.2.0/tests/test_instruction_files.py +0 -380
  48. uncoded-1.2.0/tests/test_skill.py +0 -112
  49. {uncoded-1.2.0 → uncoded-2.0.0}/.github/workflows/ci.yml +0 -0
  50. {uncoded-1.2.0 → uncoded-2.0.0}/.github/workflows/publish.yml +0 -0
  51. {uncoded-1.2.0 → uncoded-2.0.0}/.gitignore +0 -0
  52. {uncoded-1.2.0 → uncoded-2.0.0}/.markdownlint-cli2.yaml +0 -0
  53. {uncoded-1.2.0 → uncoded-2.0.0}/.pre-commit-config.yaml +0 -0
  54. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/__init__.pyi +0 -0
  55. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/ast_helpers.pyi +0 -0
  56. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/body.pyi +0 -0
  57. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/namespace_map.pyi +0 -0
  58. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/refs.pyi +0 -0
  59. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/resolver.pyi +0 -0
  60. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/stubs.pyi +0 -0
  61. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/sync.pyi +0 -0
  62. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/yaml_tree.pyi +0 -0
  63. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_body.pyi +0 -0
  64. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_namespace_map.pyi +0 -0
  65. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_refs.pyi +0 -0
  66. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_stubs.pyi +0 -0
  67. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_sync.pyi +0 -0
  68. {uncoded-1.2.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_uncoded.pyi +0 -0
  69. {uncoded-1.2.0 → uncoded-2.0.0}/CLAUDE.md +0 -0
  70. {uncoded-1.2.0 → uncoded-2.0.0}/LICENSE +0 -0
  71. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/__init__.py +0 -0
  72. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/ast_helpers.py +0 -0
  73. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/body.py +0 -0
  74. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/namespace_map.py +0 -0
  75. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/refs.py +0 -0
  76. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/resolver.py +0 -0
  77. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/stubs.py +0 -0
  78. {uncoded-1.2.0 → uncoded-2.0.0}/src/uncoded/yaml_tree.py +0 -0
  79. {uncoded-1.2.0 → uncoded-2.0.0}/tests/__init__.py +0 -0
  80. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_body.py +0 -0
  81. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_namespace_map.py +0 -0
  82. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_refs.py +0 -0
  83. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_stubs.py +0 -0
  84. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_sync.py +0 -0
  85. {uncoded-1.2.0 → uncoded-2.0.0}/tests/test_uncoded.py +0 -0
@@ -1,79 +1,16 @@
1
- # uncoded
1
+ ---
2
+ name: uncoded-code-navigation
3
+ description: Use before searching, reading, or editing Python source in a codebase indexed by uncoded — locating a symbol, reading a definition, or checking references before a refactor, rename, or delete.
4
+ ---
2
5
 
3
- ## Problem
6
+ # Code Navigation
4
7
 
5
- AI coding agents navigate codebases poorly. They grep for guessed keywords,
6
- skim the first few lines of files, and fill gaps from pretraining rather than
7
- reading the actual code. System prompts encourage this with "make reasonable
8
- assumptions." The result is plausible-looking output built on a hallucinated
9
- understanding of code that's sitting right there, unread.
10
-
11
- ## Approach
12
-
13
- A static, pre-computed index gives agents a top-down view of a codebase.
14
- The agent loads the index at the start of a task, sees the full vocabulary
15
- of the code, and navigates deterministically to what it needs — no guessing,
16
- no grep.
17
-
18
- Index artefacts — generated from the configured roots:
19
-
20
- 1. **Namespace map** (`.uncoded/namespace.yaml`) — a hierarchical YAML file
21
- listing all symbols: directories, files, classes (with attributes
22
- and methods), and functions. Covers both source and tests. Loaded into
23
- context before any task begins. Gives the agent a world view.
24
-
25
- 2. **Stub files** (`.uncoded/stubs/`) — one `.pyi` per source file, with
26
- imports, full signatures (parameter names, types, return types),
27
- module constants, and class attributes.
28
-
29
- 3. **Doc map** (`.uncoded/docs.yaml`) — a heading outline of every Markdown
30
- file in configured `doc-roots`. Each file nests its ATX headings as keys.
31
- Agents load this to orient to the documentation, then navigate to a
32
- heading with `Read` or `grep`. Generated only when `doc-roots` is set.
33
- Outline only — no `uncoded body`, `uncoded refs`, or stubs for Markdown.
34
-
35
- Alongside the index, uncoded ships `uncoded body` to read symbol bodies and
36
- `uncoded refs` to find all references. See "How to read and edit code in
37
- this codebase" below for the dispatch rule.
38
-
39
- ## Commands
40
-
41
- This project uses [uv](https://docs.astral.sh/uv/). Run `uncoded` commands
42
- via `uvx` so they run the published package without needing to install it;
43
- run project tooling (`pytest`, `pre-commit`) via `uv run`.
44
-
45
- ```sh
46
- # Generate (or update) the namespace map, stub files, docs.yaml,
47
- # and instruction-file sections
48
- uvx uncoded sync
49
-
50
- # Verify the index without writing; exits non-zero if any file would change
51
- uvx uncoded check
52
-
53
- # Print the source body of a named symbol to stdout
54
- uvx uncoded body <name_path> --in <relative_path>
55
-
56
- # Find references to a symbol
57
- uvx uncoded refs <name_path> --in <relative_path>
58
-
59
- # Run tests (branch coverage enforced; see [tool.coverage.report]
60
- # in pyproject.toml)
61
- uv run pytest
62
-
63
- # Run a subset of tests without the coverage gate
64
- uv run pytest tests/test_stubs.py --no-cov
65
- ```
66
-
67
- <!-- uncoded:start sha256=5d50217d -->
68
- ## How to read and edit code in this codebase
69
-
70
- This repo uses [uncoded](https://github.com/alimanfoo/uncoded) to maintain
71
- a symbol index over its source code, with two associated tools:
8
+ This codebase uses [uncoded](https://github.com/alimanfoo/uncoded) to maintain
9
+ a symbol index over its source code, with two associated CLI tools:
72
10
  `uncoded body` for reading a symbol's body and `uncoded refs` for finding
73
11
  references.
74
- The point of this scaffolding is one rule.
75
12
 
76
- ### The dispatch rule
13
+ ## The dispatch rule
77
14
 
78
15
  **If your search term is the name of a Python symbol — a class, function,
79
16
  method, attribute, or module-level constant — use the index. If it's a
@@ -81,17 +18,17 @@ pattern, regex, or free-text phrase, use grep.**
81
18
 
82
19
  This applies to every tool call where you are trying to find code, not
83
20
  just the first one in the session. The pretrained reflex for "find X" is
84
- grep, and that reflex is wrong here. `grep -rn 'def resolve_body'` to read a
85
- function's body is the rule firing — that is `uncoded body`. `grep -rn
86
- 'function_name'` to check whether something has callers before a refactor is
87
- the rule firing — that is `uncoded refs`. `grep` then `Edit` to delete
88
- dead code is the rule firing — that is `uncoded refs` to confirm dead,
21
+ grep, and that reflex is wrong here. `grep -rn 'def parse_config'` to read a
22
+ function's body applies the rule — that is `uncoded body`. `grep -rn
23
+ 'validate_input'` to check whether something has callers before a refactor is
24
+ another case — that is `uncoded refs`. `grep` then `Edit` to delete
25
+ dead code fits the same pattern — that is `uncoded refs` to confirm dead,
89
26
  then `Edit`. The grep version of any of these is noisier and less reliable:
90
27
  grep matches comments, strings, and unrelated attributes; grep misses
91
28
  re-exports (so caller and delete checks come back incomplete); grep forces
92
29
  offset arithmetic to slice a body. The indexed tools don't.
93
30
 
94
- ### How to execute the rule
31
+ ## How to execute the rule
95
32
 
96
33
  The index has two parts (a namespace map and per-file stubs) and three
97
34
  steps (orient, understand, act).
@@ -154,7 +91,7 @@ With the map and stub loaded, you have the exact `relative_path` and
154
91
  - **Safely delete.** `uvx uncoded refs <name_path> --in <relative_path>` must
155
92
  return empty; then `Edit` to remove.
156
93
 
157
- ### Where Read, Edit, and grep are still the right tools
94
+ ## Where Read, Edit, and grep are still the right tools
158
95
 
159
96
  The rule is about source navigation by symbol name. Outside that, Read,
160
97
  Edit, and grep stay correct:
@@ -170,11 +107,3 @@ Edit, and grep stay correct:
170
107
 
171
108
  The dispatch rule turns on the search term: a symbol name → the index; a
172
109
  regex or free-text phrase → grep.
173
- <!-- uncoded:end -->
174
- <!-- uncoded:docs:start sha256=6a530a01 -->
175
- ## How to read documentation in this codebase
176
-
177
- `.uncoded/docs.yaml` is an orientation outline: it lists every Markdown file
178
- and its heading hierarchy. Headings are literal text — use `Read` or `grep`
179
- to navigate to a section.
180
- <!-- uncoded:docs:end -->
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: 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)."
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).'
4
4
  ---
5
5
 
6
6
  # Coherence Review
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: uncoded-doc-navigation
3
+ description: Use before searching or reading a codebase's Markdown documentation indexed by uncoded — locating which file and section cover a topic, or orienting to what documentation exists.
4
+ ---
5
+
6
+ # Documentation Navigation
7
+
8
+ This codebase uses [uncoded](https://github.com/alimanfoo/uncoded) to maintain
9
+ a documentation index.
10
+
11
+ **Step 1 — Orient. Read the docs map first.** Before answering the
12
+ user, before any other tool call:
13
+
14
+ ```text
15
+ Read .uncoded/docs.yaml
16
+ ```
17
+
18
+ `.uncoded/docs.yaml` is an orientation outline: it lists every Markdown file
19
+ and its heading hierarchy. Read it in full, now.
20
+
21
+ **Step 2 — Navigate.** Headings in the map are literal text. Use `Read` or
22
+ `grep` to navigate to a specific section identified in the map.
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: uncoded-code-navigation
3
+ description: Use before searching, reading, or editing Python source in a codebase indexed by uncoded — locating a symbol, reading a definition, or checking references before a refactor, rename, or delete.
4
+ ---
5
+
6
+ # Code Navigation
7
+
8
+ This codebase uses [uncoded](https://github.com/alimanfoo/uncoded) to maintain
9
+ a symbol index over its source code, with two associated CLI tools:
10
+ `uncoded body` for reading a symbol's body and `uncoded refs` for finding
11
+ references.
12
+
13
+ ## The dispatch rule
14
+
15
+ **If your search term is the name of a Python symbol — a class, function,
16
+ method, attribute, or module-level constant — use the index. If it's a
17
+ pattern, regex, or free-text phrase, use grep.**
18
+
19
+ This applies to every tool call where you are trying to find code, not
20
+ just the first one in the session. The pretrained reflex for "find X" is
21
+ grep, and that reflex is wrong here. `grep -rn 'def parse_config'` to read a
22
+ function's body applies the rule — that is `uncoded body`. `grep -rn
23
+ 'validate_input'` to check whether something has callers before a refactor is
24
+ another case — that is `uncoded refs`. `grep` then `Edit` to delete
25
+ dead code fits the same pattern — that is `uncoded refs` to confirm dead,
26
+ then `Edit`. The grep version of any of these is noisier and less reliable:
27
+ grep matches comments, strings, and unrelated attributes; grep misses
28
+ re-exports (so caller and delete checks come back incomplete); grep forces
29
+ offset 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
34
+ steps (orient, understand, act).
35
+
36
+ **Step 1 — Orient. Read the namespace map first.** Before answering the
37
+ user, 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,
44
+ methods, functions. Without it loaded, "find X" answers come from
45
+ pretrained guesses about what a project like this *probably* contains,
46
+ rather than what is actually here. Read it once, in full, at session
47
+ start.
48
+
49
+ **Step 2 — Understand. Read the `.pyi` stub before any `.py` source.**
50
+ Stub paths mirror source paths under `.uncoded/stubs/`:
51
+
52
+ ```text
53
+ src/foo/bar.py → .uncoded/stubs/src/foo/bar.pyi
54
+ tests/test_foo.py → .uncoded/stubs/tests/test_foo.pyi
55
+ ```
56
+
57
+ Every file you intend to touch or reference, including tests. The stub
58
+ contains imports, every signature with types, module-level assignments,
59
+ and class attributes — enough for most navigation. Skipping straight to
60
+ source means reading many lines to learn what the stub would have told
61
+ you in one. If no stub exists at the expected path, the file has no
62
+ symbols indexed; in that narrow case, read source directly.
63
+
64
+ **Step 3 — Act. Use `uncoded body` to read a symbol's body;
65
+ use `uncoded refs` to find every reference to a symbol; use `Edit` (with
66
+ `uncoded body`'s output as `old_string`) to change a symbol.**
67
+ With the map and stub loaded, you have the exact `relative_path` and
68
+ `name_path` each tool needs (`ClassName/method` for a method,
69
+ `function_name` for a top-level function). Per task:
70
+
71
+ - **Read a symbol's body.** `uvx uncoded body <name_path> --in <relative_path>` —
72
+ prints the symbol's source text to stdout, byte-identical to disk.
73
+ Returns exactly the symbol; no offset arithmetic, no risk of reading
74
+ too much. Its output has every byte `Edit` needs as `old_string` — no
75
+ extra `Read` required for partial edits. Stay on stubs for a
76
+ wider sweep.
77
+
78
+ - **Find every reference to a symbol.**
79
+ `uvx uncoded refs <name_path> --in <relative_path>`. Prints one reference
80
+ per line as `file:line:col`, sorted. Grep on the name misses re-exports
81
+ and adds false positives from comments, strings, and attribute lookups
82
+ on other types. If the next move depends on the answer being complete,
83
+ grep cannot give you that.
84
+
85
+ - **Edit a symbol.** `uvx uncoded body <name_path> --in <relative_path>` gives
86
+ the exact `old_string`; then `Edit` to apply the change.
87
+
88
+ - **Rename.** `uvx uncoded refs <name_path> --in <relative_path>` enumerates
89
+ every site; then `Edit` at each.
90
+
91
+ - **Safely delete.** `uvx uncoded refs <name_path> --in <relative_path>` must
92
+ return empty; then `Edit` to remove.
93
+
94
+ ## Where Read, Edit, and grep are still the right tools
95
+
96
+ The rule is about source navigation by symbol name. Outside that, Read,
97
+ Edit, and grep stay correct:
98
+
99
+ - Free-text or pattern search outside source: Markdown, YAML, TOML,
100
+ configs, commit messages, notebook JSON, fixture data.
101
+ - Pattern search across signatures (regex over type annotations,
102
+ decorator usage, alias declarations) — these are not symbol-name
103
+ lookups even though they sit inside source.
104
+ - Partial-line edits inside a symbol body, once you have retrieved it
105
+ via `uncoded body`.
106
+ - The rare stub-less Python file that needs exploratory reading.
107
+
108
+ The dispatch rule turns on the search term: a symbol name → the index; a
109
+ regex or free-text phrase → grep.
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: 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)."
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).'
4
4
  ---
5
5
 
6
6
  # Coherence Review
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: uncoded-doc-navigation
3
+ description: Use before searching or reading a codebase's Markdown documentation indexed by uncoded — locating which file and section cover a topic, or orienting to what documentation exists.
4
+ ---
5
+
6
+ # Documentation Navigation
7
+
8
+ This codebase uses [uncoded](https://github.com/alimanfoo/uncoded) to maintain
9
+ a documentation index.
10
+
11
+ **Step 1 — Orient. Read the docs map first.** Before answering the
12
+ user, before any other tool call:
13
+
14
+ ```text
15
+ Read .uncoded/docs.yaml
16
+ ```
17
+
18
+ `.uncoded/docs.yaml` is an orientation outline: it lists every Markdown file
19
+ and its heading hierarchy. Read it in full, now.
20
+
21
+ **Step 2 — Navigate.** Headings in the map are literal text. Use `Read` or
22
+ `grep` to navigate to a specific section identified in the map.
@@ -21,14 +21,11 @@ README.md:
21
21
  Coherence review:
22
22
  Dev setup:
23
23
  Note for Windows contributors:
24
+ Upgrading from v1:
24
25
  Releasing:
25
26
  AGENTS.md:
26
27
  uncoded:
27
28
  Problem:
28
29
  Approach:
29
30
  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:
31
+ Skills:
@@ -18,6 +18,7 @@ src/:
18
18
  resolve_body:
19
19
  _extract_body:
20
20
  cli.py:
21
+ _sync_code_artefacts:
21
22
  _sync:
22
23
  _body:
23
24
  _refs:
@@ -28,7 +29,6 @@ src/:
28
29
  project_root:
29
30
  source_roots:
30
31
  doc_roots:
31
- instruction_files:
32
32
  find_pyproject_toml:
33
33
  _has_uncoded_section:
34
34
  _find_config_file:
@@ -54,24 +54,12 @@ src/:
54
54
  extract_module:
55
55
  iter_source_files:
56
56
  extract_modules:
57
- instruction_files.py:
58
- MARKER_END:
59
- MARKER_DOCS_END:
60
- MARKER_START_PREFIX:
61
- MARKER_DOCS_START_PREFIX:
62
- DEFAULT_INSTRUCTION_FILES:
63
- _CODE_SECTION_BODY:
64
- MARKER_START:
65
- SECTION_CODE:
66
- _DOCS_SECTION_BODY:
67
- MARKER_DOCS_START:
68
- SECTION_DOCS:
69
- _apply_section:
70
- sync_instruction_file:
71
57
  namespace_map.py:
72
58
  HEADER:
73
59
  build_map:
74
60
  render_map:
61
+ read_helpers.py:
62
+ _read_file_text_as_utf8:
75
63
  refs.py:
76
64
  TY_VERSION:
77
65
  Reference:
@@ -105,10 +93,18 @@ src/:
105
93
  resolve_ast_node_from_source:
106
94
  _resolve_class_member:
107
95
  skill.py:
108
- SKILL_OUTPUTS:
109
- LEGACY_SKILL_OUTPUTS:
110
- _SKILL_CONTENT:
111
- sync_skill:
96
+ SKILL_ROOTS:
97
+ SKILLS:
98
+ Skill:
99
+ name:
100
+ description:
101
+ body_file:
102
+ gate:
103
+ legacy_names:
104
+ _skill_path:
105
+ _render_content:
106
+ _remove_skill_file:
107
+ sync_skills:
112
108
  stubs.py:
113
109
  VALUE_WIDTH_CAP:
114
110
  StubParam:
@@ -211,11 +207,12 @@ tests/:
211
207
  TestResolveBodyByteIdentical:
212
208
  test_exact_source_returned:
213
209
  test_cli.py:
210
+ _CODE_SKILL_PATHS:
211
+ _DOC_SKILL_PATHS:
212
+ _SKILL_PATHS:
214
213
  TestSyncApplyMode:
215
- test_writes_namespace_map_stubs_and_instruction_file:
214
+ test_writes_namespace_map_stubs_and_skills:
216
215
  test_idempotent_second_run:
217
- test_dedupes_when_claude_md_is_symlink_to_agents_md:
218
- test_error_when_instruction_file_outside_project_root:
219
216
  test_error_when_no_config_file:
220
217
  test_error_when_source_root_outside_project_root:
221
218
  test_error_when_source_root_missing:
@@ -229,8 +226,6 @@ tests/:
229
226
  test_returns_zero_when_index_is_up_to_date:
230
227
  test_returns_one_when_source_changes_after_sync:
231
228
  test_returns_one_when_source_file_deleted:
232
- test_returns_one_when_instruction_file_drifts:
233
- test_dedupes_when_claude_md_is_symlink_to_agents_md:
234
229
  test_error_still_returns_one:
235
230
  TestMainDispatch:
236
231
  test_sync_subcommand_runs_in_apply_mode:
@@ -266,13 +261,11 @@ tests/:
266
261
  test_doc_only_writes_docs_yaml:
267
262
  test_doc_only_does_not_write_namespace_yaml:
268
263
  test_doc_only_does_not_write_stubs:
269
- test_doc_only_instruction_file_has_docs_section_only:
270
264
  test_doc_root_single_md_file:
271
265
  test_error_when_doc_root_missing:
272
266
  test_error_when_doc_root_is_non_md_file:
273
267
  test_error_when_doc_root_outside_project_root:
274
268
  test_both_roots_writes_both_artefacts:
275
- test_both_roots_instruction_file_has_both_sections:
276
269
  test_source_root_removal_cleans_code_artefacts:
277
270
  test_doc_root_removal_cleans_docs_yaml:
278
271
  test_check_returns_one_when_docs_yaml_stale:
@@ -296,9 +289,6 @@ tests/:
296
289
  test_both_roots_empty_when_section_absent:
297
290
  test_project_root_is_config_file_parent:
298
291
  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
292
  test_returns_frozen_dataclass:
303
293
  test_finds_uncoded_toml_when_no_pyproject:
304
294
  test_uncoded_toml_top_level_keys:
@@ -341,6 +331,8 @@ tests/:
341
331
  test_directory_nested_subdir:
342
332
  test_empty_directory_yields_nothing:
343
333
  test_rel_path_is_relative_to_project_root:
334
+ test_skips_non_utf8_file_with_warning:
335
+ test_single_file_root_unreadable_yields_nothing:
344
336
  TestBuildDocsMap:
345
337
  test_empty_files:
346
338
  test_headingless_file_maps_to_null:
@@ -381,51 +373,12 @@ tests/:
381
373
  test_includes_init_with_symbols:
382
374
  test_skips_empty_init:
383
375
  test_skips_syntax_errors:
376
+ test_skips_non_utf8_file_with_warning:
384
377
  TestExtractModulesFromFiles:
385
378
  test_returns_module_info_per_parseable_file:
386
379
  test_preserves_source_order:
387
380
  test_module_with_only_constants_is_kept:
388
381
  test_skips_files_with_no_symbols:
389
- test_instruction_files.py:
390
- TestCodeSection:
391
- test_contains_markers:
392
- test_markers_in_order:
393
- test_ends_with_newline:
394
- TestDocsSection:
395
- test_contains_markers:
396
- test_markers_in_order:
397
- test_ends_with_newline:
398
- test_mentions_docs_yaml:
399
- TestSyncInstructionFile:
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:
411
- test_idempotent:
412
- test_returns_true_on_first_write:
413
- test_returns_false_when_clean:
414
- test_returns_false_when_both_none_and_file_absent:
415
- test_remove_section_absent_is_noop:
416
- TestSyncInstructionFileCheckMode:
417
- test_does_not_create_file:
418
- test_does_not_update_existing_file:
419
- test_reports_no_change_when_clean:
420
- TestSyncInstructionFileProjectRootAnchor:
421
- test_project_root_anchors_create_independent_of_cwd:
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:
429
382
  test_namespace_map.py:
430
383
  TestBuildMap:
431
384
  test_single_file:
@@ -475,19 +428,35 @@ tests/:
475
428
  _init_response:
476
429
  _shutdown_response:
477
430
  test_skill.py:
478
- TestSyncSkill:
479
- test_skill_name_and_output_paths:
431
+ TestSyncSkills:
432
+ test_skill_registry:
480
433
  test_writes_skill_files:
481
434
  test_creates_parent_directories:
482
- test_returns_true_on_first_write:
483
- test_returns_false_when_already_in_sync:
435
+ test_content_has_frontmatter_and_body:
436
+ test_renderer_supplies_separator_for_body_without_leading_newline:
437
+ test_returns_change_count_on_first_write:
438
+ test_returns_zero_when_already_in_sync:
484
439
  test_idempotent:
485
440
  test_check_mode_does_not_write:
486
441
  test_check_mode_reports_change_when_missing:
487
442
  test_check_mode_reports_no_change_when_in_sync:
488
443
  test_removes_legacy_skill_files:
489
- test_check_mode_reports_legacy_skill_files_without_removing:
490
- TestSyncSkillProjectRootAnchor:
444
+ test_check_mode_reports_legacy_files_without_removing:
445
+ test_source_false_removes_existing_skill_files:
446
+ test_source_false_returns_zero_when_already_absent:
447
+ test_source_false_check_mode_reports_without_removing:
448
+ test_gate_unmet_removal_prunes_empty_directory:
449
+ test_legacy_removal_prunes_empty_directory:
450
+ test_pruning_skipped_when_directory_has_other_files:
451
+ test_docs_gate_builds_when_docs_true:
452
+ test_docs_gate_skips_when_docs_false:
453
+ test_no_legacy_names_skips_legacy_removal:
454
+ test_code_navigation_written_when_source_set:
455
+ test_code_navigation_not_written_when_source_absent:
456
+ test_doc_navigation_written_when_docs_set:
457
+ test_doc_navigation_not_written_when_docs_absent:
458
+ test_doc_navigation_content_includes_orient_step:
459
+ TestSyncSkillsProjectRootAnchor:
491
460
  test_project_root_anchors_writes_independent_of_cwd:
492
461
  test_project_root_anchors_legacy_removal_independent_of_cwd:
493
462
  test_stubs.py:
@@ -7,14 +7,16 @@ from uncoded.body import resolve_body
7
7
  from uncoded.config import ConfigError, read_config
8
8
  from uncoded.docs_map import build_docs_map, iter_doc_files, render_docs_map
9
9
  from uncoded.extract import extract_modules, iter_source_files
10
- from uncoded.instruction_files import SECTION_CODE, SECTION_DOCS, sync_instruction_file
11
10
  from uncoded.namespace_map import build_map, render_map
12
11
  from uncoded.refs import find_refs
13
12
  from uncoded.resolver import NamePath, SymbolNotFound, UnsupportedNamePath
14
- from uncoded.skill import sync_skill
13
+ from uncoded.skill import sync_skills
15
14
  from uncoded.stubs import build_stubs, remove_all_stubs
16
15
  from uncoded.sync import remove_file, sync_file
17
16
 
17
+ def _sync_code_artefacts(*, build: bool, configured_source_roots: list[Path], project_root: Path, resolved_project_root: Path, check: bool) -> int | None:
18
+ ...
19
+
18
20
  def _sync(*, start: Path | None, check: bool) -> int:
19
21
  ...
20
22
 
@@ -3,7 +3,6 @@
3
3
  import tomllib
4
4
  from dataclasses import dataclass
5
5
  from pathlib import Path
6
- from uncoded.instruction_files import DEFAULT_INSTRUCTION_FILES
7
6
 
8
7
  def find_pyproject_toml(start: Path) -> Path | None:
9
8
  ...
@@ -24,4 +23,3 @@ class Config:
24
23
  project_root: Path
25
24
  source_roots: list[Path]
26
25
  doc_roots: list[Path]
27
- instruction_files: list[Path]
@@ -2,6 +2,7 @@
2
2
 
3
3
  from collections.abc import Iterable, Iterator
4
4
  from pathlib import Path
5
+ from uncoded.read_helpers import _read_file_text_as_utf8
5
6
  from uncoded.yaml_tree import render_yaml_tree
6
7
 
7
8
  DOCS_HEADER = ...
@@ -6,6 +6,7 @@ from collections.abc import Iterable, Iterator
6
6
  from dataclasses import dataclass, field
7
7
  from pathlib import Path
8
8
  from uncoded.ast_helpers import assign_target_name, property_kind
9
+ from uncoded.read_helpers import _read_file_text_as_utf8
9
10
 
10
11
  def extract_module(source: str, rel_path: str) -> ModuleInfo:
11
12
  ...
@@ -0,0 +1,7 @@
1
+ # src/uncoded/read_helpers.py
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ def _read_file_text_as_utf8(path: Path, *, display: Path | str | None) -> str | None:
7
+ ...
@@ -0,0 +1,30 @@
1
+ # src/uncoded/skill.py
2
+
3
+ from dataclasses import dataclass
4
+ from importlib.resources import files
5
+ from pathlib import Path
6
+ from typing import Literal
7
+ import yaml
8
+ from uncoded.sync import remove_file, sync_file
9
+
10
+ SKILL_ROOTS = [Path('.claude/skills'), Path('.agents/skills')]
11
+ SKILLS: list[Skill] = ...
12
+
13
+ def _skill_path(root: Path, name: str) -> Path:
14
+ ...
15
+
16
+ def _render_content(*, skill: Skill) -> str:
17
+ ...
18
+
19
+ def _remove_skill_file(*, path: Path, project_root: Path, check: bool) -> bool:
20
+ ...
21
+
22
+ def sync_skills(*, source: bool, docs: bool, project_root: Path, check: bool) -> int:
23
+ ...
24
+
25
+ class Skill:
26
+ name: str
27
+ description: str
28
+ body_file: str
29
+ gate: Literal['code', 'docs']
30
+ legacy_names: tuple[str, ...] = ()