coderay 1.0.2__tar.gz → 1.0.4__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 (59) hide show
  1. {coderay-1.0.2/src/coderay.egg-info → coderay-1.0.4}/PKG-INFO +18 -22
  2. {coderay-1.0.2 → coderay-1.0.4}/README.md +2 -2
  3. {coderay-1.0.2 → coderay-1.0.4}/pyproject.toml +16 -19
  4. coderay-1.0.4/src/coderay/chunking/chunker.py +86 -0
  5. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/cli/commands.py +53 -65
  6. coderay-1.0.4/src/coderay/core/config.py +291 -0
  7. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/core/models.py +0 -1
  8. coderay-1.0.4/src/coderay/embedding/base.py +34 -0
  9. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/graph/__init__.py +6 -2
  10. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/graph/builder.py +53 -24
  11. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/graph/code_graph.py +6 -3
  12. coderay-1.0.4/src/coderay/graph/extractor.py +665 -0
  13. coderay-1.0.4/src/coderay/mcp_server/errors.py +11 -0
  14. coderay-1.0.4/src/coderay/mcp_server/server.py +223 -0
  15. coderay-1.0.4/src/coderay/parsing/base.py +139 -0
  16. coderay-1.0.4/src/coderay/parsing/languages.py +268 -0
  17. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/pipeline/indexer.py +38 -27
  18. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/pipeline/watcher.py +9 -14
  19. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/retrieval/boosting.py +11 -25
  20. coderay-1.0.4/src/coderay/retrieval/models.py +63 -0
  21. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/retrieval/search.py +10 -17
  22. coderay-1.0.4/src/coderay/skeleton/extractor.py +150 -0
  23. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/state/machine.py +5 -3
  24. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/storage/lancedb.py +13 -19
  25. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/vcs/git.py +2 -2
  26. {coderay-1.0.2 → coderay-1.0.4/src/coderay.egg-info}/PKG-INFO +18 -22
  27. {coderay-1.0.2 → coderay-1.0.4}/src/coderay.egg-info/SOURCES.txt +4 -2
  28. coderay-1.0.4/src/coderay.egg-info/requires.txt +29 -0
  29. coderay-1.0.2/src/coderay/chunking/chunker.py +0 -127
  30. coderay-1.0.2/src/coderay/chunking/registry.py +0 -190
  31. coderay-1.0.2/src/coderay/core/config.py +0 -73
  32. coderay-1.0.2/src/coderay/embedding/base.py +0 -60
  33. coderay-1.0.2/src/coderay/embedding/openai.py +0 -87
  34. coderay-1.0.2/src/coderay/graph/extractor.py +0 -315
  35. coderay-1.0.2/src/coderay/mcp_server/server.py +0 -178
  36. coderay-1.0.2/src/coderay/skeleton/extractor.py +0 -140
  37. coderay-1.0.2/src/coderay.egg-info/requires.txt +0 -34
  38. {coderay-1.0.2 → coderay-1.0.4}/LICENSE +0 -0
  39. {coderay-1.0.2 → coderay-1.0.4}/setup.cfg +0 -0
  40. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/__init__.py +0 -0
  41. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/chunking/__init__.py +0 -0
  42. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/cli/__init__.py +0 -0
  43. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/core/__init__.py +0 -0
  44. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/core/lock.py +0 -0
  45. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/core/timing.py +0 -0
  46. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/core/utils.py +0 -0
  47. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/embedding/__init__.py +0 -0
  48. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/embedding/local.py +0 -0
  49. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/mcp_server/__init__.py +0 -0
  50. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/pipeline/__init__.py +0 -0
  51. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/retrieval/__init__.py +0 -0
  52. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/skeleton/__init__.py +0 -0
  53. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/state/__init__.py +0 -0
  54. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/state/version.py +0 -0
  55. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/storage/__init__.py +0 -0
  56. {coderay-1.0.2 → coderay-1.0.4}/src/coderay/vcs/__init__.py +0 -0
  57. {coderay-1.0.2 → coderay-1.0.4}/src/coderay.egg-info/dependency_links.txt +0 -0
  58. {coderay-1.0.2 → coderay-1.0.4}/src/coderay.egg-info/entry_points.txt +0 -0
  59. {coderay-1.0.2 → coderay-1.0.4}/src/coderay.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coderay
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: X-ray your codebase — semantic search, code graphs, file skeletons, and MCP server
5
5
  Author-email: Bogdan Copocean <bogdancopocean@gmail.com>
6
6
  License-Expression: MIT
@@ -20,35 +20,31 @@ Classifier: Topic :: Text Processing :: Indexing
20
20
  Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: python-dotenv>=1.0.0
24
- Requires-Dist: networkx>=3.0
25
- Requires-Dist: tree-sitter>=0.24.0
23
+ Requires-Dist: python-dotenv>=1.2.0
24
+ Requires-Dist: networkx>=3.4
25
+ Requires-Dist: tree-sitter>=0.25.0
26
26
  Requires-Dist: tree-sitter-python>=0.25.0
27
- Requires-Dist: lancedb>=0.5.0
28
- Requires-Dist: pyyaml>=6.0
29
- Requires-Dist: click>=8.0
30
- Requires-Dist: filelock>=3.0
31
- Requires-Dist: fastembed>=0.4.0
32
- Requires-Dist: mcp>=1.0.0
33
- Requires-Dist: watchdog>=4.0.0
34
- Requires-Dist: pathspec>=0.12.0
35
- Provides-Extra: openai
36
- Requires-Dist: openai>=1.0.0; extra == "openai"
27
+ Requires-Dist: lancedb>=0.29.0
28
+ Requires-Dist: pyyaml>=6.0.3
29
+ Requires-Dist: click>=8.3.0
30
+ Requires-Dist: filelock>=3.25.0
31
+ Requires-Dist: fastembed>=0.7.0
32
+ Requires-Dist: fastmcp==3.1.0
33
+ Requires-Dist: watchdog>=6.0.0
34
+ Requires-Dist: pathspec>=1.0.0
37
35
  Provides-Extra: languages
38
- Requires-Dist: tree-sitter-javascript>=0.23.0; extra == "languages"
39
- Requires-Dist: tree-sitter-typescript>=0.23.0; extra == "languages"
40
- Requires-Dist: tree-sitter-go>=0.23.0; extra == "languages"
36
+ Requires-Dist: tree-sitter-javascript>=0.25.0; extra == "languages"
37
+ Requires-Dist: tree-sitter-typescript>=0.23.2; extra == "languages"
38
+ Requires-Dist: tree-sitter-go>=0.25.0; extra == "languages"
41
39
  Provides-Extra: dev
42
40
  Requires-Dist: pytest>=7.0; extra == "dev"
43
41
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
44
42
  Requires-Dist: ruff>=0.8.0; extra == "dev"
45
43
  Requires-Dist: mypy>=1.0.0; extra == "dev"
46
- Requires-Dist: openai>=1.0.0; extra == "dev"
47
- Requires-Dist: httpx>=0.27.0; extra == "dev"
48
44
  Provides-Extra: maintain
49
45
  Requires-Dist: pylance>=0.15.0; extra == "maintain"
50
46
  Provides-Extra: all
51
- Requires-Dist: coderay[dev,languages,maintain,openai]; extra == "all"
47
+ Requires-Dist: coderay[dev,languages,maintain]; extra == "all"
52
48
  Dynamic: license-file
53
49
 
54
50
  # CodeRay
@@ -72,7 +68,7 @@ AI coding assistants and a standalone CLI.
72
68
  pip install coderay
73
69
  ```
74
70
 
75
- With all extras (OpenAI embeddings, JS/TS/Go support, MCP server):
71
+ With all extras (JS/TS/Go support, MCP server tools):
76
72
 
77
73
  ```bash
