uncoded 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uncoded/__init__.py +5 -0
- uncoded/cli.py +103 -0
- uncoded/config.py +62 -0
- uncoded/extract.py +137 -0
- uncoded/instruction_files.py +133 -0
- uncoded/namespace_map.py +84 -0
- uncoded/serena_setup.py +203 -0
- uncoded/skill.py +388 -0
- uncoded/stubs.py +389 -0
- uncoded/sync.py +50 -0
- uncoded-0.5.0.dist-info/METADATA +226 -0
- uncoded-0.5.0.dist-info/RECORD +15 -0
- uncoded-0.5.0.dist-info/WHEEL +4 -0
- uncoded-0.5.0.dist-info/entry_points.txt +2 -0
- uncoded-0.5.0.dist-info/licenses/LICENSE +21 -0
uncoded/serena_setup.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Generate configuration files for Serena + ty LSP integration.
|
|
2
|
+
|
|
3
|
+
Writes three files that wire a repo up to Serena's MCP bridge with ty as
|
|
4
|
+
the Python language-server backend, in the shape Claude Code picks up
|
|
5
|
+
automatically:
|
|
6
|
+
|
|
7
|
+
* ``.mcp.json`` — registers the Serena MCP server so Claude Code launches
|
|
8
|
+
it via ``uvx`` on session start, with the web dashboard disabled.
|
|
9
|
+
* ``.serena/project.yml`` — selects ty over Serena's default backend
|
|
10
|
+
(pyright), keeps Serena out of uncoded's generated stubs, and narrows
|
|
11
|
+
Serena's surface to pure LSP operations: memory, onboarding,
|
|
12
|
+
dashboard, and shell-exec tools are all excluded. uncoded's namespace
|
|
13
|
+
map and stubs already give agents a project-wide view, so Serena's
|
|
14
|
+
memory-based project understanding is redundant and noisy alongside
|
|
15
|
+
it.
|
|
16
|
+
* ``.claude/settings.json`` — enables the Serena server and allowlists
|
|
17
|
+
the eight LSP tools (symbol lookup, reference search, and the edit
|
|
18
|
+
family) so they run without a prompt.
|
|
19
|
+
|
|
20
|
+
JSON files merge into existing content: pre-existing non-Serena MCP
|
|
21
|
+
servers and permissions are preserved, while the Serena entry itself
|
|
22
|
+
refreshes to the current ``SERENA_VERSION`` so re-running after a bump
|
|
23
|
+
propagates the pin. The YAML project file is only written when absent,
|
|
24
|
+
to avoid clobbering hand-edited Serena config.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import tomllib
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
from uncoded.config import find_pyproject_toml
|
|
32
|
+
|
|
33
|
+
# Pin the Serena version so every repo that runs setup-serena gets the
|
|
34
|
+
# same, tested integration. On bump, re-run `uncoded setup-serena` to
|
|
35
|
+
# refresh the pin in existing repos — the sync overwrites the serena
|
|
36
|
+
# entry in .mcp.json with the current MCP_SERVER_SERENA value. A
|
|
37
|
+
# dogfooding test in tests/test_serena_setup.py guards against drift.
|
|
38
|
+
SERENA_VERSION = "1.1.2"
|
|
39
|
+
|
|
40
|
+
MCP_SERVER_SERENA = {
|
|
41
|
+
"command": "uvx",
|
|
42
|
+
"args": [
|
|
43
|
+
"--from",
|
|
44
|
+
f"serena-agent=={SERENA_VERSION}",
|
|
45
|
+
"serena",
|
|
46
|
+
"start-mcp-server",
|
|
47
|
+
"--context",
|
|
48
|
+
"claude-code",
|
|
49
|
+
"--transport",
|
|
50
|
+
"stdio",
|
|
51
|
+
"--project-from-cwd",
|
|
52
|
+
"--open-web-dashboard",
|
|
53
|
+
"false",
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
SERENA_PROJECT_YML = """\
|
|
58
|
+
project_name: "{project_name}"
|
|
59
|
+
languages: ["python_ty"]
|
|
60
|
+
ignored_paths:
|
|
61
|
+
- ".uncoded"
|
|
62
|
+
excluded_tools:
|
|
63
|
+
- execute_shell_command
|
|
64
|
+
- list_memories
|
|
65
|
+
- read_memory
|
|
66
|
+
- write_memory
|
|
67
|
+
- edit_memory
|
|
68
|
+
- delete_memory
|
|
69
|
+
- rename_memory
|
|
70
|
+
- onboarding
|
|
71
|
+
- check_onboarding_performed
|
|
72
|
+
- open_dashboard
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
SERENA_ALLOWED_TOOLS = [
|
|
76
|
+
"mcp__serena__initial_instructions",
|
|
77
|
+
"mcp__serena__find_symbol",
|
|
78
|
+
"mcp__serena__find_referencing_symbols",
|
|
79
|
+
"mcp__serena__get_symbols_overview",
|
|
80
|
+
"mcp__serena__rename_symbol",
|
|
81
|
+
"mcp__serena__safe_delete_symbol",
|
|
82
|
+
"mcp__serena__insert_before_symbol",
|
|
83
|
+
"mcp__serena__insert_after_symbol",
|
|
84
|
+
"mcp__serena__replace_symbol_body",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
_STATUS_VERB = {
|
|
88
|
+
"wrote": "Wrote",
|
|
89
|
+
"updated": "Updated",
|
|
90
|
+
"unchanged": "Unchanged",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def read_project_name() -> str:
|
|
95
|
+
"""Read the project name from pyproject.toml, falling back to the cwd name."""
|
|
96
|
+
toml_path = find_pyproject_toml()
|
|
97
|
+
if toml_path is None:
|
|
98
|
+
return Path.cwd().name
|
|
99
|
+
with toml_path.open("rb") as f:
|
|
100
|
+
data = tomllib.load(f)
|
|
101
|
+
try:
|
|
102
|
+
return data["project"]["name"]
|
|
103
|
+
except KeyError:
|
|
104
|
+
return Path.cwd().name
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _sync_mcp_json(path: Path) -> str:
|
|
108
|
+
"""Write or merge Serena into ``.mcp.json``.
|
|
109
|
+
|
|
110
|
+
Non-Serena MCP servers already in the file are preserved. The
|
|
111
|
+
``serena`` entry itself is always refreshed to ``MCP_SERVER_SERENA``
|
|
112
|
+
so a ``SERENA_VERSION`` bump flows into existing repos on the next
|
|
113
|
+
``setup-serena`` run. Anyone who has hand-customised the ``serena``
|
|
114
|
+
entry should keep their edits out of this file (add a sibling entry
|
|
115
|
+
instead, or re-apply after refresh).
|
|
116
|
+
|
|
117
|
+
Returns a one-word status: ``wrote``, ``updated``, or ``unchanged``.
|
|
118
|
+
"""
|
|
119
|
+
if path.exists():
|
|
120
|
+
data = json.loads(path.read_text())
|
|
121
|
+
servers = data.setdefault("mcpServers", {})
|
|
122
|
+
if servers.get("serena") == MCP_SERVER_SERENA:
|
|
123
|
+
return "unchanged"
|
|
124
|
+
servers["serena"] = MCP_SERVER_SERENA
|
|
125
|
+
status = "updated"
|
|
126
|
+
else:
|
|
127
|
+
data = {"mcpServers": {"serena": MCP_SERVER_SERENA}}
|
|
128
|
+
status = "wrote"
|
|
129
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
130
|
+
path.write_text(json.dumps(data, indent=2) + "\n")
|
|
131
|
+
return status
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _sync_serena_project(path: Path, project_name: str) -> str:
|
|
135
|
+
"""Write ``.serena/project.yml`` if absent.
|
|
136
|
+
|
|
137
|
+
Returns ``wrote`` or ``unchanged``. An existing file is never touched:
|
|
138
|
+
YAML merging preserves neither comments nor key order, and a user who
|
|
139
|
+
has customised their Serena config should keep it.
|
|
140
|
+
"""
|
|
141
|
+
if path.exists():
|
|
142
|
+
return "unchanged"
|
|
143
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
path.write_text(SERENA_PROJECT_YML.format(project_name=project_name))
|
|
145
|
+
return "wrote"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _sync_claude_settings(path: Path) -> str:
|
|
149
|
+
"""Write or merge Serena allowlist into ``.claude/settings.json``.
|
|
150
|
+
|
|
151
|
+
Returns ``wrote``, ``updated``, or ``unchanged``.
|
|
152
|
+
"""
|
|
153
|
+
if path.exists():
|
|
154
|
+
data = json.loads(path.read_text())
|
|
155
|
+
status = "unchanged"
|
|
156
|
+
else:
|
|
157
|
+
data = {}
|
|
158
|
+
status = "wrote"
|
|
159
|
+
|
|
160
|
+
enabled = data.setdefault("enabledMcpjsonServers", [])
|
|
161
|
+
if "serena" not in enabled:
|
|
162
|
+
enabled.append("serena")
|
|
163
|
+
if status == "unchanged":
|
|
164
|
+
status = "updated"
|
|
165
|
+
|
|
166
|
+
permissions = data.setdefault("permissions", {})
|
|
167
|
+
allow = permissions.setdefault("allow", [])
|
|
168
|
+
for tool in SERENA_ALLOWED_TOOLS:
|
|
169
|
+
if tool not in allow:
|
|
170
|
+
allow.append(tool)
|
|
171
|
+
if status == "unchanged":
|
|
172
|
+
status = "updated"
|
|
173
|
+
|
|
174
|
+
if status == "unchanged":
|
|
175
|
+
return status
|
|
176
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
177
|
+
path.write_text(json.dumps(data, indent=2) + "\n")
|
|
178
|
+
return status
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def setup_serena(root: Path | None = None) -> int:
|
|
182
|
+
"""Generate Serena + ty + Claude Code configuration under ``root``.
|
|
183
|
+
|
|
184
|
+
JSON files merge into existing content, refreshing the Serena
|
|
185
|
+
entries so a re-run picks up a bumped ``SERENA_VERSION``. The
|
|
186
|
+
Serena YAML project file is only written when absent.
|
|
187
|
+
"""
|
|
188
|
+
if root is None:
|
|
189
|
+
root = Path.cwd()
|
|
190
|
+
project_name = read_project_name()
|
|
191
|
+
|
|
192
|
+
mcp_path = root / ".mcp.json"
|
|
193
|
+
serena_path = root / ".serena" / "project.yml"
|
|
194
|
+
claude_path = root / ".claude" / "settings.json"
|
|
195
|
+
|
|
196
|
+
results = [
|
|
197
|
+
(mcp_path, _sync_mcp_json(mcp_path)),
|
|
198
|
+
(serena_path, _sync_serena_project(serena_path, project_name)),
|
|
199
|
+
(claude_path, _sync_claude_settings(claude_path)),
|
|
200
|
+
]
|
|
201
|
+
for path, status in results:
|
|
202
|
+
print(f"{_STATUS_VERB[status]} {path.relative_to(root)}")
|
|
203
|
+
return 0
|
uncoded/skill.py
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Generate the coherence-review skill file for the target repository."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from uncoded.sync import remove_file, sync_file
|
|
6
|
+
|
|
7
|
+
SKILL_OUTPUTS = [
|
|
8
|
+
Path(".claude/skills/coherence-review/SKILL.md"), # Claude Code
|
|
9
|
+
Path(".agents/skills/coherence-review/SKILL.md"), # Codex
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
LEGACY_SKILL_OUTPUTS = [
|
|
13
|
+
Path(".claude/skills/uncoded-review/SKILL.md"), # Claude Code
|
|
14
|
+
Path(".agents/skills/uncoded-review/SKILL.md"), # Codex
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
_SKILL_CONTENT = """\
|
|
18
|
+
---
|
|
19
|
+
name: coherence-review
|
|
20
|
+
description: "Perform a coherence review of a Python codebase: a diagnostic sweep \
|
|
21
|
+
for semantic drift, naming inconsistency, promissory mismatch, and structural \
|
|
22
|
+
incoherence. Produces a Markdown report of findings with verbatim evidence and \
|
|
23
|
+
confidence levels, for human investigation. Assumes uncoded is installed \
|
|
24
|
+
(.uncoded/namespace.yaml and .uncoded/stubs/ present)."
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Coherence Review
|
|
28
|
+
|
|
29
|
+
A diagnostic sweep of a Python codebase for specific, observable symptoms of
|
|
30
|
+
semantic drift and incoherence. Output is a structured Markdown report of
|
|
31
|
+
candidate regions, each with verbatim evidence and a confidence level, for a
|
|
32
|
+
human to investigate. Not a bug hunt, not a lint pass, not a refactor.
|
|
33
|
+
|
|
34
|
+
## Why coherence, and what you are looking for
|
|
35
|
+
|
|
36
|
+
A codebase accumulates corruption differently from how it accumulates bugs. Bugs
|
|
37
|
+
announce themselves — tests fail, users complain, exceptions raise. Corruption
|
|
38
|
+
passes every integrity check. It is the slow divergence between what the code
|
|
39
|
+
claims to mean and what it actually does, between how one part of the codebase
|
|
40
|
+
names a concept and how another names the same concept, between an architecture
|
|
41
|
+
as declared and an architecture as practised. Each local decision that
|
|
42
|
+
contributed to it was reasonable. The accumulation is not.
|
|
43
|
+
|
|
44
|
+
Corruption's one observable signature is **internal inconsistency**. Not
|
|
45
|
+
absolute wrongness — that requires a reference outside the code, which is not
|
|
46
|
+
available here. Just pairwise disagreement between things that ought to agree:
|
|
47
|
+
a name and its behaviour, two names for the same concept, a docstring and the
|
|
48
|
+
signature it sits on, a declared architecture and the actual import graph. Every
|
|
49
|
+
symptom in the sweeps below is a form of inconsistency. The review's job is to
|
|
50
|
+
find these disagreements — not to diagnose root cause and not to fix them.
|
|
51
|
+
|
|
52
|
+
## What this skill is not
|
|
53
|
+
|
|
54
|
+
- **Not a bug-finder.** Bugs are the job of testing and code review. A coherence
|
|
55
|
+
review can run on code that passes every test and still find plenty.
|
|
56
|
+
- **Not a style pass.** Tabs versus spaces, docstring format, import ordering —
|
|
57
|
+
irrelevant. Linters exist.
|
|
58
|
+
- **Not a refactor.** No proposing fixes, no suggesting renames, no rewriting
|
|
59
|
+
code. The output is findings. The human decides what to do with them.
|
|
60
|
+
- **Not a general code review.** Performance, security, correctness — out of
|
|
61
|
+
scope unless they happen to manifest as a coherence symptom.
|
|
62
|
+
|
|
63
|
+
## Prerequisites
|
|
64
|
+
|
|
65
|
+
Verify by reading `.uncoded/namespace.yaml` — if it exists and is non-empty,
|
|
66
|
+
proceed. If not, stop and tell the user to run `uncoded sync` first; the review
|
|
67
|
+
depends on the index.
|
|
68
|
+
|
|
69
|
+
If Serena MCP tools are available (`mcp__serena__*`), the structural sweep has
|
|
70
|
+
more leverage. The review still works without Serena but will be weaker on
|
|
71
|
+
cross-file reference checks.
|
|
72
|
+
|
|
73
|
+
## Workflow
|
|
74
|
+
|
|
75
|
+
The review proceeds in four sweeps, each building on the previous:
|
|
76
|
+
|
|
77
|
+
1. **Orient** — load the navigation index and form a mental map.
|
|
78
|
+
2. **Lexical sweep** — read the namespace, look for naming-level inconsistency.
|
|
79
|
+
3. **Promissory sweep** — read stubs, check each symbol's name / signature /
|
|
80
|
+
docstring for internal disagreement.
|
|
81
|
+
4. **Structural sweep** — combine namespace and imports to find boundary and
|
|
82
|
+
shape symptoms.
|
|
83
|
+
|
|
84
|
+
Do the sweeps in order. Write findings as you go — do not hold them in memory
|
|
85
|
+
until the end.
|
|
86
|
+
|
|
87
|
+
## Step 1: Orient
|
|
88
|
+
|
|
89
|
+
Read `.uncoded/namespace.yaml` in full. This is the map — directories, files,
|
|
90
|
+
classes, methods, functions — in source order. Do not skim. Every public symbol
|
|
91
|
+
in the codebase is listed here, and the shape of the namespace itself is
|
|
92
|
+
evidence.
|
|
93
|
+
|
|
94
|
+
While reading, note:
|
|
95
|
+
|
|
96
|
+
- The vocabulary the codebase uses for its core concepts
|
|
97
|
+
- The organisational logic (domain-driven? layered? feature-based? ad hoc?)
|
|
98
|
+
- Anything that surprises you — odd names, asymmetric organisation, suspicious
|
|
99
|
+
clusters
|
|
100
|
+
|
|
101
|
+
Also read `CLAUDE.md` if present, and follow any repo-specific navigation
|
|
102
|
+
protocol throughout the review.
|
|
103
|
+
|
|
104
|
+
Before starting the sweeps, tell the user how many public symbols were indexed.
|
|
105
|
+
Then proceed without asking for further confirmation.
|
|
106
|
+
|
|
107
|
+
## Step 2: Lexical sweep
|
|
108
|
+
|
|
109
|
+
Working from the namespace alone, look for four categories of naming-level
|
|
110
|
+
symptom.
|
|
111
|
+
|
|
112
|
+
**Concept duplication under different names.** The same concept referred to by
|
|
113
|
+
different names in different parts of the codebase. Examples: `fetch_user`,
|
|
114
|
+
`get_user`, `load_user`, `retrieve_user` all appearing as separate functions
|
|
115
|
+
doing substantively the same thing. `compute_*`, `calculate_*`, `derive_*` used
|
|
116
|
+
interchangeably. Two classes called `UserRecord` and `AccountProfile` that
|
|
117
|
+
model the same entity.
|
|
118
|
+
|
|
119
|
+
Detection: scan the namespace for symbol clusters with verb or noun overlap.
|
|
120
|
+
Where suspicion arises, spot-check the relevant stubs to confirm they overlap
|
|
121
|
+
in meaning.
|
|
122
|
+
|
|
123
|
+
**Qualifier accretion.** Names carrying modifiers that are fossils of
|
|
124
|
+
iteration: `_new`, `_v2`, `_updated`, `_legacy`, `_real`, `_proper`, `_final`,
|
|
125
|
+
`_fixed`. Also prefix forms: `new_`, `old_`, `real_`. These are almost always
|
|
126
|
+
worth flagging — someone needed to distinguish a new thing from an old thing
|
|
127
|
+
and the distinction was never resolved.
|
|
128
|
+
|
|
129
|
+
Detection: scan the namespace for these qualifier patterns.
|
|
130
|
+
|
|
131
|
+
**Vocabulary islands.** A subregion of the codebase (a directory, a module
|
|
132
|
+
cluster) using a distinct vocabulary that doesn't overlap with the rest. Often
|
|
133
|
+
the result of an unintegrated contribution, or a session that added a feature
|
|
134
|
+
without looking outward.
|
|
135
|
+
|
|
136
|
+
Detection: look for directories whose namespace entries share few word-roots
|
|
137
|
+
with the rest of the codebase.
|
|
138
|
+
|
|
139
|
+
**Collision with drift.** The same name appearing in multiple places with
|
|
140
|
+
subtly different meanings — visible as different signatures, different docstring
|
|
141
|
+
content, or different domain associations.
|
|
142
|
+
|
|
143
|
+
Detection: identify name collisions in the namespace, then examine the stubs to
|
|
144
|
+
see whether the uses agree.
|
|
145
|
+
|
|
146
|
+
## Step 3: Promissory sweep
|
|
147
|
+
|
|
148
|
+
Working from stubs, examine each public symbol's name / signature / docstring
|
|
149
|
+
triple for internal disagreement.
|
|
150
|
+
|
|
151
|
+
For each non-trivial public symbol (skip trivial one-liners and `__init__` with
|
|
152
|
+
no meaningful body):
|
|
153
|
+
|
|
154
|
+
**Name–signature mismatch.** Does the name's verb fit the signature's return? A
|
|
155
|
+
function called `validate_*` that returns the validated object rather than
|
|
156
|
+
raising or returning bool. A `get_*` that mutates. A noun-named thing that is a
|
|
157
|
+
verb's worth of work. A boolean-returning function whose name doesn't start with
|
|
158
|
+
`is_`, `has_`, `can_`, or similar.
|
|
159
|
+
|
|
160
|
+
**Docstring–signature mismatch.** Does the docstring refer to parameters not in
|
|
161
|
+
the signature, or fail to mention parameters that are? Does the docstring
|
|
162
|
+
describe return behaviour that contradicts the type annotation?
|
|
163
|
+
|
|
164
|
+
**Docstring–name mismatch.** Does the docstring describe an operation noticeably
|
|
165
|
+
more specific, more general, or simply different from what the name advertises?
|
|
166
|
+
"Normalises and validates the record" on a function called `check_record`.
|
|
167
|
+
|
|
168
|
+
**Defensive docstrings.** Docstrings that warn about the function rather than
|
|
169
|
+
describe it. "Note: this does not actually X despite the name." "Do not use
|
|
170
|
+
this for Y; use Z instead." These are confessions — someone noticed drift and
|
|
171
|
+
documented it rather than fixing it.
|
|
172
|
+
|
|
173
|
+
The stub itself is the evidence. Quote the stub excerpt (name, signature,
|
|
174
|
+
first-line docstring) verbatim in the finding.
|
|
175
|
+
|
|
176
|
+
**When to read source.** The stub is usually sufficient for discovery — the
|
|
177
|
+
inconsistency IS the mismatch between name, signature, and docstring, all of
|
|
178
|
+
which the stub provides. Read the symbol body when a finding is already
|
|
179
|
+
identified but confidence is genuinely uncertain: an undocumented parameter
|
|
180
|
+
where significance depends on what it controls; a name–behaviour mismatch where
|
|
181
|
+
the stub alone doesn't confirm it; a defensive docstring you want to verify is
|
|
182
|
+
accurate. Use Serena's `find_symbol` with `include_body=True` — targeted to the
|
|
183
|
+
symbol, no offset arithmetic, no risk of over-reading. Never read a whole source
|
|
184
|
+
file during this sweep.
|
|
185
|
+
|
|
186
|
+
## Step 4: Structural sweep
|
|
187
|
+
|
|
188
|
+
Combine the namespace with the import graph and, if available, Serena's
|
|
189
|
+
reference resolution.
|
|
190
|
+
|
|
191
|
+
**Overgrown public surfaces / god modules.** A module or class whose public
|
|
192
|
+
namespace is much larger than its siblings, or spans obviously different
|
|
193
|
+
concerns. Look for outlier symbol counts: a file with forty public symbols where
|
|
194
|
+
its neighbours have five; a class with thirty methods covering multiple domains.
|
|
195
|
+
|
|
196
|
+
**Boundary violations.** One module importing private symbols (leading
|
|
197
|
+
underscore) from another. Scan `from module import _thing` patterns across
|
|
198
|
+
stubs' import sections. Each instance is a finding.
|
|
199
|
+
|
|
200
|
+
**Cross-vocabulary imports.** Imports that cross domain boundaries in suspicious
|
|
201
|
+
directions — a `core/` or `utils/` module importing from a specific business
|
|
202
|
+
domain; a module in domain A importing from domain B when those domains appear
|
|
203
|
+
meant to be independent. Flag candidates and note the direction; let the human
|
|
204
|
+
decide.
|
|
205
|
+
|
|
206
|
+
**Zero-caller public symbols.** A public symbol (no leading underscore) with no
|
|
207
|
+
references anywhere in the codebase. Either dead code or an unused API surface.
|
|
208
|
+
|
|
209
|
+
Check systematically, not by spot-check:
|
|
210
|
+
|
|
211
|
+
1. From the namespace map, list all public symbols in each source module.
|
|
212
|
+
2. Cross-reference with stub import sections — any symbol imported by another
|
|
213
|
+
source module is live; remove it from the candidate list. This culls the
|
|
214
|
+
obvious cases cheaply.
|
|
215
|
+
3. For remaining candidates, use Serena's `find_referencing_symbols` to verify.
|
|
216
|
+
If Serena is unavailable, note findings as lower confidence.
|
|
217
|
+
4. Distinguish two sub-cases when reporting:
|
|
218
|
+
- *No callers anywhere* — dead code; highest priority.
|
|
219
|
+
- *Callers only in tests* — the symbol is tested but not used in source;
|
|
220
|
+
may be an exposed internal that should be private.
|
|
221
|
+
|
|
222
|
+
**Redundant public surface.** A public constant and a public parameterless
|
|
223
|
+
function in the same module where the function's sole body is `return
|
|
224
|
+
<constant>`. Both symbols being public exposes an implementation detail
|
|
225
|
+
unnecessarily — only one needs to be public. Detection: use the stubs to find
|
|
226
|
+
public parameterless functions near public constants, then verify each
|
|
227
|
+
candidate body with Serena's `find_symbol` with `include_body=True` before
|
|
228
|
+
reporting.
|
|
229
|
+
|
|
230
|
+
## Report format
|
|
231
|
+
|
|
232
|
+
Save the report as `.uncoded/reviews/YYYY-MM-DD-HHMMSS.md`, using today's date
|
|
233
|
+
and current time (timestamped to preserve multiple runs on the same day).
|
|
234
|
+
Create the directory if it does not exist.
|
|
235
|
+
|
|
236
|
+
Use this structure:
|
|
237
|
+
|
|
238
|
+
```markdown
|
|
239
|
+
# Coherence Review — <repo name>
|
|
240
|
+
|
|
241
|
+
**Date:** YYYY-MM-DD
|
|
242
|
+
**Symbols indexed:** N
|
|
243
|
+
**Sweeps run:** lexical, promissory, structural
|
|
244
|
+
**Findings:** N total (N lexical · N promissory · N structural)
|
|
245
|
+
|
|
246
|
+
## Priority regions
|
|
247
|
+
|
|
248
|
+
Regions with two or more findings — examine these first:
|
|
249
|
+
|
|
250
|
+
- `path/to/file.py` — N findings
|
|
251
|
+
- ...
|
|
252
|
+
|
|
253
|
+
*(Omit this section if no region has more than one finding.)*
|
|
254
|
+
|
|
255
|
+
## Findings
|
|
256
|
+
|
|
257
|
+
### 1 · <symptom summary>
|
|
258
|
+
|
|
259
|
+
**Category:** lexical | promissory | structural
|
|
260
|
+
**Symptom:** concept-duplication | qualifier-accretion | vocabulary-island |
|
|
261
|
+
collision-with-drift | name-signature-mismatch | docstring-signature-mismatch |
|
|
262
|
+
docstring-name-mismatch | defensive-docstring | god-module |
|
|
263
|
+
boundary-violation | cross-vocabulary-import | zero-caller
|
|
264
|
+
**Location:** `path/to/file.py` · `ClassName/method_name`
|
|
265
|
+
**Confidence:** high | medium | low
|
|
266
|
+
|
|
267
|
+
**Evidence:**
|
|
268
|
+
> Verbatim quote from namespace.yaml, stub, or import statement.
|
|
269
|
+
|
|
270
|
+
One or two sentences describing the inconsistency. Not a diagnosis. Not a fix.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
### 2 · ...
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Principles
|
|
278
|
+
|
|
279
|
+
**Coverage, not filtering.** Report every finding at its confidence level. Do
|
|
280
|
+
not silently drop findings judged low-severity. A low-confidence finding with
|
|
281
|
+
clear evidence is useful — the human can filter. A dropped finding is not.
|
|
282
|
+
|
|
283
|
+
**Confidence is part of the finding, not a gate.**
|
|
284
|
+
|
|
285
|
+
- `high` — the inconsistency is explicit; evidence is directly in the stub or
|
|
286
|
+
namespace
|
|
287
|
+
- `medium` — strongly implied but depends on judgement about intent
|
|
288
|
+
- `low` — pattern-based suspicion that needs human interpretation
|
|
289
|
+
|
|
290
|
+
**Evidence must be verbatim.** Quote the relevant namespace line, stub excerpt,
|
|
291
|
+
or import statement exactly. A finding the human cannot quickly verify is worse
|
|
292
|
+
than no finding.
|
|
293
|
+
|
|
294
|
+
**One finding per inconsistency.** If a single symbol has a name–signature
|
|
295
|
+
mismatch and a docstring–name mismatch, that is two findings on the same
|
|
296
|
+
symbol, not one combined finding. Let the report show the density.
|
|
297
|
+
|
|
298
|
+
**Do not propose fixes.** No renaming suggestions, no refactoring proposals, no
|
|
299
|
+
"this should be moved to". The finding describes what is inconsistent. The
|
|
300
|
+
human owns remediation.
|
|
301
|
+
|
|
302
|
+
**Do not flag style.** Docstring format, type annotation style, import ordering,
|
|
303
|
+
naming conventions — out of scope. Coherence is about semantic consistency, not
|
|
304
|
+
surface consistency.
|
|
305
|
+
|
|
306
|
+
**Do not fabricate.** Every finding must be anchored to code you actually
|
|
307
|
+
examined. If a sweep suggests a pattern but you cannot find concrete instances,
|
|
308
|
+
do not include it.
|
|
309
|
+
|
|
310
|
+
## Scope control
|
|
311
|
+
|
|
312
|
+
If the codebase has more than ~1000 public symbols:
|
|
313
|
+
|
|
314
|
+
1. Complete the lexical sweep in full (the namespace is compact enough).
|
|
315
|
+
2. For the promissory sweep, prioritise: core domain modules (identified from
|
|
316
|
+
the namespace structure), any module that appeared in a lexical finding, and
|
|
317
|
+
a representative sample of the rest (~30% of remaining stubs).
|
|
318
|
+
3. For the structural sweep, focus on the module and package level first,
|
|
319
|
+
descending to individual symbols only where the higher-level scan raised
|
|
320
|
+
flags.
|
|
321
|
+
|
|
322
|
+
Note the scope chosen in the report summary so the human knows what was and was
|
|
323
|
+
not examined.
|
|
324
|
+
|
|
325
|
+
## Examples
|
|
326
|
+
|
|
327
|
+
<example>
|
|
328
|
+
<scenario>Three public functions: `fetch_user(id)`, `get_user_by_id(id)`,
|
|
329
|
+
`load_user(user_id)`, in three different files, all returning a User and all
|
|
330
|
+
doing substantively the same lookup.</scenario>
|
|
331
|
+
<flag>Yes. Concept duplication, high confidence. Evidence: three stub excerpts
|
|
332
|
+
quoted verbatim.</flag>
|
|
333
|
+
</example>
|
|
334
|
+
|
|
335
|
+
<example>
|
|
336
|
+
<scenario>A function `validate_user(user)` whose signature returns `User` rather
|
|
337
|
+
than `bool` or `None`, and whose docstring says "Validates and returns the user
|
|
338
|
+
if valid, raising UserValidationError otherwise."</scenario>
|
|
339
|
+
<flag>Yes. Name–signature mismatch, medium confidence. The name reads as a
|
|
340
|
+
predicate but the behaviour is a validator-filter. Stub quoted as
|
|
341
|
+
evidence.</flag>
|
|
342
|
+
</example>
|
|
343
|
+
|
|
344
|
+
<example>
|
|
345
|
+
<scenario>Two modules, `storage/cache.py` and `runtime/memoize.py`, both
|
|
346
|
+
defining functions that wrap callables with LRU caching, with slightly different
|
|
347
|
+
cache-size defaults.</scenario>
|
|
348
|
+
<flag>Yes. Concept duplication, medium confidence. Both stubs quoted. Note the
|
|
349
|
+
difference — the human may determine it is intentional.</flag>
|
|
350
|
+
</example>
|
|
351
|
+
|
|
352
|
+
<example>
|
|
353
|
+
<scenario>A class `OrderProcessor` with 34 methods spanning order creation,
|
|
354
|
+
validation, payment capture, fulfilment dispatch, refund handling, and
|
|
355
|
+
reporting.</scenario>
|
|
356
|
+
<flag>Yes. God module, high confidence. Method list quoted from
|
|
357
|
+
namespace.</flag>
|
|
358
|
+
</example>
|
|
359
|
+
|
|
360
|
+
<example>
|
|
361
|
+
<scenario>A function uses `x` as a parameter name in a mathematical formula
|
|
362
|
+
module.</scenario>
|
|
363
|
+
<flag>No. Short variable names in mathematical contexts are conventional and do
|
|
364
|
+
not indicate drift.</flag>
|
|
365
|
+
</example>
|
|
366
|
+
|
|
367
|
+
<example>
|
|
368
|
+
<scenario>A function is 80 lines of non-trivial logic.</scenario>
|
|
369
|
+
<flag>No, not by itself. Complexity is not incoherence. Flag only if the
|
|
370
|
+
complexity manifests as inconsistency — e.g. the function's behaviour has
|
|
371
|
+
drifted from what its name or docstring promise.</flag>
|
|
372
|
+
</example>
|
|
373
|
+
|
|
374
|
+
<example>
|
|
375
|
+
<scenario>The codebase uses both `Optional[X]` and `X | None` in different
|
|
376
|
+
files.</scenario>
|
|
377
|
+
<flag>No. Both are valid Python and mean the same thing. Flag only if the
|
|
378
|
+
semantics of absence differ — e.g. some functions return None on failure while
|
|
379
|
+
others raise, for the same kind of operation.</flag>
|
|
380
|
+
</example>
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def sync_skill(*, check: bool) -> bool:
|
|
385
|
+
"""Write the coherence-review skill file to all supported agent locations."""
|
|
386
|
+
results = [sync_file(path, _SKILL_CONTENT, check=check) for path in SKILL_OUTPUTS]
|
|
387
|
+
results.extend(remove_file(path, check=check) for path in LEGACY_SKILL_OUTPUTS)
|
|
388
|
+
return any(results)
|