seam-code 0.3.0__py3-none-any.whl
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.
- seam/__init__.py +3 -0
- seam/_data/schema.sql +225 -0
- seam/_web/assets/index-BL_tqprR.js +216 -0
- seam/_web/assets/index-GTKUhVyD.css +1 -0
- seam/_web/index.html +13 -0
- seam/analysis/__init__.py +14 -0
- seam/analysis/affected.py +254 -0
- seam/analysis/builtins.py +966 -0
- seam/analysis/byte_budget.py +217 -0
- seam/analysis/changes.py +709 -0
- seam/analysis/cluster_naming.py +260 -0
- seam/analysis/clustering.py +216 -0
- seam/analysis/confidence.py +699 -0
- seam/analysis/embeddings.py +195 -0
- seam/analysis/flows.py +708 -0
- seam/analysis/impact.py +444 -0
- seam/analysis/imports.py +994 -0
- seam/analysis/imports_ext.py +780 -0
- seam/analysis/imports_resolve.py +176 -0
- seam/analysis/processes.py +453 -0
- seam/analysis/relevance.py +155 -0
- seam/analysis/rwr.py +129 -0
- seam/analysis/staleness.py +328 -0
- seam/analysis/steer.py +282 -0
- seam/analysis/synthesis.py +253 -0
- seam/analysis/synthesis_channels.py +433 -0
- seam/analysis/testpaths.py +103 -0
- seam/analysis/traversal.py +470 -0
- seam/cli/__init__.py +0 -0
- seam/cli/install.py +232 -0
- seam/cli/main.py +2602 -0
- seam/cli/output.py +137 -0
- seam/cli/read.py +244 -0
- seam/cli/serve.py +145 -0
- seam/config.py +551 -0
- seam/indexer/__init__.py +0 -0
- seam/indexer/cluster_index.py +425 -0
- seam/indexer/db.py +496 -0
- seam/indexer/embedding_index.py +183 -0
- seam/indexer/field_access.py +536 -0
- seam/indexer/field_access_c_cpp.py +643 -0
- seam/indexer/field_access_ext.py +708 -0
- seam/indexer/field_access_ext2.py +408 -0
- seam/indexer/field_access_go_rust.py +737 -0
- seam/indexer/field_access_php_swift.py +888 -0
- seam/indexer/field_access_ts.py +626 -0
- seam/indexer/graph.py +321 -0
- seam/indexer/graph_c.py +562 -0
- seam/indexer/graph_c_cpp.py +39 -0
- seam/indexer/graph_common.py +644 -0
- seam/indexer/graph_cpp.py +615 -0
- seam/indexer/graph_csharp.py +651 -0
- seam/indexer/graph_go.py +723 -0
- seam/indexer/graph_go_rust.py +39 -0
- seam/indexer/graph_java.py +689 -0
- seam/indexer/graph_java_csharp.py +38 -0
- seam/indexer/graph_php.py +914 -0
- seam/indexer/graph_python.py +628 -0
- seam/indexer/graph_ruby.py +748 -0
- seam/indexer/graph_rust.py +653 -0
- seam/indexer/graph_scope_infer.py +902 -0
- seam/indexer/graph_scope_infer_ext.py +723 -0
- seam/indexer/graph_scope_infer_ext2.py +992 -0
- seam/indexer/graph_swift.py +1014 -0
- seam/indexer/graph_swift_infer.py +515 -0
- seam/indexer/graph_typescript.py +663 -0
- seam/indexer/migrations.py +816 -0
- seam/indexer/parser.py +204 -0
- seam/indexer/pipeline.py +197 -0
- seam/indexer/signatures.py +634 -0
- seam/indexer/signatures_ext.py +780 -0
- seam/indexer/sync.py +287 -0
- seam/indexer/synthesis_index.py +291 -0
- seam/indexer/tokenize.py +79 -0
- seam/installer/__init__.py +67 -0
- seam/installer/claude.py +97 -0
- seam/installer/codex.py +94 -0
- seam/installer/core.py +127 -0
- seam/installer/cursor.py +61 -0
- seam/installer/guide.py +110 -0
- seam/installer/jsonfile.py +85 -0
- seam/installer/markdownfile.py +146 -0
- seam/installer/tomlfile.py +72 -0
- seam/query/__init__.py +0 -0
- seam/query/clusters.py +206 -0
- seam/query/comments.py +217 -0
- seam/query/context.py +293 -0
- seam/query/engine.py +940 -0
- seam/query/fts.py +328 -0
- seam/query/names.py +470 -0
- seam/query/pack.py +433 -0
- seam/query/semantic.py +339 -0
- seam/query/structure.py +727 -0
- seam/server/__init__.py +0 -0
- seam/server/graph_api.py +437 -0
- seam/server/handler_common.py +323 -0
- seam/server/impact_handler.py +615 -0
- seam/server/mcp.py +556 -0
- seam/server/tools.py +697 -0
- seam/server/trace_handler.py +184 -0
- seam/server/web.py +922 -0
- seam/watcher/__init__.py +0 -0
- seam/watcher/__main__.py +56 -0
- seam/watcher/daemon.py +237 -0
- seam_code-0.3.0.dist-info/METADATA +318 -0
- seam_code-0.3.0.dist-info/RECORD +109 -0
- seam_code-0.3.0.dist-info/WHEEL +4 -0
- seam_code-0.3.0.dist-info/entry_points.txt +2 -0
- seam_code-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Local embedding wrapper for semantic search (T3).
|
|
2
|
+
|
|
3
|
+
Leaf module — imports ONLY stdlib + fastembed (lazy).
|
|
4
|
+
numpy is NEVER imported here; we only call .tobytes() on numpy arrays returned by fastembed.
|
|
5
|
+
NO server/cli/query imports.
|
|
6
|
+
|
|
7
|
+
Design decisions:
|
|
8
|
+
- fastembed is an OPTIONAL extra ([semantic]). This module degrades gracefully
|
|
9
|
+
when it is absent: is_available() → False, embed_texts() → [], embed_query() → b''.
|
|
10
|
+
- WHY no numpy import here: numpy is a fastembed transitive dep. Importing it at module
|
|
11
|
+
scope would make this module fail to import in environments where only the base package is
|
|
12
|
+
installed (no [semantic] extra). We call .tobytes() on the array objects fastembed returns
|
|
13
|
+
— that is safe because if fastembed is importable, numpy is also present.
|
|
14
|
+
- TextEmbedding is lazily loaded and cached per model name via _MODEL_CACHE.
|
|
15
|
+
WHY cached: FastEmbed model loading takes ~200ms; reloading on every call would make the
|
|
16
|
+
read path unacceptably slow. FastEmbed models are read-safe; the dict is not locked
|
|
17
|
+
(worst case: two concurrent callers each create an instance; one is discarded — harmless).
|
|
18
|
+
- Never raises: any error in the real path is logged as a warning and the function
|
|
19
|
+
returns the safe default ([] or b'').
|
|
20
|
+
|
|
21
|
+
Public API:
|
|
22
|
+
is_available() -> bool
|
|
23
|
+
symbol_text(name, signature, docstring) -> str
|
|
24
|
+
embed_texts(texts, model) -> list[bytes]
|
|
25
|
+
embed_query(text, model) -> bytes
|
|
26
|
+
_get_model(model) <- internal, exposed for monkeypatching in tests
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
# ── Availability cache ────────────────────────────────────────────────────────
|
|
35
|
+
# None = not yet checked; True/False = cached result.
|
|
36
|
+
# Reset to None in tests that need to simulate absence.
|
|
37
|
+
_fastembed_available: bool | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def is_available() -> bool:
|
|
41
|
+
"""Return True if the fastembed package is importable.
|
|
42
|
+
|
|
43
|
+
The result is cached after the first call — subsequent calls are O(1).
|
|
44
|
+
Never raises: any import error returns False gracefully.
|
|
45
|
+
|
|
46
|
+
WHY cached: this function is called once per query in the read path;
|
|
47
|
+
caching avoids repeated sys.modules lookups in hot code.
|
|
48
|
+
"""
|
|
49
|
+
global _fastembed_available # noqa: PLW0603
|
|
50
|
+
if _fastembed_available is None:
|
|
51
|
+
try:
|
|
52
|
+
import fastembed # noqa: F401
|
|
53
|
+
|
|
54
|
+
_fastembed_available = True
|
|
55
|
+
except Exception: # noqa: BLE001
|
|
56
|
+
_fastembed_available = False
|
|
57
|
+
return bool(_fastembed_available)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ── Model cache ───────────────────────────────────────────────────────────────
|
|
61
|
+
# Maps model_name → TextEmbedding instance. Single-process; not thread-locked
|
|
62
|
+
# (fastembed models are read-safe; worst case we create two and discard one).
|
|
63
|
+
_MODEL_CACHE: dict[str, Any] = {}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _get_model(model: str) -> Any:
|
|
67
|
+
"""Lazy-load and cache a fastembed TextEmbedding instance.
|
|
68
|
+
|
|
69
|
+
WHY: FastEmbed model loading (~200ms) must not happen at import time.
|
|
70
|
+
The cache avoids reloading across multiple calls within one process.
|
|
71
|
+
|
|
72
|
+
Raises: any exception from fastembed (caller is responsible for catching).
|
|
73
|
+
"""
|
|
74
|
+
if model not in _MODEL_CACHE:
|
|
75
|
+
from fastembed import TextEmbedding # type: ignore[import-untyped]
|
|
76
|
+
|
|
77
|
+
_MODEL_CACHE[model] = TextEmbedding(model_name=model)
|
|
78
|
+
return _MODEL_CACHE[model]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ── Public API ────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def symbol_text(
|
|
85
|
+
name: str,
|
|
86
|
+
signature: str | None,
|
|
87
|
+
docstring: str | None,
|
|
88
|
+
) -> str:
|
|
89
|
+
"""Build the canonical text string to embed for a symbol.
|
|
90
|
+
|
|
91
|
+
Combines name + signature + docstring into a single string. Fields that are
|
|
92
|
+
None or empty are omitted (no trailing whitespace, no 'None' literals).
|
|
93
|
+
|
|
94
|
+
WHY this order: the symbol name is the highest-signal token; signature provides
|
|
95
|
+
parameter types and return type (shape of the function); docstring provides
|
|
96
|
+
intent and usage context. Together they cover both syntactic and semantic search.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
name: Symbol name (always included).
|
|
100
|
+
signature: Function/class signature, or None.
|
|
101
|
+
docstring: Docstring / documentation text, or None.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
A single str suitable for embedding. Never raises.
|
|
105
|
+
"""
|
|
106
|
+
parts: list[str] = [name]
|
|
107
|
+
if signature:
|
|
108
|
+
parts.append(signature)
|
|
109
|
+
if docstring:
|
|
110
|
+
parts.append(docstring)
|
|
111
|
+
return "\n".join(parts)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def embed_texts(texts: list[str], model: str) -> list[bytes]:
|
|
115
|
+
"""Embed a list of texts using the local fastembed model.
|
|
116
|
+
|
|
117
|
+
Returns one float32 blob (numpy.array.tobytes()) per input text.
|
|
118
|
+
Returns [] when:
|
|
119
|
+
- fastembed is not available (graceful degradation)
|
|
120
|
+
- the texts list is empty (no-op fast path)
|
|
121
|
+
- any exception occurs (logs a warning, returns [])
|
|
122
|
+
|
|
123
|
+
WHY: The caller (embedding_index.py) batches its own inputs; this function
|
|
124
|
+
processes them all in one fastembed call. Returning [] on error mirrors the
|
|
125
|
+
index_clusters pattern: a single failure logs a warning and the index is left
|
|
126
|
+
unpopulated rather than crashing.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
texts: List of strings to embed. May be empty.
|
|
130
|
+
model: FastEmbed model name (e.g. "BAAI/bge-small-en-v1.5").
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
list[bytes] of length len(texts), each a float32 numpy array serialised
|
|
134
|
+
with .tobytes(). Returns [] on any failure.
|
|
135
|
+
"""
|
|
136
|
+
if not is_available():
|
|
137
|
+
return []
|
|
138
|
+
if not texts:
|
|
139
|
+
return []
|
|
140
|
+
try:
|
|
141
|
+
emb_model = _get_model(model)
|
|
142
|
+
# fastembed TextEmbedding.embed() returns a Generator of numpy float32 arrays.
|
|
143
|
+
vectors = list(emb_model.embed(texts))
|
|
144
|
+
return [v.tobytes() for v in vectors]
|
|
145
|
+
except Exception as exc: # noqa: BLE001
|
|
146
|
+
logger.warning(
|
|
147
|
+
"embeddings.embed_texts: failed to embed %d texts with model %r (%s: %s); "
|
|
148
|
+
"returning [] — run 'seam init --semantic' again or check fastembed install.",
|
|
149
|
+
len(texts),
|
|
150
|
+
model,
|
|
151
|
+
type(exc).__name__,
|
|
152
|
+
exc,
|
|
153
|
+
)
|
|
154
|
+
return []
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def embed_query(text: str, model: str) -> bytes:
|
|
158
|
+
"""Embed a single query string using the local fastembed model.
|
|
159
|
+
|
|
160
|
+
Returns float32 bytes (numpy.array.tobytes()) for the query vector.
|
|
161
|
+
Returns b'' when:
|
|
162
|
+
- fastembed is not available (graceful degradation)
|
|
163
|
+
- any exception occurs (logs a warning, returns b'')
|
|
164
|
+
|
|
165
|
+
WHY query_embed vs embed: FastEmbed offers a separate query_embed() path that
|
|
166
|
+
prepends the BGE query prefix ("Represent this sentence for searching relevant
|
|
167
|
+
passages:"). Using it ensures the query vector lives in the same space as the
|
|
168
|
+
passage embeddings built with embed().
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
text: The search query string.
|
|
172
|
+
model: FastEmbed model name.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
bytes (float32 numpy array serialised with .tobytes()), or b'' on failure.
|
|
176
|
+
"""
|
|
177
|
+
if not is_available():
|
|
178
|
+
return b""
|
|
179
|
+
try:
|
|
180
|
+
emb_model = _get_model(model)
|
|
181
|
+
# query_embed returns a Generator; we consume the first (and only) element.
|
|
182
|
+
vectors = list(emb_model.query_embed([text]))
|
|
183
|
+
if not vectors:
|
|
184
|
+
return b""
|
|
185
|
+
return vectors[0].tobytes()
|
|
186
|
+
except Exception as exc: # noqa: BLE001
|
|
187
|
+
logger.warning(
|
|
188
|
+
"embeddings.embed_query: failed to embed query %r with model %r (%s: %s); "
|
|
189
|
+
"returning b'' — semantic search will return no results.",
|
|
190
|
+
text,
|
|
191
|
+
model,
|
|
192
|
+
type(exc).__name__,
|
|
193
|
+
exc,
|
|
194
|
+
)
|
|
195
|
+
return b""
|