78
74
  pip install "coderay[all]"
@@ -132,7 +128,7 @@ Optional `config.yaml` in the index directory:
132
128
 
133
129
  ```yaml
134
130
  embedder:
135
- provider: local # local | openai
131
+ provider: local
136
132
  model: all-MiniLM-L6-v2
137
133
  dimensions: 384
138
134
 
@@ -19,7 +19,7 @@ AI coding assistants and a standalone CLI.
19
19
  pip install coderay
20
20
  ```
21
21
 
22
- With all extras (OpenAI embeddings, JS/TS/Go support, MCP server):
22
+ With all extras (JS/TS/Go support, MCP server tools):
23
23
 
24
24
  ```bash
25
25
  pip install "coderay[all]"
@@ -79,7 +79,7 @@ Optional `config.yaml` in the index directory:
79
79
 
80
80
  ```yaml
81
81
  embedder:
82
- provider: local # local | openai
82
+ provider: local
83
83
  model: all-MiniLM-L6-v2
84
84
  dimensions: 384
85
85
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "coderay"
7
- version = "1.0.2"
7
+ version = "1.0.4"
8
8
  description = "X-ray your codebase — semantic search, code graphs, file skeletons, and MCP server"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -25,18 +25,18 @@ classifiers = [
25
25
  "Topic :: Text Processing :: Indexing",
26
26
  ]
27
27
  dependencies = [
28
- "python-dotenv>=1.0.0",
29
- "networkx>=3.0",
30
- "tree-sitter>=0.24.0",
28
+ "python-dotenv>=1.2.0",
29
+ "networkx>=3.4",
30
+ "tree-sitter>=0.25.0",
31
31
  "tree-sitter-python>=0.25.0",
32
- "lancedb>=0.5.0",
33
- "pyyaml>=6.0",
34
- "click>=8.0",
35
- "filelock>=3.0",
36
- "fastembed>=0.4.0",
37
- "mcp>=1.0.0",
38
- "watchdog>=4.0.0",
39
- "pathspec>=0.12.0",
32
+ "lancedb>=0.29.0",
33
+ "pyyaml>=6.0.3",
34
+ "click>=8.3.0",
35
+ "filelock>=3.25.0",
36
+ "fastembed>=0.7.0",
37
+ "fastmcp==3.1.0",
38
+ "watchdog>=6.0.0",
39
+ "pathspec>=1.0.0",
40
40
  ]
41
41
 
42
42
  [project.urls]
@@ -45,25 +45,22 @@ Repository = "https://github.com/bogdan-copocean/coderay"
45
45
  Issues = "https://github.com/bogdan-copocean/coderay/issues"
46
46
 
47
47
  [project.optional-dependencies]
48
- openai = ["openai>=1.0.0"]
49
48
  languages = [
50
- "tree-sitter-javascript>=0.23.0",
51
- "tree-sitter-typescript>=0.23.0",
52
- "tree-sitter-go>=0.23.0",
49
+ "tree-sitter-javascript>=0.25.0",
50
+ "tree-sitter-typescript>=0.23.2",
51
+ "tree-sitter-go>=0.25.0",
53
52
  ]
54
53
  dev = [
55
54
  "pytest>=7.0",
56
55
  "pytest-cov>=4.0",
57
56
  "ruff>=0.8.0",
58
57
  "mypy>=1.0.0",
59
- "openai>=1.0.0",
60
- "httpx>=0.27.0",
61
58
  ]
62
59
  maintain = [
63
60
  "pylance>=0.15.0",
64
61
  ]
65
62
  all = [
66
- "coderay[openai,languages,dev,maintain]",
63
+ "coderay[languages,dev,maintain]",
67
64
  ]
68
65
 
