Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<details>
<summary>Controlling which files are indexed</summary>
Expand Down Expand Up @@ -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).

</details>

<details>
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions src/semble/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import argparse
import asyncio
import io
import json
import re
import sys
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion src/semble/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/semble/version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version_triple__ = (0, 5, 3)
__version_triple__ = (0, 5, 4)
__version__ = ".".join(map(str, __version_triple__))
4 changes: 4 additions & 0 deletions tests/test_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
28 changes: 13 additions & 15 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading