codeshield-ai 0.1.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,174 @@
1
+ """
2
+ ContextVault Capture - Save coding context
3
+
4
+ Saves your mental state like a game save file:
5
+ - Open files
6
+ - Cursor position
7
+ - Recent changes
8
+ - Timestamps
9
+ """
10
+
11
+ import json
12
+ import sqlite3
13
+ from pathlib import Path
14
+ from datetime import datetime
15
+ from dataclasses import dataclass, asdict
16
+ from typing import Optional
17
+
18
+
19
+ DB_PATH = Path.home() / ".codeshield" / "context_vault.sqlite"
20
+
21
+
22
+ @dataclass
23
+ class CodingContext:
24
+ """A saved coding context"""
25
+ name: str
26
+ created_at: str
27
+ files: list[str]
28
+ cursor: Optional[dict] = None
29
+ notes: Optional[str] = None
30
+ last_edited_file: Optional[str] = None
31
+
32
+ def to_dict(self) -> dict:
33
+ return asdict(self)
34
+
35
+
36
+ def _ensure_db():
37
+ """Ensure database exists and has schema"""
38
+ DB_PATH.parent.mkdir(parents=True, exist_ok=True)
39
+
40
+ conn = sqlite3.connect(str(DB_PATH))
41
+ cursor = conn.cursor()
42
+
43
+ cursor.execute("""
44
+ CREATE TABLE IF NOT EXISTS contexts (
45
+ name TEXT PRIMARY KEY,
46
+ created_at TEXT NOT NULL,
47
+ files TEXT NOT NULL,
48
+ cursor TEXT,
49
+ notes TEXT,
50
+ last_edited_file TEXT
51
+ )
52
+ """)
53
+
54
+ conn.commit()
55
+ conn.close()
56
+
57
+
58
+ def save_context(
59
+ name: str,
60
+ files: list[str] = None,
61
+ cursor: dict = None,
62
+ notes: str = None,
63
+ last_edited_file: str = None,
64
+ ) -> dict:
65
+ """
66
+ Save coding context.
67
+
68
+ Args:
69
+ name: Unique name for this context
70
+ files: List of open file paths
71
+ cursor: Cursor position {file, line, column}
72
+ notes: Optional notes about current work
73
+ last_edited_file: Last file that was edited
74
+
75
+ Returns:
76
+ Dict with save confirmation
77
+ """
78
+ _ensure_db()
79
+
80
+ context = CodingContext(
81
+ name=name,
82
+ created_at=datetime.now().isoformat(),
83
+ files=files or [],
84
+ cursor=cursor,
85
+ notes=notes,
86
+ last_edited_file=last_edited_file,
87
+ )
88
+
89
+ conn = sqlite3.connect(str(DB_PATH))
90
+ cursor_db = conn.cursor()
91
+
92
+ cursor_db.execute("""
93
+ INSERT OR REPLACE INTO contexts
94
+ (name, created_at, files, cursor, notes, last_edited_file)
95
+ VALUES (?, ?, ?, ?, ?, ?)
96
+ """, (
97
+ context.name,
98
+ context.created_at,
99
+ json.dumps(context.files),
100
+ json.dumps(context.cursor) if context.cursor else None,
101
+ context.notes,
102
+ context.last_edited_file,
103
+ ))
104
+
105
+ conn.commit()
106
+ conn.close()
107
+
108
+ return {
109
+ "success": True,
110
+ "message": f"Context '{name}' saved at {context.created_at}",
111
+ "context": context.to_dict(),
112
+ }
113
+
114
+
115
+ def list_contexts() -> list[dict]:
116
+ """List all saved contexts"""
117
+ _ensure_db()
118
+
119
+ conn = sqlite3.connect(str(DB_PATH))
120
+ cursor = conn.cursor()
121
+
122
+ cursor.execute("SELECT name, created_at, notes FROM contexts ORDER BY created_at DESC")
123
+ rows = cursor.fetchall()
124
+
125
+ conn.close()
126
+
127
+ return [
128
+ {"name": row[0], "created_at": row[1], "notes": row[2]}
129
+ for row in rows
130
+ ]
131
+
132
+
133
+ def get_context(name: str) -> Optional[CodingContext]:
134
+ """Get a specific context by name"""
135
+ _ensure_db()
136
+
137
+ conn = sqlite3.connect(str(DB_PATH))
138
+ cursor = conn.cursor()
139
+
140
+ cursor.execute("""
141
+ SELECT name, created_at, files, cursor, notes, last_edited_file
142
+ FROM contexts WHERE name = ?
143
+ """, (name,))
144
+
145
+ row = cursor.fetchone()
146
+ conn.close()
147
+
148
+ if not row:
149
+ return None
150
+
151
+ return CodingContext(
152
+ name=row[0],
153
+ created_at=row[1],
154
+ files=json.loads(row[2]),
155
+ cursor=json.loads(row[3]) if row[3] else None,
156
+ notes=row[4],
157
+ last_edited_file=row[5],
158
+ )
159
+
160
+
161
+ def delete_context(name: str) -> bool:
162
+ """Delete a context by name"""
163
+ _ensure_db()
164
+
165
+ conn = sqlite3.connect(str(DB_PATH))
166
+ cursor = conn.cursor()
167
+
168
+ cursor.execute("DELETE FROM contexts WHERE name = ?", (name,))
169
+ deleted = cursor.rowcount > 0
170
+
171
+ conn.commit()
172
+ conn.close()
173
+
174
+ return deleted
@@ -0,0 +1,115 @@
1
+ """
2
+ ContextVault Restore - Restore coding context
3
+
4
+ Restores your mental state with AI briefing:
5
+ - Reopens files
6
+ - Returns AI summary of where you left off
7
+ """
8
+
9
+ from typing import Optional
10
+ from datetime import datetime
11
+
12
+ from codeshield.contextvault.capture import get_context, CodingContext
13
+
14
+
15
+ def restore_context(name: str) -> dict:
16
+ """
17
+ Restore a previously saved coding context.
18
+
19
+ Args:
20
+ name: Name of context to restore
21
+
22
+ Returns:
23
+ Dict with context info and AI briefing
24
+ """
25
+ context = get_context(name)
26
+
27
+ if not context:
28
+ return {
29
+ "success": False,
30
+ "error": f"Context '{name}' not found",
31
+ "available_contexts": [], # Could list available ones
32
+ }
33
+
34
+ # Generate AI briefing
35
+ briefing = generate_briefing(context)
36
+
37
+ return {
38
+ "success": True,
39
+ "context": context.to_dict(),
40
+ "briefing": briefing,
41
+ "files_to_open": context.files,
42
+ "cursor_position": context.cursor,
43
+ }
44
+
45
+
46
+ def generate_briefing(context: CodingContext) -> str:
47
+ """
48
+ Generate a natural language briefing about the context.
49
+
50
+ Uses LLM if available, otherwise generates basic briefing.
51
+ """
52
+ # Calculate time since context was saved
53
+ try:
54
+ saved_time = datetime.fromisoformat(context.created_at)
55
+ time_diff = datetime.now() - saved_time
56
+
57
+ if time_diff.days > 0:
58
+ time_str = f"{time_diff.days} days ago"
59
+ elif time_diff.seconds > 3600:
60
+ hours = time_diff.seconds // 3600
61
+ time_str = f"{hours} hours ago"
62
+ else:
63
+ minutes = time_diff.seconds // 60
64
+ time_str = f"{minutes} minutes ago"
65
+ except:
66
+ time_str = "unknown time ago"
67
+
68
+ # Try to use LLM for briefing
69
+ try:
70
+ from codeshield.utils.llm import get_llm_client
71
+
72
+ client = get_llm_client()
73
+ llm_briefing = client.generate_context_briefing({
74
+ "files": context.files,
75
+ "last_edited": context.last_edited_file,
76
+ "cursor": context.cursor,
77
+ "notes": context.notes,
78
+ "time_ago": time_str,
79
+ })
80
+
81
+ if llm_briefing:
82
+ return llm_briefing
83
+ except:
84
+ pass
85
+
86
+ # Fallback to basic briefing
87
+ briefing_parts = []
88
+
89
+ if context.files:
90
+ file_count = len(context.files)
91
+ last_file = context.files[-1].split('/')[-1].split('\\')[-1] if context.files else "unknown"
92
+ briefing_parts.append(f"You had {file_count} files open. Last working on '{last_file}'.")
93
+
94
+ if context.cursor:
95
+ line = context.cursor.get('line', '?')
96
+ briefing_parts.append(f"Cursor was at line {line}.")
97
+
98
+ if context.notes:
99
+ briefing_parts.append(f"Your notes: {context.notes}")
100
+
101
+ briefing_parts.append(f"This was saved {time_str}.")
102
+
103
+ return " ".join(briefing_parts)
104
+
105
+
106
+ def quick_restore() -> Optional[dict]:
107
+ """Quick restore the most recent context"""
108
+ from codeshield.contextvault.capture import list_contexts
109
+
110
+ contexts = list_contexts()
111
+ if not contexts:
112
+ return None
113
+
114
+ most_recent = contexts[0]["name"]
115
+ return restore_context(most_recent)
@@ -0,0 +1 @@
1
+ """MCP Server module"""
@@ -0,0 +1,65 @@
1
+ """
2
+ Code Interception Hooks
3
+
4
+ This module provides hooks that can be integrated into:
5
+ 1. Pre-commit hooks (Git)
6
+ 2. CI/CD pipelines
7
+ 3. IDE save actions (if supported)
8
+
9
+ It serves as the entry point for automated code verification.
10
+ """
11
+
12
+ import sys
13
+ import os
14
+ from pathlib import Path
15
+ from codeshield.trustgate.checker import verify_code, verify_file
16
+
17
+ def pre_commit_hook(files: list[str]) -> bool:
18
+ """
19
+ Git pre-commit hook: Verify all changed files.
20
+ Returns True if verification passes, False otherwise.
21
+ """
22
+ print("🛡️ CodeShield Verification Hook Running...")
23
+
24
+ all_passed = True
25
+
26
+ for file_path in files:
27
+ if not file_path.endswith('.py'):
28
+ continue
29
+
30
+ print(f"Checking {file_path}...")
31
+
32
+ # Verify file
33
+ try:
34
+ result = verify_file(file_path)
35
+
36
+ if not result.is_valid:
37
+ all_passed = False
38
+ print(f"❌ Issues in {file_path}:")
39
+ for issue in result.issues:
40
+ print(f" [{issue.severity}] {issue.message}")
41
+ else:
42
+ print(f"✅ {file_path} passed ({result.confidence_score:.0%} confidence)")
43
+
44
+ except Exception as e:
45
+ print(f"⚠️ Error checking {file_path}: {e}")
46
+ all_passed = False
47
+
48
+ return all_passed
49
+
50
+ def code_interceptor(code: str, source: str = "unknown") -> dict:
51
+ """
52
+ General purpose interceptor for any code snippet.
53
+ Useful for IDE plugins or content scripts to verify code on the fly.
54
+ """
55
+ result = verify_code(code)
56
+ return result.to_dict()
57
+
58
+ if __name__ == "__main__":
59
+ # Example CLI usage: python -m codeshield.mcp.hooks file1.py file2.py
60
+ if len(sys.argv) > 1:
61
+ files = sys.argv[1:]
62
+ success = pre_commit_hook(files)
63
+ sys.exit(0 if success else 1)
64
+ else:
65
+ print("Usage: python -m codeshield.mcp.hooks <file1> <file2> ...")
@@ -0,0 +1,319 @@
1
+ """
2
+ MCP Server - Proper FastMCP implementation with LeanMCP Observability
3
+
4
+ Uses the official MCP Python SDK (FastMCP) to expose CodeShield tools
5
+ as a proper MCP server that integrates with Claude, Cursor, etc.
6
+
7
+ Integrations:
8
+ - FastMCP: MCP protocol implementation
9
+ - LeanMCP: Observability, metrics, and analytics tracking
10
+ - CometAPI/Novita/AIML: LLM providers for code fixes
11
+ """
12
+
13
+ from typing import Optional, Any
14
+ import json
15
+ import time
16
+
17
+ # Try to import FastMCP, fallback to simple HTTP if not available
18
+ try:
19
+ from mcp.server.fastmcp import FastMCP
20
+ HAS_FASTMCP = True
21
+ except ImportError:
22
+ HAS_FASTMCP = False
23
+ FastMCP = None
24
+
25
+
26
+ def create_mcp_server():
27
+ """Create and configure the CodeShield MCP server with LeanMCP observability"""
28
+
29
+ if not HAS_FASTMCP:
30
+ raise ImportError(
31
+ "MCP SDK not installed. Run: pip install mcp"
32
+ )
33
+
34
+ # Initialize LeanMCP tracking
35
+ from codeshield.utils.leanmcp import get_leanmcp_client
36
+ leanmcp = get_leanmcp_client()
37
+
38
+ # Create FastMCP server
39
+ mcp = FastMCP("CodeShield")
40
+
41
+ # ============================================
42
+ # TOOL: verify_code
43
+ # ============================================
44
+ @mcp.tool()
45
+ def verify_code(code: str, auto_fix: bool = True) -> dict:
46
+ """
47
+ Verify Python code for syntax errors, missing imports, and other issues.
48
+ Returns verification report with confidence score and optional auto-fixes.
49
+
50
+ Args:
51
+ code: Python code to verify
52
+ auto_fix: Whether to automatically fix issues (default: True)
53
+
54
+ Returns:
55
+ Verification report with issues, fixes, and confidence score
56
+ """
57
+ start_time = time.time()
58
+ try:
59
+ from codeshield.trustgate.checker import verify_code as _verify
60
+
61
+ result = _verify(code, auto_fix=auto_fix)
62
+ duration_ms = int((time.time() - start_time) * 1000)
63
+ leanmcp.track_tool_call("verify_code", duration_ms=duration_ms, success=True)
64
+ return result.to_dict()
65
+ except Exception as e:
66
+ duration_ms = int((time.time() - start_time) * 1000)
67
+ leanmcp.track_tool_call("verify_code", duration_ms=duration_ms, success=False, error_message=str(e))
68
+ raise
69
+
70
+ # ============================================
71
+ # TOOL: full_verify (with sandbox execution)
72
+ # ============================================
73
+ @mcp.tool()
74
+ def full_verify(code: str) -> dict:
75
+ """
76
+ Complete verification: syntax + imports + sandbox execution.
77
+ Runs code in secure sandbox (Daytona) to confirm it actually works.
78
+
79
+ Args:
80
+ code: Python code to verify
81
+
82
+ Returns:
83
+ Comprehensive verification report including execution results
84
+ """
85
+ start_time = time.time()
86
+ try:
87
+ from codeshield.trustgate.sandbox import full_verification
88
+
89
+ result = full_verification(code)
90
+ duration_ms = int((time.time() - start_time) * 1000)
91
+ leanmcp.track_tool_call("full_verify", duration_ms=duration_ms, success=True)
92
+ return result
93
+ except Exception as e:
94
+ duration_ms = int((time.time() - start_time) * 1000)
95
+ leanmcp.track_tool_call("full_verify", duration_ms=duration_ms, success=False, error_message=str(e))
96
+ raise
97
+
98
+ # ============================================
99
+ # TOOL: check_style
100
+ # ============================================
101
+ @mcp.tool()
102
+ def check_style(code: str, codebase_path: str = ".") -> dict:
103
+ """
104
+ Check code against codebase conventions.
105
+ Detects naming mismatches and suggests corrections.
106
+
107
+ Args:
108
+ code: Code to check
109
+ codebase_path: Path to codebase for convention extraction
110
+
111
+ Returns:
112
+ Style check results with issues and corrections
113
+ """
114
+ start_time = time.time()
115
+ try:
116
+ from codeshield.styleforge.corrector import check_style as _check
117
+
118
+ result = _check(code, codebase_path)
119
+ duration_ms = int((time.time() - start_time) * 1000)
120
+ leanmcp.track_tool_call("check_style", duration_ms=duration_ms, success=True)
121
+ return result.to_dict()
122
+ except Exception as e:
123
+ duration_ms = int((time.time() - start_time) * 1000)
124
+ leanmcp.track_tool_call("check_style", duration_ms=duration_ms, success=False, error_message=str(e))
125
+ raise
126
+
127
+ # ============================================
128
+ # TOOL: save_context
129
+ # ============================================
130
+ @mcp.tool()
131
+ def save_context(
132
+ name: str,
133
+ files: list[str] = None,
134
+ cursor: dict = None,
135
+ notes: str = None,
136
+ ) -> dict:
137
+ """
138
+ Save current coding context for later restoration.
139
+ Like a game save file for your coding session.
140
+
141
+ Args:
142
+ name: Unique name for this context snapshot
143
+ files: List of open file paths
144
+ cursor: Current cursor position {file, line, column}
145
+ notes: Optional notes about current work
146
+
147
+ Returns:
148
+ Confirmation of saved context
149
+ """
150
+ from codeshield.contextvault.capture import save_context as _save
151
+
152
+ return _save(
153
+ name=name,
154
+ files=files or [],
155
+ cursor=cursor,
156
+ notes=notes,
157
+ )
158
+
159
+ # ============================================
160
+ # TOOL: restore_context
161
+ # ============================================
162
+ @mcp.tool()
163
+ def restore_context(name: str) -> dict:
164
+ """
165
+ Restore a previously saved coding context.
166
+ Returns AI briefing of where you left off.
167
+
168
+ Args:
169
+ name: Name of context to restore
170
+
171
+ Returns:
172
+ Context info with AI briefing
173
+ """
174
+ from codeshield.contextvault.restore import restore_context as _restore
175
+
176
+ return _restore(name=name)
177
+
178
+ # ============================================
179
+ # TOOL: list_contexts
180
+ # ============================================
181
+ @mcp.tool()
182
+ def list_contexts() -> list[dict]:
183
+ """
184
+ List all saved coding contexts.
185
+
186
+ Returns:
187
+ List of saved contexts with names and timestamps
188
+ """
189
+ from codeshield.contextvault.capture import list_contexts as _list
190
+
191
+ return _list()
192
+
193
+ # ============================================
194
+ # TOOL: mcp_health (Observability)
195
+ # ============================================
196
+ @mcp.tool()
197
+ def mcp_health() -> dict:
198
+ """
199
+ Check MCP server health and connectivity status.
200
+ Returns status of all integrated services and providers.
201
+
202
+ Use this to verify:
203
+ - MCP server is running correctly
204
+ - LLM providers are configured (CometAPI, Novita, AIML)
205
+ - All CodeShield modules are loaded
206
+
207
+ Returns:
208
+ Health status with provider configurations and stats
209
+ """
210
+ from codeshield.utils.llm import get_llm_client, get_provider_stats
211
+
212
+ # Check LLM providers
213
+ llm = get_llm_client()
214
+ provider_status = llm.get_status()
215
+ provider_stats = get_provider_stats()
216
+
217
+ # Check module availability
218
+ modules_status = {}
219
+ try:
220
+ from codeshield.trustgate import checker
221
+ modules_status["trustgate"] = "loaded"
222
+ except ImportError:
223
+ modules_status["trustgate"] = "not_available"
224
+
225
+ try:
226
+ from codeshield.styleforge import corrector
227
+ modules_status["styleforge"] = "loaded"
228
+ except ImportError:
229
+ modules_status["styleforge"] = "not_available"
230
+
231
+ try:
232
+ from codeshield.contextvault import capture
233
+ modules_status["contextvault"] = "loaded"
234
+ except ImportError:
235
+ modules_status["contextvault"] = "not_available"
236
+
237
+ # Get LeanMCP metrics
238
+ leanmcp_status = leanmcp.get_status()
239
+ leanmcp_metrics = leanmcp.get_metrics()
240
+
241
+ # Check Daytona configuration
242
+ import os
243
+ daytona_configured = bool(os.getenv("DAYTONA_API_KEY"))
244
+
245
+ return {
246
+ "status": "healthy",
247
+ "mcp_server": "CodeShield",
248
+ "version": "1.0.0",
249
+ "integrations": {
250
+ "leanmcp": leanmcp_status,
251
+ "daytona": {
252
+ "configured": daytona_configured,
253
+ "api_url": os.getenv("DAYTONA_API_URL", "https://app.daytona.io/api")
254
+ }
255
+ },
256
+ "llm_providers": provider_status,
257
+ "llm_stats": provider_stats,
258
+ "mcp_metrics": leanmcp_metrics,
259
+ "modules": modules_status,
260
+ "message": "MCP server is running with LeanMCP observability."
261
+ }
262
+
263
+ # ============================================
264
+ # TOOL: test_llm_connection
265
+ # ============================================
266
+ @mcp.tool()
267
+ def test_llm_connection(provider: str = None) -> dict:
268
+ """
269
+ Test LLM provider connectivity with a simple request.
270
+
271
+ Args:
272
+ provider: Optional specific provider to test (cometapi, novita, aiml)
273
+ If not specified, tests the first available provider.
274
+
275
+ Returns:
276
+ Connection test result with provider used and response time
277
+ """
278
+ import time
279
+ from codeshield.utils.llm import get_llm_client
280
+
281
+ llm = get_llm_client()
282
+ if provider:
283
+ llm.preferred_provider = provider
284
+
285
+ start_time = time.time()
286
+ response = llm.chat(
287
+ prompt="Reply with exactly: 'CodeShield MCP connected'",
288
+ max_tokens=20
289
+ )
290
+ elapsed = time.time() - start_time
291
+
292
+ if response:
293
+ return {
294
+ "success": True,
295
+ "provider": response.provider,
296
+ "model": response.model,
297
+ "response": response.content,
298
+ "response_time_ms": round(elapsed * 1000),
299
+ "tokens_used": response.tokens_used
300
+ }
301
+ else:
302
+ return {
303
+ "success": False,
304
+ "error": "No LLM provider available or all providers failed",
305
+ "hint": "Check that at least one of COMETAPI_KEY, NOVITA_API_KEY, or AIML_API_KEY is set"
306
+ }
307
+
308
+ return mcp
309
+
310
+
311
+ def run_mcp_server():
312
+ """Run the MCP server using stdio transport"""
313
+ mcp = create_mcp_server()
314
+ mcp.run()
315
+
316
+
317
+ # For direct execution
318
+ if __name__ == "__main__":
319
+ run_mcp_server()
@@ -0,0 +1 @@
1
+ """StyleForge - Convention enforcement module"""