69
66
  [project.scripts]
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ from coderay.core.models import Chunk
7
+ from coderay.parsing.base import BaseTreeSitterParser, parse_file
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ MODULE_SYMBOL = "<module>"
12
+
13
+
14
+ class ChunkingTreeSitterParser(BaseTreeSitterParser):
15
+ """Tree-sitter based chunker for source files."""
16
+
17
+ def collect_chunks(self) -> list[Chunk]:
18
+ """Collect all chunks for the configured file and language."""
19
+ tree = self.get_tree()
20
+ root = tree.root_node
21
+ chunks: list[Chunk] = []
22
+
23
+ if preamble_lines := self._collect_preamble_lines(root):
24
+ chunks.append(
25
+ Chunk(
26
+ path=self.file_path,
27
+ start_line=1,
28
+ end_line=root.end_point[0] + 1,
29
+ symbol=MODULE_SYMBOL,
30
+ content="\n".join(preamble_lines),
31
+ )
32
+ )
33
+
34
+ def _dfs(node) -> None:
35
+ if node.type in self._ctx.lang_cfg.chunker.chunk_types:
36
+ if (
37
+ node.parent
38
+ and node.parent.type in self._ctx.lang_cfg.chunker.chunk_types
39
+ ):
40
+ for child in node.children:
41
+ _dfs(child)
42
+ return
43
+ start_line = node.start_point[0] + 1
44
+ end_line = node.end_point[0] + 1
45
+ text = self.node_text(node)
46
+ symbol = self.identifier_from_node(node) or f"<{node.type}>"
47
+ chunks.append(
48
+ Chunk(
49
+ path=self.file_path,
50
+ start_line=start_line,
51
+ end_line=end_line,
52
+ symbol=symbol,
53
+ content=text,
54
+ )
55
+ )
56
+ for child in node.children:
57
+ _dfs(child)
58
+
59
+ _dfs(root)
60
+ logger.debug("Chunked %s: %d chunks", self.file_path, len(chunks))
61
+ return chunks
62
+
63
+ def _collect_preamble_lines(self, root) -> list[str]:
64
+ """Collect top-level lines that are NOT part of any chunk_type definition."""
65
+ lines: list[str] = []
66
+ for child in root.children:
67
+ if child.type in self._ctx.lang_cfg.chunker.chunk_types:
68
+ continue
69
+ text = self.node_text(child).strip()
70
+ if text:
71
+ lines.append(text)
72
+ return lines
73
+
74
+
75
+ def chunk_file(path: str | Path, content: str) -> list[Chunk]:
76
+ """Chunk a source file into semantic units (functions, classes, preamble)."""
77
+ ctx = parse_file(path, content)
78
+ if ctx is None:
79
+ logger.warning("No language config for %s", path)
80
+ return []
81
+ parser = ChunkingTreeSitterParser(ctx)
82
+ try:
83
+ return parser.collect_chunks()
84
+ except Exception as e: # pragma: no cover - defensive logging
85
+ logger.warning("Chunking failed for %s: %s", path, e)
86
+ return []
@@ -14,7 +14,7 @@ from coderay.retrieval.search import Retrieval
14
14
  from coderay.state.machine import StateMachine
15
15
  from coderay.storage.lancedb import index_exists
16
16
 
17
- # Load .env so OPENAI_API_KEY etc. are available (e.g. for embedder).
17
+ # Load .env so configuration and environment variables are available.
18
18
  load_dotenv()
19
19
 
20
20
  # ANSI colors (safe when not TTY we can strip or leave)
@@ -39,21 +39,22 @@ def _setup_logging(verbose: bool = False) -> None:
39
39
  level=level,
40
40
  datefmt="%H:%M:%S",
41
41
  )
42
- # Suppress noisy OpenAI/HTTP logging; keep only warnings and errors
43
- for name in ("openai", "httpx", "httpcore"):
42
+ # Suppress noisy HTTP logging; keep only warnings and errors
43
+ for name in ("httpx", "httpcore"):
44
44
  logging.getLogger(name).setLevel(logging.WARNING)
