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.
Files changed (109) hide show
  1. seam/__init__.py +3 -0
  2. seam/_data/schema.sql +225 -0
  3. seam/_web/assets/index-BL_tqprR.js +216 -0
  4. seam/_web/assets/index-GTKUhVyD.css +1 -0
  5. seam/_web/index.html +13 -0
  6. seam/analysis/__init__.py +14 -0
  7. seam/analysis/affected.py +254 -0
  8. seam/analysis/builtins.py +966 -0
  9. seam/analysis/byte_budget.py +217 -0
  10. seam/analysis/changes.py +709 -0
  11. seam/analysis/cluster_naming.py +260 -0
  12. seam/analysis/clustering.py +216 -0
  13. seam/analysis/confidence.py +699 -0
  14. seam/analysis/embeddings.py +195 -0
  15. seam/analysis/flows.py +708 -0
  16. seam/analysis/impact.py +444 -0
  17. seam/analysis/imports.py +994 -0
  18. seam/analysis/imports_ext.py +780 -0
  19. seam/analysis/imports_resolve.py +176 -0
  20. seam/analysis/processes.py +453 -0
  21. seam/analysis/relevance.py +155 -0
  22. seam/analysis/rwr.py +129 -0
  23. seam/analysis/staleness.py +328 -0
  24. seam/analysis/steer.py +282 -0
  25. seam/analysis/synthesis.py +253 -0
  26. seam/analysis/synthesis_channels.py +433 -0
  27. seam/analysis/testpaths.py +103 -0
  28. seam/analysis/traversal.py +470 -0
  29. seam/cli/__init__.py +0 -0
  30. seam/cli/install.py +232 -0
  31. seam/cli/main.py +2602 -0
  32. seam/cli/output.py +137 -0
  33. seam/cli/read.py +244 -0
  34. seam/cli/serve.py +145 -0
  35. seam/config.py +551 -0
  36. seam/indexer/__init__.py +0 -0
  37. seam/indexer/cluster_index.py +425 -0
  38. seam/indexer/db.py +496 -0
  39. seam/indexer/embedding_index.py +183 -0
  40. seam/indexer/field_access.py +536 -0
  41. seam/indexer/field_access_c_cpp.py +643 -0
  42. seam/indexer/field_access_ext.py +708 -0
  43. seam/indexer/field_access_ext2.py +408 -0
  44. seam/indexer/field_access_go_rust.py +737 -0
  45. seam/indexer/field_access_php_swift.py +888 -0
  46. seam/indexer/field_access_ts.py +626 -0
  47. seam/indexer/graph.py +321 -0
  48. seam/indexer/graph_c.py +562 -0
  49. seam/indexer/graph_c_cpp.py +39 -0
  50. seam/indexer/graph_common.py +644 -0
  51. seam/indexer/graph_cpp.py +615 -0
  52. seam/indexer/graph_csharp.py +651 -0
  53. seam/indexer/graph_go.py +723 -0
  54. seam/indexer/graph_go_rust.py +39 -0
  55. seam/indexer/graph_java.py +689 -0
  56. seam/indexer/graph_java_csharp.py +38 -0
  57. seam/indexer/graph_php.py +914 -0
  58. seam/indexer/graph_python.py +628 -0
  59. seam/indexer/graph_ruby.py +748 -0
  60. seam/indexer/graph_rust.py +653 -0
  61. seam/indexer/graph_scope_infer.py +902 -0
  62. seam/indexer/graph_scope_infer_ext.py +723 -0
  63. seam/indexer/graph_scope_infer_ext2.py +992 -0
  64. seam/indexer/graph_swift.py +1014 -0
  65. seam/indexer/graph_swift_infer.py +515 -0
  66. seam/indexer/graph_typescript.py +663 -0
  67. seam/indexer/migrations.py +816 -0
  68. seam/indexer/parser.py +204 -0
  69. seam/indexer/pipeline.py +197 -0
  70. seam/indexer/signatures.py +634 -0
  71. seam/indexer/signatures_ext.py +780 -0
  72. seam/indexer/sync.py +287 -0
  73. seam/indexer/synthesis_index.py +291 -0
  74. seam/indexer/tokenize.py +79 -0
  75. seam/installer/__init__.py +67 -0
  76. seam/installer/claude.py +97 -0
  77. seam/installer/codex.py +94 -0
  78. seam/installer/core.py +127 -0
  79. seam/installer/cursor.py +61 -0
  80. seam/installer/guide.py +110 -0
  81. seam/installer/jsonfile.py +85 -0
  82. seam/installer/markdownfile.py +146 -0
  83. seam/installer/tomlfile.py +72 -0
  84. seam/query/__init__.py +0 -0
  85. seam/query/clusters.py +206 -0
  86. seam/query/comments.py +217 -0
  87. seam/query/context.py +293 -0
  88. seam/query/engine.py +940 -0
  89. seam/query/fts.py +328 -0
  90. seam/query/names.py +470 -0
  91. seam/query/pack.py +433 -0
  92. seam/query/semantic.py +339 -0
  93. seam/query/structure.py +727 -0
  94. seam/server/__init__.py +0 -0
  95. seam/server/graph_api.py +437 -0
  96. seam/server/handler_common.py +323 -0
  97. seam/server/impact_handler.py +615 -0
  98. seam/server/mcp.py +556 -0
  99. seam/server/tools.py +697 -0
  100. seam/server/trace_handler.py +184 -0
  101. seam/server/web.py +922 -0
  102. seam/watcher/__init__.py +0 -0
  103. seam/watcher/__main__.py +56 -0
  104. seam/watcher/daemon.py +237 -0
  105. seam_code-0.3.0.dist-info/METADATA +318 -0
  106. seam_code-0.3.0.dist-info/RECORD +109 -0
  107. seam_code-0.3.0.dist-info/WHEEL +4 -0
  108. seam_code-0.3.0.dist-info/entry_points.txt +2 -0
  109. seam_code-0.3.0.dist-info/licenses/LICENSE +21 -0
