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.
Files changed (60) hide show
  1. {codespine-0.6.2 → codespine-0.7.0}/PKG-INFO +1 -1
  2. {codespine-0.6.2 → codespine-0.7.0}/codespine/__init__.py +1 -1
  3. {codespine-0.6.2 → codespine-0.7.0}/codespine/cli.py +12 -0
  4. {codespine-0.6.2 → codespine-0.7.0}/codespine/db/store.py +16 -1
  5. codespine-0.7.0/codespine/guide.py +169 -0
  6. {codespine-0.6.2 → codespine-0.7.0}/codespine/mcp/server.py +16 -0
  7. {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/PKG-INFO +1 -1
  8. {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/SOURCES.txt +1 -0
  9. {codespine-0.6.2 → codespine-0.7.0}/pyproject.toml +1 -1
  10. {codespine-0.6.2 → codespine-0.7.0}/LICENSE +0 -0
  11. {codespine-0.6.2 → codespine-0.7.0}/README.md +0 -0
  12. {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/__init__.py +0 -0
  13. {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/community.py +0 -0
  14. {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/context.py +0 -0
  15. {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/coupling.py +0 -0
  16. {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/crossmodule.py +0 -0
  17. {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/deadcode.py +0 -0
  18. {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/flow.py +0 -0
  19. {codespine-0.6.2 → codespine-0.7.0}/codespine/analysis/impact.py +0 -0
  20. {codespine-0.6.2 → codespine-0.7.0}/codespine/config.py +0 -0
  21. {codespine-0.6.2 → codespine-0.7.0}/codespine/db/__init__.py +0 -0
  22. {codespine-0.6.2 → codespine-0.7.0}/codespine/db/schema.py +0 -0
  23. {codespine-0.6.2 → codespine-0.7.0}/codespine/diff/__init__.py +0 -0
  24. {codespine-0.6.2 → codespine-0.7.0}/codespine/diff/branch_diff.py +0 -0
  25. {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/__init__.py +0 -0
  26. {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/call_resolver.py +0 -0
  27. {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/engine.py +0 -0
  28. {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/java_parser.py +0 -0
  29. {codespine-0.6.2 → codespine-0.7.0}/codespine/indexer/symbol_builder.py +0 -0
  30. {codespine-0.6.2 → codespine-0.7.0}/codespine/mcp/__init__.py +0 -0
  31. {codespine-0.6.2 → codespine-0.7.0}/codespine/noise/__init__.py +0 -0
  32. {codespine-0.6.2 → codespine-0.7.0}/codespine/noise/blocklist.py +0 -0
  33. {codespine-0.6.2 → codespine-0.7.0}/codespine/overlay/__init__.py +0 -0
  34. {codespine-0.6.2 → codespine-0.7.0}/codespine/overlay/git_state.py +0 -0
  35. {codespine-0.6.2 → codespine-0.7.0}/codespine/overlay/merge.py +0 -0
  36. {codespine-0.6.2 → codespine-0.7.0}/codespine/overlay/store.py +0 -0
  37. {codespine-0.6.2 → codespine-0.7.0}/codespine/search/__init__.py +0 -0
  38. {codespine-0.6.2 → codespine-0.7.0}/codespine/search/bm25.py +0 -0
  39. {codespine-0.6.2 → codespine-0.7.0}/codespine/search/fuzzy.py +0 -0
  40. {codespine-0.6.2 → codespine-0.7.0}/codespine/search/hybrid.py +0 -0
  41. {codespine-0.6.2 → codespine-0.7.0}/codespine/search/rrf.py +0 -0
  42. {codespine-0.6.2 → codespine-0.7.0}/codespine/search/vector.py +0 -0
  43. {codespine-0.6.2 → codespine-0.7.0}/codespine/watch/__init__.py +0 -0
  44. {codespine-0.6.2 → codespine-0.7.0}/codespine/watch/watcher.py +0 -0
  45. {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/dependency_links.txt +0 -0
  46. {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/entry_points.txt +0 -0
  47. {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/requires.txt +0 -0
  48. {codespine-0.6.2 → codespine-0.7.0}/codespine.egg-info/top_level.txt +0 -0
  49. {codespine-0.6.2 → codespine-0.7.0}/gindex.py +0 -0
  50. {codespine-0.6.2 → codespine-0.7.0}/setup.cfg +0 -0
  51. {codespine-0.6.2 → codespine-0.7.0}/tests/test_branch_diff_normalize.py +0 -0
  52. {codespine-0.6.2 → codespine-0.7.0}/tests/test_call_resolver.py +0 -0
  53. {codespine-0.6.2 → codespine-0.7.0}/tests/test_community_detection.py +0 -0
  54. {codespine-0.6.2 → codespine-0.7.0}/tests/test_deadcode.py +0 -0
  55. {codespine-0.6.2 → codespine-0.7.0}/tests/test_index_and_hybrid.py +0 -0
  56. {codespine-0.6.2 → codespine-0.7.0}/tests/test_java_parser.py +0 -0
  57. {codespine-0.6.2 → codespine-0.7.0}/tests/test_multimodule_index.py +0 -0
  58. {codespine-0.6.2 → codespine-0.7.0}/tests/test_overlay.py +0 -0
  59. {codespine-0.6.2 → codespine-0.7.0}/tests/test_search_ranking.py +0 -0
  60. {codespine-0.6.2 → codespine-0.7.0}/tests/test_store_recovery.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codespine
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Local Java code intelligence indexer backed by a graph database
5
5
  Author: CodeSpine contributors
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
1
  """CodeSpine package."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.6.2"
4
+ __version__ = "0.7.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
- frame = self.execute(query, params or {}).get_as_df()
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
  # ------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codespine
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Local Java code intelligence indexer backed by a graph database
5
5
  Author: CodeSpine contributors
6
6
  License: MIT License
@@ -5,6 +5,7 @@ pyproject.toml
5
5
  codespine/__init__.py
6
6
  codespine/cli.py
7
7
  codespine/config.py
8
+ codespine/guide.py
8
9
  codespine.egg-info/PKG-INFO
9
10
  codespine.egg-info/SOURCES.txt
10
11
  codespine.egg-info/dependency_links.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codespine"
7
- version = "0.6.2"
7
+ version = "0.7.0"
8
8
  description = "Local Java code intelligence indexer backed by a graph database"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes
File without changes
File without changes
File without changes