45
45
 
46
46
 
47
47
  @click.group()
48
- @click.option("--index-dir", default=".index", help="Index directory (default .index)")
49
48
  @click.option("-v", "--verbose", is_flag=True, default=False, help="Verbose logging")
50
49
  @click.pass_context
51
- def cli(ctx: click.Context, index_dir: str, verbose: bool) -> None:
50
+ def cli(ctx: click.Context, verbose: bool) -> None:
52
51
  """CodeRay — build, update, search, and inspect the index."""
53
52
  _setup_logging(verbose)
54
53
  ctx.ensure_object(dict)
55
- ctx.obj["index_dir"] = Path(index_dir)
56
54
  ctx.obj["verbose"] = verbose
55
+ from coderay.core.config import get_config
56
+
57
+ get_config()
57
58
 
58
59
 
59
60
  @cli.command()
@@ -67,9 +68,12 @@ def cli(ctx: click.Context, index_dir: str, verbose: bool) -> None:
67
68
  @click.pass_context
68
69
  def build(ctx: click.Context, full: bool, repo: Path) -> None:
69
70
  """Build or rebuild the index."""
70
- index_dir = ctx.obj["index_dir"]
71
+ from coderay.core.config import get_config
72
+
73
+ config = get_config()
74
+ index_dir = Path(config.index.path)
71
75
  index_dir.mkdir(parents=True, exist_ok=True)
72
- indexer = Indexer(repo, index_dir)
76
+ indexer = Indexer(repo)
73
77
  t0 = time.time()
74
78
  try:
75
79
  with acquire_indexer_lock(index_dir):
@@ -108,8 +112,11 @@ def build(ctx: click.Context, full: bool, repo: Path) -> None:
108
112
  @click.pass_context
109
113
  def update(ctx: click.Context, repo: Path) -> None:
110
114
  """Incremental update (only changed files). Uses file lock."""
111
- index_dir = ctx.obj["index_dir"]
112
- indexer = Indexer(repo, index_dir)
115
+ from coderay.core.config import get_config
116
+
117
+ config = get_config()
118
+ index_dir = Path(config.index.path)
119
+ indexer = Indexer(repo)
113
120
  t0 = time.time()
114
121
 
115
122
  if not indexer.index_exists():
@@ -132,28 +139,29 @@ def update(ctx: click.Context, repo: Path) -> None:
132
139
  @click.argument("query_text", required=True)
133
140
  @click.option("--top-k", "top_k", default=10, help="Number of results")
134
141
  @click.option("--path-prefix", help="Filter by path prefix")
135
- @click.option("--language", help="Filter by language (e.g. python)")
136
142
  @click.pass_context
137
143
  def search_cmd(
138
144
  ctx: click.Context,
139
145
  query_text: str,
140
146
  top_k: int,
141
147
  path_prefix: str | None,
142
- language: str | None,
143
148
  ) -> None:
144
149
  """Semantic search the index."""
145
- index_dir = ctx.obj["index_dir"]
150
+ from coderay.core.config import get_config
151
+
152
+ config = get_config()
153
+ index_dir = Path(config.index.path)
146
154
  if not index_exists(index_dir):
147
155
  click.echo(_color("No index found. Run 'coderay build' first.", YELLOW))
148
156
  ctx.exit(1)
149
157
 
150
- sm = StateMachine(index_dir)
158
+ sm = StateMachine()
151
159
  current_state = sm.current_state
152
160
  if current_state is None:
153
161
  click.echo(_color("No index state. Run 'coderay build' first.", YELLOW))
154
162
  ctx.exit(1)
155
163
 
156
- retrieval = Retrieval(index_dir)
164
+ retrieval = Retrieval()
157
165
  click.echo(_color(f"Searching: {query_text!r}", CYAN))
158
166
  t0 = time.perf_counter()
159
167
 
