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/indexer/parser.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Tree-sitter parsing layer — one function per supported language.
|
|
2
|
+
|
|
3
|
+
Returns raw tree-sitter root Nodes for graph.py to interpret.
|
|
4
|
+
Never raises on parse errors; returns None instead.
|
|
5
|
+
|
|
6
|
+
Guards applied before parsing:
|
|
7
|
+
1. File size > SEAM_MAX_FILE_BYTES → None
|
|
8
|
+
2. Binary file (null byte in first 1KB) → None
|
|
9
|
+
3. Any read / OS error → None
|
|
10
|
+
4. broad Exception backstop → None
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import tree_sitter_c as tsc
|
|
16
|
+
import tree_sitter_c_sharp as tscsharp
|
|
17
|
+
import tree_sitter_cpp as tscpp
|
|
18
|
+
import tree_sitter_go as tsgo
|
|
19
|
+
import tree_sitter_java as tsjava
|
|
20
|
+
import tree_sitter_php as tsphp
|
|
21
|
+
import tree_sitter_python as tspython
|
|
22
|
+
import tree_sitter_ruby as tsruby
|
|
23
|
+
import tree_sitter_rust as tsrust
|
|
24
|
+
import tree_sitter_swift as tsswift
|
|
25
|
+
import tree_sitter_typescript as tstypescript
|
|
26
|
+
from tree_sitter import Language, Node, Parser
|
|
27
|
+
|
|
28
|
+
import seam.config as config
|
|
29
|
+
|
|
30
|
+
# Build Language objects once at module level (cheap singletons).
|
|
31
|
+
# Phase 9: six new grammars added alongside the original five.
|
|
32
|
+
_PY_LANG = Language(tspython.language())
|
|
33
|
+
_TS_LANG = Language(tstypescript.language_typescript())
|
|
34
|
+
# TSX grammar is a superset of JS+JSX — used for .js/.mjs/.cjs (no separate JS dep)
|
|
35
|
+
_TSX_LANG = Language(tstypescript.language_tsx())
|
|
36
|
+
_GO_LANG = Language(tsgo.language())
|
|
37
|
+
_RUST_LANG = Language(tsrust.language())
|
|
38
|
+
# Phase 9 grammars
|
|
39
|
+
_JAVA_LANG = Language(tsjava.language())
|
|
40
|
+
_CSHARP_LANG = Language(tscsharp.language())
|
|
41
|
+
_RUBY_LANG = Language(tsruby.language())
|
|
42
|
+
_C_LANG = Language(tsc.language())
|
|
43
|
+
_CPP_LANG = Language(tscpp.language())
|
|
44
|
+
# PHP: language_php() (not language()) to handle the <?php open tag correctly.
|
|
45
|
+
_PHP_LANG = Language(tsphp.language_php())
|
|
46
|
+
# Phase 10 — Swift
|
|
47
|
+
_SWIFT_LANG = Language(tsswift.language())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse(path: Path, language: Language) -> Node | None:
|
|
51
|
+
"""Internal helper: guard checks then parse with tree-sitter.
|
|
52
|
+
|
|
53
|
+
Returns the root_node of the parsed tree, or None if the file should
|
|
54
|
+
be skipped. Never raises — the outer except is the final backstop.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
# Guard 1: file size
|
|
58
|
+
try:
|
|
59
|
+
file_size = path.stat().st_size
|
|
60
|
+
except OSError:
|
|
61
|
+
return None # file does not exist or unreadable
|
|
62
|
+
|
|
63
|
+
if file_size > config.SEAM_MAX_FILE_BYTES:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
# Guard 2: binary check — read first 1 KB and look for null byte
|
|
67
|
+
try:
|
|
68
|
+
with path.open("rb") as fh:
|
|
69
|
+
header = fh.read(1024)
|
|
70
|
+
except OSError:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
if b"\x00" in header:
|
|
74
|
+
return None # binary file, skip gracefully
|
|
75
|
+
|
|
76
|
+
# Guard 3: read full content
|
|
77
|
+
try:
|
|
78
|
+
source_bytes = path.read_bytes()
|
|
79
|
+
except OSError:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
# Parse — tree-sitter never raises on syntax errors, returns ERROR nodes
|
|
83
|
+
parser = Parser(language)
|
|
84
|
+
tree = parser.parse(source_bytes)
|
|
85
|
+
return tree.root_node
|
|
86
|
+
|
|
87
|
+
except Exception: # noqa: BLE001 — broad backstop so parsers never raise
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def parse_python(path: Path) -> Node | None:
|
|
92
|
+
"""Parse a Python source file.
|
|
93
|
+
|
|
94
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
95
|
+
Malformed Python still returns a (possibly partial) tree with ERROR nodes.
|
|
96
|
+
"""
|
|
97
|
+
return _parse(path, _PY_LANG)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def parse_typescript(path: Path) -> Node | None:
|
|
101
|
+
"""Parse a TypeScript (.ts / .tsx) source file.
|
|
102
|
+
|
|
103
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
104
|
+
"""
|
|
105
|
+
return _parse(path, _TS_LANG)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def parse_javascript(path: Path) -> Node | None:
|
|
109
|
+
"""Parse a JavaScript (.js / .mjs / .cjs) source file using the TSX grammar.
|
|
110
|
+
|
|
111
|
+
tree-sitter-typescript's TSX grammar is a superset that covers JS+JSX.
|
|
112
|
+
No separate JS grammar is needed; this is a deliberate Phase 0 decision.
|
|
113
|
+
See lessons.md: '2026-06-01 — JavaScript parsed via the TSX grammar'.
|
|
114
|
+
"""
|
|
115
|
+
return _parse(path, _TSX_LANG)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def parse_go(path: Path) -> Node | None:
|
|
119
|
+
"""Parse a Go source file (.go).
|
|
120
|
+
|
|
121
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
122
|
+
Malformed Go still returns a (possibly partial) tree with ERROR nodes.
|
|
123
|
+
"""
|
|
124
|
+
return _parse(path, _GO_LANG)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def parse_rust(path: Path) -> Node | None:
|
|
128
|
+
"""Parse a Rust source file (.rs).
|
|
129
|
+
|
|
130
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
131
|
+
Malformed Rust still returns a (possibly partial) tree with ERROR nodes.
|
|
132
|
+
"""
|
|
133
|
+
return _parse(path, _RUST_LANG)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ── Phase 9 parsers ────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def parse_java(path: Path) -> Node | None:
|
|
140
|
+
"""Parse a Java source file (.java).
|
|
141
|
+
|
|
142
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
143
|
+
Malformed Java still returns a (possibly partial) tree with ERROR nodes.
|
|
144
|
+
"""
|
|
145
|
+
return _parse(path, _JAVA_LANG)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def parse_csharp(path: Path) -> Node | None:
|
|
149
|
+
"""Parse a C# source file (.cs).
|
|
150
|
+
|
|
151
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
152
|
+
Malformed C# still returns a (possibly partial) tree with ERROR nodes.
|
|
153
|
+
"""
|
|
154
|
+
return _parse(path, _CSHARP_LANG)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def parse_ruby(path: Path) -> Node | None:
|
|
158
|
+
"""Parse a Ruby source file (.rb).
|
|
159
|
+
|
|
160
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
161
|
+
Malformed Ruby still returns a (possibly partial) tree with ERROR nodes.
|
|
162
|
+
"""
|
|
163
|
+
return _parse(path, _RUBY_LANG)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def parse_c(path: Path) -> Node | None:
|
|
167
|
+
"""Parse a C source file (.c / .h).
|
|
168
|
+
|
|
169
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
170
|
+
Both .c and .h files use the same C grammar — .h→C is a deliberate MVP decision.
|
|
171
|
+
Malformed C still returns a (possibly partial) tree with ERROR nodes.
|
|
172
|
+
"""
|
|
173
|
+
return _parse(path, _C_LANG)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def parse_cpp(path: Path) -> Node | None:
|
|
177
|
+
"""Parse a C++ source file (.cpp / .cc / .cxx / .hpp / .hh / .hxx / .c++).
|
|
178
|
+
|
|
179
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
180
|
+
Malformed C++ still returns a (possibly partial) tree with ERROR nodes.
|
|
181
|
+
"""
|
|
182
|
+
return _parse(path, _CPP_LANG)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def parse_php(path: Path) -> Node | None:
|
|
186
|
+
"""Parse a PHP source file (.php).
|
|
187
|
+
|
|
188
|
+
Uses language_php() (not language()) which handles the <?php open tag.
|
|
189
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
190
|
+
Malformed PHP still returns a (possibly partial) tree with ERROR nodes.
|
|
191
|
+
"""
|
|
192
|
+
return _parse(path, _PHP_LANG)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ── Phase 10 parser ────────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def parse_swift(path: Path) -> Node | None:
|
|
199
|
+
"""Parse a Swift source file (.swift).
|
|
200
|
+
|
|
201
|
+
Returns tree-sitter root Node, or None for binary/oversized/unreadable files.
|
|
202
|
+
Malformed Swift still returns a (possibly partial) tree with ERROR nodes.
|
|
203
|
+
"""
|
|
204
|
+
return _parse(path, _SWIFT_LANG)
|
seam/indexer/pipeline.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Indexing pipeline — the shared parse -> extract -> upsert path.
|
|
2
|
+
|
|
3
|
+
Lives in indexer/ (not cli/) because BOTH the CLI (`seam init`) and the watcher
|
|
4
|
+
daemon consume it. Putting it here keeps the import hierarchy intact:
|
|
5
|
+
cli -> indexer.pipeline and watcher -> indexer.pipeline
|
|
6
|
+
(watcher importing from cli would violate the layer rules in BACKEND_STRUCTURE.md).
|
|
7
|
+
|
|
8
|
+
Pure-ish glue: no Typer, no watchdog — just stdlib + the indexer modules.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import logging
|
|
13
|
+
import sqlite3
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import seam.config as config
|
|
17
|
+
from seam.analysis.imports import extract_import_mappings
|
|
18
|
+
from seam.indexer.db import upsert_file, upsert_import_mappings
|
|
19
|
+
from seam.indexer.graph import extract_comments, extract_edges, extract_symbols
|
|
20
|
+
from seam.indexer.parser import (
|
|
21
|
+
parse_c,
|
|
22
|
+
parse_cpp,
|
|
23
|
+
parse_csharp,
|
|
24
|
+
parse_go,
|
|
25
|
+
parse_java,
|
|
26
|
+
parse_javascript,
|
|
27
|
+
parse_php,
|
|
28
|
+
parse_python,
|
|
29
|
+
parse_ruby,
|
|
30
|
+
parse_rust,
|
|
31
|
+
parse_swift,
|
|
32
|
+
parse_typescript,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
# Directories to skip when walking the project tree.
|
|
38
|
+
# Dot-dirs are skipped by default; this list catches common non-dot dirs.
|
|
39
|
+
#
|
|
40
|
+
# WHY build/output dirs matter: indexing compiled artifacts (e.g. a minified JS
|
|
41
|
+
# bundle in seam/_web/assets/) injects thousands of garbage symbols and pollutes
|
|
42
|
+
# clustering with meaningless "areas" named after the bundle file. We skip the
|
|
43
|
+
# standard build/output/vendor dir names so `seam init` indexes SOURCE, not
|
|
44
|
+
# generated output. (`dist`/`build`/`node_modules` were always here; `_web`,
|
|
45
|
+
# `target`, `out`, `coverage`, `vendor` close the remaining common gaps.)
|
|
46
|
+
SKIP_DIRS: frozenset[str] = frozenset(
|
|
47
|
+
{
|
|
48
|
+
".git",
|
|
49
|
+
"node_modules",
|
|
50
|
+
".venv",
|
|
51
|
+
"__pycache__",
|
|
52
|
+
".seam",
|
|
53
|
+
"dist",
|
|
54
|
+
"build",
|
|
55
|
+
"_web", # built SPA output (seam/_web/) — minified, not source
|
|
56
|
+
"out", # common JS/TS build output
|
|
57
|
+
"target", # Rust/Maven/Gradle compiled output
|
|
58
|
+
"vendor", # Go/PHP third-party deps (not first-party source)
|
|
59
|
+
"coverage", # coverage report output
|
|
60
|
+
"site", # mkdocs default build output (bundled/minified JS — pollutes clusters)
|
|
61
|
+
"_site", # Jekyll default build output
|
|
62
|
+
".mypy_cache",
|
|
63
|
+
".ruff_cache",
|
|
64
|
+
".pytest_cache",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def sha1(content: bytes) -> str:
|
|
70
|
+
"""Return the SHA-1 hex digest of file content bytes (change detection only)."""
|
|
71
|
+
return hashlib.sha1(content).hexdigest() # noqa: S324 — not used for security
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _dispatch_parser(path: Path, language: str): # type: ignore[return]
|
|
75
|
+
"""Call the correct parser for a language string. Returns root Node or None."""
|
|
76
|
+
if language == "python":
|
|
77
|
+
return parse_python(path)
|
|
78
|
+
if language == "typescript":
|
|
79
|
+
return parse_typescript(path)
|
|
80
|
+
if language == "javascript":
|
|
81
|
+
return parse_javascript(path)
|
|
82
|
+
if language == "go":
|
|
83
|
+
return parse_go(path)
|
|
84
|
+
if language == "rust":
|
|
85
|
+
return parse_rust(path)
|
|
86
|
+
# Phase 9 — new languages
|
|
87
|
+
if language == "java":
|
|
88
|
+
return parse_java(path)
|
|
89
|
+
if language == "csharp":
|
|
90
|
+
return parse_csharp(path)
|
|
91
|
+
if language == "ruby":
|
|
92
|
+
return parse_ruby(path)
|
|
93
|
+
if language == "c":
|
|
94
|
+
return parse_c(path)
|
|
95
|
+
if language == "cpp":
|
|
96
|
+
return parse_cpp(path)
|
|
97
|
+
if language == "php":
|
|
98
|
+
return parse_php(path)
|
|
99
|
+
# Phase 10 — Swift
|
|
100
|
+
if language == "swift":
|
|
101
|
+
return parse_swift(path)
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def index_one_file(conn: sqlite3.Connection, path: Path) -> tuple[int, int] | None:
|
|
106
|
+
"""Parse, extract, and upsert a single source file.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
(symbol_count, edge_count) when the file was INDEXED (upserted) — note
|
|
110
|
+
this is (0, 0) for a valid-but-empty file like an empty __init__.py,
|
|
111
|
+
which IS indexed.
|
|
112
|
+
None when the file was SKIPPED (unsupported ext, oversize, binary/
|
|
113
|
+
unreadable, or an error) — the reason is logged at DEBUG so a
|
|
114
|
+
systematic failure (e.g. a grammar mismatch breaking every .ts file)
|
|
115
|
+
is recoverable instead of silently invisible.
|
|
116
|
+
|
|
117
|
+
Distinguishing None (skipped) from (0, 0) (indexed-but-empty) lets callers
|
|
118
|
+
report an honest skipped-file count. Never raises.
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
ext = path.suffix.lower()
|
|
122
|
+
language = config.SEAM_LANGUAGE_MAP.get(ext)
|
|
123
|
+
if language is None:
|
|
124
|
+
logger.debug("skip %s: unsupported extension", path)
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
if path.stat().st_size > config.SEAM_MAX_FILE_BYTES:
|
|
129
|
+
logger.debug("skip %s: over size limit", path)
|
|
130
|
+
return None
|
|
131
|
+
except OSError as exc:
|
|
132
|
+
logger.debug("skip %s: stat failed: %s", path, exc)
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
root = _dispatch_parser(path, language)
|
|
136
|
+
if root is None:
|
|
137
|
+
logger.debug("skip %s: parser returned None (binary/unreadable)", path)
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
content = path.read_bytes()
|
|
142
|
+
except OSError as exc:
|
|
143
|
+
logger.debug("skip %s: read failed: %s", path, exc)
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
file_hash = sha1(content)
|
|
147
|
+
symbols = extract_symbols(root, language, path)
|
|
148
|
+
# Pass symbols so extract_edges can resolve confidence (EXTRACTED/AMBIGUOUS/INFERRED)
|
|
149
|
+
# against the same-file symbol set. Cross-file ambiguity is handled at query time.
|
|
150
|
+
edges = extract_edges(root, language, path, symbols=symbols)
|
|
151
|
+
# Extract semantic comments (WHY/HACK/NOTE/TODO/FIXME); never raises.
|
|
152
|
+
comments = extract_comments(root, language, path)
|
|
153
|
+
|
|
154
|
+
upsert_file(conn, path, language, file_hash, symbols, edges, comments)
|
|
155
|
+
|
|
156
|
+
# Phase 5: extract and store import mappings for this file.
|
|
157
|
+
# Only runs when SEAM_IMPORT_RESOLUTION is 'on' (default).
|
|
158
|
+
# extract_import_mappings never raises; failures silently return [].
|
|
159
|
+
if config.SEAM_IMPORT_RESOLUTION == "on":
|
|
160
|
+
import_mappings = extract_import_mappings(root, path, language)
|
|
161
|
+
upsert_import_mappings(conn, path, import_mappings)
|
|
162
|
+
|
|
163
|
+
return len(symbols), len(edges)
|
|
164
|
+
|
|
165
|
+
except Exception as exc: # noqa: BLE001 — one bad file must not abort the run
|
|
166
|
+
logger.debug("skip %s: unexpected error: %s", path, exc)
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def walk_project(root: Path) -> list[Path]:
|
|
171
|
+
"""Walk root recursively, skipping ignored dirs, returning indexable files.
|
|
172
|
+
|
|
173
|
+
Rules:
|
|
174
|
+
- Skip any directory whose name starts with '.' (hidden dirs).
|
|
175
|
+
- Skip any directory in SKIP_DIRS.
|
|
176
|
+
- Skip minified bundles (name contains '.min.', e.g. foo.min.js) — they are
|
|
177
|
+
generated, not source, and inject garbage symbols that pollute clustering.
|
|
178
|
+
- Collect files whose suffix is in config.SEAM_LANGUAGE_MAP.
|
|
179
|
+
|
|
180
|
+
The dot/skip check uses only the parts BELOW root (root may itself sit under
|
|
181
|
+
a dot-dir, e.g. ~/.config/proj — that must not exclude everything).
|
|
182
|
+
"""
|
|
183
|
+
files: list[Path] = []
|
|
184
|
+
root_depth = len(root.parts)
|
|
185
|
+
for item in root.rglob("*"):
|
|
186
|
+
if any(
|
|
187
|
+
part.startswith(".") or part in SKIP_DIRS
|
|
188
|
+
for part in item.parts[root_depth:]
|
|
189
|
+
):
|
|
190
|
+
continue
|
|
191
|
+
if (
|
|
192
|
+
item.is_file()
|
|
193
|
+
and item.suffix.lower() in config.SEAM_LANGUAGE_MAP
|
|
194
|
+
and ".min." not in item.name # skip minified bundles (foo.min.js, bundle.min.mjs)
|
|
195
|
+
):
|
|
196
|
+
files.append(item)
|
|
197
|
+
return files
|