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.
- hyperflash/__init__.py +29 -0
- hyperflash/cli.py +303 -0
- hyperflash/core.py +419 -0
- hyperflash/mcp_server.py +342 -0
- hyperflash/polyglot.py +240 -0
- hyperflash-1.0.0.dist-info/METADATA +663 -0
- hyperflash-1.0.0.dist-info/RECORD +11 -0
- hyperflash-1.0.0.dist-info/WHEEL +5 -0
- hyperflash-1.0.0.dist-info/entry_points.txt +2 -0
- hyperflash-1.0.0.dist-info/licenses/LICENSE +21 -0
- hyperflash-1.0.0.dist-info/top_level.txt +1 -0
hyperflash/mcp_server.py
ADDED
|
@@ -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
|
+
}
|