seam/indexer/graph.py ADDED
@@ -0,0 +1,321 @@
1
+ """Symbol and edge extraction from tree-sitter AST nodes.
2
+
3
+ Pure functions: take AST node + metadata, return structured data.
4
+ No I/O, no DB, no side effects.
5
+
6
+ LAYER: sits between graph_common/graph_go_rust (below) and pipeline.py/db.py (above).
7
+ - Imports shared types and helpers from graph_common (leaf — no seam deps).
8
+ - Imports Go/Rust extractors from graph_go_rust (which also imports graph_common only).
9
+ - Re-exports all public TypedDicts so callers can continue using:
10
+ from seam.indexer.graph import Symbol, Edge, Comment, Confidence
11
+
12
+ Contract (evolved from Phase-0 FROZEN — see docs/CONTRACT.md):
13
+ Symbol fields: name, kind, file, start_line, end_line, docstring
14
+ Edge fields: source, target, kind, file, line, confidence (Phase 1 addition)
15
+
16
+ Confidence — two-layer model:
17
+ Layer 1 — stored column (same-file scope, index time):
18
+ Computed by _resolve_confidence_multi against the symbol list from the SAME FILE.
19
+ EXTRACTED — target resolves to exactly one symbol in the same-file set.
20
+ AMBIGUOUS — target matches more than one symbol in the same-file set.
21
+ INFERRED — target not in the same-file set (heuristic / external).
22
+ This is a cheap debugging hint only — NOT authoritative for cross-file edges.
23
+
24
+ Layer 2 — read-time whole-index resolution (authoritative, see seam/analysis/confidence.py):
25
+ At query time, confidence is re-resolved against the full symbol index.
26
+ EXTRACTED — target name is unique across the ENTIRE index.
27
+ AMBIGUOUS — target name is shared by more than one indexed symbol.
28
+ INFERRED — target name is not in the index at all (external, stdlib, dynamic).
29
+ This overrides the stored column value; no schema change is needed.
30
+ """
31
+
32
+ import logging
33
+ from pathlib import Path
34
+
35
+ from tree_sitter import Node
36
+
37
+ # All seam imports in one block (alphabetically ordered, as required by ruff/isort).
38
+ # Layer structure:
39
+ # graph_common (leaf — no seam deps)
40
+ # graph_scope_infer (leaf — imports graph_common only; Tier B B4 receiver-type inference)
41
+ # graph_c / graph_cpp / graph_go / graph_rust / graph_java / graph_csharp /
42
+ # graph_python / graph_typescript (language leaves — import graph_common only; no cycle)
43
+ # graph_c_cpp / graph_go_rust / graph_java_csharp (thin re-exporters)
44
+ # graph_php / graph_ruby / graph_swift (language families)
45
+ # graph.py (this file — dispatcher only; imports all of the above)
46
+ from seam.indexer.graph_c_cpp import (
47
+ _extract_comments_c,
48
+ _extract_comments_cpp,
49
+ _extract_edges_c,
50
+ _extract_edges_cpp,
51
+ _extract_symbols_c,
52
+ _extract_symbols_cpp,
53
+ )
54
+ from seam.indexer.graph_common import (
55
+ SEMANTIC_MARKERS,
56
+ Comment,
57
+ Confidence,
58
+ Edge,
59
+ Symbol,
60
+ )
61
+ from seam.indexer.graph_go_rust import (
62
+ _extract_comments_go,
63
+ _extract_comments_rust,
64
+ _extract_edges_go,
65
+ _extract_edges_rust,
66
+ _extract_symbols_go,
67
+ _extract_symbols_rust,
68
+ )
69
+ from seam.indexer.graph_java_csharp import (
70
+ _extract_comments_csharp,
71
+ _extract_comments_java,
72
+ _extract_edges_csharp,
73
+ _extract_edges_java,
74
+ _extract_symbols_csharp,
75
+ _extract_symbols_java,
76
+ )
77
+ from seam.indexer.graph_php import (
78
+ _extract_comments_php,
79
+ _extract_edges_php,
80
+ _extract_symbols_php,
81
+ )
82
+ from seam.indexer.graph_python import (
83
+ _extract_comments_python,
84
+ _extract_edges_python,
85
+ _extract_symbols_python,
86
+ )
87
+ from seam.indexer.graph_ruby import (
88
+ _extract_comments_ruby,
89
+ _extract_edges_ruby,
90
+ _extract_symbols_ruby,
91
+ )
92
+ from seam.indexer.graph_swift import (
93
+ _extract_comments_swift,
94
+ _extract_edges_swift,
95
+ _extract_symbols_swift,
96
+ )
97
+ from seam.indexer.graph_typescript import (
98
+ _extract_comments_typescript,
99
+ _extract_edges_typescript,
100
+ _extract_symbols_typescript,
101
+ )
102
+
103
+ # Keep these names visible for `from seam.indexer.graph import ...` callers.
104
+ __all__ = [
105
+ "Comment",
106
+ "Confidence",
107
+ "Edge",
108
+ "Symbol",
109
+ "SEMANTIC_MARKERS",
110
+ "extract_comments",
111
+ "extract_edges",
112
+ "extract_symbols",
113
+ ]
114
+
115
+ logger = logging.getLogger(__name__)
116
+
117
+
118
+ # ── Internal confidence helper ─────────────────────────────────────────────────
119
+
120
+
121
+ def _resolve_confidence_multi(target_name: str, symbol_name_counts: dict[str, int]) -> Confidence:
122
+ """Resolve confidence using a same-file name->count mapping.
123
+
124
+ SCOPE: same-file only — this is a lower-bound hint stored on the edge.
125
+ The authoritative whole-index resolution lives in seam/analysis/confidence.py.
126
+
127
+ Args:
128
+ target_name: The edge target name to resolve.
129
+ symbol_name_counts: Mapping of symbol_name -> occurrence count in THIS file only.
130
+ """
131
+ count = symbol_name_counts.get(target_name, 0)
132
+ if count == 1:
133
+ return "EXTRACTED"
134
+ if count > 1:
135
+ return "AMBIGUOUS"
136
+ return "INFERRED"
137
+
138
+
139
+ # NOTE: Python extraction (_extract_symbols_python, _extract_edges_python) lives in
140
+ # graph_python.py. TypeScript/JS extraction lives in graph_typescript.py.
141
+ # Both are imported at the top of this file. graph.py is now a pure dispatcher.
142
+
143
+ # ── Public API ─────────────────────────────────────────────────────────────────
144
+
145
+
146
+ def extract_symbols(node: object, language: str, filepath: Path) -> list[Symbol]:
147
+ """Extract all symbol definitions from an AST root node.
148
+
149
+ Args:
150
+ node: tree-sitter root node returned by parser.parse_*(path)
151
+ language: 'python' | 'typescript' | 'javascript' | 'go' | 'rust' |
152
+ 'java' | 'csharp' | 'ruby' | 'c' | 'cpp' | 'php'
153
+ filepath: resolved absolute Path to the source file
154
+
155
+ Returns list of Symbol TypedDicts (may be empty, never raises).
156
+ """
157
+ if not isinstance(node, Node):
158
+ return []
159
+ try:
160
+ if language == "python":
161
+ return _extract_symbols_python(node, filepath)
162
+ elif language in ("typescript", "javascript"):
163
+ return _extract_symbols_typescript(node, filepath)
164
+ elif language == "go":
165
+ return _extract_symbols_go(node, filepath)
166
+ elif language == "rust":
167
+ return _extract_symbols_rust(node, filepath)
168
+ # Phase 9 — new languages (stubs return []; family agents fill logic)
169
+ elif language == "java":
170
+ return _extract_symbols_java(node, filepath)
171
+ elif language == "csharp":
172
+ return _extract_symbols_csharp(node, filepath)
173
+ elif language == "ruby":
174
+ return _extract_symbols_ruby(node, filepath)
175
+ elif language == "c":
176
+ return _extract_symbols_c(node, filepath)
177
+ elif language == "cpp":
178
+ return _extract_symbols_cpp(node, filepath)
179
+ elif language == "php":
180
+ return _extract_symbols_php(node, filepath)
181
+ # Phase 10 — Swift
182
+ elif language == "swift":
183
+ return _extract_symbols_swift(node, filepath)
184
+ except Exception: # noqa: BLE001
185
+ # WHY log: a silent except here would make a grammar-version break
186
+ # or a bad language string completely invisible. Logging at debug
187
+ # preserves the never-raise contract while surfacing the root cause.
188
+ logger.debug(
189
+ "extract_symbols: unhandled exception for language=%r file=%s",
190
+ language,
191
+ filepath,
192
+ exc_info=True,
193
+ )
194
+ return []
195
+ return []
196
+
197
+
198
+ def extract_comments(node: object, language: str, filepath: Path) -> list[Comment]:
199
+ """Extract semantic comments from an AST root node.
200
+
201
+ Only WHY/HACK/NOTE/TODO/FIXME-tagged comments are returned; plain comments
202
+ are silently ignored. The marker is normalized to UPPERCASE.
203
+
204
+ Args:
205
+ node: tree-sitter root node returned by parser.parse_*(path).
206
+ language: 'python' | 'typescript' | 'javascript' | 'go' | 'rust' |
207
+ 'java' | 'csharp' | 'ruby' | 'c' | 'cpp' | 'php'
208
+ filepath: resolved absolute Path to the source file.
209
+
210
+ Returns list of Comment TypedDicts (may be empty, never raises).
211
+ """
212
+ if not isinstance(node, Node):
213
+ return []
214
+ try:
215
+ if language == "python":
216
+ return _extract_comments_python(node, filepath)
217
+ elif language in ("typescript", "javascript"):
218
+ return _extract_comments_typescript(node, filepath)
219
+ elif language == "go":
220
+ return _extract_comments_go(node, filepath)
221
+ elif language == "rust":
222
+ return _extract_comments_rust(node, filepath)
223
+ # Phase 9 — new languages (stubs return []; family agents fill logic)
224
+ elif language == "java":
225
+ return _extract_comments_java(node, filepath)
226
+ elif language == "csharp":
227
+ return _extract_comments_csharp(node, filepath)
228
+ elif language == "ruby":
229
+ return _extract_comments_ruby(node, filepath)
230
+ elif language == "c":
231
+ return _extract_comments_c(node, filepath)
232
+ elif language == "cpp":
233
+ return _extract_comments_cpp(node, filepath)
234
+ elif language == "php":
235
+ return _extract_comments_php(node, filepath)
236
+ # Phase 10 — Swift
237
+ elif language == "swift":
238
+ return _extract_comments_swift(node, filepath)
239
+ except Exception: # noqa: BLE001
240
+ logger.debug(
241
+ "extract_comments: unhandled exception for language=%r file=%s",
242
+ language,
243
+ filepath,
244
+ exc_info=True,
245
+ )
246
+ return []
247
+ return []
248
+
249
+
250
+ def extract_edges(
251
+ node: object,
252
+ language: str,
253
+ filepath: Path,
254
+ symbols: list[Symbol] | None = None,
255
+ ) -> list[Edge]:
256
+ """Extract import and call edges from an AST root node.
257
+
258
+ Args:
259
+ node: tree-sitter root node returned by parser.parse_*(path)
260
+ language: 'python' | 'typescript' | 'javascript' | 'go' | 'rust'
261
+ filepath: resolved absolute Path to the source file
262
+ symbols: Optional list of symbols extracted from the same file.
263
+ When provided, each edge's confidence is resolved:
264
+ EXTRACTED — target name matches exactly one symbol in the list
265
+ AMBIGUOUS — target name matches more than one symbol
266
+ INFERRED — target not in the symbol list (default/heuristic)
267
+ When omitted, all edges carry confidence='INFERRED'.
268
+
269
+ Returns list of Edge TypedDicts (may be empty, never raises).
270
+ """
271
+ if not isinstance(node, Node):
272
+ return []
273
+ try:
274
+ if language == "python":
275
+ raw_edges = _extract_edges_python(node, filepath)
276
+ elif language in ("typescript", "javascript"):
277
+ raw_edges = _extract_edges_typescript(node, filepath)
278
+ elif language == "go":
279
+ raw_edges = _extract_edges_go(node, filepath)
280
+ elif language == "rust":
281
+ raw_edges = _extract_edges_rust(node, filepath)
282
+ # Phase 9 — new languages (stubs return []; family agents fill logic)
283
+ elif language == "java":
284
+ raw_edges = _extract_edges_java(node, filepath)
285
+ elif language == "csharp":
286
+ raw_edges = _extract_edges_csharp(node, filepath)
287
+ elif language == "ruby":
288
+ raw_edges = _extract_edges_ruby(node, filepath)
289
+ elif language == "c":
290
+ raw_edges = _extract_edges_c(node, filepath)
291
+ elif language == "cpp":
292
+ raw_edges = _extract_edges_cpp(node, filepath)
293
+ elif language == "php":
294
+ raw_edges = _extract_edges_php(node, filepath)
295
+ # Phase 10 — Swift
296
+ elif language == "swift":
297
+ raw_edges = _extract_edges_swift(node, filepath)
298
+ else:
299
+ return []
300
+
301
+ if symbols is None:
302
+ return raw_edges
303
+
304
+ # Build a name-count map from the symbol list to detect same-file duplicates.
305
+ name_counts: dict[str, int] = {}
306
+ for sym in symbols:
307
+ name_counts[sym["name"]] = name_counts.get(sym["name"], 0) + 1
308
+
309
+ # Annotate each edge's confidence based on resolution against the symbol set.
310
+ for edge in raw_edges:
311
+ edge["confidence"] = _resolve_confidence_multi(edge["target"], name_counts)
312
+ return raw_edges
313
+
314
+ except Exception: # noqa: BLE001
315
+ logger.debug(
316
+ "extract_edges: unhandled exception for language=%r file=%s",
317
+ language,
318
+ filepath,
319
+ exc_info=True,
320
+ )
321
+ return []