diff --git a/README.md b/README.md
index 87901f15..6d1c6489 100644
--- a/README.md
+++ b/README.md
@@ -98,9 +98,12 @@ semble search "deployment guide" ./my-project --content docs # or: config, all
# Find code similar to a known location
semble find-related src/auth.py 42 ./my-project
+
+# Show only the first N lines of each result's snippet (0 = path/line range only)
+semble search "authentication flow" ./my-project --max-snippet-lines 10
```
-`--content` accepts `code` (default), `docs`, `config`, or `all`. `path` defaults to the current directory when omitted; git URLs are accepted. If `semble` is not on `$PATH`, use `uvx --from "semble[mcp]" semble` in its place.
+`--content` accepts `code` (default), `docs`, `config`, or `all`. `path` defaults to the current directory when omitted; git URLs are accepted. If `semble` is not on `$PATH`, use `uvx --from "semble[mcp]" semble` in its place. `semble --version` (or `-V`) prints the installed version.
Controlling which files are indexed
@@ -173,6 +176,8 @@ By default, your Semble savings statistics and any saved indexes are stored in t
On first use, Semble also downloads the embedding model from Hugging Face and caches it in the standard Hugging Face cache (`~/.cache/huggingface/` by default, or `$HF_HOME` if set); this only happens once and requires network access.
+Use `semble clear` to remove cached data: `semble clear index` (saved indexes), `semble clear savings` (usage stats), `semble clear orphans` (indexes for repos no longer present on disk), or `semble clear all` (everything).
+
diff --git a/pyproject.toml b/pyproject.toml
index 5da88b1d..665fd5ce 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,7 +30,6 @@ dependencies = [
"vicinity>=0.4.4",
"numpy>=1.24.0",
"pathspec>=0.12",
- "tree-sitter>=0.25,<0.26",
"orjson",
"questionary>=2.0,<3.0",
"semble-grammars>=0.1.2",
diff --git a/src/semble/cli.py b/src/semble/cli.py
index dad4770d..eddd3df2 100644
--- a/src/semble/cli.py
+++ b/src/semble/cli.py
@@ -1,5 +1,6 @@
import argparse
import asyncio
+import io
import json
import re
import sys
@@ -66,6 +67,10 @@ def _add_content_args(p: argparse.ArgumentParser) -> None:
def main() -> None:
"""Entry point for the semble command-line tool."""
+ # Non-UTF-8 Windows consoles can't encode glyphs like "✓" and would otherwise crash.
+ for stream in (sys.stdout, sys.stderr):
+ if isinstance(stream, io.TextIOWrapper):
+ stream.reconfigure(errors="replace")
if len(sys.argv) > 1 and sys.argv[1] in _CLI_DISPATCH_ARGS:
_cli_main()
else:
diff --git a/src/semble/utils.py b/src/semble/utils.py
index 19d786c9..640b0c42 100644
--- a/src/semble/utils.py
+++ b/src/semble/utils.py
@@ -22,9 +22,11 @@ def resolve_chunk(chunks: list[Chunk], file_path: str, line: int) -> Chunk | Non
Reconstructs a Chunk from its JSON-primitive MCP tool arguments (file_path + line)
before calling into the library.
"""
+ # Normalize separators: file_path is stored with the platform's native separator.
+ file_path = file_path.replace("\\", "/")
fallback = None
for chunk in chunks:
- if chunk.file_path == file_path and chunk.start_line <= line <= chunk.end_line:
+ if chunk.file_path.replace("\\", "/") == file_path and chunk.start_line <= line <= chunk.end_line:
if line < chunk.end_line:
return chunk
if fallback is None: # line == end_line: boundary; keep as fallback for end-of-file chunks
diff --git a/src/semble/version.py b/src/semble/version.py
index ef409fb0..5073cce7 100644
--- a/src/semble/version.py
+++ b/src/semble/version.py
@@ -1,2 +1,2 @@
-__version_triple__ = (0, 5, 3)
+__version_triple__ = (0, 5, 4)
__version__ = ".".join(map(str, __version_triple__))
diff --git a/tests/test_mcp.py b/tests/test_mcp.py
index 77a46046..0f318f26 100644
--- a/tests/test_mcp.py
+++ b/tests/test_mcp.py
@@ -65,6 +65,10 @@ def test_resolve_chunk() -> None:
# Line out of range returns None.
assert resolve_chunk([interior], "src/a.py", 99) is None
+ # Separator mismatch (e.g. backslash-stored path, forward-slash query) still matches.
+ backslash_chunk = make_chunk("line1\nline2\nline3", "src\\a.py")
+ assert resolve_chunk([backslash_chunk], "src/a.py", 2) is backslash_chunk
+
@pytest.mark.parametrize(
("path", "expected"),
diff --git a/uv.lock b/uv.lock
index 74fc034b..7630799b 100644
--- a/uv.lock
+++ b/uv.lock
@@ -328,7 +328,7 @@ resolution-markers = [
"python_full_version < '3.11'",
]
dependencies = [
- { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" }
wheels = [
@@ -400,7 +400,7 @@ resolution-markers = [
"python_full_version == '3.11.*'",
]
dependencies = [
- { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" } },
]
sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" }
wheels = [
@@ -730,7 +730,7 @@ name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+ { name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
@@ -2900,10 +2900,10 @@ resolution-markers = [
"python_full_version < '3.11'",
]
dependencies = [
- { name = "joblib", marker = "python_full_version < '3.11'" },
- { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
- { name = "threadpoolctl", marker = "python_full_version < '3.11'" },
+ { name = "joblib" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } },
+ { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } },
+ { name = "threadpoolctl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" }
wheels = [
@@ -2949,10 +2949,10 @@ resolution-markers = [
"python_full_version == '3.11.*'",
]
dependencies = [
- { name = "joblib", marker = "python_full_version >= '3.11'" },
- { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
- { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
- { name = "threadpoolctl", marker = "python_full_version >= '3.11'" },
+ { name = "joblib" },
+ { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" } },
+ { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" } },
+ { name = "threadpoolctl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" }
wheels = [
@@ -3002,7 +3002,7 @@ resolution-markers = [
"python_full_version < '3.11'",
]
dependencies = [
- { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } },
]
sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
wheels = [
@@ -3063,7 +3063,7 @@ resolution-markers = [
"python_full_version == '3.11.*'",
]
dependencies = [
- { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" } },
]
sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" }
wheels = [
@@ -3140,7 +3140,6 @@ dependencies = [
{ name = "pathspec" },
{ name = "questionary" },
{ name = "semble-grammars" },
- { name = "tree-sitter" },
{ name = "vicinity" },
]
@@ -3187,7 +3186,6 @@ requires-dist = [
{ name = "semble-grammars", specifier = ">=0.1.2" },
{ name = "sentence-transformers", marker = "extra == 'benchmark'", specifier = ">=3.0" },
{ name = "tiktoken", marker = "extra == 'benchmark'", specifier = ">=0.7" },
- { name = "tree-sitter", specifier = ">=0.25,<0.26" },
{ name = "vicinity", specifier = ">=0.4.4" },
]
provides-extras = ["mcp", "benchmark", "dev"]