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,217 @@
|
|
|
1
|
+
"""Byte-ceiling trimmer for seam_impact output (E1-FULL).
|
|
2
|
+
|
|
3
|
+
LEAF MODULE — pure functions over plain dicts. Imports only stdlib (json, typing).
|
|
4
|
+
No database access, no config import, no IO, never raises.
|
|
5
|
+
Mirrors the leaf discipline of seam/analysis/relevance.py.
|
|
6
|
+
|
|
7
|
+
WHY this module exists (the usability gap it closes):
|
|
8
|
+
seam_impact output size is bounded by SEAM_IMPACT_MAX_RESULTS (an entry-count cap),
|
|
9
|
+
but entry count is a poor proxy for byte size: a tier of 25 entries with long
|
|
10
|
+
signatures and qualified names can be many kilobytes, while 25 short entries is tiny.
|
|
11
|
+
AI agents budget their context window in tokens (≈ characters), not entry counts, so
|
|
12
|
+
the count cap cannot guarantee the output fits a context budget.
|
|
13
|
+
|
|
14
|
+
fit_to_byte_budget() receives an already-assembled seam_impact response dict (after
|
|
15
|
+
the per-tier count cap and E2/E3 relevance ordering have already run) and trims entries
|
|
16
|
+
from the LEAST-VALUABLE end of a global priority order until the serialized response
|
|
17
|
+
fits the budget. Because entries are already ordered by priority (externals before
|
|
18
|
+
self-refs, production before test, nearest tier first), the survivors are guaranteed
|
|
19
|
+
the highest-signal dependents that fit.
|
|
20
|
+
|
|
21
|
+
Priority order (highest to lowest — front entries survive, tail entries are dropped):
|
|
22
|
+
1. direction: upstream before downstream (what calls me? is more valuable than what I call)
|
|
23
|
+
2. tier risk: WILL_BREAK → LIKELY_AFFECTED → MAY_NEED_TESTING
|
|
24
|
+
3. intra-tier: existing E2/E3 entry order preserved (front entries survive)
|
|
25
|
+
|
|
26
|
+
Conservatism contract:
|
|
27
|
+
- NEVER exceed the budget for the trimmed response body — `running` is a proven UPPER
|
|
28
|
+
bound on the real serialized size (see the algorithm note), so `running <= budget`
|
|
29
|
+
guarantees `serialized_size(trimmed) <= budget`. The ONLY case the body can exceed
|
|
30
|
+
budget is when the budget is smaller than the irreducible envelope (no entries can
|
|
31
|
+
fit) — then every entry is dropped and the bare envelope is returned.
|
|
32
|
+
- NEVER raise on any input — the public entry point wraps the whole body and returns
|
|
33
|
+
(response, {}, 0) on failure (no trimming is safer than a crash; the handler degrades).
|
|
34
|
+
- NEVER mutate the input response dict.
|
|
35
|
+
- Non-direction keys (found, target, risk_summary, truncated, etc.) are ALWAYS copied
|
|
36
|
+
through unchanged — trimming touches only direction-tier entry lists.
|
|
37
|
+
- Budget <= 0 means unlimited — return input unchanged immediately.
|
|
38
|
+
|
|
39
|
+
Unit = characters of json.dumps(obj, ensure_ascii=False) — the SAME serialization the
|
|
40
|
+
CLI uses to emit results (seam/cli/output.py emit_json). Measuring with the emit
|
|
41
|
+
serializer means the budget bounds the actual rendered bytes, not a more-compact proxy
|
|
42
|
+
that would let the real output overrun the ceiling. This is a deterministic,
|
|
43
|
+
dependency-free token proxy (~4 chars/token); no tokenizer is used (a real tokenizer
|
|
44
|
+
would be an external model-specific dep violating zero-external-services).
|
|
45
|
+
|
|
46
|
+
Algorithm note (O(n) running-total prefix):
|
|
47
|
+
Entries are walked in keep-priority order. Each entry is charged its own serialized
|
|
48
|
+
size PLUS one separator char (the comma that joins it to its siblings). The running
|
|
49
|
+
total starts from the envelope size (the response with all tier lists emptied) and
|
|
50
|
+
accumulates per kept entry. The +1-per-entry charge is a deliberate OVER-estimate:
|
|
51
|
+
the first entry in a tier has no preceding comma, so `running` is always >= the true
|
|
52
|
+
serialized size — which is exactly what makes `running <= budget` a hard guarantee.
|
|
53
|
+
The first entry that would overflow stops all further placement (stop-at-first-overflow
|
|
54
|
+
prefix): every entry before position k is kept, every entry at or after k is dropped.
|
|
55
|
+
This is O(n) (no full re-serialization per placement), so it stays cheap even on the
|
|
56
|
+
documented limit=0 / unbounded blast-radius path.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
import json
|
|
60
|
+
from typing import Any
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def serialized_size(obj: Any) -> int:
|
|
64
|
+
"""Return the character count of obj under the CLI emit serialization.
|
|
65
|
+
|
|
66
|
+
Matches seam/cli/output.py emit_json (json.dumps with default separators and
|
|
67
|
+
ensure_ascii=False) so the measured budget reflects the bytes actually rendered.
|
|
68
|
+
Exported as the single source of truth — the handler and tests measure with this
|
|
69
|
+
same function so budget arithmetic cannot drift between layers.
|
|
70
|
+
"""
|
|
71
|
+
return len(json.dumps(obj, ensure_ascii=False))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def fit_to_byte_budget(
|
|
75
|
+
response: dict[str, Any],
|
|
76
|
+
*,
|
|
77
|
+
budget: int,
|
|
78
|
+
direction_order: tuple[str, ...] = ("upstream", "downstream"),
|
|
79
|
+
tier_order: tuple[str, ...] = ("WILL_BREAK", "LIKELY_AFFECTED", "MAY_NEED_TESTING"),
|
|
80
|
+
) -> tuple[dict[str, Any], dict[str, dict[str, int]], int]:
|
|
81
|
+
"""Trim seam_impact entries to fit within a byte budget.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
response: The fully-assembled seam_impact response dict. NOT mutated.
|
|
85
|
+
budget: Maximum serialized size in characters. 0 or negative = unlimited
|
|
86
|
+
(return the input unchanged, no trimming).
|
|
87
|
+
direction_order: Keep-priority for directions (first = highest priority).
|
|
88
|
+
Default: upstream before downstream.
|
|
89
|
+
tier_order: Keep-priority for tiers within each direction (first = highest).
|
|
90
|
+
Default: WILL_BREAK → LIKELY_AFFECTED → MAY_NEED_TESTING.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
A 3-tuple:
|
|
94
|
+
trimmed_response — a NEW dict (input never mutated). Non-direction keys
|
|
95
|
+
(found, target, risk_summary, truncated, etc.) are copied
|
|
96
|
+
through unchanged. Direction-tier lists may be shorter.
|
|
97
|
+
byte_dropped — {direction: {tier: count}} of entries this function removed.
|
|
98
|
+
Only non-zero counts and non-empty direction dicts are included.
|
|
99
|
+
total_omitted — sum of all counts in byte_dropped (0 = ceiling did not fire /
|
|
100
|
+
everything fit).
|
|
101
|
+
|
|
102
|
+
Never raises. On any exception returns (response, {}, 0).
|
|
103
|
+
"""
|
|
104
|
+
# Budget 0 or negative = unlimited. Return unchanged immediately (byte-identical path).
|
|
105
|
+
if budget <= 0:
|
|
106
|
+
return response, {}, 0
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
return _fit_to_byte_budget_impl(response, budget, direction_order, tier_order)
|
|
110
|
+
except Exception:
|
|
111
|
+
# Safety net: return input unchanged on any failure (no trimming > crash).
|
|
112
|
+
# The handler detects this degradation (it knew the response did NOT fit yet
|
|
113
|
+
# nothing was dropped) and logs it, so the silent path is observable upstream.
|
|
114
|
+
return response, {}, 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _collect_entry_walk(
|
|
118
|
+
response: dict[str, Any],
|
|
119
|
+
direction_order: tuple[str, ...],
|
|
120
|
+
tier_order: tuple[str, ...],
|
|
121
|
+
) -> list[tuple[str, str, Any]]:
|
|
122
|
+
"""Build the priority-ordered flat walk of (direction, tier, entry) triples.
|
|
123
|
+
|
|
124
|
+
Only includes entries from directions/tiers that are BOTH present in the response
|
|
125
|
+
AND have a list value (malformed non-list tier values are skipped here; they are
|
|
126
|
+
copied through in the envelope, never trimmed).
|
|
127
|
+
|
|
128
|
+
The walk order is the keep-priority order: for direction in direction_order, for
|
|
129
|
+
tier in tier_order, for entry in that tier's list — earlier triples are more
|
|
130
|
+
valuable and survive trimming.
|
|
131
|
+
"""
|
|
132
|
+
walk: list[tuple[str, str, Any]] = []
|
|
133
|
+
for direction in direction_order:
|
|
134
|
+
dir_group = response.get(direction)
|
|
135
|
+
if not isinstance(dir_group, dict):
|
|
136
|
+
continue
|
|
137
|
+
for tier in tier_order:
|
|
138
|
+
tier_val = dir_group.get(tier)
|
|
139
|
+
if not isinstance(tier_val, list):
|
|
140
|
+
continue
|
|
141
|
+
for entry in tier_val:
|
|
142
|
+
walk.append((direction, tier, entry))
|
|
143
|
+
return walk
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _build_envelope(
|
|
147
|
+
response: dict[str, Any],
|
|
148
|
+
direction_order: tuple[str, ...],
|
|
149
|
+
) -> dict[str, Any]:
|
|
150
|
+
"""Build a response copy with all direction-tier lists EMPTIED.
|
|
151
|
+
|
|
152
|
+
Non-direction keys and direction groups present in the original are preserved in
|
|
153
|
+
structure; only the entry lists are set to []. This gives the 'overhead' size that
|
|
154
|
+
any kept entry must fit on top of. Malformed tier values (non-list) are copied
|
|
155
|
+
through unchanged (they add to overhead but cannot be trimmed — conservatism).
|
|
156
|
+
"""
|
|
157
|
+
envelope: dict[str, Any] = {}
|
|
158
|
+
for key, value in response.items():
|
|
159
|
+
if key in direction_order and isinstance(value, dict):
|
|
160
|
+
dir_copy: dict[str, Any] = {}
|
|
161
|
+
for tier_key, tier_val in value.items():
|
|
162
|
+
dir_copy[tier_key] = [] if isinstance(tier_val, list) else tier_val
|
|
163
|
+
envelope[key] = dir_copy
|
|
164
|
+
else:
|
|
165
|
+
envelope[key] = value
|
|
166
|
+
return envelope
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _fit_to_byte_budget_impl(
|
|
170
|
+
response: dict[str, Any],
|
|
171
|
+
budget: int,
|
|
172
|
+
direction_order: tuple[str, ...],
|
|
173
|
+
tier_order: tuple[str, ...],
|
|
174
|
+
) -> tuple[dict[str, Any], dict[str, dict[str, int]], int]:
|
|
175
|
+
"""Inner implementation — only called when budget > 0. Caller wraps for never-raises."""
|
|
176
|
+
# Fast path: the full response already fits.
|
|
177
|
+
if serialized_size(response) <= budget:
|
|
178
|
+
return response, {}, 0
|
|
179
|
+
|
|
180
|
+
walk = _collect_entry_walk(response, direction_order, tier_order)
|
|
181
|
+
# Running total starts at the envelope size (overhead with all tier lists empty).
|
|
182
|
+
running = serialized_size(_build_envelope(response, direction_order))
|
|
183
|
+
|
|
184
|
+
kept: dict[str, dict[str, list[Any]]] = {}
|
|
185
|
+
dropped: dict[str, dict[str, int]] = {}
|
|
186
|
+
overflow = False # once True, all remaining (lower-priority) entries are dropped
|
|
187
|
+
|
|
188
|
+
for direction, tier, entry in walk:
|
|
189
|
+
if not overflow:
|
|
190
|
+
# +1 charges the comma that joins this entry to its siblings. The first
|
|
191
|
+
# entry in a tier has no preceding comma, so this OVER-estimates — which is
|
|
192
|
+
# what makes `running <= budget` a hard upper bound on the real size.
|
|
193
|
+
cost = serialized_size(entry) + 1
|
|
194
|
+
if running + cost <= budget:
|
|
195
|
+
kept.setdefault(direction, {}).setdefault(tier, []).append(entry)
|
|
196
|
+
running += cost
|
|
197
|
+
continue
|
|
198
|
+
overflow = True
|
|
199
|
+
dir_dropped = dropped.setdefault(direction, {})
|
|
200
|
+
dir_dropped[tier] = dir_dropped.get(tier, 0) + 1
|
|
201
|
+
|
|
202
|
+
# Build the trimmed response: a fresh empty-list envelope with the kept lists filled.
|
|
203
|
+
# kept holds the SAME entry-dict references as the input (never copied, never mutated).
|
|
204
|
+
trimmed = _build_envelope(response, direction_order)
|
|
205
|
+
for direction, tier_map in kept.items():
|
|
206
|
+
for tier, entries in tier_map.items():
|
|
207
|
+
trimmed[direction][tier] = entries
|
|
208
|
+
|
|
209
|
+
# byte_dropped: drop zero counts and empty direction dicts.
|
|
210
|
+
byte_dropped: dict[str, dict[str, int]] = {}
|
|
211
|
+
for direction, count_map in dropped.items():
|
|
212
|
+
filtered = {tier: cnt for tier, cnt in count_map.items() if cnt > 0}
|
|
213
|
+
if filtered:
|
|
214
|
+
byte_dropped[direction] = filtered
|
|
215
|
+
total_omitted = sum(cnt for count_map in byte_dropped.values() for cnt in count_map.values())
|
|
216
|
+
|
|
217
|
+
return trimmed, byte_dropped, total_omitted
|