hyperflash 1.0.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.
@@ -0,0 +1,342 @@
1
+ """
2
+ Model Context Protocol (MCP) stdio server for HyperFlash:
3
+ Exposes deterministic reliability tools to Cursor, Claude Code, Windsurf, and custom agent harnesses.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from hyperflash.core import (
13
+ check_for_elision,
14
+ generate_symbol_graph,
15
+ load_contract,
16
+ run_verification,
17
+ save_contract,
18
+ )
19
+
20
+ TOOL_DEFINITIONS: list[dict[str, Any]] = [
21
+ {
22
+ "name": "hyperflash_safe_patch",
23
+ "description": "Applies a surgical code patch to a file with Invariant 7 (Zero Elision) and Invariant 2 (Scope) guardrails.",
24
+ "inputSchema": {
25
+ "type": "object",
26
+ "properties": {
27
+ "target_file": {
28
+ "type": "string",
29
+ "description": "Path to the target file relative to workspace root.",
30
+ },
31
+ "search_text": {
32
+ "type": "string",
33
+ "description": "Exact text substring to find and replace.",
34
+ },
35
+ "replacement_text": {
36
+ "type": "string",
37
+ "description": "100% complete replacement text without placeholder comments or truncations.",
38
+ },
39
+ },
40
+ "required": ["target_file", "search_text", "replacement_text"],
41
+ },
42
+ },
43
+ {
44
+ "name": "hyperflash_verify",
45
+ "description": "Executes the HyperFlash deterministic verification hierarchy (syntax, types, linter, tests).",
46
+ "inputSchema": {
47
+ "type": "object",
48
+ "properties": {
49
+ "tier": {
50
+ "type": "integer",
51
+ "description": "Maximum verification tier to execute (1 to 9). Default is 6.",
52
+ "default": 6,
53
+ },
54
+ "custom_cmd": {
55
+ "type": "string",
56
+ "description": "Optional custom test or validation command to run.",
57
+ "default": "",
58
+ },
59
+ },
60
+ },
61
+ },
62
+ {
63
+ "name": "hyperflash_init_contract",
64
+ "description": "Initializes or updates .agents/state/contract.json with operational mode, allowed write scope, and criteria.",
65
+ "inputSchema": {
66
+ "type": "object",
67
+ "properties": {
68
+ "mode": {
69
+ "type": "string",
70
+ "description": "Execution mode (implement, fix, refactor, direct).",
71
+ "default": "implement",
72
+ },
73
+ "allowed_scope": {
74
+ "type": "array",
75
+ "items": {"type": "string"},
76
+ "description": "List of allowed file paths or directory prefixes for writes.",
77
+ "default": [],
78
+ },
79
+ "acceptance_criteria": {
80
+ "type": "array",
81
+ "items": {"type": "string"},
82
+ "description": "List of explicit success criteria to satisfy.",
83
+ "default": [],
84
+ },
85
+ },
86
+ },
87
+ },
88
+ {
89
+ "name": "hyperflash_get_symbol_graph",
90
+ "description": "Returns the workspace AST symbol graph detailing classes, methods, functions, and imports.",
91
+ "inputSchema": {
92
+ "type": "object",
93
+ "properties": {},
94
+ },
95
+ },
96
+ ]
97
+
98
+
99
+ def hyperflash_safe_patch(
100
+ target_file: str,
101
+ search_text: str,
102
+ replacement_text: str,
103
+ workspace_dir: str = ".",
104
+ ) -> str:
105
+ """
106
+ Applies surgical text replacement ensuring zero code elision and scope conformance.
107
+ """
108
+ # Invariant 7: Zero Elision
109
+ has_elision, pattern = check_for_elision(replacement_text)
110
+ if has_elision:
111
+ return (
112
+ f"ERROR: Invariant 7 Violation (Code Truncation Blocked): Prohibited elision pattern '{pattern}' detected. "
113
+ "HyperFlash requires 100% complete, fully implemented code blocks."
114
+ )
115
+
116
+ workspace = Path(workspace_dir).resolve()
117
+ target_path = (workspace / target_file).resolve()
118
+ norm_target = str(target_path.as_posix())
119
+
120
+ # Invariant 2: Scope Confinement
121
+ contract_path = workspace / ".agents/state/contract.json"
122
+ contract = load_contract(contract_path)
123
+ allowed_scope = [str(Path(p).as_posix()) for p in contract.get("allowed_write_scope", [])]
124
+ if allowed_scope:
125
+ if not any(norm_target.endswith(s) or s in norm_target for s in allowed_scope):
126
+ return (
127
+ f"ERROR: Invariant 2 Violation (Out of Scope): '{target_file}' is outside allowed_write_scope {allowed_scope}."
128
+ )
129
+
130
+ if not target_path.exists():
131
+ return f"ERROR: Target file '{target_file}' does not exist."
132
+
133
+ try:
134
+ content = target_path.read_text(encoding="utf-8")
135
+ except Exception as exc:
136
+ return f"ERROR: Failed reading '{target_file}': {exc}"
137
+
138
+ if search_text not in content:
139
+ return f"ERROR: Search text not found in '{target_file}'."
140
+
141
+ new_content = content.replace(search_text, replacement_text, 1)
142
+ try:
143
+ target_path.write_text(new_content, encoding="utf-8")
144
+ except Exception as exc:
145
+ return f"ERROR: Failed writing '{target_file}': {exc}"
146
+
147
+ import time
148
+ contract["last_mutation_ts"] = time.time()
149
+ save_contract(contract, contract_path)
150
+ return f"SUCCESS: Successfully patched '{target_file}'."
151
+
152
+
153
+ def hyperflash_verify(tier: int = 6, custom_cmd: str = "", workspace_dir: str = ".") -> str:
154
+ """
155
+ Runs deterministic verification hierarchy and returns formatted report.
156
+ """
157
+ result = run_verification(tier=tier, custom_cmd=custom_cmd, workspace_dir=workspace_dir)
158
+ diagnostics_str = json.dumps(result.get("diagnostics", []), indent=2)
159
+ return f"{result.get('verdict', 'VERDICT: UNKNOWN')}\n\nDiagnostics:\n{diagnostics_str}"
160
+
161
+
162
+ def hyperflash_init_contract(
163
+ mode: str = "implement",
164
+ allowed_scope: list[str] | None = None,
165
+ acceptance_criteria: list[str] | None = None,
166
+ workspace_dir: str = ".",
167
+ ) -> str:
168
+ """
169
+ Initializes contract state in active workspace.
170
+ """
171
+ workspace = Path(workspace_dir).resolve()
172
+ contract_path = workspace / ".agents/state/contract.json"
173
+ contract = load_contract(contract_path)
174
+ contract["mode"] = mode
175
+ contract["allowed_write_scope"] = allowed_scope or []
176
+ contract["acceptance_criteria"] = acceptance_criteria or []
177
+ contract["last_verdict"] = "PENDING"
178
+ save_contract(contract, contract_path)
179
+ return f"SUCCESS: Initialized contract at '{contract_path.as_posix()}' with mode='{mode}'."
180
+
181
+
182
+ def hyperflash_get_symbol_graph(workspace_dir: str = ".") -> str:
183
+ """
184
+ Generates and returns JSON AST symbol graph of active workspace.
185
+ """
186
+ graph = generate_symbol_graph(workspace_dir)
187
+ return json.dumps(graph, indent=2)
188
+
189
+
190
+ def dispatch_tool_call(tool_name: str, arguments: dict[str, Any], workspace_dir: str = ".") -> tuple[str, bool]:
191
+ """
192
+ Routes tool calls to the appropriate implementation. Returns (output_text, is_error).
193
+ """
194
+ if tool_name == "hyperflash_safe_patch":
195
+ target = str(arguments.get("target_file", ""))
196
+ search = str(arguments.get("search_text", ""))
197
+ repl = str(arguments.get("replacement_text", ""))
198
+ res = hyperflash_safe_patch(target, search, repl, workspace_dir=workspace_dir)
199
+ is_err = res.startswith("ERROR:")
200
+ return res, is_err
201
+
202
+ elif tool_name == "hyperflash_verify":
203
+ tier_val = int(arguments.get("tier", 6))
204
+ cmd_val = str(arguments.get("custom_cmd", ""))
205
+ res = hyperflash_verify(tier=tier_val, custom_cmd=cmd_val, workspace_dir=workspace_dir)
206
+ is_err = "FAIL" in res
207
+ return res, is_err
208
+
209
+ elif tool_name == "hyperflash_init_contract":
210
+ mode_val = str(arguments.get("mode", "implement"))
211
+ scope_val = arguments.get("allowed_scope", [])
212
+ if not isinstance(scope_val, list):
213
+ scope_val = []
214
+ crit_val = arguments.get("acceptance_criteria", [])
215
+ if not isinstance(crit_val, list):
216
+ crit_val = []
217
+ res = hyperflash_init_contract(
218
+ mode=mode_val,
219
+ allowed_scope=scope_val,
220
+ acceptance_criteria=crit_val,
221
+ workspace_dir=workspace_dir,
222
+ )
223
+ return res, False
224
+
225
+ elif tool_name == "hyperflash_get_symbol_graph":
226
+ res = hyperflash_get_symbol_graph(workspace_dir=workspace_dir)
227
+ return res, False
228
+
229
+ return f"ERROR: Unknown tool '{tool_name}'", True
230
+
231
+
232
+ def process_jsonrpc_request(req: dict[str, Any], workspace_dir: str = ".") -> dict[str, Any] | None:
233
+ """
234
+ Handles a single JSON-RPC 2.0 MCP request and returns the JSON-RPC response dict or None.
235
+ """
236
+ msg_id = req.get("id")
237
+ method = req.get("method", "")
238
+ params = req.get("params", {})
239
+
240
+ # Notifications do not return a response
241
+ if msg_id is None and method.startswith("notifications/"):
242
+ return None
243
+
244
+ if method == "initialize":
245
+ return {
246
+ "jsonrpc": "2.0",
247
+ "id": msg_id,
248
+ "result": {
249
+ "protocolVersion": "2024-11-05",
250
+ "capabilities": {
251
+ "tools": {},
252
+ },
253
+ "serverInfo": {
254
+ "name": "hyperflash",
255
+ "version": "1.0.0",
256
+ },
257
+ },
258
+ }
259
+
260
+ elif method == "ping":
261
+ return {
262
+ "jsonrpc": "2.0",
263
+ "id": msg_id,
264
+ "result": {},
265
+ }
266
+
267
+ elif method == "tools/list":
268
+ return {
269
+ "jsonrpc": "2.0",
270
+ "id": msg_id,
271
+ "result": {
272
+ "tools": TOOL_DEFINITIONS,
273
+ },
274
+ }
275
+
276
+ elif method == "tools/call":
277
+ tool_name = str(params.get("name", ""))
278
+ arguments = params.get("arguments", {})
279
+ if not isinstance(arguments, dict):
280
+ arguments = {}
281
+ output_text, is_error = dispatch_tool_call(tool_name, arguments, workspace_dir=workspace_dir)
282
+ return {
283
+ "jsonrpc": "2.0",
284
+ "id": msg_id,
285
+ "result": {
286
+ "content": [
287
+ {
288
+ "type": "text",
289
+ "text": output_text,
290
+ }
291
+ ],
292
+ "isError": is_error,
293
+ },
294
+ }
295
+
296
+ # Method not found
297
+ return {
298
+ "jsonrpc": "2.0",
299
+ "id": msg_id,
300
+ "error": {
301
+ "code": -32601,
302
+ "message": f"Method '{method}' not found",
303
+ },
304
+ }
305
+
306
+
307
+ def run_stdio_server(workspace_dir: str = ".") -> None:
308
+ """
309
+ Main loop for MCP stdio server. Reads newline-delimited JSON-RPC from stdin.
310
+ """
311
+ for raw_line in sys.stdin:
312
+ line = raw_line.strip()
313
+ if not line:
314
+ continue
315
+ try:
316
+ req = json.loads(line)
317
+ except Exception as exc:
318
+ err_resp = {
319
+ "jsonrpc": "2.0",
320
+ "id": None,
321
+ "error": {
322
+ "code": -32700,
323
+ "message": f"Parse error: {exc}",
324
+ },
325
+ }
326
+ sys.stdout.write(json.dumps(err_resp) + "\n")
327
+ sys.stdout.flush()
328
+ continue
329
+
330
+ resp = process_jsonrpc_request(req, workspace_dir=workspace_dir)
331
+ if resp is not None:
332
+ sys.stdout.write(json.dumps(resp) + "\n")
333
+ sys.stdout.flush()
334
+
335
+
336
+ def main() -> None:
337
+ """CLI entrypoint for standalone MCP server."""
338
+ run_stdio_server()
339
+
340
+
341
+ if __name__ == "__main__":
342
+ main()
hyperflash/polyglot.py ADDED
@@ -0,0 +1,240 @@
1
+ """
2
+ Polyglot configuration generators for HyperFlash:
3
+ Enables 1-click instant setup for Cursor, Claude Code, Windsurf, Git Hooks, and Antigravity.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import os
9
+ import shutil
10
+ import stat
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ CURSOR_RULES_CONTENT: str = """# HyperFlash Engineering Rules for Cursor
15
+
16
+ 1. GROUND BEFORE MUTATING: Always view the target file completely before proposing or executing edits.
17
+ 2. ZERO CODE ELISION: Never emit `// ... rest of code`, `# ... existing code`, or `TODO: implement`. Write 100% complete, fully implemented code blocks.
18
+ 3. EXHAUSTIVE STATIC TYPING: Every function signature, parameter, and return type must carry explicit static type annotations.
19
+ 4. STAY IN DECLARED SCOPE: Confine all changes strictly to files approved for the current task. Do not drift into unrelated modules.
20
+ 5. REPORT REALITY ONLY: Never claim tests passed or bugs are resolved unless verified by running compilation or test commands in this session.
21
+ 6. BYTE-IDENTICAL LOOP REJECTION: Never retry an identical failed modification. Alter implementation strategy if a patch fails.
22
+ 7. CHUNKED LONG-DECODE: For files over 200 lines, emit typed class/function skeletons first, then populate method bodies in surgical diffs.
23
+ 8. MANDATORY VERIFICATION: Always run project verification (`pytest`, `tsc`, `npm test`, or `hyperflash verify`) before concluding tasks.
24
+ """
25
+
26
+ CLAUDE_MD_CONTENT: str = """# HyperFlash Reliability Constitution for Claude Code
27
+
28
+ 1. GROUND FIRST: Never call file editing tools on an existing file without calling View or Cat first in this session.
29
+ 2. ZERO CODE ELISION: Never emit `// ... rest of code`, `# ... existing code`, or `TODO: implement`. Every edit must be 100% complete and executable.
30
+ 3. CONTRACT BOUNDARY: Only mutate files explicitly discussed and approved for the current task. Do not touch external modules.
31
+ 4. EVIDENCE OVER CLAIMS: Never declare a task complete or a bug fixed unless you have executed the project test command in this session and observed exit code 0.
32
+ 5. CHUNKED DECODE: For files exceeding 200 lines, emit class/function skeletons first, then populate method bodies in surgical 100-150 line diffs.
33
+ 6. EXHAUSTIVE STATIC TYPING: Full static types on all functions, parameters, and returns.
34
+ 7. NEVER EXACT-RETRY: Change your approach if an edit or test fails.
35
+ 8. AUTOMATED VERIFICATION: Run `hyperflash verify` to validate syntax, typing, linting, and tests.
36
+ """
37
+
38
+ WINDSURF_RULES_CONTENT: str = """# HyperFlash Engineering Rules for Windsurf
39
+
40
+ 1. GROUND BEFORE MUTATING: Always view the target file completely before proposing or executing edits.
41
+ 2. ZERO CODE ELISION: Never emit `// ... rest of code`, `# ... existing code`, or `TODO: implement`. Write 100% complete, fully implemented code blocks.
42
+ 3. EXHAUSTIVE STATIC TYPING: Every function signature, parameter, and return type must carry explicit static type annotations.
43
+ 4. STAY IN DECLARED SCOPE: Confine all changes strictly to files approved for the current task. Do not drift into unrelated modules.
44
+ 5. REPORT REALITY ONLY: Never claim tests passed or bugs are resolved unless verified by running compilation or test commands in this session.
45
+ 6. BYTE-IDENTICAL LOOP REJECTION: Never retry an identical failed modification. Alter implementation strategy if a patch fails.
46
+ 7. CHUNKED LONG-DECODE: For files over 200 lines, emit typed class/function skeletons first, then populate method bodies in surgical diffs.
47
+ 8. MANDATORY VERIFICATION: Always run project verification (`pytest`, `tsc`, `npm test`, or `hyperflash verify`) before concluding tasks.
48
+ """
49
+
50
+ GIT_PRE_COMMIT_CONTENT: str = """#!/usr/bin/env bash
51
+ # HyperFlash Zero-Elision & Integrity Gate
52
+ set -e
53
+ echo "⚡ Running HyperFlash pre-commit audit..."
54
+
55
+ # Check for lazy elision patterns in staged changes
56
+ if git diff --cached | grep -E '(//|#|/\\*)[[:space:]]*(\\.\\.\\.|rest of|existing code|TODO: implement)'; then
57
+ echo "❌ [HYPERFLASH ERROR] Code elision detected in staged changes!"
58
+ echo " Never commit '// ... rest of code' or placeholder comments."
59
+ exit 1
60
+ fi
61
+
62
+ # Optional verification if hyperflash is in PATH
63
+ if command -v hyperflash &> /dev/null; then
64
+ hyperflash verify --tier 5 || {
65
+ echo "❌ [HYPERFLASH ERROR] Verification hierarchy failed."
66
+ exit 1
67
+ }
68
+ fi
69
+
70
+ echo "✅ HyperFlash checks passed."
71
+ exit 0
72
+ """
73
+
74
+ MCP_CONFIG_DICT: dict[str, Any] = {
75
+ "mcpServers": {
76
+ "hyperflash": {
77
+ "command": "hyperflash",
78
+ "args": ["mcp"]
79
+ }
80
+ }
81
+ }
82
+
83
+
84
+ def merge_json_config(file_path: Path, new_data: dict[str, Any]) -> None:
85
+ """
86
+ Safely merges new_data into an existing JSON file without deleting existing keys.
87
+ """
88
+ file_path.parent.mkdir(parents=True, exist_ok=True)
89
+ existing: dict[str, Any] = {}
90
+ if file_path.is_file():
91
+ try:
92
+ existing = json.loads(file_path.read_text(encoding="utf-8"))
93
+ except Exception:
94
+ existing = {}
95
+
96
+ # Merge mcpServers specifically if present
97
+ if "mcpServers" in new_data:
98
+ mcp_servers = existing.get("mcpServers", {})
99
+ mcp_servers.update(new_data["mcpServers"])
100
+ existing["mcpServers"] = mcp_servers
101
+
102
+ for k, v in new_data.items():
103
+ if k != "mcpServers":
104
+ existing[k] = v
105
+
106
+ file_path.write_text(json.dumps(existing, indent=2), encoding="utf-8")
107
+
108
+
109
+ def setup_cursor(workspace_dir: Path | str = ".") -> list[str]:
110
+ """
111
+ Configures HyperFlash rules and stdio MCP server for Cursor.
112
+ """
113
+ workspace = Path(workspace_dir).resolve()
114
+ created_files: list[str] = []
115
+
116
+ rules_path = workspace / ".cursorrules"
117
+ rules_path.write_text(CURSOR_RULES_CONTENT, encoding="utf-8")
118
+ created_files.append(str(rules_path))
119
+
120
+ mcp_path = workspace / ".cursor" / "mcp.json"
121
+ merge_json_config(mcp_path, MCP_CONFIG_DICT)
122
+ created_files.append(str(mcp_path))
123
+
124
+ return created_files
125
+
126
+
127
+ def setup_claude(workspace_dir: Path | str = ".") -> list[str]:
128
+ """
129
+ Configures HyperFlash rules and stdio MCP server for Claude Code.
130
+ """
131
+ workspace = Path(workspace_dir).resolve()
132
+ created_files: list[str] = []
133
+
134
+ claude_md = workspace / "CLAUDE.md"
135
+ claude_md.write_text(CLAUDE_MD_CONTENT, encoding="utf-8")
136
+ created_files.append(str(claude_md))
137
+
138
+ claude_json = workspace / ".claude.json"
139
+ merge_json_config(claude_json, MCP_CONFIG_DICT)
140
+ created_files.append(str(claude_json))
141
+
142
+ return created_files
143
+
144
+
145
+ def setup_windsurf(workspace_dir: Path | str = ".") -> list[str]:
146
+ """
147
+ Configures HyperFlash rules and stdio MCP server for Windsurf.
148
+ """
149
+ workspace = Path(workspace_dir).resolve()
150
+ created_files: list[str] = []
151
+
152
+ rules_path = workspace / ".windsurfrules"
153
+ rules_path.write_text(WINDSURF_RULES_CONTENT, encoding="utf-8")
154
+ created_files.append(str(rules_path))
155
+
156
+ mcp_path = workspace / ".windsurf" / "mcp.json"
157
+ merge_json_config(mcp_path, MCP_CONFIG_DICT)
158
+ created_files.append(str(mcp_path))
159
+
160
+ return created_files
161
+
162
+
163
+ def setup_git_hooks(workspace_dir: Path | str = ".") -> list[str]:
164
+ """
165
+ Installs HyperFlash pre-commit hook into .git/hooks.
166
+ """
167
+ workspace = Path(workspace_dir).resolve()
168
+ git_hooks_dir = workspace / ".git" / "hooks"
169
+ created_files: list[str] = []
170
+
171
+ if not (workspace / ".git").exists():
172
+ # Not a git root, create directory anyway for user convenience
173
+ git_hooks_dir.mkdir(parents=True, exist_ok=True)
174
+ else:
175
+ git_hooks_dir.mkdir(parents=True, exist_ok=True)
176
+
177
+ hook_file = git_hooks_dir / "pre-commit"
178
+ hook_file.write_text(GIT_PRE_COMMIT_CONTENT, encoding="utf-8")
179
+
180
+ # Set executable permissions on POSIX
181
+ if os.name != "nt":
182
+ current_stat = hook_file.stat()
183
+ hook_file.chmod(current_stat.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
184
+
185
+ created_files.append(str(hook_file))
186
+ return created_files
187
+
188
+
189
+ def setup_antigravity(workspace_dir: Path | str = ".") -> list[str]:
190
+ """
191
+ Installs or updates HyperFlash skill and agents in Antigravity (~/.gemini/config/).
192
+ """
193
+ home = Path.home()
194
+ config_dir = home / ".gemini" / "config"
195
+ created_files: list[str] = []
196
+
197
+ # If local repo has src/skills/hyperflash, copy to target
198
+ repo_root = Path(workspace_dir).resolve()
199
+ local_skill = repo_root / "src" / "skills" / "hyperflash"
200
+ dest_skill = config_dir / "skills" / "hyperflash"
201
+
202
+ if local_skill.exists():
203
+ dest_skill.parent.mkdir(parents=True, exist_ok=True)
204
+ if dest_skill.exists():
205
+ shutil.rmtree(dest_skill)
206
+ shutil.copytree(local_skill, dest_skill)
207
+ created_files.append(str(dest_skill))
208
+
209
+ local_agents = repo_root / "src" / "agents"
210
+ dest_agents = config_dir / "agents"
211
+ if local_agents.exists():
212
+ dest_agents.mkdir(parents=True, exist_ok=True)
213
+ for agent_file in local_agents.glob("*.md"):
214
+ dest_file = dest_agents / agent_file.name
215
+ shutil.copy2(agent_file, dest_file)
216
+ created_files.append(str(dest_file))
217
+
218
+ local_rules = repo_root / "src" / "rules"
219
+ dest_rules = config_dir / "rules"
220
+ if local_rules.exists():
221
+ dest_rules.mkdir(parents=True, exist_ok=True)
222
+ for rule_file in local_rules.glob("*.md"):
223
+ dest_file = dest_rules / rule_file.name
224
+ shutil.copy2(rule_file, dest_file)
225
+ created_files.append(str(dest_file))
226
+
227
+ return created_files
228
+
229
+
230
+ def setup_all(workspace_dir: Path | str = ".") -> dict[str, list[str]]:
231
+ """
232
+ Executes polyglot configuration across all supported platforms.
233
+ """
234
+ return {
235
+ "cursor": setup_cursor(workspace_dir),
236
+ "claude": setup_claude(workspace_dir),
237
+ "windsurf": setup_windsurf(workspace_dir),
238
+ "git": setup_git_hooks(workspace_dir),
239
+ "antigravity": setup_antigravity(workspace_dir),
240
+ }