@@ -162,7 +170,6 @@ def search_cmd(
162
170
  current_state=current_state,
163
171
  top_k=top_k,
164
172
  path_prefix=path_prefix,
165
- language=language,
166
173
  )
167
174
  elapsed = time.perf_counter() - t0
168
175
  click.echo(_color(f"Query took {elapsed:.2f}s", BOLD))
@@ -171,31 +178,16 @@ def search_cmd(
171
178
  click.echo(_color("No results.", YELLOW))
172
179
  return
173
180
 
174
- score_type = results[0].get("score_type", "cosine")
175
- if score_type == "rrf":
176
- click.echo(
177
- _color("Scoring: hybrid (RRF) — relative ranking, not a percentage", CYAN)
178
- )
179
- else:
180
- click.echo(_color("Scoring: cosine similarity (0-1)", CYAN))
181
-
182
181
  for i, r in enumerate(results, 1):
183
182
  path = r.get("path", "?")
184
183
  start = r.get("start_line", 0)
185
184
  end = r.get("end_line", 0)
186
185
  symbol = r.get("symbol", "?")
187
- score = r.get("score", 0)
188
- if score_type == "cosine":
189
- score_str = f"score={score:.4f} ({score:.0%})"
190
- else:
191
- score_str = f"score={score:.4f} (rrf)"
192
186
  preview = (r.get("content") or "")[:200].replace("\n", " ")
193
187
  if len(r.get("content") or "") > 200:
194
188
  preview += "..."
195
189
  click.echo("")
196
- click.echo(
197
- _color(f" {i}. {path}:{start}-{end} ({symbol}) {score_str}", GREEN)
198
- )
190
+ click.echo(_color(f" {i}. {path}:{start}-{end} ({symbol})", GREEN))
199
191
  click.echo(f" {preview}")
200
192
 
201
193
 
@@ -226,8 +218,11 @@ def list_cmd(
226
218
  show_content: bool,
227
219
  ) -> None:
228
220
  """Show what is in the index: chunk counts and/or chunk list."""
229
- index_dir = ctx.obj["index_dir"]
230
- retrieval = Retrieval(index_dir)
221
+ from coderay.core.config import get_config
222
+
223
+ config = get_config()
224
+ index_dir = Path(config.index.path)
225
+ retrieval = Retrieval()
231
226
  if not index_exists(index_dir):
232
227
  click.echo(_color("No index found. Run 'coderay build' first.", YELLOW))
233
228
  ctx.exit(1)
@@ -261,23 +256,23 @@ def list_cmd(
261
256
  @click.pass_context
262
257
  def status(ctx: click.Context) -> None:
263
258
  """Show index status: state, branch, commit, chunk count."""
264
- index_dir = ctx.obj["index_dir"]
259
+ from coderay.core.config import get_config
260
+ from coderay.state.version import read_index_version
261
+ from coderay.storage.lancedb import Store
262
+
263
+ config = get_config()
264
+ index_dir = Path(config.index.path)
265
265
  if not index_exists(index_dir):
266
266
  click.echo(_color("No index found. Run 'coderay build' first.", YELLOW))
267
267
  ctx.exit(1)
268
268
 
269
- sm = StateMachine(index_dir)
269
+ sm = StateMachine()
270
270
  state = sm.current_state
271
271
  if state is None:
272
272
  click.echo(_color("No index state found.", YELLOW))
273
273
  ctx.exit(1)
274
274
 
275
- from coderay.core.config import get_embedding_dimensions, load_config
276
- from coderay.state.version import read_index_version
277
- from coderay.storage.lancedb import Store
278
-
279
- config = load_config(index_dir)
280
- store = Store(index_dir, dimensions=get_embedding_dimensions(config))
275
+ store = Store()
281
276
  chunks = store.chunk_count()
282
277
  version = read_index_version(index_dir)
283
278
 
@@ -302,12 +297,15 @@ def status(ctx: click.Context) -> None:
302
297
  @click.pass_context
303
298
  def maintain(ctx: click.Context, repo: Path) -> None:
304
299
  """Reclaim space and compact the index."""
305
- index_dir = ctx.obj["index_dir"]
300
+ from coderay.core.config import get_config
301
+
302
+ config = get_config()
303
+ index_dir = Path(config.index.path)
306
304
  if not index_exists(index_dir):
307
305
  click.echo(_color("No index found. Run 'coderay build' first.", YELLOW))
308
306
  ctx.exit(1)
309
307
  click.echo(_color("Maintaining index...", CYAN))
310
- indexer = Indexer(repo, index_dir)
308
+ indexer = Indexer(repo)
311
309
  with acquire_indexer_lock(index_dir):
312
310
  result = indexer.maintain()
313
311
  if result.get("cleanup_done"):
@@ -357,8 +355,11 @@ def graph_cmd(
357
355
  limit: int,
358
356
  ) -> None:
359
357
  """List call and import graph edges (who calls who, who imports what)."""
360
- index_dir = ctx.obj["index_dir"]
361
- retrieval = Retrieval(index_dir)
358
+ from coderay.core.config import get_config
359
+
360
+ config = get_config()
361
+ index_dir = Path(config.index.path)
362
+ retrieval = Retrieval()
362
363
  if not index_exists(index_dir):
363
364
  click.echo(_color("No index found. Run 'coderay build' first.", YELLOW))
364
365
  ctx.exit(1)
@@ -394,12 +395,6 @@ def graph_cmd(
394
395
  type=click.Path(exists=True, path_type=Path),
395
396
  help="Repo root",
396
397
  )
397
- @click.option(
398
- "--debounce",
399
- type=float,
400
- default=None,
401
- help="Debounce seconds (default from config, typically 2s)",
402
- )
403
398
  @click.option(
404
399
  "--quiet",
405
400
  is_flag=True,
@@ -409,14 +404,14 @@ def graph_cmd(
409
404
  def watch(
410
405
  ctx: click.Context,
411
406
  repo: Path,
412
- debounce: float | None,
413
407
  quiet: bool,
414
408
  ) -> None:
415
409
  """Watch for file changes and re-index automatically."""
416
- from coderay.core.config import load_config
410
+ from coderay.core.config import get_config
417
411
  from coderay.pipeline.watcher import FileWatcher
418
412
 
419
- index_dir = ctx.obj["index_dir"]
413
+ config = get_config()
414
+ index_dir = Path(config.index.path)
420
415
  if not index_exists(index_dir):
421
416
  click.echo(
422
417
  _color(
@@ -426,27 +421,20 @@ def watch(
426
421
  )
427
422
  ctx.exit(1)
428
423
 
429
- config = load_config(index_dir)
430
- if debounce is not None:
431
- config.setdefault("watch", {})["debounce_seconds"] = debounce
432
-
433
424
  if quiet:
434
425
  logging.getLogger("coderay.pipeline.watcher").setLevel(logging.WARNING)
435
426
 
436
- watcher = FileWatcher(repo, index_dir, config=config)
427
+ watcher = FileWatcher(repo, index_dir)
437
428
 
438
429
  click.echo(
439
430
  _color(
440
431
  f"Watching {repo.resolve()} "
441
- f"(debounce={config.get('watch', {}).get('debounce_seconds', 2)}s, "
442
- f"Ctrl+C to stop)",
432
+ f"(debounce={config.watcher.debounce}s, Ctrl+C to stop)",
443
433
  CYAN,
444
434
  )
445
435
  )
446
- # Do an incremental update at start-up
447
- index_dir = ctx.obj["index_dir"]
448
436
  index_dir.mkdir(parents=True, exist_ok=True)
449
- indexer = Indexer(repo, index_dir)
437
+ indexer = Indexer(repo)
450
438
  indexer.update_incremental()
451
439
 
452
440
  watcher.start()