semantic-sift 0.2.1__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,3 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 Luis Kobayashi. All rights reserved.
3
+ """Semantic-Sift package namespace."""
semantic_sift/cli.py ADDED
@@ -0,0 +1,69 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 Luis Kobayashi. All rights reserved.
3
+
4
+ import sys
5
+ import argparse
6
+ import subprocess
7
+ import logging
8
+
9
+ from semantic_sift import kernel
10
+
11
+ # We use stderr for logging so it doesn't corrupt the stdout data stream!
12
+ logging.basicConfig(stream=sys.stderr, level=logging.INFO, format="[Sift-CLI] %(message)s")
13
+ logger = logging.getLogger("semantic_sift_cli")
14
+
15
+ def main():
16
+ parser = argparse.ArgumentParser(description="Semantic-Sift Universal CLI (Hybrid Engine)")
17
+ parser.add_argument("type", choices=["logs", "semantic", "doc", "extraction", "auto"], default="auto", nargs="?", help="Type of distillation")
18
+ parser.add_argument("--rate", type=float, default=0.5, help="Compression rate for semantic tasks")
19
+
20
+ args = parser.parse_args()
21
+
22
+ # 1. Read from standard input
23
+ input_data = sys.stdin.read()
24
+ if not input_data:
25
+ return
26
+
27
+ char_count = len(input_data)
28
+
29
+ # 2. Hybrid Engine Routing Decision
30
+ if args.type == "logs" or (args.type == "auto" and char_count < 1000):
31
+ # FAST PATH: Heuristics or small files
32
+ # Ideally, we shell out to `sift-core logs` here if available.
33
+ # For now, we use the Python kernel equivalent.
34
+ logger.info(f"Routing {char_count} chars to Heuristic Engine.")
35
+ result = kernel.apply_heuristic_sieve(input_data)
36
+
37
+ else:
38
+ # NEURAL PATH: Semantic compression
39
+ if char_count > 30000:
40
+ logger.info(f"Massive payload ({char_count} chars). Routing to PyTorch Engine (Flash Attention).")
41
+ # In the future, this explicitly loads PyTorch.
42
+ # Currently, the python kernel uses PyTorch (llmlingua).
43
+ result = kernel.perform_semantic_sift(input_data, rate=args.rate)
44
+ else:
45
+ logger.info(f"Standard payload ({char_count} chars). Routing to Rust/ONNX Engine.")
46
+ # Shell out to the Rust sidecar for low-latency ONNX execution
47
+ try:
48
+ process = subprocess.Popen(
49
+ ["sift-core", "semantic", "--rate", str(args.rate)],
50
+ stdin=subprocess.PIPE,
51
+ stdout=subprocess.PIPE,
52
+ stderr=subprocess.PIPE,
53
+ text=True
54
+ )
55
+ stdout, stderr = process.communicate(input=input_data)
56
+ if process.returncode == 0:
57
+ result = stdout
58
+ else:
59
+ logger.warning(f"Rust engine failed: {stderr}. Falling back to PyTorch.")
60
+ result = kernel.perform_semantic_sift(input_data, rate=args.rate)
61
+ except FileNotFoundError:
62
+ logger.info("Rust engine not found. Falling back to PyTorch.")
63
+ result = kernel.perform_semantic_sift(input_data, rate=args.rate)
64
+
65
+ # 3. Output to standard output
66
+ sys.stdout.write(result)
67
+
68
+ if __name__ == "__main__":
69
+ main()
semantic_sift/hook.py ADDED
@@ -0,0 +1,5 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 Luis Kobayashi. All rights reserved.
3
+ """Compatibility wrapper for hook module extraction."""
4
+
5
+ from sift_hook import * # noqa: F401,F403
@@ -0,0 +1,526 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 Luis Kobayashi. All rights reserved.
3
+ import json
4
+ import os
5
+ import re
6
+ import sys
7
+
8
+
9
+ def build_runtime_hook_command() -> tuple[str, str, str]:
10
+ python_exe = os.path.abspath(sys.executable)
11
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12
+ hook_script = os.path.abspath(os.path.join(repo_root, "sift_hook.py"))
13
+ if not os.path.exists(hook_script):
14
+ raise RuntimeError(
15
+ f"Semantic-Sift startup failed: hook script not found at '{hook_script}'. "
16
+ "Ensure sift_hook.py is present next to server.py."
17
+ )
18
+ cmd_str = f'"{python_exe}" "{hook_script}"'
19
+ return python_exe, hook_script, cmd_str
20
+
21
+
22
+ def get_windsurf_gateway_command() -> str:
23
+ if sys.platform == "win32":
24
+ return (
25
+ 'pwsh -NoProfile -Command "$p=$env:WINDSURF_TOOL_ARGS; '
26
+ 'if (Test-Path $p) { '
27
+ 'if ((Get-Item $p).Length -gt 1024) { '
28
+ '[Console]::Error.WriteLine(\"[BLOCKED by Semantic-Sift] File > 1KB. Use sift_read_file instead.\"); '
29
+ 'exit 2 } }"'
30
+ )
31
+
32
+ return (
33
+ 'SIZE=$(stat -c %s "$WINDSURF_TOOL_ARGS" 2>/dev/null || stat -f %z "$WINDSURF_TOOL_ARGS" 2>/dev/null || wc -c < "$WINDSURF_TOOL_ARGS" 2>/dev/null); '
34
+ 'if [ "$SIZE" -gt 1024 ] 2>/dev/null; then '
35
+ 'echo "[BLOCKED by Semantic-Sift] File > 1KB. Use sift_read_file instead." > /dev/stderr; '
36
+ 'exit 2; fi'
37
+ )
38
+
39
+
40
+ def discover_agent_configs(target_dir: str) -> list[str]:
41
+ found_paths = []
42
+ agent_dirs = [".codex/agents", ".cursor/agents", ".junie/agents", ".agents"]
43
+
44
+ for d in agent_dirs:
45
+ full_dir = os.path.join(target_dir, d)
46
+ if os.path.exists(full_dir):
47
+ for f in os.listdir(full_dir):
48
+ if f.endswith((".toml", ".md")):
49
+ found_paths.append(os.path.join(full_dir, f))
50
+
51
+ for root, _, files in os.walk(target_dir):
52
+ depth = root[len(target_dir):].count(os.sep)
53
+ if depth > 3:
54
+ continue
55
+ if "AGENTS.md" in files and root != target_dir:
56
+ found_paths.append(os.path.join(root, "AGENTS.md"))
57
+
58
+ return found_paths
59
+
60
+
61
+ def update_toml_config(path: str, section_id: str, content: str) -> bool:
62
+ try:
63
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
64
+ file_content = f.read()
65
+
66
+ block_id = f"# SIFT_SECTION_START:{section_id}"
67
+ block_end = f"# SIFT_SECTION_END:{section_id}"
68
+ full_payload = f"\n{block_id}\n# ---\n# {content}\n{block_end}\n"
69
+
70
+ pattern = re.compile(rf'{re.escape(block_id)}.*?{re.escape(block_end)}', re.DOTALL)
71
+ if pattern.search(file_content):
72
+ new_content = pattern.sub(full_payload.strip(), file_content)
73
+ else:
74
+ if "instructions =" in file_content:
75
+ new_content = file_content.replace("instructions = \"\"\"", f"instructions = \"\"\"\n{content}\n")
76
+ else:
77
+ new_content = file_content + full_payload
78
+
79
+ with open(path, "w", encoding="utf-8", errors="replace") as f:
80
+ f.write(new_content)
81
+ return True
82
+ except (OSError, re.error):
83
+ return False
84
+
85
+
86
+ def merge_hook_json(path: str, hook_key: str, new_hook: dict, version: int | None = None) -> bool:
87
+ data: dict = {"hooks": {}}
88
+ if version:
89
+ data["version"] = version
90
+ if os.path.exists(path):
91
+ try:
92
+ with open(path, "r", encoding="utf-8") as f:
93
+ data = json.load(f)
94
+ except (OSError, json.JSONDecodeError):
95
+ pass
96
+
97
+ if "hooks" not in data:
98
+ data["hooks"] = {}
99
+ hooks_list = data["hooks"].get(hook_key, [])
100
+
101
+ exists = any(h.get("command") == new_hook.get("command") for h in hooks_list)
102
+ if not exists:
103
+ data["hooks"][hook_key] = [new_hook] + hooks_list
104
+ with open(path, "w", encoding="utf-8") as f:
105
+ json.dump(data, f, indent=2)
106
+ return True
107
+ return False
108
+
109
+
110
+ def update_instruction_files(
111
+ section_id: str,
112
+ header: str,
113
+ content: str,
114
+ instruction_targets: list[str],
115
+ runtime_python_exe: str,
116
+ runtime_hook_script: str,
117
+ runtime_hook_command: str,
118
+ target_dir: str | None = None,
119
+ environment: str | None = None,
120
+ ) -> list[str]:
121
+ actions = []
122
+ cwd = target_dir if target_dir else os.getcwd()
123
+ python_exe = runtime_python_exe
124
+ hook_script = runtime_hook_script
125
+ cmd_str = runtime_hook_command
126
+ block_id = f"<!-- SIFT_SECTION_START:{section_id} -->"
127
+ block_end = f"<!-- SIFT_SECTION_END:{section_id} -->"
128
+ full_payload = f"\n{block_id}\n---\n\n{header}\n{content}\n{block_end}\n"
129
+
130
+ env_lower = environment.lower() if environment else ""
131
+
132
+ targets = instruction_targets[:]
133
+ subagent_paths = discover_agent_configs(cwd)
134
+ targets.extend(subagent_paths)
135
+
136
+ for target in targets:
137
+ target_path = target if os.path.isabs(target) else os.path.join(cwd, target)
138
+ if os.path.exists(target_path):
139
+ try:
140
+ filename = os.path.basename(target_path)
141
+
142
+ if target_path.endswith(".toml"):
143
+ if update_toml_config(target_path, section_id, content):
144
+ actions.append(f"Shielded subagent config: `{filename}`.")
145
+ continue
146
+
147
+ if not filename.endswith((".md", ".clinerules", ".cursorrules", ".windsurfrules")):
148
+ continue
149
+
150
+ with open(target_path, "r", encoding="utf-8", errors="replace") as f:
151
+ file_content = f.read()
152
+
153
+ if any(x in file_content.lower() for x in ["always use view_file", "read the full file", "read entire file"]):
154
+ actions.append(f"⚠️ WARNING: Found potentially contradictory 'read full file' instructions in `{filename}`. The Sift Mandate override has been appended.")
155
+
156
+ pattern = re.compile(rf'{re.escape(block_id)}.*?{re.escape(block_end)}', re.DOTALL)
157
+ if pattern.search(file_content):
158
+ new_content = pattern.sub(full_payload.strip(), file_content)
159
+ with open(target_path, "w", encoding="utf-8", errors="replace") as f:
160
+ f.write(new_content)
161
+ actions.append(f"Updated `{filename}`.")
162
+ else:
163
+ with open(target_path, "a", encoding="utf-8", errors="replace") as f:
164
+ f.write(full_payload)
165
+ actions.append(f"Injected into `{filename}`.")
166
+ except (OSError, re.error) as e:
167
+ actions.append(f"Error updating `{target_path}`: {str(e)}")
168
+
169
+ if "cursor" in env_lower:
170
+ cursor_path = os.path.join(cwd, ".cursor", "hooks.json")
171
+ os.makedirs(os.path.dirname(cursor_path), exist_ok=True)
172
+
173
+ if os.path.exists(cursor_path):
174
+ try:
175
+ with open(cursor_path, "r", encoding="utf-8") as f:
176
+ cursor_data = json.load(f)
177
+ if "hooks" in cursor_data and "beforeMCPExecution" in cursor_data["hooks"]:
178
+ actions.append("🚨 ALERT: `beforeMCPExecution` security gateway detected in Cursor hooks. You MUST whitelist `sift_read_file` and `sift_analyze_file` or they will be blocked.")
179
+ except (OSError, json.JSONDecodeError):
180
+ pass
181
+
182
+ if merge_hook_json(cursor_path, "postToolUse", {"command": cmd_str}, version=1):
183
+ actions.append("Merged into Cursor hooks.")
184
+
185
+ if "vscode" in env_lower or "github" in env_lower:
186
+ vscode_path = os.path.join(cwd, ".github", "hooks", "semantic-sift.json")
187
+ os.makedirs(os.path.dirname(vscode_path), exist_ok=True)
188
+ if merge_hook_json(vscode_path, "PostToolUse", {"type": "command", "command": cmd_str}):
189
+ actions.append("Merged into VS Code hooks.")
190
+
191
+ if "gemini" in env_lower:
192
+ gemini_commands_dir = os.path.join(cwd, ".gemini", "commands")
193
+ os.makedirs(gemini_commands_dir, exist_ok=True)
194
+ gemini_command_path = os.path.join(gemini_commands_dir, "sift-stats.toml")
195
+
196
+ gemini_command_content = """description = "View Semantic-Sift token savings and telemetry dashboard"
197
+ prompt = \"\"\"
198
+ !{semantic-sift-stats}
199
+ \"\"\"
200
+ """
201
+ if not os.path.exists(gemini_command_path):
202
+ try:
203
+ with open(gemini_command_path, "w", encoding="utf-8") as f:
204
+ f.write(gemini_command_content)
205
+ actions.append("Injected `/sift-stats` custom command into Gemini CLI.")
206
+ except OSError as e:
207
+ actions.append(f"Error configuring Gemini CLI command: {str(e)}")
208
+
209
+ if "opencode" in env_lower:
210
+ opencode_plugin_path = os.path.join(cwd, ".opencode", "plugins", "semantic-sift.ts")
211
+ os.makedirs(os.path.dirname(opencode_plugin_path), exist_ok=True)
212
+ plugin_content = f"""/**
213
+ * Semantic-Sift Native OpenCode Plugin
214
+ */
215
+ export const SemanticSiftPlugin = async ({{ $ }}) => {{
216
+ return {{
217
+ hooks: {{
218
+ "tool.execute.after": async (input, output) => {{
219
+ const rawContent = output.result;
220
+ if (typeof rawContent !== 'string' || rawContent.length < 500) return;
221
+ if (rawContent.includes("--- [Semantic-Sift: Native Execution] ---")) return;
222
+ try {{
223
+ const pythonExe = "{python_exe}";
224
+ const siftScript = "{hook_script}";
225
+ const payload = {{ hook_event_name: "AfterTool", tool_name: input.tool, tool_args: input.args, tool_response: {{ llmContent: rawContent }} }};
226
+ const response = await $`${{pythonExe}} ${{siftScript}}`.input(JSON.stringify(payload)).text();
227
+ const siftedData = JSON.parse(response);
228
+ if (siftedData?.tool_response?.llmContent) output.result = siftedData.tool_response.llmContent;
229
+ }} catch (error) {{ console.error("[Semantic-Sift Plugin] failed:", error); }}
230
+ }}
231
+ }}
232
+ }};
233
+ }};
234
+ export default SemanticSiftPlugin;
235
+ """
236
+ try:
237
+ if not os.path.exists(opencode_plugin_path):
238
+ with open(opencode_plugin_path, "w", encoding="utf-8") as f:
239
+ f.write(plugin_content)
240
+ actions.append("Configured OpenCode native plugin.")
241
+ except OSError as e:
242
+ actions.append(f"Error configuring OpenCode plugin: {str(e)}")
243
+
244
+ opencode_config_path = os.path.join(cwd, "opencode.json")
245
+ if os.path.exists(opencode_config_path):
246
+ try:
247
+ with open(opencode_config_path, "r", encoding="utf-8") as f:
248
+ opencode_config = json.load(f)
249
+
250
+ if "commands" not in opencode_config:
251
+ opencode_config["commands"] = {}
252
+
253
+ if "/sift-onboard" not in opencode_config["commands"]:
254
+ opencode_config["commands"]["/sift-onboard"] = {
255
+ "description": "Initialize Semantic-Sift in this project",
256
+ "action": "run_mcp_tool",
257
+ "server": "semantic-sift",
258
+ "tool": "sift_onboard",
259
+ "args": {"environment": "OpenCode"}
260
+ }
261
+
262
+ if "/sift-stats" not in opencode_config["commands"]:
263
+ opencode_config["commands"]["/sift-stats"] = {
264
+ "description": "View Semantic-Sift token savings and telemetry dashboard",
265
+ "action": "run_mcp_tool",
266
+ "server": "semantic-sift",
267
+ "tool": "get_sift_stats",
268
+ "args": {"scope": "all"}
269
+ }
270
+ with open(opencode_config_path, "w", encoding="utf-8") as f:
271
+ json.dump(opencode_config, f, indent=2)
272
+ actions.append("Injected `/sift-stats` command into opencode.json.")
273
+ except (OSError, json.JSONDecodeError) as e:
274
+ actions.append(f"Error updating opencode.json commands: {str(e)}")
275
+
276
+ if "claude" in env_lower:
277
+ claude_paths = [
278
+ os.path.join(os.path.expanduser("~"), ".claude", "settings.json"),
279
+ os.path.join(cwd, ".claude", "settings.json"),
280
+ ]
281
+ for c_path in claude_paths:
282
+ if os.path.exists(c_path):
283
+ try:
284
+ with open(c_path, "r", encoding="utf-8") as f:
285
+ c_data = json.load(f)
286
+ except (OSError, json.JSONDecodeError):
287
+ c_data = {}
288
+
289
+ if "hooks" not in c_data:
290
+ c_data["hooks"] = {}
291
+ if "PostToolUse" not in c_data["hooks"]:
292
+ c_data["hooks"]["PostToolUse"] = []
293
+
294
+ exists = False
295
+ for pt_hook in c_data["hooks"]["PostToolUse"]:
296
+ if pt_hook.get("matcher") == "mcp__.*__.*":
297
+ for inner_hook in pt_hook.get("hooks", []):
298
+ if inner_hook.get("command") == cmd_str:
299
+ exists = True
300
+ break
301
+
302
+ if not exists:
303
+ claude_hook = {
304
+ "matcher": "mcp__.*__.*",
305
+ "hooks": [{"type": "command", "command": cmd_str}],
306
+ }
307
+ c_data["hooks"]["PostToolUse"] = [claude_hook] + c_data["hooks"]["PostToolUse"]
308
+ try:
309
+ with open(c_path, "w", encoding="utf-8") as f:
310
+ json.dump(c_data, f, indent=2)
311
+ actions.append(f"Merged into Claude Code hooks at {c_path}.")
312
+ except OSError as e:
313
+ actions.append(f"Failed to merge Claude Code hooks: {e}")
314
+
315
+ if "qwen" in env_lower:
316
+ qwen_paths = [
317
+ os.path.join(os.path.expanduser("~"), ".qwen", "settings.json"),
318
+ os.path.join(cwd, ".qwen", "settings.json"),
319
+ ]
320
+ for q_path in qwen_paths:
321
+ if os.path.exists(q_path):
322
+ try:
323
+ with open(q_path, "r", encoding="utf-8") as f:
324
+ q_data = json.load(f)
325
+ except (OSError, json.JSONDecodeError):
326
+ q_data = {}
327
+
328
+ if "hooks" not in q_data:
329
+ q_data["hooks"] = {}
330
+ if "PostToolUse" not in q_data["hooks"]:
331
+ q_data["hooks"]["PostToolUse"] = []
332
+
333
+ exists = False
334
+ for pt_hook in q_data["hooks"]["PostToolUse"]:
335
+ if pt_hook.get("matcher") == "mcp__.*__.*":
336
+ for inner_hook in pt_hook.get("hooks", []):
337
+ if inner_hook.get("command") == cmd_str:
338
+ exists = True
339
+ break
340
+ if not exists:
341
+ qwen_hook = {
342
+ "matcher": "mcp__.*__.*",
343
+ "hooks": [{"type": "command", "command": cmd_str}],
344
+ }
345
+ q_data["hooks"]["PostToolUse"] = [qwen_hook] + q_data["hooks"]["PostToolUse"]
346
+ try:
347
+ with open(q_path, "w", encoding="utf-8") as f:
348
+ json.dump(q_data, f, indent=2)
349
+ actions.append(f"Merged into Qwen CLI hooks at {q_path}.")
350
+ except OSError as e:
351
+ actions.append(f"Failed to merge Qwen CLI hooks: {e}")
352
+
353
+ if "windsurf" in env_lower:
354
+ windsurf_paths = [
355
+ os.path.join(os.path.expanduser("~"), ".codeium", "windsurf", "hooks.json"),
356
+ os.path.join(cwd, ".windsurf", "hooks.json"),
357
+ ]
358
+ for w_path in windsurf_paths:
359
+ if os.path.exists(w_path):
360
+ try:
361
+ with open(w_path, "r", encoding="utf-8") as f:
362
+ w_data = json.load(f)
363
+ except (OSError, json.JSONDecodeError):
364
+ w_data = {}
365
+ if "pre_mcp_tool_use" not in w_data:
366
+ w_data["pre_mcp_tool_use"] = []
367
+
368
+ gateway_cmd = get_windsurf_gateway_command()
369
+
370
+ exists = any(h.get("command") == gateway_cmd for h in w_data["pre_mcp_tool_use"])
371
+ if not exists:
372
+ w_data["pre_mcp_tool_use"].insert(
373
+ 0,
374
+ {
375
+ "matcher": "mcp__.*__(read_file|view_file)",
376
+ "type": "command",
377
+ "command": gateway_cmd,
378
+ },
379
+ )
380
+ try:
381
+ with open(w_path, "w", encoding="utf-8") as f:
382
+ json.dump(w_data, f, indent=2)
383
+ actions.append(f"Injected Security Gateway into Windsurf hooks at {w_path}.")
384
+ except OSError as e:
385
+ actions.append(f"Failed to merge Windsurf hooks: {e}")
386
+
387
+ if "openclaw" in env_lower:
388
+ openclaw_plugin_path = os.path.join(cwd, ".openclaw", "plugins", "semantic-sift.ts")
389
+ os.makedirs(os.path.dirname(openclaw_plugin_path), exist_ok=True)
390
+ openclaw_plugin_content = f"""/**
391
+ * Semantic-Sift Native OpenClaw Plugin
392
+ */
393
+ export default function (api) {{
394
+ api.on("tool:after", async (event, ctx) => {{
395
+ const rawContent = ctx.result;
396
+ if (typeof rawContent !== 'string' || rawContent.length < 500) return;
397
+ if (rawContent.includes("--- [Semantic-Sift: Native Execution] ---")) return;
398
+ try {{
399
+ const pythonExe = "{python_exe}";
400
+ const siftScript = "{hook_script}";
401
+ const payload = {{ hook_event_name: "AfterTool", tool_name: ctx.toolName, tool_response: {{ llmContent: rawContent }} }};
402
+
403
+ // Execute Python interceptor
404
+ const {{ execSync }} = require('child_process');
405
+ const response = execSync(`${{pythonExe}} ${{siftScript}}`, {{ input: JSON.stringify(payload), encoding: 'utf-8' }});
406
+
407
+ const siftedData = JSON.parse(response);
408
+ if (siftedData?.tool_response?.llmContent) {{
409
+ ctx.result = siftedData.tool_response.llmContent;
410
+ }}
411
+ }} catch (error) {{ console.error("[Semantic-Sift Plugin] failed:", error); }}
412
+ }});
413
+ }};
414
+ """
415
+ try:
416
+ if not os.path.exists(openclaw_plugin_path):
417
+ with open(openclaw_plugin_path, "w", encoding="utf-8") as f:
418
+ f.write(openclaw_plugin_content)
419
+ actions.append("Configured OpenClaw native plugin.")
420
+ except OSError as e:
421
+ actions.append(f"Error configuring OpenClaw plugin: {str(e)}")
422
+
423
+ if "kilocode" in env_lower:
424
+ kilo_rule_dir = os.path.join(cwd, ".kilocode", "rules")
425
+ os.makedirs(kilo_rule_dir, exist_ok=True)
426
+ kilo_rule_path = os.path.join(kilo_rule_dir, "context.md")
427
+ if not os.path.exists(kilo_rule_path):
428
+ try:
429
+ with open(kilo_rule_path, "w", encoding="utf-8") as f:
430
+ f.write(f"# Semantic-Sift Kilo Code Constraints\n\n{content}")
431
+ actions.append("Injected Kilo Code workspace rules.")
432
+ except OSError as e:
433
+ actions.append(f"Error configuring Kilo Code rules: {str(e)}")
434
+
435
+ if "cline" in env_lower or "roo" in env_lower:
436
+ cline_hooks_dir = os.path.join(cwd, ".clinerules", "hooks")
437
+ os.makedirs(cline_hooks_dir, exist_ok=True)
438
+
439
+ cline_ps1_path = os.path.join(cline_hooks_dir, "PreToolUse.ps1")
440
+ cline_ps1_content = """$inputJson = $input | ConvertFrom-Json
441
+ if ($inputJson.preToolUse.toolName -eq 'read_file' -or $inputJson.preToolUse.toolName -eq 'view_file') {
442
+ $filePath = $inputJson.preToolUse.parameters.path
443
+ if (Test-Path $filePath) {
444
+ $size = (Get-Item $filePath).Length
445
+ if ($size -gt 1024) {
446
+ $response = @{ cancel = $true; errorMessage = "[BLOCKED by Semantic-Sift] File > 1KB. Use sift_read_file instead." }
447
+ $response | ConvertTo-Json -Compress | Write-Output
448
+ exit 0
449
+ }
450
+ }
451
+ }
452
+ $response = @{ cancel = $false }
453
+ $response | ConvertTo-Json -Compress | Write-Output
454
+ """
455
+ if not os.path.exists(cline_ps1_path):
456
+ try:
457
+ with open(cline_ps1_path, "w", encoding="utf-8") as f:
458
+ f.write(cline_ps1_content)
459
+ actions.append("Injected Cline PreToolUse.ps1 hook.")
460
+ except OSError as e:
461
+ actions.append(f"Error configuring Cline PS1 hook: {str(e)}")
462
+
463
+ cline_bash_path = os.path.join(cline_hooks_dir, "PreToolUse")
464
+ cline_bash_content = """#!/bin/bash
465
+ INPUT=$(cat)
466
+ TOOL_NAME=$(echo "$INPUT" | grep -oP '(?<="toolName":")[^"]*')
467
+ if [[ "$TOOL_NAME" == "read_file" ]] || [[ "$TOOL_NAME" == "view_file" ]]; then
468
+ FILE_PATH=$(echo "$INPUT" | grep -oP '(?<="path":")[^"]*')
469
+ if [[ -f "$FILE_PATH" ]]; then
470
+ SIZE=$(wc -c < "$FILE_PATH" 2>/dev/null || stat -f %s "$FILE_PATH" 2>/dev/null || stat -c %s "$FILE_PATH" 2>/dev/null)
471
+ if [[ "$SIZE" -gt 1024 ]]; then
472
+ echo '{"cancel": true, "errorMessage": "[BLOCKED by Semantic-Sift] File > 1KB. Use sift_read_file instead."}'
473
+ exit 0
474
+ fi
475
+ fi
476
+ fi
477
+ echo '{"cancel": false}'
478
+ """
479
+ if not os.path.exists(cline_bash_path):
480
+ try:
481
+ with open(cline_bash_path, "w", encoding="utf-8", newline="\n") as f:
482
+ f.write(cline_bash_content)
483
+ os.chmod(cline_bash_path, 0o755)
484
+ actions.append("Injected Cline PreToolUse bash hook.")
485
+ except OSError as e:
486
+ actions.append(f"Error configuring Cline bash hook: {str(e)}")
487
+
488
+ if "codex" in env_lower:
489
+ codex_paths = [
490
+ os.path.join(os.path.expanduser("~"), ".codex", "settings.json"),
491
+ os.path.join(cwd, ".codex", "settings.json"),
492
+ ]
493
+ for co_path in codex_paths:
494
+ if os.path.exists(co_path):
495
+ try:
496
+ with open(co_path, "r", encoding="utf-8") as f:
497
+ co_data = json.load(f)
498
+ except (OSError, json.JSONDecodeError):
499
+ co_data = {}
500
+
501
+ if "hooks" not in co_data:
502
+ co_data["hooks"] = {}
503
+ if "PostToolUse" not in co_data["hooks"]:
504
+ co_data["hooks"]["PostToolUse"] = []
505
+
506
+ exists = False
507
+ for pt_hook in co_data["hooks"]["PostToolUse"]:
508
+ if pt_hook.get("matcher") == "mcp__.*__.*":
509
+ for inner_hook in pt_hook.get("hooks", []):
510
+ if inner_hook.get("command") == cmd_str:
511
+ exists = True
512
+ break
513
+ if not exists:
514
+ codex_hook = {
515
+ "matcher": "mcp__.*__.*",
516
+ "hooks": [{"type": "command", "command": cmd_str}],
517
+ }
518
+ co_data["hooks"]["PostToolUse"] = [codex_hook] + co_data["hooks"]["PostToolUse"]
519
+ try:
520
+ with open(co_path, "w", encoding="utf-8") as f:
521
+ json.dump(co_data, f, indent=2)
522
+ actions.append(f"Merged into Codex CLI hooks at {co_path}.")
523
+ except OSError as e:
524
+ actions.append(f"Failed to merge Codex CLI hooks: {e}")
525
+
526
+ return actions
@@ -0,0 +1,5 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 Luis Kobayashi. All rights reserved.
3
+ """Compatibility wrapper for kernel module extraction."""
4
+
5
+ from sift_kernel import * # noqa: F401,F403