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
seam/config.py
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
"""Seam configuration — all settings read from environment with sensible defaults."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# Path to the SQLite database, relative to the project root that runs `seam`
|
|
7
|
+
SEAM_DB_PATH: str = os.getenv("SEAM_DB_PATH", ".seam/seam.db")
|
|
8
|
+
|
|
9
|
+
# Logging level: DEBUG | INFO | WARNING | ERROR
|
|
10
|
+
SEAM_LOG_LEVEL: str = os.getenv("SEAM_LOG_LEVEL", "INFO")
|
|
11
|
+
|
|
12
|
+
# Debounce delay for file watcher (milliseconds)
|
|
13
|
+
SEAM_DEBOUNCE_MS: int = int(os.getenv("SEAM_DEBOUNCE_MS", "500"))
|
|
14
|
+
|
|
15
|
+
# Maximum file size to index (bytes). Files above this are silently skipped.
|
|
16
|
+
SEAM_MAX_FILE_BYTES: int = int(os.getenv("SEAM_MAX_FILE_BYTES", str(1024 * 1024))) # 1MB
|
|
17
|
+
|
|
18
|
+
# Maximum number of changed symbol names to run impact() on in one detect_changes call.
|
|
19
|
+
# If the diff touches more real symbols than this cap, only the first N are analyzed
|
|
20
|
+
# and ChangeReport.partial is set to True. Raise via env var on large codebases.
|
|
21
|
+
SEAM_MAX_IMPACT_SYMBOLS: int = int(os.getenv("SEAM_MAX_IMPACT_SYMBOLS", "50"))
|
|
22
|
+
|
|
23
|
+
# File extensions to index, mapped to language identifier.
|
|
24
|
+
# Phase 9: added Java, C#, Ruby, C/H, C++, and PHP extensions.
|
|
25
|
+
# WHY .h → C (not C++): mixed C/C++ projects use .h for both C and C++ headers.
|
|
26
|
+
# Routing .h to the C++ grammar would break C-only projects; routing to C is the
|
|
27
|
+
# safer default and handles the common case (struct/typedef/function prototypes).
|
|
28
|
+
# C++-only header patterns (.hpp/.hh/.hxx) are explicitly mapped to C++.
|
|
29
|
+
# See ADR-008, limitation (a).
|
|
30
|
+
SEAM_LANGUAGE_MAP: dict[str, str] = {
|
|
31
|
+
".py": "python",
|
|
32
|
+
".ts": "typescript",
|
|
33
|
+
".tsx": "typescript",
|
|
34
|
+
".js": "javascript",
|
|
35
|
+
".mjs": "javascript",
|
|
36
|
+
".cjs": "javascript",
|
|
37
|
+
".go": "go",
|
|
38
|
+
".rs": "rust",
|
|
39
|
+
# Phase 9 — Java
|
|
40
|
+
".java": "java",
|
|
41
|
+
# Phase 9 — C# (csharp to avoid keyword collision)
|
|
42
|
+
".cs": "csharp",
|
|
43
|
+
# Phase 9 — Ruby
|
|
44
|
+
".rb": "ruby",
|
|
45
|
+
# Phase 9 — C (header .h → C; MVP decision)
|
|
46
|
+
".c": "c",
|
|
47
|
+
".h": "c",
|
|
48
|
+
# Phase 9 — C++ (all common variants)
|
|
49
|
+
".cpp": "cpp",
|
|
50
|
+
".cc": "cpp",
|
|
51
|
+
".cxx": "cpp",
|
|
52
|
+
".c++": "cpp",
|
|
53
|
+
".hpp": "cpp",
|
|
54
|
+
".hh": "cpp",
|
|
55
|
+
".hxx": "cpp",
|
|
56
|
+
# Phase 9 — PHP
|
|
57
|
+
".php": "php",
|
|
58
|
+
# Phase 10 — Swift
|
|
59
|
+
".swift": "swift",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ── Phase 2: Clustering configuration ────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
# Cluster naming mode: "deterministic" (default) or "llm" (opt-in).
|
|
66
|
+
# When set to "llm", SEAM_LLM_API_KEY must also be set or naming falls back
|
|
67
|
+
# to "deterministic". LLM naming runs only during `seam init`, never in MCP.
|
|
68
|
+
SEAM_CLUSTER_NAMING: str = os.getenv("SEAM_CLUSTER_NAMING", "deterministic")
|
|
69
|
+
|
|
70
|
+
# Optional LLM API key for cluster naming (only used when SEAM_CLUSTER_NAMING=llm).
|
|
71
|
+
# When absent/empty, LLM naming is silently skipped and deterministic is used.
|
|
72
|
+
SEAM_LLM_API_KEY: str | None = os.getenv("SEAM_LLM_API_KEY") or None
|
|
73
|
+
|
|
74
|
+
# LLM model for cluster naming. Uses a small/fast model by default.
|
|
75
|
+
SEAM_LLM_MODEL: str = os.getenv("SEAM_LLM_MODEL", "gpt-4o-mini")
|
|
76
|
+
|
|
77
|
+
# Minimum cluster size. Communities with fewer distinct graph nodes than this
|
|
78
|
+
# are NOT persisted as clusters — their symbols get cluster_id=NULL (unclustered).
|
|
79
|
+
# Default 2: kills pure singletons (symbols with no edges) so `seam clusters`
|
|
80
|
+
# shows functional areas, not 200 one-symbol rows.
|
|
81
|
+
# Set to 1 to retain all singletons as their own clusters.
|
|
82
|
+
SEAM_CLUSTER_MIN_SIZE: int = int(os.getenv("SEAM_CLUSTER_MIN_SIZE", "2"))
|
|
83
|
+
|
|
84
|
+
# P2 — confidence-filtered Louvain. On LARGE graphs (symbol_count > this threshold),
|
|
85
|
+
# only high-trust edges (EXTRACTED + import-kind INFERRED) are passed to community
|
|
86
|
+
# detection, so noisy AMBIGUOUS/inferred-call edges can't merge unrelated modules.
|
|
87
|
+
# Small repos are unaffected (the full edge set keeps recall on sparse graphs).
|
|
88
|
+
# Special values: "off" disables the filter entirely (always pass all edges);
|
|
89
|
+
# "0" forces the filter on for any non-empty graph (used by tests). Default 1000.
|
|
90
|
+
SEAM_CLUSTER_CONFIDENCE_FILTER: str = os.getenv("SEAM_CLUSTER_CONFIDENCE_FILTER", "1000")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ── Phase 3: Affected-tests configuration ────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
# Maximum hop depth for the affected-tests traversal.
|
|
96
|
+
# Controls how far upstream (through the call/import graph) the `seam affected`
|
|
97
|
+
# command walks from each changed symbol to find dependent test files.
|
|
98
|
+
# Higher values find more distant test files but increase runtime on large graphs.
|
|
99
|
+
SEAM_AFFECTED_DEPTH: int = int(os.getenv("SEAM_AFFECTED_DEPTH", "5"))
|
|
100
|
+
|
|
101
|
+
# Maximum number of changed files accepted by handle_seam_affected.
|
|
102
|
+
# Inputs larger than this are rejected with INVALID_INPUT (agent mistake guard).
|
|
103
|
+
# Mirrors the _clamp discipline used by other bounded handlers.
|
|
104
|
+
SEAM_MAX_AFFECTED_FILES: int = int(os.getenv("SEAM_MAX_AFFECTED_FILES", "200"))
|
|
105
|
+
|
|
106
|
+
# Maximum symbols analyzed per file in affected().
|
|
107
|
+
# When a file defines more symbols than this, only the first N are traversed
|
|
108
|
+
# and AffectedResult.partial is set to True.
|
|
109
|
+
# Reuses the SEAM_MAX_IMPACT_SYMBOLS env var pattern for consistency.
|
|
110
|
+
SEAM_MAX_AFFECTED_SYMBOLS: int = int(os.getenv("SEAM_MAX_AFFECTED_SYMBOLS", "50"))
|
|
111
|
+
|
|
112
|
+
# ── Phase 3: Search / fuzzy fallback configuration ───────────────────────────
|
|
113
|
+
|
|
114
|
+
# Maximum Damerau-Levenshtein edit distance for the fuzzy fallback.
|
|
115
|
+
# Applied when both FTS and LIKE fallbacks return zero rows.
|
|
116
|
+
# 1 = catch single-char typos (conservative); 2 = broader (may add noise).
|
|
117
|
+
SEAM_FUZZY_MAX_DIST: int = int(os.getenv("SEAM_FUZZY_MAX_DIST", "1"))
|
|
118
|
+
|
|
119
|
+
# Maximum candidate symbol names to evaluate in the fuzzy fallback.
|
|
120
|
+
# Caps the O(n) edit-distance scan over distinct symbol names.
|
|
121
|
+
# On very large codebases, raise this via env var if precision matters more.
|
|
122
|
+
SEAM_FUZZY_MAX_CANDIDATES: int = int(os.getenv("SEAM_FUZZY_MAX_CANDIDATES", "500"))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ── Phase 4: Node-field enrichment configuration ─────────────────────────────
|
|
126
|
+
|
|
127
|
+
# Hard cap on stored signature length. Without a cap, a function with many type-annotated
|
|
128
|
+
# parameters can produce a 500+ character signature that dominates the FTS index and makes
|
|
129
|
+
# MCP responses painful to read. 300 chars captures the full header of all but pathological
|
|
130
|
+
# cases; truncation appends '...' so consumers can detect incomplete signatures.
|
|
131
|
+
SEAM_MAX_SIGNATURE_LEN: int = int(os.getenv("SEAM_MAX_SIGNATURE_LEN", "300"))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ── Phase 5: Import Resolution configuration ─────────────────────────────────
|
|
135
|
+
|
|
136
|
+
# Master switch for builtin filtering. When "off", count==0 names always resolve
|
|
137
|
+
# to INFERRED 'unresolved' regardless of whether they are known builtins.
|
|
138
|
+
SEAM_BUILTIN_FILTERING: str = os.getenv("SEAM_BUILTIN_FILTERING", "on")
|
|
139
|
+
|
|
140
|
+
# Master switch for import-resolution promotion (step A). When "off", import
|
|
141
|
+
# mappings are not extracted at index time and not used for promotion at read time.
|
|
142
|
+
# The name-count rule (existing behavior) is used exclusively.
|
|
143
|
+
SEAM_IMPORT_RESOLUTION: str = os.getenv("SEAM_IMPORT_RESOLUTION", "on")
|
|
144
|
+
|
|
145
|
+
# Cap on candidate declaring files evaluated per import-resolution lookup (step A).
|
|
146
|
+
# Prevents runaway DB queries on pathologically ambiguous indexes.
|
|
147
|
+
SEAM_MAX_IMPORT_CANDIDATES: int = int(os.getenv("SEAM_MAX_IMPORT_CANDIDATES", "25"))
|
|
148
|
+
|
|
149
|
+
# P6a — master switch for inheritance edge extraction. When "on" (default), class
|
|
150
|
+
# base-class / interface clauses are emitted as kind='extends' / kind='implements'
|
|
151
|
+
# edges (string-name-keyed: source=subclass, target=base), so an interface/base
|
|
152
|
+
# change surfaces its subclasses/implementers in seam_impact via upstream traversal.
|
|
153
|
+
# When "off", no inheritance edges are emitted — byte-identical to pre-P6a indexes.
|
|
154
|
+
SEAM_INHERITANCE_EDGES: str = os.getenv("SEAM_INHERITANCE_EDGES", "on")
|
|
155
|
+
|
|
156
|
+
# Cap on collision candidates ranked by file-path proximity (step D).
|
|
157
|
+
# Prevents O(n) proximity computation on large symbol tables.
|
|
158
|
+
SEAM_PROXIMITY_MAX_CANDIDATES: int = int(os.getenv("SEAM_PROXIMITY_MAX_CANDIDATES", "25"))
|
|
159
|
+
|
|
160
|
+
# P4 — barrel re-export following. Max hops to chase a named import through
|
|
161
|
+
# barrel index.ts/re-export files before giving up. When import promotion finds
|
|
162
|
+
# a candidate file that does NOT itself declare the exported name (i.e. it is a
|
|
163
|
+
# barrel that re-exports from siblings), the resolver follows that file's OWN
|
|
164
|
+
# import_mappings up to this many hops to find the real declarer. Bounded and
|
|
165
|
+
# cached per (file, name) within a single resolution → no unbounded read cost.
|
|
166
|
+
# Default 3 matches CodeGraph's barrel-chasing depth. Set to 0 to DISABLE barrel
|
|
167
|
+
# following entirely (byte-identical to pre-P4 behavior).
|
|
168
|
+
SEAM_BARREL_DEPTH: int = int(os.getenv("SEAM_BARREL_DEPTH", "3"))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ── Phase 8: Lean output + impact cap ────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
# Per-tier entry cap for seam_impact. When a tier contains more entries than this
|
|
174
|
+
# cap, the list is sliced to this many entries (the closest/highest-risk first,
|
|
175
|
+
# since tiers are distance-ordered by construction). A summary count per tier
|
|
176
|
+
# (risk_summary) is always computed BEFORE capping so the histogram is honest.
|
|
177
|
+
# Set to 0 to disable the cap and return all entries.
|
|
178
|
+
# Default 25 prevents the "hub symbol dumps 200+ entries" problem (issue #33).
|
|
179
|
+
SEAM_IMPACT_MAX_RESULTS: int = int(os.getenv("SEAM_IMPACT_MAX_RESULTS", "25"))
|
|
180
|
+
|
|
181
|
+
# ── E2/E3: seam_impact output relevance shaping ─────────────────────────────
|
|
182
|
+
# Master switch for relevance ordering in seam_impact (handler-layer, read-path
|
|
183
|
+
# only). "on" (default) ranks EXTERNAL dependents ahead of the target's own
|
|
184
|
+
# container-members (self-references) BEFORE the per-tier cap, so the cap drops
|
|
185
|
+
# self-refs first and external dependents survive truncation. "off" reverts to
|
|
186
|
+
# the prior production-before-test ordering for byte-identical output.
|
|
187
|
+
# Handler-only: seam_changes / seam_affected call the analysis layer directly and
|
|
188
|
+
# are unaffected regardless of this setting.
|
|
189
|
+
SEAM_IMPACT_RELEVANCE_SORT: str = os.getenv("SEAM_IMPACT_RELEVANCE_SORT", "on")
|
|
190
|
+
|
|
191
|
+
# How seam_impact treats the target's own container-members (self-references):
|
|
192
|
+
# "rank" (default) — keep them, but sort last so the cap drops them first
|
|
193
|
+
# (lossless: risk_summary still counts the full blast radius).
|
|
194
|
+
# "hide" — drop self-refs from entry lists, surface a hidden_self_refs
|
|
195
|
+
# count (mirrors hidden_tests); frees the most external budget.
|
|
196
|
+
# "show" — legacy: no self-ref special treatment (ordering falls back to
|
|
197
|
+
# production-before-test, same as RELEVANCE_SORT="off").
|
|
198
|
+
SEAM_IMPACT_SELF_REF: str = os.getenv("SEAM_IMPACT_SELF_REF", "rank")
|
|
199
|
+
|
|
200
|
+
# E1 — drop `best_candidate` from seam_impact entries when it is null. best_candidate
|
|
201
|
+
# is meaningful only for AMBIGUOUS entries (it is the proximity pick); for every
|
|
202
|
+
# EXTRACTED/INFERRED entry it is null and carries no signal. Omitting null is lossless
|
|
203
|
+
# (null ≡ absent per the established null-contract) and reclaims ~25 B/entry so more
|
|
204
|
+
# high-signal dependents survive an agent's byte/context budget under the per-tier cap.
|
|
205
|
+
# "off" = byte-identical revert (keeps `best_candidate: null`). Handler-layer, read-path
|
|
206
|
+
# only — no schema change, no re-index. seam_changes/seam_affected are unaffected (they
|
|
207
|
+
# call the analysis layer directly). resolved_by is always kept (genuine provenance).
|
|
208
|
+
SEAM_IMPACT_OMIT_NULL_CANDIDATE: str = os.getenv("SEAM_IMPACT_OMIT_NULL_CANDIDATE", "on")
|
|
209
|
+
|
|
210
|
+
# E1-FULL — opt-in byte ceiling for seam_impact output.
|
|
211
|
+
#
|
|
212
|
+
# WHY a byte ceiling (not just the per-tier count cap):
|
|
213
|
+
# SEAM_IMPACT_MAX_RESULTS caps entries-per-tier, but a tier of 25 entries with long
|
|
214
|
+
# signatures can be many kilobytes while a tier of 25 short entries is tiny. Agents
|
|
215
|
+
# budget their context window in tokens (≈ bytes/chars), not entry counts, so the
|
|
216
|
+
# count cap cannot guarantee the output fits a context budget. This knob adds a
|
|
217
|
+
# hard byte ceiling so agents can say "give me the highest-signal dependents that
|
|
218
|
+
# fit in N characters."
|
|
219
|
+
#
|
|
220
|
+
# HOW it works (handler-layer only, no re-index, no schema change):
|
|
221
|
+
# After the existing per-tier count cap and E2/E3 relevance ordering, handle_seam_impact
|
|
222
|
+
# trims entries from the least-valuable end of a global priority order — downstream
|
|
223
|
+
# before upstream, MAY_NEED_TESTING before WILL_BREAK, tail of each tier before front —
|
|
224
|
+
# until the serialized output fits the budget. Because E2/E3 already ordered entries
|
|
225
|
+
# (externals first, production before test), the survivors are the highest-signal
|
|
226
|
+
# dependents that fit.
|
|
227
|
+
#
|
|
228
|
+
# UNIT = characters (compact JSON byte count). A real tokenizer is an external,
|
|
229
|
+
# model-specific dependency that violates Seam's zero-external-services rule.
|
|
230
|
+
# Characters are deterministic and a stable ~4-chars/token proxy.
|
|
231
|
+
#
|
|
232
|
+
# DEFAULT = 0 (unlimited). 0 or any negative value means the byte ceiling is INACTIVE
|
|
233
|
+
# and the output is byte-identical to the pre-feature behavior. Set to a positive
|
|
234
|
+
# integer (e.g. 8000) to activate the ceiling.
|
|
235
|
+
#
|
|
236
|
+
# SCOPE: handler-layer and read-path only. seam_changes and seam_affected call the
|
|
237
|
+
# analysis-layer impact() directly (below the handler) and are unaffected. No schema
|
|
238
|
+
# migration or re-index is required — the ceiling is applied at response-assembly time.
|
|
239
|
+
SEAM_IMPACT_MAX_BYTES: int = int(os.getenv("SEAM_IMPACT_MAX_BYTES", "0"))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ── E4: Edge provenance + truncation steer ───────────────────────────────────
|
|
243
|
+
|
|
244
|
+
# Master switch for edge-provenance fields on seam_impact entries and seam_trace hops.
|
|
245
|
+
# When "on" (default), each seam_impact tier entry and each seam_trace hop carries:
|
|
246
|
+
# kind — the edge kind that reached the dependent (call, reads, holds, …)
|
|
247
|
+
# synthesized_by — synthesis channel name when heuristic, null for static edges
|
|
248
|
+
# This surfaces information already stored in v12 edges.kind + edges.synthesized_by
|
|
249
|
+
# so agents can distinguish a hard call edge from a heuristic synthesized edge.
|
|
250
|
+
# "off" = byte-identical pre-E4 output (neither field is emitted). Handler-layer and
|
|
251
|
+
# read-path only — no schema change, no re-index. seam_changes/seam_affected unaffected.
|
|
252
|
+
SEAM_EDGE_PROVENANCE: str = os.getenv("SEAM_EDGE_PROVENANCE", "on")
|
|
253
|
+
|
|
254
|
+
# Master switch for the next_actions truncation steer on seam_impact output.
|
|
255
|
+
# When "on" (default), a top-level `next_actions: list[str]` of ready-to-act prose
|
|
256
|
+
# hints is attached to the seam_impact response when ≥1 entry was trimmed by the
|
|
257
|
+
# per-tier count cap or the E1-FULL byte ceiling. The steer names the exact remedy
|
|
258
|
+
# (e.g. "Raise limit to 17 to see 12 more WILL_BREAK dependents"). ABSENT when
|
|
259
|
+
# nothing was trimmed — so its presence is an unambiguous "there is more" signal.
|
|
260
|
+
# "off" = byte-identical pre-E4 output (no next_actions key ever). Handler-layer and
|
|
261
|
+
# read-path only — no schema change, no re-index. seam_changes/seam_affected unaffected.
|
|
262
|
+
SEAM_IMPACT_STEER: str = os.getenv("SEAM_IMPACT_STEER", "on")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# ── P2: Index staleness banner ────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
# Master switch for the index-staleness check on graph-traversal MCP read tools.
|
|
268
|
+
# When "on" (default), the 5 graph-traversal handlers (seam_impact, seam_changes,
|
|
269
|
+
# seam_affected, seam_context, seam_trace) attach a structured index_status banner to
|
|
270
|
+
# their output when the index is stale — "this index is stale; results may be wrong;
|
|
271
|
+
# run seam sync/init". When fresh → no banner → output byte-identical to pre-feature.
|
|
272
|
+
# "off" = no banner ever, no stat IO, byte-identical to pre-feature. Handler-layer and
|
|
273
|
+
# read-path only — no schema change, no re-index. Single source of truth via
|
|
274
|
+
# seam/analysis/staleness.py (seam status also delegates to the same module).
|
|
275
|
+
SEAM_STALENESS_CHECK: str = os.getenv("SEAM_STALENESS_CHECK", "on")
|
|
276
|
+
|
|
277
|
+
# Maximum number of files stat'd per staleness verdict. Only the N most-recently-indexed
|
|
278
|
+
# real files are checked (newest indexed_at first, LIMIT N). A stale file that falls
|
|
279
|
+
# outside this window is not detected — documented limitation. Default 200 bounds the
|
|
280
|
+
# stat IO to ~5-20ms even on a network filesystem and prevents O(files) checks on the
|
|
281
|
+
# hot MCP read path.
|
|
282
|
+
SEAM_STALENESS_SCAN_CAP: int = int(os.getenv("SEAM_STALENESS_SCAN_CAP", "200"))
|
|
283
|
+
|
|
284
|
+
# Per-process verdict cache TTL in seconds. Within this window, repeated MCP read-tool
|
|
285
|
+
# calls in one server session reuse the cached verdict instead of re-stat'ing files.
|
|
286
|
+
# Default 5s: fresh enough for interactive use; prevents re-stat on every tool call in a
|
|
287
|
+
# rapid burst (e.g. an agent running seam_impact + seam_context back-to-back). Set to 0
|
|
288
|
+
# to disable caching (always re-stat; useful for testing).
|
|
289
|
+
SEAM_STALENESS_TTL_SECONDS: int = int(os.getenv("SEAM_STALENESS_TTL_SECONDS", "5"))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# ── Phase 6: Context-Pack configuration ─────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
# Maximum enriched callers AND maximum enriched callees in one context_pack bundle.
|
|
295
|
+
# When the raw neighbor list exceeds this, the list is truncated and the count of
|
|
296
|
+
# dropped entries is reported in ContextPack.truncated.callers/callees.
|
|
297
|
+
SEAM_PACK_NEIGHBOR_LIMIT: int = int(os.getenv("SEAM_PACK_NEIGHBOR_LIMIT", "10"))
|
|
298
|
+
|
|
299
|
+
# Maximum neighbor entries from any single file (homonym diversity cap).
|
|
300
|
+
# When a hot utility file defines many same-named symbols, capping per file
|
|
301
|
+
# keeps the bundle diverse across the codebase.
|
|
302
|
+
# Applied BEFORE the global neighbor limit (PRD §4.5a).
|
|
303
|
+
SEAM_PACK_PER_FILE_CAP: int = int(os.getenv("SEAM_PACK_PER_FILE_CAP", "3"))
|
|
304
|
+
|
|
305
|
+
# Maximum WHY/HACK/NOTE/TODO/FIXME comments in the bundle.
|
|
306
|
+
SEAM_PACK_MAX_COMMENTS: int = int(os.getenv("SEAM_PACK_MAX_COMMENTS", "10"))
|
|
307
|
+
|
|
308
|
+
# E3 — rank context_pack neighbors by personalized-PageRank (RWR) relevance to the seed symbol
|
|
309
|
+
# BEFORE the per-file + global caps, so the kept N are the most relevant neighbors rather than the
|
|
310
|
+
# lowest-symbol-id ones. With restart-at-seed, a neighbor woven into the seed's local neighborhood
|
|
311
|
+
# (shares callers/callees → same functional cluster) outranks a globally-popular but topically-
|
|
312
|
+
# distant neighbor — relevance-to-the-seed, which raw degree cannot express. Pure-offline,
|
|
313
|
+
# deterministic; the stable sort preserves the prior min_id order within ties. "off" = byte-
|
|
314
|
+
# identical revert (min_id order). Read-path / MCP-tool only; no schema change, no re-index.
|
|
315
|
+
SEAM_PACK_RELEVANCE_RANK: str = os.getenv("SEAM_PACK_RELEVANCE_RANK", "on")
|
|
316
|
+
|
|
317
|
+
# Max nodes in the bounded local subgraph the RWR walk runs over (cost ceiling). The subgraph is a
|
|
318
|
+
# depth-capped BFS from the seed; once this many nodes are collected, expansion stops. Keeps RWR
|
|
319
|
+
# O(subgraph) — ~hundreds of nodes × ~30 power-iterations — never a whole-graph walk.
|
|
320
|
+
SEAM_RWR_MAX_NODES: int = int(os.getenv("SEAM_RWR_MAX_NODES", "500"))
|
|
321
|
+
|
|
322
|
+
# Max BFS depth (hops from the seed) when collecting the local subgraph for the RWR walk.
|
|
323
|
+
SEAM_RWR_MAX_DEPTH: int = int(os.getenv("SEAM_RWR_MAX_DEPTH", "3"))
|
|
324
|
+
|
|
325
|
+
# ── Execution flows configuration (seam_flows) ───────────────────────────────
|
|
326
|
+
|
|
327
|
+
# Max entry points returned by seam_flows in list mode.
|
|
328
|
+
# Entry points are call-graph roots ranked by downstream reach; the top N are
|
|
329
|
+
# the program's main execution starting points (CLI commands, web routes, etc.).
|
|
330
|
+
SEAM_FLOW_ENTRY_LIMIT: int = int(os.getenv("SEAM_FLOW_ENTRY_LIMIT", "20"))
|
|
331
|
+
|
|
332
|
+
# Max depth (levels of callees) when expanding a single flow tree.
|
|
333
|
+
# Bounds the tree on deep call chains; nodes beyond this are cut and the flow
|
|
334
|
+
# is marked truncated=True.
|
|
335
|
+
SEAM_FLOW_MAX_DEPTH: int = int(os.getenv("SEAM_FLOW_MAX_DEPTH", "6"))
|
|
336
|
+
|
|
337
|
+
# Max children (callees) shown per node when expanding a flow tree.
|
|
338
|
+
# Caps fan-out at hub symbols (a function that calls 50 helpers); excess callees
|
|
339
|
+
# are dropped and the flow is marked truncated=True.
|
|
340
|
+
SEAM_FLOW_MAX_BREADTH: int = int(os.getenv("SEAM_FLOW_MAX_BREADTH", "8"))
|
|
341
|
+
|
|
342
|
+
# BFS depth used only to SCORE entry-point reach (how many symbols a root reaches).
|
|
343
|
+
# Separate from MAX_DEPTH: scoring wants a stable ranking signal, not a full walk.
|
|
344
|
+
SEAM_FLOW_REACH_DEPTH: int = int(os.getenv("SEAM_FLOW_REACH_DEPTH", "5"))
|
|
345
|
+
|
|
346
|
+
# P6b — framework entry-point scoring. When "on" (default), a per-symbol
|
|
347
|
+
# entry_score float is computed at INDEX time from the file's path pattern
|
|
348
|
+
# (e.g. views.py, routes/, controllers/) and the symbol's decorator text
|
|
349
|
+
# (e.g. @app.route, @router.get). list_entry_points() then ranks by
|
|
350
|
+
# entry_score * reach instead of raw reach, so a framework route (low reach)
|
|
351
|
+
# can outrank a deep utility. When "off", entry_score is still stored as the
|
|
352
|
+
# neutral baseline (1.0) and ranking is byte-identical to raw reach (pre-P6b).
|
|
353
|
+
SEAM_ENTRY_SCORE: str = os.getenv("SEAM_ENTRY_SCORE", "on")
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# ── Semantic search configuration (opt-in, Phase Semantic) ───────────────────
|
|
357
|
+
|
|
358
|
+
# Master switch for semantic (embedding-based) search. "off" by default — opt-in.
|
|
359
|
+
# Set to "on" to enable hybrid FTS5 + cosine recall in seam_search / seam_query.
|
|
360
|
+
# Requires: (a) `[semantic]` extra installed, (b) `seam init --semantic` run to
|
|
361
|
+
# populate the `embeddings` table. When "off" (or extra absent, or no embeddings),
|
|
362
|
+
# falls back to the existing pure-FTS5 path — behaviour is byte-identical to today.
|
|
363
|
+
SEAM_SEMANTIC: str = os.getenv("SEAM_SEMANTIC", "off")
|
|
364
|
+
|
|
365
|
+
# Local embedding model identifier (fastembed / HuggingFace model name).
|
|
366
|
+
# Default: bge-small-en-v1.5 — 384-dim, quantized ONNX on CPU, ~67MB, MIT.
|
|
367
|
+
# The model is downloaded ONCE on first `seam init --semantic`; subsequent runs use
|
|
368
|
+
# the local fastembed cache. Changing this value requires a full `seam init --semantic`
|
|
369
|
+
# to repopulate the embeddings table — mixing model vectors silently degrades quality.
|
|
370
|
+
SEAM_EMBED_MODEL: str = os.getenv("SEAM_EMBED_MODEL", "BAAI/bge-small-en-v1.5")
|
|
371
|
+
|
|
372
|
+
# Top-k semantic candidates fetched before merging with FTS results via RRF.
|
|
373
|
+
# Higher values improve recall but cost more cosine comparisons. 20 is a good
|
|
374
|
+
# default at the scale of a typical codebase (1k–20k symbols).
|
|
375
|
+
SEAM_SEMANTIC_LIMIT: int = int(os.getenv("SEAM_SEMANTIC_LIMIT", "20"))
|
|
376
|
+
|
|
377
|
+
# Maximum number of stored embedding rows loaded per semantic scan.
|
|
378
|
+
# Bounds the brute-force cosine scan: rows beyond this cap are never loaded.
|
|
379
|
+
# Protects against unbounded memory use on very large indexes.
|
|
380
|
+
# Default 20000: covers ~20k symbols; adjust up if your codebase is larger.
|
|
381
|
+
SEAM_SEMANTIC_SCAN_CAP: int = int(os.getenv("SEAM_SEMANTIC_SCAN_CAP", "20000"))
|
|
382
|
+
|
|
383
|
+
# RRF smoothing constant k (used in Reciprocal Rank Fusion).
|
|
384
|
+
# k=60 is the standard value from Cormack, Clarke & Buettcher (SIGIR 2009).
|
|
385
|
+
# Higher k flattens rank differences; lower k amplifies them.
|
|
386
|
+
SEAM_RRF_K: int = int(os.getenv("SEAM_RRF_K", "60"))
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
# ── Tier A Slice 3: class/container member fan-out ───────────────────────────
|
|
390
|
+
|
|
391
|
+
# Maximum number of member bare names included in edge_match_names() when the
|
|
392
|
+
# queried symbol is a class/interface/struct container. Caps the fan-out on
|
|
393
|
+
# "god-class" containers that have hundreds of methods, which would otherwise
|
|
394
|
+
# produce huge IN clauses and bloat the token budget.
|
|
395
|
+
# Default 50: covers virtually all real-world classes; raise via env var on
|
|
396
|
+
# very large containers if precision matters more than query cost.
|
|
397
|
+
SEAM_NAME_EXPANSION_CAP: int = int(os.getenv("SEAM_NAME_EXPANSION_CAP", "50"))
|
|
398
|
+
|
|
399
|
+
# ── Tier A Slice 2: bare-name suffix scan cap ────────────────────────────────
|
|
400
|
+
|
|
401
|
+
# Maximum rows returned by the suffix scan (LIKE '%.name') inside
|
|
402
|
+
# resolve_query_to_defs(). Without a cap, a bare name like "get", "parse", or
|
|
403
|
+
# "run" can match thousands of qualified symbols (full-table scan, no index),
|
|
404
|
+
# and each row then triggers additional DB calls in context() — O(N*4) queries.
|
|
405
|
+
# Default 25 matches SEAM_MAX_IMPORT_CANDIDATES and is consistent with other caps.
|
|
406
|
+
# Set to 0 for unlimited (not recommended on large codebases).
|
|
407
|
+
SEAM_BARE_RESOLVE_CAP: int = int(os.getenv("SEAM_BARE_RESOLVE_CAP", "25"))
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
# ── Slice #77: Composition (holds) edges ────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
# Master switch for composition edge extraction. When "on" (default), the extractor
|
|
413
|
+
# emits an Edge(kind="holds", confidence="INFERRED") from a class to every plain user
|
|
414
|
+
# type it stores as a typed field/property OR receives as a typed constructor/init
|
|
415
|
+
# parameter. This captures DI and composition relationships in the call graph so that
|
|
416
|
+
# seam_impact traversal surfaces which classes depend on a given type structurally (not
|
|
417
|
+
# only via explicit calls). Conservatism contract: only plain user type names bind — the
|
|
418
|
+
# same refusal rules as SEAM_TYPE_INFERENCE (optionals, generics, containers, primitives,
|
|
419
|
+
# dotted expressions are all rejected). When "off", the collector pass is skipped entirely
|
|
420
|
+
# and the produced edge set is byte-identical to pre-Slice-#77 behavior.
|
|
421
|
+
SEAM_COMPOSITION_EDGES: str = os.getenv("SEAM_COMPOSITION_EDGES", "on")
|
|
422
|
+
|
|
423
|
+
# ── Method-param composition: 'uses' edges ──────────────────────────────────
|
|
424
|
+
# When "on" (default), the extractor emits an Edge(kind="uses", confidence="INFERRED")
|
|
425
|
+
# from a function/method to every plain user type it references as a PARAMETER in its
|
|
426
|
+
# signature — e.g. `func showOverlay(companionManager: CompanionManager)` emits
|
|
427
|
+
# showOverlay -> CompanionManager. This makes a param-injected dependency a DIRECT (d=1)
|
|
428
|
+
# upstream dependent of the type, complementing `holds` (which captures only STORED
|
|
429
|
+
# composition — fields and constructor params). Conservatism contract is identical to
|
|
430
|
+
# SEAM_COMPOSITION_EDGES: only plain user type names bind (optionals/generics/containers/
|
|
431
|
+
# primitives/dotted expressions rejected via the same per-language plain-type helpers).
|
|
432
|
+
# Higher-volume than `holds` (most typed functions have ≥1 user-typed param) → impact/
|
|
433
|
+
# changes/affected verdicts WIDEN. Extraction-time only; "off" = byte-identical to
|
|
434
|
+
# pre-feature; requires `seam init` re-index to populate.
|
|
435
|
+
SEAM_PARAM_EDGES: str = os.getenv("SEAM_PARAM_EDGES", "on")
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
# ── Tier B B4: Receiver-type inference (Python + TypeScript/JS) ──────────────
|
|
439
|
+
|
|
440
|
+
# Master switch for receiver-type inference in Python and TypeScript/JS extractors.
|
|
441
|
+
# When "on" (default), the extractor resolves receiver expressions (class fields,
|
|
442
|
+
# function parameters, and local variables with type annotations) to qualified
|
|
443
|
+
# 'Type.method' call targets at INDEX time — e.g. `client: Client` → `Client.send`.
|
|
444
|
+
# This fixes the cross-class call collapse (AMBIGUOUS) by emitting the right target.
|
|
445
|
+
# Conservatism contract: only plain user types bind; optionals/generics/unknowns
|
|
446
|
+
# → None → bare target kept (never emit a wrong edge).
|
|
447
|
+
# When "off", inference is skipped entirely and targets remain bare — byte-identical
|
|
448
|
+
# to pre-Tier-B behavior. Gating via env var so tests can toggle without monkey-patching.
|
|
449
|
+
SEAM_TYPE_INFERENCE: str = os.getenv("SEAM_TYPE_INFERENCE", "on")
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
# ── Tier D #12: identifier compound-split tokenization (search recall) ───────
|
|
453
|
+
|
|
454
|
+
# When "on" (default), the indexer writes a camelCase/snake_case-split version of each
|
|
455
|
+
# symbol name (+ qualified_name segments) into symbols.search_text — a dedicated FTS5
|
|
456
|
+
# column — so a natural-language query like "push to talk monitor" matches the camelCase
|
|
457
|
+
# symbol GlobalPushToTalkShortcutMonitor. The query layer (fts.py) splits query terms with
|
|
458
|
+
# the SAME splitter so both sides tokenize identically.
|
|
459
|
+
# Index-time: toggling requires `seam init` re-index (search_text is computed at write time).
|
|
460
|
+
# When "off", search_text is stored NULL and query-term expansion is skipped — byte-identical
|
|
461
|
+
# to pre-Tier-D #12 keyword search.
|
|
462
|
+
SEAM_TOKENIZE_IDENTIFIERS: str = os.getenv("SEAM_TOKENIZE_IDENTIFIERS", "on")
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
# ── P5: Swift inter-class call resolution ────────────────────────────────────
|
|
466
|
+
|
|
467
|
+
# Lightweight receiver-type inference for Swift call edges. When "on" (default),
|
|
468
|
+
# the Swift extractor resolves two HIGH-VALUE member-call patterns to qualified
|
|
469
|
+
# 'Type.method' edges at INDEX time:
|
|
470
|
+
# (1) self.method() → '<EnclosingType>.method'
|
|
471
|
+
# (2) ClassName().method() OR a var assigned from a class instantiation in the
|
|
472
|
+
# SAME function scope (let x = Foo(); x.bar()) → 'Foo.bar'
|
|
473
|
+
# Tracking is function-scope-local (a var→class dict during the AST walk) — no
|
|
474
|
+
# cross-file inference. Set to "off" to revert to bare-identifier-only call edges
|
|
475
|
+
# (byte-identical to pre-P5 behavior). See ADR-009.
|
|
476
|
+
SEAM_SWIFT_TYPE_INFERENCE: str = os.getenv("SEAM_SWIFT_TYPE_INFERENCE", "on")
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
# ── Tier D11: Structure view configuration (seam_structure) ──────────────────
|
|
480
|
+
|
|
481
|
+
# Maximum nesting depth of the structure tree.
|
|
482
|
+
# The root dir node is depth 0; its immediate children are depth 1, and so on.
|
|
483
|
+
# Nodes at depth > max_depth are cut from the tree (not returned); the cut
|
|
484
|
+
# count is added to StructureResult.truncated. Default 8 handles virtually
|
|
485
|
+
# all real codebases (3–5 levels is typical); raise via env var for monorepos.
|
|
486
|
+
SEAM_STRUCTURE_MAX_DEPTH: int = int(os.getenv("SEAM_STRUCTURE_MAX_DEPTH", "8"))
|
|
487
|
+
|
|
488
|
+
# Maximum total number of nodes in the structure tree (excluding the root itself).
|
|
489
|
+
# When the tree would exceed this count, excess nodes are dropped BFS-style (closest
|
|
490
|
+
# to root survive) and StructureResult.truncated reports how many were omitted.
|
|
491
|
+
# Default 2000: sufficient for most repos; prevents MCP token-budget explosions on
|
|
492
|
+
# giant codebases with thousands of files and containers.
|
|
493
|
+
# 0 (or any value <= 0) = UNLIMITED — no node cap (matches the seam_impact limit=0
|
|
494
|
+
# convention; avoids the footgun where a negative value silently empties the tree).
|
|
495
|
+
SEAM_STRUCTURE_MAX_NODES: int = int(os.getenv("SEAM_STRUCTURE_MAX_NODES", "2000"))
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
# ── Edge-synthesis post-pass (PRD #83, Slice #1) ─────────────────────────────
|
|
499
|
+
|
|
500
|
+
# Master switch for the edge-synthesis post-pass. When "on" (default), the post-pass
|
|
501
|
+
# runs after `seam init` and `seam sync` (gated on graph_changed or --force-synthesis)
|
|
502
|
+
# and synthesizes dynamic-dispatch edges the parser cannot see: e.g. interface→
|
|
503
|
+
# implementation method fan-out (A2 channel). Synthesized edges are stored as ordinary
|
|
504
|
+
# call edges tagged with the channel that produced them and lower confidence (INFERRED),
|
|
505
|
+
# so existing kind-agnostic traversal in seam_impact/seam_context/seam_trace picks them
|
|
506
|
+
# up automatically — no read-path changes needed.
|
|
507
|
+
#
|
|
508
|
+
# When "off", the synthesis pass is completely skipped; the graph is byte-identical to
|
|
509
|
+
# pre-synthesis behavior. This is an extraction-time knob: toggling it requires a full
|
|
510
|
+
# `seam init` re-index to take effect (the edges are already stored; changing the knob
|
|
511
|
+
# at read time has no retroactive effect).
|
|
512
|
+
SEAM_EDGE_SYNTHESIS: str = os.getenv("SEAM_EDGE_SYNTHESIS", "on")
|
|
513
|
+
|
|
514
|
+
# Per-channel fan-out cap for edge synthesis. For each base method that participates
|
|
515
|
+
# in the interface-override channel (A2), at most this many synthesized call edges are
|
|
516
|
+
# emitted. This bounds the graph explosion on a widely-implemented interface with many
|
|
517
|
+
# concrete types — e.g. a logger interface with 100 implementations would produce at
|
|
518
|
+
# most SEAM_SYNTHESIS_FANOUT_CAP edges per method, not 100. Conservative default of 40
|
|
519
|
+
# matches the typical class hierarchy depth in a real codebase.
|
|
520
|
+
# Set to 0 to disable the cap (emit all synthesized edges, potentially unbounded).
|
|
521
|
+
SEAM_SYNTHESIS_FANOUT_CAP: int = int(os.getenv("SEAM_SYNTHESIS_FANOUT_CAP", "40"))
|
|
522
|
+
|
|
523
|
+
# Total budget (bytes) of source text the synthesis pass loads into memory for the
|
|
524
|
+
# source-text channels (closure-collection, event-emitter). The pass reads every
|
|
525
|
+
# indexed file's text into one dict; on a very large monorepo that could exhaust
|
|
526
|
+
# memory at the final init step (after clustering already succeeded). Once the
|
|
527
|
+
# cumulative loaded size crosses this budget, no further files are read and a WARNING
|
|
528
|
+
# is logged — synthesis under-produces rather than OOM-killing the indexer. Mirrors
|
|
529
|
+
# the bounded-scan philosophy of SEAM_SEMANTIC_SCAN_CAP. Default 50 MB; 0 = unlimited.
|
|
530
|
+
SEAM_SYNTHESIS_MAX_SOURCE_BYTES: int = int(
|
|
531
|
+
os.getenv("SEAM_SYNTHESIS_MAX_SOURCE_BYTES", str(50 * 1024 * 1024))
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# ── A3: Field-access edges (reads/writes) + fields as first-class symbols ────
|
|
536
|
+
|
|
537
|
+
# Master switch for field-access edge extraction. When "on" (default), the extractor
|
|
538
|
+
# emits an Edge(kind="reads"|"writes") for each attribute access that is NOT in call
|
|
539
|
+
# position. Attribute accesses in call position (obj.method()) remain 'call' edges and
|
|
540
|
+
# are unchanged. Field/property declarations and first self.x = ... assignments become
|
|
541
|
+
# Symbol(kind="field", qualified_name="Type.field") when this is "on".
|
|
542
|
+
# When "off", the extractor produces a graph byte-identical to pre-A3 behavior:
|
|
543
|
+
# no 'field' symbols, no 'reads'/'writes' edges. Extraction-time only — toggling
|
|
544
|
+
# requires a full `seam init` re-index to take effect (same contract as
|
|
545
|
+
# SEAM_COMPOSITION_EDGES / SEAM_TYPE_INFERENCE).
|
|
546
|
+
SEAM_FIELD_ACCESS_EDGES: str = os.getenv("SEAM_FIELD_ACCESS_EDGES", "on")
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def get_db_path(project_root: Path) -> Path:
|
|
550
|
+
"""Resolve the database path relative to the project root."""
|
|
551
|
+
return project_root / SEAM_DB_PATH
|
seam/indexer/__init__.py
ADDED
|
File without changes
|