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/cli/install.py ADDED
@@ -0,0 +1,232 @@
1
+ """`seam install` / `seam uninstall` CLI commands.
2
+
3
+ Lives in its own module (not main.py) because main.py is already large; these
4
+ two Typer command functions are registered onto the app in main.py.
5
+
6
+ CLI-first by default: `seam install` writes the token-lean CLI **guidance** (a
7
+ Claude Code skill, a Cursor rule, an AGENTS.md block) into the repo. `--with-mcp`
8
+ ALSO writes the MCP server config (the heavier, ~6k-token-standing path). Guidance
9
+ is project-scoped (it lives in the repo); only the MCP config respects `--location`.
10
+ The config-writing logic lives in seam/installer/* — this layer parses options,
11
+ orchestrates over the selected target(s), and renders Rich / JSON output.
12
+ """
13
+
14
+ from pathlib import Path
15
+ from typing import Any, NoReturn
16
+
17
+ import typer
18
+ from rich.console import Console
19
+
20
+ import seam.config as config
21
+ from seam.cli.output import emit_json, emit_json_error
22
+ from seam.installer import TARGETS, AgentTarget, get_target, resolve_seam_command
23
+
24
+ console = Console()
25
+
26
+ _ALL = "all"
27
+
28
+
29
+ def _select_targets(target: str) -> list[AgentTarget] | None:
30
+ """Resolve the --target value to a list of AgentTargets, or None if unknown."""
31
+ if target == _ALL:
32
+ return list(TARGETS.values())
33
+ one = get_target(target)
34
+ return [one] if one is not None else None
35
+
36
+
37
+ def _install_target(
38
+ tgt: AgentTarget, root: Path, location: str, command: str, args: list[str], with_mcp: bool
39
+ ) -> dict[str, Any]:
40
+ """Write guidance (always) and, if requested, the MCP config for one target."""
41
+ entry: dict[str, Any] = {
42
+ "target": tgt.name,
43
+ "guidance": [{"action": r.action, "path": r.path} for r in tgt.install_guidance(root)],
44
+ "mcp": None,
45
+ }
46
+ if not with_mcp:
47
+ return entry
48
+ if location in tgt.supported_locations():
49
+ res = tgt.install(root, location, command, args)
50
+ entry["mcp"] = {"action": res.action, "path": res.path, "backed_up": res.backed_up}
51
+ else:
52
+ # Only reachable under `all` — an explicit single target is validated upfront.
53
+ entry["mcp"] = {
54
+ "action": "skipped",
55
+ "reason": f"MCP location '{location}' unsupported (supports {tgt.supported_locations()})",
56
+ "path": None,
57
+ }
58
+ return entry
59
+
60
+
61
+ def _preview_target(
62
+ tgt: AgentTarget, root: Path, location: str, command: str, args: list[str], with_mcp: bool
63
+ ) -> dict[str, Any]:
64
+ """Build the --print-config preview for one target (writes nothing)."""
65
+ entry: dict[str, Any] = {
66
+ "target": tgt.name,
67
+ "guidance_preview": [{"path": p, "content": c} for p, c in tgt.guidance_previews(root)],
68
+ "mcp_preview": None,
69
+ }
70
+ if with_mcp and location in tgt.supported_locations():
71
+ entry["mcp_preview"] = {
72
+ "path": str(tgt.config_path(root, location)),
73
+ "config": tgt.render_entry(command, args),
74
+ }
75
+ return entry
76
+
77
+
78
+ def install_command(
79
+ path: str = typer.Argument(".", help="Project root to index (default: current directory)."),
80
+ target: str = typer.Option("claude", "--target", help="claude | cursor | codex | all"),
81
+ location: str = typer.Option(
82
+ "project", "--location", help="MCP config scope: project | user (codex: user only)."
83
+ ),
84
+ with_mcp: bool = typer.Option(
85
+ False, "--with-mcp", help="Also write the MCP server config (CLI guidance is the default)."
86
+ ),
87
+ print_config: bool = typer.Option(
88
+ False, "--print-config", help="Print what would be written; write nothing."
89
+ ),
90
+ json_: bool = typer.Option(False, "--json", help="Emit a structured JSON envelope to stdout."),
91
+ ) -> None:
92
+ """Set up Seam for a coding agent (Claude Code / Cursor / Codex).
93
+
94
+ Default: writes the token-lean CLI guidance into the repo so the agent queries
95
+ via the `seam` CLI. Add `--with-mcp` to also wire the MCP server. Idempotent and
96
+ reversible with `seam uninstall`.
97
+ """
98
+ root = Path(path).resolve()
99
+ if not root.is_dir():
100
+ _fail(json_, "INVALID_INPUT", f"'{root}' is not a directory.")
101
+
102
+ targets = _select_targets(target)
103
+ if targets is None:
104
+ _fail(json_, "INVALID_INPUT", f"unknown target '{target}' (expected: {', '.join(TARGETS)} or all)")
105
+
106
+ # Validate an explicit single target's MCP location BEFORE any write, so we
107
+ # never leave guidance written and then fail. (Under `all`, a bad location is
108
+ # skipped per-target instead.)
109
+ if with_mcp and target != _ALL and location not in targets[0].supported_locations():
110
+ _fail(
111
+ json_,
112
+ "INVALID_INPUT",
113
+ f"target '{targets[0].name}' MCP config does not support location '{location}' "
114
+ f"(supports: {', '.join(targets[0].supported_locations())})",
115
+ )
116
+
117
+ command, found = resolve_seam_command()
118
+ args = ["start", str(root)]
119
+ index_present = config.get_db_path(root).exists()
120
+
121
+ results: list[dict[str, Any]] = []
122
+ for tgt in targets:
123
+ if print_config:
124
+ results.append(_preview_target(tgt, root, location, command, args, with_mcp))
125
+ else:
126
+ results.append(_install_target(tgt, root, location, command, args, with_mcp))
127
+
128
+ warnings: list[str] = []
129
+ if with_mcp and not found:
130
+ warnings.append(
131
+ "Could not locate the 'seam' executable — wrote the bare command 'seam' "
132
+ "(valid once Seam is on PATH / published)."
133
+ )
134
+ if not index_present:
135
+ warnings.append(f"No index at {root}/.seam — run 'seam init' so the agent has something to query.")
136
+
137
+ data = {
138
+ "with_mcp": with_mcp,
139
+ "seam_command": command if with_mcp else None,
140
+ "args": args if with_mcp else None,
141
+ "index_present": index_present,
142
+ "print_config": print_config,
143
+ "results": results,
144
+ "warnings": warnings,
145
+ }
146
+ if json_:
147
+ emit_json(data)
148
+ return
149
+ _render_install(data)
150
+
151
+
152
+ def uninstall_command(
153
+ path: str = typer.Argument(".", help="Project root the entry points at (default: current directory)."),
154
+ target: str = typer.Option("claude", "--target", help="claude | cursor | codex | all"),
155
+ location: str = typer.Option("project", "--location", help="MCP config scope: project | user."),
156
+ json_: bool = typer.Option(False, "--json", help="Emit a structured JSON envelope to stdout."),
157
+ ) -> None:
158
+ """Remove Seam's guidance AND MCP config from a coding agent (reverses `seam install`)."""
159
+ root = Path(path).resolve()
160
+ targets = _select_targets(target)
161
+ if targets is None:
162
+ _fail(json_, "INVALID_INPUT", f"unknown target '{target}' (expected: {', '.join(TARGETS)} or all)")
163
+
164
+ results: list[dict[str, Any]] = []
165
+ for tgt in targets:
166
+ entry: dict[str, Any] = {
167
+ "target": tgt.name,
168
+ "guidance": [{"action": r.action, "path": r.path} for r in tgt.uninstall_guidance(root)],
169
+ }
170
+ if location in tgt.supported_locations():
171
+ res = tgt.uninstall(root, location)
172
+ entry["mcp"] = {"action": res.action, "path": res.path}
173
+ else:
174
+ entry["mcp"] = {"action": "skipped", "path": None}
175
+ results.append(entry)
176
+
177
+ data = {"results": results}
178
+ if json_:
179
+ emit_json(data)
180
+ return
181
+ for entry in results:
182
+ guide_str = ", ".join(f"{Path(g['path']).name}:{g['action']}" for g in entry["guidance"])
183
+ console.print(
184
+ f" [bold]{entry['target']}[/bold] guidance: [dim]{guide_str}[/dim] "
185
+ f"MCP: [dim]{entry['mcp']['action']}[/dim]"
186
+ )
187
+
188
+
189
+ def _fail(json_: bool, code: str, message: str) -> NoReturn:
190
+ """Emit an error in the requested format and exit non-zero (always raises)."""
191
+ if json_:
192
+ emit_json_error(code, message)
193
+ else:
194
+ console.print(f"[red]Error:[/red] {message}")
195
+ raise typer.Exit(code=1)
196
+
197
+
198
+ def _render_install(data: dict[str, Any]) -> None:
199
+ """Human-readable Rich output for `seam install`."""
200
+ if data["print_config"]:
201
+ for entry in data["results"]:
202
+ console.print(f"\n[bold]{entry['target']}[/bold]")
203
+ for prev in entry["guidance_preview"]:
204
+ console.print(f" [dim]{prev['path']}[/dim]")
205
+ console.print(prev["content"])
206
+ if entry["mcp_preview"]:
207
+ console.print(f" [dim]{entry['mcp_preview']['path']} (MCP)[/dim]")
208
+ console.print(entry["mcp_preview"]["config"])
209
+ return
210
+
211
+ mode = "guidance + MCP" if data["with_mcp"] else "guidance"
212
+ console.print(f"[bold]seam install[/bold] [dim]({mode})[/dim]")
213
+ for entry in data["results"]:
214
+ guide_str = ", ".join(f"{Path(g['path']).name}:{g['action']}" for g in entry["guidance"])
215
+ console.print(f" [green]✓ {entry['target']}[/green] guidance: [dim]{guide_str}[/dim]")
216
+ mcp = entry["mcp"]
217
+ if mcp is None:
218
+ continue
219
+ if mcp["action"] == "skipped":
220
+ console.print(f" [yellow]⊘ MCP skipped[/yellow] [dim]({mcp.get('reason', '')})[/dim]")
221
+ else:
222
+ backed = " [yellow](backed up corrupt config)[/yellow]" if mcp.get("backed_up") else ""
223
+ console.print(f" [green]MCP: {mcp['action']}[/green] [dim]{mcp['path']}[/dim]{backed}")
224
+
225
+ for warning in data["warnings"]:
226
+ console.print(f" [yellow]![/yellow] {warning}")
227
+ # Claude project-scoped MCP servers need a one-time approval the first time the agent runs.
228
+ if data["with_mcp"] and any(
229
+ e["target"] == "claude" and e["mcp"] and e["mcp"].get("action") in ("created", "updated")
230
+ for e in data["results"]
231
+ ):
232
+ console.print(" [dim]Claude Code will prompt once to approve the project MCP server on next launch.[/dim]")