codespine 0.4.2__tar.gz → 0.4.3__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.
- {codespine-0.4.2 → codespine-0.4.3}/PKG-INFO +2 -2
- {codespine-0.4.2 → codespine-0.4.3}/codespine/__init__.py +1 -1
- {codespine-0.4.2 → codespine-0.4.3}/codespine/analysis/deadcode.py +49 -18
- {codespine-0.4.2 → codespine-0.4.3}/codespine/mcp/server.py +42 -8
- {codespine-0.4.2 → codespine-0.4.3}/codespine.egg-info/PKG-INFO +2 -2
- {codespine-0.4.2 → codespine-0.4.3}/codespine.egg-info/requires.txt +1 -1
- {codespine-0.4.2 → codespine-0.4.3}/pyproject.toml +2 -2
- {codespine-0.4.2 → codespine-0.4.3}/LICENSE +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/README.md +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/analysis/__init__.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/analysis/community.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/analysis/context.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/analysis/coupling.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/analysis/flow.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/analysis/impact.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/cli.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/config.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/db/__init__.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/db/schema.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/db/store.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/diff/__init__.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/diff/branch_diff.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/indexer/__init__.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/indexer/call_resolver.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/indexer/engine.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/indexer/java_parser.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/indexer/symbol_builder.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/mcp/__init__.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/noise/__init__.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/noise/blocklist.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/search/__init__.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/search/bm25.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/search/fuzzy.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/search/hybrid.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/search/rrf.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/search/vector.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/watch/__init__.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine/watch/watcher.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine.egg-info/SOURCES.txt +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine.egg-info/dependency_links.txt +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine.egg-info/entry_points.txt +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/codespine.egg-info/top_level.txt +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/gindex.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/setup.cfg +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/tests/test_branch_diff_normalize.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/tests/test_call_resolver.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/tests/test_index_and_hybrid.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/tests/test_java_parser.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/tests/test_multimodule_index.py +0 -0
- {codespine-0.4.2 → codespine-0.4.3}/tests/test_search_ranking.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codespine
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Local Java code intelligence indexer backed by a graph database
|
|
5
5
|
Author: CodeSpine contributors
|
|
6
6
|
License: MIT License
|
|
@@ -46,7 +46,7 @@ Requires-Dist: click
|
|
|
46
46
|
Requires-Dist: kuzu
|
|
47
47
|
Requires-Dist: tree-sitter
|
|
48
48
|
Requires-Dist: tree-sitter-java
|
|
49
|
-
Requires-Dist: fastmcp
|
|
49
|
+
Requires-Dist: fastmcp>=2.3.0
|
|
50
50
|
Requires-Dist: psutil
|
|
51
51
|
Requires-Dist: watchfiles
|
|
52
52
|
Provides-Extra: ml
|
|
@@ -74,8 +74,17 @@ def _modifier_tokens(modifiers) -> set[str]:
|
|
|
74
74
|
return {str(m).strip() for m in modifiers}
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
def detect_dead_code(store, limit: int = 200, project: str | None = None) -> list[dict]:
|
|
78
|
-
"""Java-aware dead code detection with exemption passes.
|
|
77
|
+
def detect_dead_code(store, limit: int = 200, project: str | None = None) -> list[dict] | None:
|
|
78
|
+
"""Java-aware dead code detection with exemption passes.
|
|
79
|
+
|
|
80
|
+
Returns a list of dead method dicts, each with:
|
|
81
|
+
method_id, name, signature, class_fqcn, file_path, reason.
|
|
82
|
+
|
|
83
|
+
The return value is augmented with a ``_stats`` entry (a sentinel dict
|
|
84
|
+
with key ``_stats``) containing pre/post-exemption counts so callers can
|
|
85
|
+
show users that the exemption logic is actually working:
|
|
86
|
+
candidates_with_no_callers, exempted, dead_returned
|
|
87
|
+
"""
|
|
79
88
|
if project:
|
|
80
89
|
candidates = store.query_records(
|
|
81
90
|
"""
|
|
@@ -88,16 +97,17 @@ def detect_dead_code(store, limit: int = 200, project: str | None = None) -> lis
|
|
|
88
97
|
m.modifiers as modifiers,
|
|
89
98
|
c.fqcn as class_fqcn,
|
|
90
99
|
m.is_constructor as is_constructor,
|
|
91
|
-
m.is_test as is_test
|
|
100
|
+
m.is_test as is_test,
|
|
101
|
+
f.path as file_path
|
|
92
102
|
LIMIT $limit
|
|
93
103
|
""",
|
|
94
|
-
{"limit": int(limit *
|
|
104
|
+
{"limit": int(limit * 5), "proj": project},
|
|
95
105
|
)
|
|
96
106
|
else:
|
|
97
107
|
candidates = store.query_records(
|
|
98
108
|
"""
|
|
99
|
-
MATCH (m:Method), (c:Class)
|
|
100
|
-
WHERE m.class_id = c.id
|
|
109
|
+
MATCH (m:Method), (c:Class), (f:File)
|
|
110
|
+
WHERE m.class_id = c.id AND c.file_id = f.id
|
|
101
111
|
AND NOT EXISTS { MATCH (:Method)-[:CALLS]->(m) }
|
|
102
112
|
RETURN m.id as method_id,
|
|
103
113
|
m.name as name,
|
|
@@ -105,15 +115,17 @@ def detect_dead_code(store, limit: int = 200, project: str | None = None) -> lis
|
|
|
105
115
|
m.modifiers as modifiers,
|
|
106
116
|
c.fqcn as class_fqcn,
|
|
107
117
|
m.is_constructor as is_constructor,
|
|
108
|
-
m.is_test as is_test
|
|
118
|
+
m.is_test as is_test,
|
|
119
|
+
f.path as file_path
|
|
109
120
|
LIMIT $limit
|
|
110
121
|
""",
|
|
111
|
-
{"limit": int(limit *
|
|
122
|
+
{"limit": int(limit * 5)},
|
|
112
123
|
)
|
|
113
124
|
|
|
114
125
|
if not candidates:
|
|
115
126
|
return []
|
|
116
127
|
|
|
128
|
+
n_candidates = len(candidates)
|
|
117
129
|
exempt: set[str] = set()
|
|
118
130
|
|
|
119
131
|
# Exempt constructors, test methods, and Java main entrypoints.
|
|
@@ -138,22 +150,19 @@ def detect_dead_code(store, limit: int = 200, project: str | None = None) -> lis
|
|
|
138
150
|
if name in {"valueOf", "fromString", "builder"}:
|
|
139
151
|
exempt.add(c["method_id"])
|
|
140
152
|
|
|
141
|
-
# Exempt override
|
|
153
|
+
# Exempt methods that DIRECTLY override another method (precise: only the
|
|
154
|
+
# specific overriding method is exempted, not the entire implementing class).
|
|
155
|
+
# NOTE: we intentionally do NOT use the class-level IMPLEMENTS relation here
|
|
156
|
+
# because that would exempt ALL methods of every class that implements ANY
|
|
157
|
+
# interface — in a typical Spring project that wipes out almost everything
|
|
158
|
+
# and produces 0 dead code results.
|
|
142
159
|
override_methods = store.query_records(
|
|
143
160
|
"""
|
|
144
161
|
MATCH (m:Method)-[:OVERRIDES]->(:Method)
|
|
145
162
|
RETURN DISTINCT m.id as method_id
|
|
146
163
|
"""
|
|
147
164
|
)
|
|
148
|
-
interface_methods = store.query_records(
|
|
149
|
-
"""
|
|
150
|
-
MATCH (c:Class)-[:IMPLEMENTS]->(:Class), (m:Method)
|
|
151
|
-
WHERE m.class_id = c.id
|
|
152
|
-
RETURN DISTINCT m.id as method_id
|
|
153
|
-
"""
|
|
154
|
-
)
|
|
155
165
|
exempt.update(r["method_id"] for r in override_methods)
|
|
156
|
-
exempt.update(r["method_id"] for r in interface_methods)
|
|
157
166
|
|
|
158
167
|
dead = []
|
|
159
168
|
for c in candidates:
|
|
@@ -164,8 +173,30 @@ def detect_dead_code(store, limit: int = 200, project: str | None = None) -> lis
|
|
|
164
173
|
"method_id": c["method_id"],
|
|
165
174
|
"name": c.get("name"),
|
|
166
175
|
"signature": c.get("signature"),
|
|
176
|
+
"class_fqcn": c.get("class_fqcn"),
|
|
177
|
+
"file_path": c.get("file_path"),
|
|
167
178
|
"reason": "no_incoming_calls_after_exemptions",
|
|
168
179
|
}
|
|
169
180
|
)
|
|
170
181
|
|
|
171
|
-
|
|
182
|
+
result = dead[:limit]
|
|
183
|
+
|
|
184
|
+
# Append stats as a sentinel entry so the MCP layer can surface them
|
|
185
|
+
# without changing the return type. Callers should strip entries that
|
|
186
|
+
# have a "_stats" key when iterating over method results.
|
|
187
|
+
result.append({
|
|
188
|
+
"_stats": {
|
|
189
|
+
"candidates_with_no_callers": n_candidates,
|
|
190
|
+
"exempted": len(exempt),
|
|
191
|
+
"dead_returned": len(result),
|
|
192
|
+
"note": (
|
|
193
|
+
"Exemptions cover: constructors, test methods, main(), "
|
|
194
|
+
"toString/hashCode/equals/compareTo, public getters/setters, "
|
|
195
|
+
"methods with DI/framework annotations, and direct method overrides. "
|
|
196
|
+
"The class-level IMPLEMENTS exemption has been removed — only "
|
|
197
|
+
"methods with direct OVERRIDES relations are now exempted."
|
|
198
|
+
),
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
return result
|
|
@@ -253,11 +253,31 @@ def build_mcp_server(store, repo_path_provider):
|
|
|
253
253
|
"""
|
|
254
254
|
Detect methods with no incoming calls (after Java-aware exemptions).
|
|
255
255
|
Pass project to scope to a single module.
|
|
256
|
+
|
|
257
|
+
Returns dead_code list, count, and an exemption_stats dict showing
|
|
258
|
+
how many candidates were found and how many were filtered out by the
|
|
259
|
+
exemption rules — useful for validating that the feature is working
|
|
260
|
+
even when the dead list is empty.
|
|
256
261
|
"""
|
|
257
|
-
|
|
258
|
-
if
|
|
262
|
+
raw = detect_dead_code_analysis(store, limit=limit, project=project)
|
|
263
|
+
if raw is None:
|
|
259
264
|
return _no_symbols_response()
|
|
260
|
-
|
|
265
|
+
|
|
266
|
+
# Separate the sentinel stats entry appended by the analysis function.
|
|
267
|
+
stats: dict = {}
|
|
268
|
+
dead = []
|
|
269
|
+
for entry in raw:
|
|
270
|
+
if "_stats" in entry:
|
|
271
|
+
stats = entry["_stats"]
|
|
272
|
+
else:
|
|
273
|
+
dead.append(entry)
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
"available": True,
|
|
277
|
+
"dead_code": dead,
|
|
278
|
+
"count": len(dead),
|
|
279
|
+
"exemption_stats": stats,
|
|
280
|
+
}
|
|
261
281
|
|
|
262
282
|
@mcp.tool()
|
|
263
283
|
def trace_execution_flows(entry_symbol: str | None = None, max_depth: int = 6, project: str | None = None):
|
|
@@ -589,11 +609,24 @@ def build_mcp_server(store, repo_path_provider):
|
|
|
589
609
|
}
|
|
590
610
|
|
|
591
611
|
@mcp.tool()
|
|
592
|
-
def compare_branches(base_ref: str, head_ref: str):
|
|
593
|
-
"""
|
|
594
|
-
|
|
612
|
+
def compare_branches(base_ref: str, head_ref: str, project: str | None = None):
|
|
613
|
+
"""
|
|
614
|
+
Symbol-level diff between two git refs (branches, tags, commits).
|
|
615
|
+
Pass project=<project_id> so the tool can resolve the correct git
|
|
616
|
+
repository root from the indexed project path rather than relying on
|
|
617
|
+
the MCP server's working directory (which may point to the graph DB
|
|
618
|
+
location, not the source tree).
|
|
619
|
+
"""
|
|
620
|
+
repo = _resolve_repo_path(store, project, repo_path_provider)
|
|
595
621
|
if not _git_available(repo):
|
|
596
|
-
return {
|
|
622
|
+
return {
|
|
623
|
+
"available": False,
|
|
624
|
+
"note": (
|
|
625
|
+
"Not a git repository (or git not installed). "
|
|
626
|
+
"Pass project=<project_id> so the tool can resolve the repo "
|
|
627
|
+
"from the indexed project path. Use list_projects() to see available IDs."
|
|
628
|
+
),
|
|
629
|
+
}
|
|
597
630
|
result = compare_branches_analysis(repo, base_ref, head_ref)
|
|
598
631
|
return {"available": True, **result}
|
|
599
632
|
|
|
@@ -981,6 +1014,7 @@ def build_mcp_server(store, repo_path_provider):
|
|
|
981
1014
|
@mcp.tool()
|
|
982
1015
|
def run_cypher(query: str):
|
|
983
1016
|
"""Run a raw Cypher query against the graph. For advanced exploration."""
|
|
984
|
-
|
|
1017
|
+
records = store.query_records(query)
|
|
1018
|
+
return {"available": True, "records": records, "count": len(records)}
|
|
985
1019
|
|
|
986
1020
|
return mcp
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codespine
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Local Java code intelligence indexer backed by a graph database
|
|
5
5
|
Author: CodeSpine contributors
|
|
6
6
|
License: MIT License
|
|
@@ -46,7 +46,7 @@ Requires-Dist: click
|
|
|
46
46
|
Requires-Dist: kuzu
|
|
47
47
|
Requires-Dist: tree-sitter
|
|
48
48
|
Requires-Dist: tree-sitter-java
|
|
49
|
-
Requires-Dist: fastmcp
|
|
49
|
+
Requires-Dist: fastmcp>=2.3.0
|
|
50
50
|
Requires-Dist: psutil
|
|
51
51
|
Requires-Dist: watchfiles
|
|
52
52
|
Provides-Extra: ml
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codespine"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.3"
|
|
8
8
|
description = "Local Java code intelligence indexer backed by a graph database"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
"kuzu",
|
|
31
31
|
"tree-sitter",
|
|
32
32
|
"tree-sitter-java",
|
|
33
|
-
"fastmcp",
|
|
33
|
+
"fastmcp>=2.3.0",
|
|
34
34
|
"psutil",
|
|
35
35
|
"watchfiles"
|
|
36
36
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|