codespine 0.6.2__tar.gz → 0.7.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.
- {codespine-0.6.2 → codespine-0.7.0}/PKG-INFO +1 -1
- {codespine-0.6.2 → codespine-0.7.0}/codespine/__init__.py +1 -1
- {codespine-0.6.2 → codespine-0.7.0}/codespine/cli.py +12 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/db/store.py +16 -1
- codespine-0.7.0/codespine/guide.py +169 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/mcp/server.py +16 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/PKG-INFO +1 -1
- {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/SOURCES.txt +1 -0
- {codespine-0.6.2 → codespine-0.7.0}/pyproject.toml +1 -1
- {codespine-0.6.2 → codespine-0.7.0}/LICENSE +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/README.md +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/__init__.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/community.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/context.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/coupling.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/crossmodule.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/deadcode.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/flow.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/impact.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/config.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/db/__init__.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/db/schema.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/diff/__init__.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/diff/branch_diff.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/__init__.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/call_resolver.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/engine.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/java_parser.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/symbol_builder.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/mcp/__init__.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/noise/__init__.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/noise/blocklist.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/overlay/__init__.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/overlay/git_state.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/overlay/merge.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/overlay/store.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/search/__init__.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/search/bm25.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/search/fuzzy.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/search/hybrid.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/search/rrf.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/search/vector.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/watch/__init__.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine/watch/watcher.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/dependency_links.txt +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/entry_points.txt +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/requires.txt +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/top_level.txt +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/gindex.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/setup.cfg +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_branch_diff_normalize.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_call_resolver.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_community_detection.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_deadcode.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_index_and_hybrid.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_java_parser.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_multimodule_index.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_overlay.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_search_ranking.py +0 -0
- {codespine-0.6.2 → codespine-0.7.0}/tests/test_store_recovery.py +0 -0
|
@@ -852,6 +852,18 @@ def force_reset_cmd(force: bool) -> None:
|
|
|
852
852
|
click.secho("Nothing to remove — already clean.", fg="yellow")
|
|
853
853
|
|
|
854
854
|
|
|
855
|
+
@main.command()
|
|
856
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
|
857
|
+
def guide(as_json: bool) -> None:
|
|
858
|
+
"""Show what CodeSpine can do: tool catalog, workflows, and tips."""
|
|
859
|
+
from codespine.guide import GUIDE_SECTIONS, format_guide_terminal
|
|
860
|
+
|
|
861
|
+
if as_json:
|
|
862
|
+
_echo_json({"sections": GUIDE_SECTIONS}, as_json=True)
|
|
863
|
+
else:
|
|
864
|
+
click.echo(format_guide_terminal())
|
|
865
|
+
|
|
866
|
+
|
|
855
867
|
@main.command()
|
|
856
868
|
def setup() -> None:
|
|
857
869
|
"""Print local setup checks and next steps."""
|
|
@@ -604,6 +604,13 @@ class GraphStore:
|
|
|
604
604
|
self.db = kuzu.Database(path)
|
|
605
605
|
self._tls = threading.local()
|
|
606
606
|
ensure_schema(self._conn())
|
|
607
|
+
# Force Kuzu to flush the WAL to the main data files so that a
|
|
608
|
+
# subsequent read-only open (stats, MCP snapshot) can see the schema
|
|
609
|
+
# without needing WAL replay (which read-only mode cannot do).
|
|
610
|
+
try:
|
|
611
|
+
self._conn().execute("CHECKPOINT")
|
|
612
|
+
except Exception:
|
|
613
|
+
pass
|
|
607
614
|
|
|
608
615
|
def set_community(self, community_id: str, label: str, cohesion: float, symbol_ids: list[str]) -> None:
|
|
609
616
|
self.execute(
|
|
@@ -692,7 +699,15 @@ class GraphStore:
|
|
|
692
699
|
return False
|
|
693
700
|
|
|
694
701
|
def query_records(self, query: str, params: dict[str, Any] | None = None) -> list[dict[str, Any]]:
|
|
695
|
-
|
|
702
|
+
try:
|
|
703
|
+
frame = self.execute(query, params or {}).get_as_df()
|
|
704
|
+
except RuntimeError as exc:
|
|
705
|
+
# In read-only mode the DB may have been cleared but the schema
|
|
706
|
+
# hasn't been flushed from WAL, or the DB is brand-new. Return
|
|
707
|
+
# an empty result instead of crashing callers (stats, MCP, etc.).
|
|
708
|
+
if self.read_only and "does not exist" in str(exc).lower():
|
|
709
|
+
return []
|
|
710
|
+
raise
|
|
696
711
|
if frame.empty:
|
|
697
712
|
return []
|
|
698
713
|
return json.loads(frame.to_json(orient="records"))
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""CodeSpine guide – single source of truth for MCP + CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from codespine import __version__
|
|
6
|
+
|
|
7
|
+
GUIDE_SECTIONS: list[dict] = [
|
|
8
|
+
{
|
|
9
|
+
"id": "overview",
|
|
10
|
+
"title": "What is CodeSpine?",
|
|
11
|
+
"body": (
|
|
12
|
+
"CodeSpine is a Java code-intelligence engine. It parses Java source "
|
|
13
|
+
"into a graph database (Kuzu) and exposes hybrid search, impact analysis, "
|
|
14
|
+
"dead-code detection, execution-flow tracing, community detection, and "
|
|
15
|
+
"git change-coupling -- all via MCP tools or CLI commands.\n"
|
|
16
|
+
"Language: Java. Layouts: single-module, multi-module (Maven/Gradle), workspaces."
|
|
17
|
+
),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "quickstart",
|
|
21
|
+
"title": "Recommended First Steps",
|
|
22
|
+
"body": (
|
|
23
|
+
"1. Call get_capabilities() to see what is indexed and which features are ready.\n"
|
|
24
|
+
"2. If nothing is indexed: call analyse_project(path) with the Java project root.\n"
|
|
25
|
+
" Poll with get_analyse_status() until it finishes.\n"
|
|
26
|
+
"3. Once indexed: use search_hybrid(query) to find symbols, then drill in with\n"
|
|
27
|
+
" get_impact(), get_symbol_context(), or get_neighborhood().\n"
|
|
28
|
+
"4. For active development: call start_watch(path) to keep the index fresh\n"
|
|
29
|
+
" as files change on disk."
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
# ── Tool catalog ──────────────────────────────────────────────
|
|
33
|
+
{
|
|
34
|
+
"id": "tools_discovery",
|
|
35
|
+
"title": "Discovery & Status",
|
|
36
|
+
"tools": [
|
|
37
|
+
{"name": "get_capabilities", "one_liner": "What is indexed and which features are available right now. Call this first."},
|
|
38
|
+
{"name": "list_projects", "one_liner": "All indexed projects with symbol/file counts."},
|
|
39
|
+
{"name": "get_codebase_stats", "one_liner": "Per-project stats: files, classes, methods, call edges, embeddings."},
|
|
40
|
+
{"name": "list_packages", "one_liner": "Java packages in the index (optional project= filter)."},
|
|
41
|
+
{"name": "ping", "one_liner": "Verify the MCP server is alive."},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "tools_search",
|
|
46
|
+
"title": "Search & Lookup",
|
|
47
|
+
"tools": [
|
|
48
|
+
{"name": "search_hybrid", "one_liner": "Ranked symbol search (BM25 + vector + fuzzy via RRF). Start here."},
|
|
49
|
+
{"name": "find_symbol", "one_liner": "Exact/prefix name lookup. Use to resolve ambiguity or list overloads."},
|
|
50
|
+
{"name": "get_symbol_context", "one_liner": "One-shot deep context: search + impact + community + flows in one call."},
|
|
51
|
+
{"name": "get_neighborhood", "one_liner": "Callers, callees, siblings, and override/implements for a symbol."},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "tools_analysis",
|
|
56
|
+
"title": "Analysis",
|
|
57
|
+
"body": "Some analyses require 'analyse_project(path, deep=True)' to populate data.",
|
|
58
|
+
"tools": [
|
|
59
|
+
{"name": "get_impact", "one_liner": "Caller-tree impact analysis grouped by depth with confidence scores."},
|
|
60
|
+
{"name": "detect_dead_code", "one_liner": "Methods with no callers (Java-aware exemptions). strict=True for thorough audit."},
|
|
61
|
+
{"name": "trace_execution_flows", "one_liner": "Execution paths from entry points (main methods, tests, controllers)."},
|
|
62
|
+
{"name": "get_symbol_community", "one_liner": "Architectural community cluster a symbol belongs to."},
|
|
63
|
+
{"name": "get_change_coupling", "one_liner": "Files that historically change together (git co-change analysis)."},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "tools_git",
|
|
68
|
+
"title": "Git Integration",
|
|
69
|
+
"tools": [
|
|
70
|
+
{"name": "git_log", "one_liner": "Recent git commits for a project or a specific file."},
|
|
71
|
+
{"name": "git_diff", "one_liner": "Git diff (working tree vs ref, or between two refs)."},
|
|
72
|
+
{"name": "compare_branches", "one_liner": "Symbol-level diff between two git refs (branches/tags/commits)."},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "tools_indexing",
|
|
77
|
+
"title": "Indexing & Watch",
|
|
78
|
+
"tools": [
|
|
79
|
+
{"name": "analyse_project", "one_liner": "Index a Java project (background). Poll get_analyse_status() for progress."},
|
|
80
|
+
{"name": "get_analyse_status", "one_liner": "Status of current/recent background analysis job."},
|
|
81
|
+
{"name": "reindex_file", "one_liner": "Re-index a single .java file via overlay (<1 s)."},
|
|
82
|
+
{"name": "start_watch", "one_liner": "Watch a directory for .java changes; updates overlay in real time."},
|
|
83
|
+
{"name": "stop_watch", "one_liner": "Stop the background watch process."},
|
|
84
|
+
{"name": "get_watch_status", "one_liner": "Is watch mode running? Path, uptime."},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "tools_overlay",
|
|
89
|
+
"title": "Overlay (Dirty-State Tracking)",
|
|
90
|
+
"body": (
|
|
91
|
+
"The overlay tracks uncommitted file changes separately from the base index.\n"
|
|
92
|
+
"search_hybrid and find_symbol merge both layers automatically.\n"
|
|
93
|
+
"Deep analyses (dead code, flows, communities) use the committed base only."
|
|
94
|
+
),
|
|
95
|
+
"tools": [
|
|
96
|
+
{"name": "get_overlay_status", "one_liner": "Uncommitted overlay state by project/module."},
|
|
97
|
+
{"name": "promote_overlay", "one_liner": "Commit dirty overlay into the base index immediately."},
|
|
98
|
+
{"name": "clear_overlay", "one_liner": "Discard dirty overlay without changing the base index."},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "tools_reset",
|
|
103
|
+
"title": "Index Reset",
|
|
104
|
+
"tools": [
|
|
105
|
+
{"name": "reset_project", "one_liner": "Remove all data for one project. Re-index with analyse_project()."},
|
|
106
|
+
{"name": "reset_index", "one_liner": "Remove ALL data across every project (clean slate)."},
|
|
107
|
+
{"name": "force_reset_index", "one_liner": "Emergency: delete data files when normal reset fails (OOM/corruption)."},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "tools_advanced",
|
|
112
|
+
"title": "Advanced",
|
|
113
|
+
"tools": [
|
|
114
|
+
{"name": "run_cypher", "one_liner": "Run a raw Cypher query against the graph DB."},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
# ── Workflows ─────────────────────────────────────────────────
|
|
118
|
+
{
|
|
119
|
+
"id": "workflows",
|
|
120
|
+
"title": "Common Workflows",
|
|
121
|
+
"body": (
|
|
122
|
+
"Understand a symbol:\n"
|
|
123
|
+
" search_hybrid('PaymentService') -> get_impact('processPayment') -> get_neighborhood('processPayment')\n"
|
|
124
|
+
"\n"
|
|
125
|
+
"Find dead code:\n"
|
|
126
|
+
" detect_dead_code(strict=True) -> get_impact(candidate) to verify each hit\n"
|
|
127
|
+
"\n"
|
|
128
|
+
"Review a branch:\n"
|
|
129
|
+
" compare_branches('main', 'feature-x') -> get_impact() on each changed symbol\n"
|
|
130
|
+
"\n"
|
|
131
|
+
"Active development:\n"
|
|
132
|
+
" analyse_project(path) -> start_watch(path) -> search/analyse as needed\n"
|
|
133
|
+
"\n"
|
|
134
|
+
"Explore architecture:\n"
|
|
135
|
+
" list_packages() -> get_symbol_community(symbol) -> trace_execution_flows()"
|
|
136
|
+
),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"id": "tips",
|
|
140
|
+
"title": "Tips",
|
|
141
|
+
"body": (
|
|
142
|
+
"- Most tools accept an optional project= parameter to scope results.\n"
|
|
143
|
+
" Use list_projects() to see available project IDs.\n"
|
|
144
|
+
"- Multi-module projects use IDs like 'myapp::core', 'myapp::web'.\n"
|
|
145
|
+
"- get_symbol_context() is the best single call for understanding any symbol.\n"
|
|
146
|
+
"- For git tools, pass project= so the correct repo root is resolved.\n"
|
|
147
|
+
"- BM25 + fuzzy search works without embeddings. Semantic search needs\n"
|
|
148
|
+
" 'pip install codespine[ml]' and 'analyse_project(path, embed=True)'.\n"
|
|
149
|
+
"- If community/flow/coupling data is missing, re-run:\n"
|
|
150
|
+
" analyse_project(path, deep=True)"
|
|
151
|
+
),
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def format_guide_terminal() -> str:
|
|
157
|
+
"""Render GUIDE_SECTIONS as formatted terminal text."""
|
|
158
|
+
lines: list[str] = [f"CodeSpine v{__version__} -- Guide\n"]
|
|
159
|
+
for section in GUIDE_SECTIONS:
|
|
160
|
+
lines.append(f"{'=' * 64}")
|
|
161
|
+
lines.append(f" {section['title']}")
|
|
162
|
+
lines.append(f"{'=' * 64}")
|
|
163
|
+
if "body" in section:
|
|
164
|
+
lines.append(section["body"])
|
|
165
|
+
if "tools" in section:
|
|
166
|
+
for t in section["tools"]:
|
|
167
|
+
lines.append(f" {t['name']:<28} {t['one_liner']}")
|
|
168
|
+
lines.append("")
|
|
169
|
+
return "\n".join(lines)
|
|
@@ -362,6 +362,22 @@ def build_mcp_server(store, repo_path_provider):
|
|
|
362
362
|
"notes": notes,
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
# ------------------------------------------------------------------
|
|
366
|
+
# Guide – static tool catalog + workflows for agents
|
|
367
|
+
# ------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
@mcp.tool()
|
|
370
|
+
def guide():
|
|
371
|
+
"""
|
|
372
|
+
How to use CodeSpine: system overview, tool catalog, recommended
|
|
373
|
+
workflows, and tips. Call this FIRST if you have never used
|
|
374
|
+
CodeSpine before. For live index state (what is indexed right now),
|
|
375
|
+
call get_capabilities() instead.
|
|
376
|
+
"""
|
|
377
|
+
from codespine.guide import GUIDE_SECTIONS
|
|
378
|
+
|
|
379
|
+
return _json({"version": __version__, "sections": GUIDE_SECTIONS})
|
|
380
|
+
|
|
365
381
|
# ------------------------------------------------------------------
|
|
366
382
|
# Project listing
|
|
367
383
|
# ------------------------------------------------------------------
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|