uncoded 1.1.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.1.0/AGENTS.md → uncoded-2.0.0/.agents/skills/uncoded-code-navigation/SKILL.md +15 -86
  2. {uncoded-1.1.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.1.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.1.0 → uncoded-2.0.0}/.uncoded/docs.yaml +2 -5
  8. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/namespace.yaml +47 -67
  9. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/cli.pyi +4 -2
  10. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/config.pyi +0 -2
  11. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/docs_map.pyi +1 -0
  12. {uncoded-1.1.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.1.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_cli.pyi +15 -20
  16. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_config.pyi +0 -10
  17. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_docs_map.pyi +6 -0
  18. {uncoded-1.1.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.1.0 → uncoded-2.0.0}/PKG-INFO +83 -20
  22. {uncoded-1.1.0 → uncoded-2.0.0}/README.md +81 -19
  23. {uncoded-1.1.0 → uncoded-2.0.0}/pyproject.toml +1 -0
  24. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/cli.py +86 -92
  25. uncoded-1.1.0/src/uncoded/dispatch_rule.md → uncoded-2.0.0/src/uncoded/code_navigation.md +11 -12
  26. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/coherence_review.md +0 -5
  27. {uncoded-1.1.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.1.0 → uncoded-2.0.0}/src/uncoded/docs_map.py +13 -2
  30. {uncoded-1.1.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.1.0 → uncoded-2.0.0}/src/uncoded/sync.py +2 -2
  34. {uncoded-1.1.0 → uncoded-2.0.0}/tests/test_cli.py +56 -150
  35. {uncoded-1.1.0 → uncoded-2.0.0}/tests/test_config.py +2 -35
  36. {uncoded-1.1.0 → uncoded-2.0.0}/tests/test_docs_map.py +21 -0
  37. {uncoded-1.1.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.1.0 → uncoded-2.0.0}/uv.lock +23 -0
  40. uncoded-1.1.0/.uncoded/stubs/src/uncoded/instruction_files.pyi +0 -21
  41. uncoded-1.1.0/.uncoded/stubs/src/uncoded/skill.pyi +0 -12
  42. uncoded-1.1.0/.uncoded/stubs/tests/test_instruction_files.pyi +0 -93
  43. uncoded-1.1.0/.uncoded/stubs/tests/test_skill.pyi +0 -45
  44. uncoded-1.1.0/src/uncoded/docs_rule.md +0 -5
  45. uncoded-1.1.0/src/uncoded/instruction_files.py +0 -91
  46. uncoded-1.1.0/src/uncoded/skill.py +0 -36
  47. uncoded-1.1.0/tests/test_instruction_files.py +0 -297
  48. uncoded-1.1.0/tests/test_skill.py +0 -112
  49. {uncoded-1.1.0 → uncoded-2.0.0}/.github/workflows/ci.yml +0 -0
  50. {uncoded-1.1.0 → uncoded-2.0.0}/.github/workflows/publish.yml +0 -0
  51. {uncoded-1.1.0 → uncoded-2.0.0}/.gitignore +0 -0
  52. {uncoded-1.1.0 → uncoded-2.0.0}/.markdownlint-cli2.yaml +0 -0
  53. {uncoded-1.1.0 → uncoded-2.0.0}/.pre-commit-config.yaml +0 -0
  54. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/__init__.pyi +0 -0
  55. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/ast_helpers.pyi +0 -0
  56. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/body.pyi +0 -0
  57. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/namespace_map.pyi +0 -0
  58. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/refs.pyi +0 -0
  59. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/resolver.pyi +0 -0
  60. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/stubs.pyi +0 -0
  61. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/sync.pyi +0 -0
  62. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/src/uncoded/yaml_tree.pyi +0 -0
  63. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_body.pyi +0 -0
  64. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_namespace_map.pyi +0 -0
  65. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_refs.pyi +0 -0
  66. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_stubs.pyi +0 -0
  67. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_sync.pyi +0 -0
  68. {uncoded-1.1.0 → uncoded-2.0.0}/.uncoded/stubs/tests/test_uncoded.pyi +0 -0
  69. {uncoded-1.1.0 → uncoded-2.0.0}/CLAUDE.md +0 -0
  70. {uncoded-1.1.0 → uncoded-2.0.0}/LICENSE +0 -0
  71. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/__init__.py +0 -0
  72. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/ast_helpers.py +0 -0
  73. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/body.py +0 -0
  74. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/namespace_map.py +0 -0
  75. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/refs.py +0 -0
  76. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/resolver.py +0 -0
  77. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/stubs.py +0 -0
  78. {uncoded-1.1.0 → uncoded-2.0.0}/src/uncoded/yaml_tree.py +0 -0
  79. {uncoded-1.1.0 → uncoded-2.0.0}/tests/__init__.py +0 -0
  80. {uncoded-1.1.0 → uncoded-2.0.0}/tests/test_body.py +0 -0
  81. {uncoded-1.1.0 → uncoded-2.0.0}/tests/test_namespace_map.py +0 -0
  82. {uncoded-1.1.0 → uncoded-2.0.0}/tests/test_refs.py +0 -0
  83. {uncoded-1.1.0 → uncoded-2.0.0}/tests/test_stubs.py +0 -0
  84. {uncoded-1.1.0 → uncoded-2.0.0}/tests/test_sync.py +0 -0
  85. {uncoded-1.1.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 -->
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 -->
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,22 +54,12 @@ src/:
54
54
  extract_module:
55
55
  iter_source_files:
56
56
  extract_modules:
57
- instruction_files.py:
58
- MARKER_START:
59
- MARKER_END:
60
- MARKER_DOCS_START:
61
- MARKER_DOCS_END:
62
- DEFAULT_INSTRUCTION_FILES:
63
- _CODE_SECTION_BODY:
64
- SECTION_CODE:
65
- _DOCS_SECTION_BODY:
66
- SECTION_DOCS:
67
- _apply_section:
68
- sync_instruction_file:
69
57
  namespace_map.py:
70
58
  HEADER:
71
59
  build_map:
72
60
  render_map:
61
+ read_helpers.py:
62
+ _read_file_text_as_utf8:
73
63
  refs.py:
74
64
  TY_VERSION:
75
65
  Reference:
@@ -103,10 +93,18 @@ src/:
103
93
  resolve_ast_node_from_source:
104
94
  _resolve_class_member:
105
95
  skill.py:
106
- SKILL_OUTPUTS:
107
- LEGACY_SKILL_OUTPUTS:
108
- _SKILL_CONTENT:
109
- 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:
110
108
  stubs.py:
111
109
  VALUE_WIDTH_CAP:
112
110
  StubParam:
@@ -209,11 +207,12 @@ tests/:
209
207
  TestResolveBodyByteIdentical:
210
208
  test_exact_source_returned:
211
209
  test_cli.py:
210
+ _CODE_SKILL_PATHS:
211
+ _DOC_SKILL_PATHS:
212
+ _SKILL_PATHS:
212
213
  TestSyncApplyMode:
213
- test_writes_namespace_map_stubs_and_instruction_file:
214
+ test_writes_namespace_map_stubs_and_skills:
214
215
  test_idempotent_second_run:
215
- test_dedupes_when_claude_md_is_symlink_to_agents_md:
216
- test_error_when_instruction_file_outside_project_root:
217
216
  test_error_when_no_config_file:
218
217
  test_error_when_source_root_outside_project_root:
219
218
  test_error_when_source_root_missing:
@@ -227,8 +226,6 @@ tests/:
227
226
  test_returns_zero_when_index_is_up_to_date:
228
227
  test_returns_one_when_source_changes_after_sync:
229
228
  test_returns_one_when_source_file_deleted:
230
- test_returns_one_when_instruction_file_drifts:
231
- test_dedupes_when_claude_md_is_symlink_to_agents_md:
232
229
  test_error_still_returns_one:
233
230
  TestMainDispatch:
234
231
  test_sync_subcommand_runs_in_apply_mode:
@@ -264,18 +261,19 @@ tests/:
264
261
  test_doc_only_writes_docs_yaml:
265
262
  test_doc_only_does_not_write_namespace_yaml:
266
263
  test_doc_only_does_not_write_stubs:
267
- test_doc_only_instruction_file_has_docs_section_only:
268
264
  test_doc_root_single_md_file:
269
265
  test_error_when_doc_root_missing:
270
266
  test_error_when_doc_root_is_non_md_file:
271
267
  test_error_when_doc_root_outside_project_root:
272
268
  test_both_roots_writes_both_artefacts:
273
- test_both_roots_instruction_file_has_both_sections:
274
269
  test_source_root_removal_cleans_code_artefacts:
275
270
  test_doc_root_removal_cleans_docs_yaml:
276
271
  test_check_returns_one_when_docs_yaml_stale:
277
272
  test_check_returns_one_when_stubs_should_be_removed:
278
273
  test_check_returns_one_when_docs_yaml_should_be_removed:
274
+ test_doc_only_does_not_write_skill:
275
+ test_doc_only_removes_preexisting_skill:
276
+ test_check_returns_one_when_skill_should_be_removed:
279
277
  test_idempotent_doc_only:
280
278
  _init_repo:
281
279
  _init_doc_repo:
@@ -291,9 +289,6 @@ tests/:
291
289
  test_both_roots_empty_when_section_absent:
292
290
  test_project_root_is_config_file_parent:
293
291
  test_finds_pyproject_in_parent_directory:
294
- test_instruction_files_default_when_key_absent:
295
- test_instruction_files_configured:
296
- test_instruction_files_empty_list_respected:
297
292
  test_returns_frozen_dataclass:
298
293
  test_finds_uncoded_toml_when_no_pyproject:
299
294
  test_uncoded_toml_top_level_keys:
@@ -336,6 +331,8 @@ tests/:
336
331
  test_directory_nested_subdir:
337
332
  test_empty_directory_yields_nothing:
338
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:
339
336
  TestBuildDocsMap:
340
337
  test_empty_files:
341
338
  test_headingless_file_maps_to_null:
@@ -376,45 +373,12 @@ tests/:
376
373
  test_includes_init_with_symbols:
377
374
  test_skips_empty_init:
378
375
  test_skips_syntax_errors:
376
+ test_skips_non_utf8_file_with_warning:
379
377
  TestExtractModulesFromFiles:
380
378
  test_returns_module_info_per_parseable_file:
381
379
  test_preserves_source_order:
382
380
  test_module_with_only_constants_is_kept:
383
381
  test_skips_files_with_no_symbols:
384
- test_instruction_files.py:
385
- TestCodeSection:
386
- test_contains_markers:
387
- test_markers_in_order:
388
- test_ends_with_newline:
389
- TestDocsSection:
390
- test_contains_markers:
391
- test_markers_in_order:
392
- test_ends_with_newline:
393
- test_mentions_docs_yaml:
394
- TestSyncInstructionFile:
395
- test_creates_file_with_code_section:
396
- test_appends_code_section_to_existing_file:
397
- test_replaces_existing_code_section:
398
- test_preserves_content_after_code_section:
399
- test_removes_code_section_on_none:
400
- test_appends_docs_section:
401
- test_removes_docs_section_on_none:
402
- test_both_sections_appended_in_order:
403
- test_both_sections_replaced_independently:
404
- test_code_only_does_not_write_docs_markers:
405
- test_docs_only_does_not_write_code_markers:
406
- test_idempotent:
407
- test_returns_true_on_first_write:
408
- test_returns_false_when_clean:
409
- test_returns_false_when_both_none_and_file_absent:
410
- test_remove_section_absent_is_noop:
411
- TestSyncInstructionFileCheckMode:
412
- test_does_not_create_file:
413
- test_does_not_update_existing_file:
414
- test_reports_no_change_when_clean:
415
- TestSyncInstructionFileProjectRootAnchor:
416
- test_project_root_anchors_create_independent_of_cwd:
417
- test_project_root_anchors_update_of_existing_file:
418
382
  test_namespace_map.py:
419
383
  TestBuildMap:
420
384
  test_single_file:
@@ -464,19 +428,35 @@ tests/:
464
428
  _init_response:
465
429
  _shutdown_response:
466
430
  test_skill.py:
467
- TestSyncSkill:
468
- test_skill_name_and_output_paths:
431
+ TestSyncSkills:
432
+ test_skill_registry:
469
433
  test_writes_skill_files:
470
434
  test_creates_parent_directories:
471
- test_returns_true_on_first_write:
472
- 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:
473
439
  test_idempotent:
474
440
  test_check_mode_does_not_write:
475
441
  test_check_mode_reports_change_when_missing:
476
442
  test_check_mode_reports_no_change_when_in_sync:
477
443
  test_removes_legacy_skill_files:
478
- test_check_mode_reports_legacy_skill_files_without_removing:
479
- 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:
480
460
  test_project_root_anchors_writes_independent_of_cwd:
481
461
  test_project_root_anchors_legacy_removal_independent_of_cwd:
482
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, ...] = ()