sallima commited on
Commit
917b3f5
·
verified ·
1 Parent(s): d62e71f

claude improvement

Browse files
Files changed (1) hide show
  1. app.py +285 -30
app.py CHANGED
@@ -1,37 +1,292 @@
1
  import os, hashlib, re, base64, requests, gradio as gr
 
 
 
2
  GH = "https://api.github.com"
3
  TOKEN = os.getenv("GITHUB_TOKEN")
4
- RULES_REPO = os.getenv("RULES_REPO") # e.g. "stefanoallima/awesome-cursorrules"
5
  DEFAULT_REF = os.getenv("DEFAULT_REF", "main")
6
 
7
- def _hdr(): return {"Authorization": f"Bearer {TOKEN}", "Accept":"application/vnd.github+json"}
8
- def _sha256(b): return hashlib.sha256(b).hexdigest()
9
-
10
- def list_rules(tech_key: str|None=None, ref: str|None=None):
11
- ref = ref or DEFAULT_REF
12
- r = requests.get(f"{GH}/repos/{RULES_REPO}/git/trees/{ref}?recursive=1", headers=_hdr()); r.raise_for_status()
13
- out=[]
14
- for e in r.json().get("tree", []):
15
- if e.get("type")=="blob" and re.search(r"\.(md|mdc|cursorrules)$", e["path"], re.I):
16
- tk = re.sub(r"\.(md|mdc|cursorrules)$","",e["path"].split("/")[-1].lower())
17
- if not tech_key or tk==tech_key.lower():
18
- out.append({"tech_key": tk, "path": e["path"], "repo": RULES_REPO, "commit_sha": ref})
19
- return out
20
-
21
- def fetch_rule(tech_key: str, ref: str|None=None):
22
- ref = ref or DEFAULT_REF
23
- files = list_rules(tech_key=tech_key, ref=ref)
24
- if not files: raise ValueError(f"no rule for '{tech_key}' in {RULES_REPO}@{ref}")
25
- path = files[0]["path"]
26
- r = requests.get(f"{GH}/repos/{RULES_REPO}/contents/{path}?ref={ref}", headers=_hdr()); r.raise_for_status()
27
- j = r.json(); raw = base64.b64decode(j["content"]) if j.get("encoding")=="base64" else j["content"].encode()
28
- return {"filename": path.split("/")[-1], "content": raw.decode("utf-8","replace"),
29
- "repo": RULES_REPO, "commit_sha": ref, "sha256": _sha256(raw)}
30
-
31
- with gr.Blocks() as demo:
32
- gr.Markdown("# Rules Catalog MCP (read-only)")
33
- gr.api(fn=list_rules)
34
- gr.api(fn=fetch_rule)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  if __name__ == "__main__":
37
- demo.launch(mcp_server=True)
 
1
  import os, hashlib, re, base64, requests, gradio as gr
2
+ from typing import List, Dict, Optional, Any
3
+ import json
4
+
5
  GH = "https://api.github.com"
6
  TOKEN = os.getenv("GITHUB_TOKEN")
7
+ RULES_REPO = os.getenv("RULES_REPO", "stefanoallima/awesome-cursorrules")
8
  DEFAULT_REF = os.getenv("DEFAULT_REF", "main")
9
 
10
+ def _hdr():
11
+ return {"Authorization": f"Bearer {TOKEN}", "Accept":"application/vnd.github+json"}
12
+
13
+ def _sha256(b):
14
+ return hashlib.sha256(b).hexdigest()
15
+
16
+ def get_readme_content(ref: str = None) -> str:
17
+ """Fetch README content from the repository for context"""
18
+ ref = ref or DEFAULT_REF
19
+ try:
20
+ r = requests.get(f"{GH}/repos/{RULES_REPO}/contents/README.md?ref={ref}", headers=_hdr())
21
+ r.raise_for_status()
22
+ j = r.json()
23
+ raw = base64.b64decode(j["content"]) if j.get("encoding") == "base64" else j["content"].encode()
24
+ return raw.decode("utf-8", "replace")
25
+ except Exception as e:
26
+ return f"Error fetching README: {str(e)}"
27
+
28
+ def extract_available_technologies(ref: str = None) -> List[str]:
29
+ """Extract all available technologies from the rules directory"""
30
+ ref = ref or DEFAULT_REF
31
+ try:
32
+ r = requests.get(f"{GH}/repos/{RULES_REPO}/git/trees/{ref}?recursive=1", headers=_hdr())
33
+ r.raise_for_status()
34
+
35
+ technologies = []
36
+ for item in r.json().get("tree", []):
37
+ if item.get("type") == "blob" and item["path"].startswith("rules/"):
38
+ # Extract technology name from directory structure
39
+ path_parts = item["path"].split("/")
40
+ if len(path_parts) >= 2:
41
+ tech_dir = path_parts[1]
42
+ # Clean up the directory name to extract technology
43
+ tech_name = tech_dir.replace("-cursorrules-prompt-file", "").replace("-", " ")
44
+ if tech_name not in technologies:
45
+ technologies.append(tech_name)
46
+
47
+ return sorted(technologies)
48
+ except Exception as e:
49
+ return [f"Error: {str(e)}"]
50
+
51
+ def semantic_match_technologies(requested_techs: List[str], available_techs: List[str]) -> Dict[str, List[str]]:
52
+ """Use simple semantic matching to find relevant technologies"""
53
+ matches = {}
54
+
55
+ for requested in requested_techs:
56
+ requested_lower = requested.lower()
57
+ matched_techs = []
58
+
59
+ for available in available_techs:
60
+ available_lower = available.lower()
61
+
62
+ # Direct match
63
+ if requested_lower == available_lower:
64
+ matched_techs.append(available)
65
+ continue
66
+
67
+ # Partial match (contains)
68
+ if requested_lower in available_lower or available_lower in requested_lower:
69
+ matched_techs.append(available)
70
+ continue
71
+
72
+ # Common technology mappings
73
+ tech_mappings = {
74
+ 'python': ['python', 'django', 'fastapi', 'flask'],
75
+ 'javascript': ['javascript', 'js', 'node', 'react', 'vue', 'angular'],
76
+ 'typescript': ['typescript', 'ts', 'react', 'angular', 'nextjs'],
77
+ 'react': ['react', 'nextjs', 'typescript'],
78
+ 'vue': ['vue', 'vuejs', 'nuxt'],
79
+ 'node': ['node', 'nodejs', 'javascript'],
80
+ 'postgres': ['postgres', 'postgresql', 'database'],
81
+ 'fastapi': ['fastapi', 'python', 'api'],
82
+ 'nextjs': ['nextjs', 'next', 'react', 'typescript']
83
+ }
84
+
85
+ # Check if requested tech maps to available tech
86
+ if requested_lower in tech_mappings:
87
+ for mapped_tech in tech_mappings[requested_lower]:
88
+ if mapped_tech in available_lower:
89
+ matched_techs.append(available)
90
+ break
91
+
92
+ matches[requested] = matched_techs
93
+
94
+ return matches
95
+
96
+ def list_rules(tech_key: str = None, ref: str = None) -> List[Dict[str, Any]]:
97
+ """List available coding rules with enhanced metadata"""
98
+ ref = ref or DEFAULT_REF
99
+ try:
100
+ r = requests.get(f"{GH}/repos/{RULES_REPO}/git/trees/{ref}?recursive=1", headers=_hdr())
101
+ r.raise_for_status()
102
+
103
+ rules = []
104
+ for item in r.json().get("tree", []):
105
+ if item.get("type") == "blob" and item["path"].startswith("rules/"):
106
+ path_parts = item["path"].split("/")
107
+ if len(path_parts) >= 2:
108
+ tech_dir = path_parts[1]
109
+ tech_name = tech_dir.replace("-cursorrules-prompt-file", "").replace("-", " ")
110
+
111
+ if not tech_key or tech_key.lower() in tech_name.lower():
112
+ rules.append({
113
+ "tech_key": tech_name,
114
+ "directory": tech_dir,
115
+ "path": item["path"],
116
+ "repo": RULES_REPO,
117
+ "commit_sha": ref,
118
+ "url": f"https://github.com/{RULES_REPO}/tree/{ref}/{item['path']}"
119
+ })
120
+
121
+ return rules
122
+ except Exception as e:
123
+ return [{"error": str(e)}]
124
+
125
+ def fetch_rule_content(tech_directory: str, ref: str = None) -> Dict[str, Any]:
126
+ """Fetch the actual rule content from a technology directory"""
127
+ ref = ref or DEFAULT_REF
128
+ try:
129
+ # Get files in the specific rule directory
130
+ r = requests.get(f"{GH}/repos/{RULES_REPO}/contents/rules/{tech_directory}?ref={ref}", headers=_hdr())
131
+ r.raise_for_status()
132
+
133
+ files = r.json()
134
+ if not isinstance(files, list):
135
+ files = [files]
136
+
137
+ # Look for .cursorrules or .md files
138
+ rule_file = None
139
+ for file in files:
140
+ if file["name"].endswith(('.cursorrules', '.md')):
141
+ rule_file = file
142
+ break
143
+
144
+ if not rule_file:
145
+ return {"error": f"No rule file found in {tech_directory}"}
146
+
147
+ # Fetch the file content
148
+ content_r = requests.get(rule_file["download_url"])
149
+ content_r.raise_for_status()
150
+
151
+ return {
152
+ "tech_key": tech_directory.replace("-cursorrules-prompt-file", "").replace("-", " "),
153
+ "filename": rule_file["name"],
154
+ "content": content_r.text,
155
+ "directory": tech_directory,
156
+ "repo": RULES_REPO,
157
+ "commit_sha": ref,
158
+ "sha256": _sha256(content_r.content),
159
+ "url": rule_file["html_url"]
160
+ }
161
+ except Exception as e:
162
+ return {"error": str(e)}
163
+
164
+ def fetch_rule(tech_key: str, ref: str = None) -> Dict[str, Any]:
165
+ """Fetch rule with semantic matching fallback"""
166
+ ref = ref or DEFAULT_REF
167
+
168
+ # First try direct match
169
+ rules = list_rules(tech_key=tech_key, ref=ref)
170
+ if rules and "error" not in rules[0]:
171
+ return fetch_rule_content(rules[0]["directory"], ref)
172
+
173
+ # If no direct match, try semantic matching
174
+ available_techs = extract_available_technologies(ref)
175
+ matches = semantic_match_technologies([tech_key], available_techs)
176
+
177
+ if tech_key in matches and matches[tech_key]:
178
+ # Return the first match
179
+ best_match = matches[tech_key][0]
180
+ tech_directory = best_match.replace(" ", "-") + "-cursorrules-prompt-file"
181
+ return fetch_rule_content(tech_directory, ref)
182
+
183
+ return {"error": f"No rule found for '{tech_key}' in {RULES_REPO}@{ref}"}
184
+
185
+ def get_guidelines_for_stack(tech_stack: List[str], ref: str = None) -> Dict[str, Any]:
186
+ """Get coding guidelines for multiple technologies in a stack"""
187
+ ref = ref or DEFAULT_REF
188
+
189
+ available_techs = extract_available_technologies(ref)
190
+ matches = semantic_match_technologies(tech_stack, available_techs)
191
+
192
+ guidelines = {}
193
+ for requested_tech, matched_techs in matches.items():
194
+ guidelines[requested_tech] = []
195
+ for matched_tech in matched_techs[:3]: # Limit to top 3 matches
196
+ tech_directory = matched_tech.replace(" ", "-") + "-cursorrules-prompt-file"
197
+ rule_content = fetch_rule_content(tech_directory, ref)
198
+ if "error" not in rule_content:
199
+ guidelines[requested_tech].append(rule_content)
200
+
201
+ return {
202
+ "tech_stack": tech_stack,
203
+ "guidelines": guidelines,
204
+ "available_technologies": available_techs,
205
+ "matches": matches,
206
+ "repo": RULES_REPO,
207
+ "commit_sha": ref
208
+ }
209
+
210
+ def analyze_project_stack(framework_list: str, ref: str = None) -> Dict[str, Any]:
211
+ """Analyze a project's technology stack and return relevant guidelines"""
212
+ ref = ref or DEFAULT_REF
213
+
214
+ # Parse the framework list (assume comma-separated or newline-separated)
215
+ techs = []
216
+ for line in framework_list.replace(",", "\n").split("\n"):
217
+ tech = line.strip()
218
+ if tech:
219
+ techs.append(tech)
220
+
221
+ if not techs:
222
+ return {"error": "No technologies found in the provided list"}
223
+
224
+ # Get README for context
225
+ readme_content = get_readme_content(ref)
226
+
227
+ # Get guidelines for the entire stack
228
+ stack_guidelines = get_guidelines_for_stack(techs, ref)
229
+
230
+ return {
231
+ "project_analysis": {
232
+ "detected_technologies": techs,
233
+ "readme_context": readme_content[:1000] + "..." if len(readme_content) > 1000 else readme_content,
234
+ },
235
+ "guidelines": stack_guidelines,
236
+ "summary": f"Found guidelines for {len([g for g in stack_guidelines['guidelines'].values() if g])} out of {len(techs)} requested technologies"
237
+ }
238
+
239
+ # Gradio Interface
240
+ with gr.Blocks(title="Enhanced MCP Coding Guidelines Server") as demo:
241
+ gr.Markdown("# 🚀 Enhanced MCP Coding Guidelines Server")
242
+ gr.Markdown("Intelligent coding guideline retrieval with semantic matching")
243
+
244
+ with gr.Tab("Single Technology"):
245
+ with gr.Row():
246
+ tech_input = gr.Textbox(label="Technology", placeholder="e.g., python, react, fastapi")
247
+ ref_input = gr.Textbox(label="Git Reference", value="main", placeholder="main")
248
+ fetch_btn = gr.Button("Fetch Guidelines")
249
+ single_output = gr.JSON(label="Guidelines")
250
+
251
+ fetch_btn.click(
252
+ fn=fetch_rule,
253
+ inputs=[tech_input, ref_input],
254
+ outputs=single_output
255
+ )
256
+
257
+ with gr.Tab("Technology Stack"):
258
+ stack_input = gr.Textbox(
259
+ label="Technology Stack",
260
+ placeholder="python, fastapi, postgres, react, typescript",
261
+ lines=3
262
+ )
263
+ stack_ref_input = gr.Textbox(label="Git Reference", value="main")
264
+ analyze_btn = gr.Button("Analyze Stack")
265
+ stack_output = gr.JSON(label="Stack Analysis")
266
+
267
+ analyze_btn.click(
268
+ fn=analyze_project_stack,
269
+ inputs=[stack_input, stack_ref_input],
270
+ outputs=stack_output
271
+ )
272
+
273
+ with gr.Tab("Available Technologies"):
274
+ list_ref_input = gr.Textbox(label="Git Reference", value="main")
275
+ list_btn = gr.Button("List Available Technologies")
276
+ list_output = gr.JSON(label="Available Technologies")
277
+
278
+ list_btn.click(
279
+ fn=extract_available_technologies,
280
+ inputs=[list_ref_input],
281
+ outputs=list_output
282
+ )
283
+
284
+ # Register MCP API endpoints
285
+ gr.api(fn=list_rules, name="list_rules")
286
+ gr.api(fn=fetch_rule, name="fetch_rule")
287
+ gr.api(fn=get_guidelines_for_stack, name="get_guidelines_for_stack")
288
+ gr.api(fn=analyze_project_stack, name="analyze_project_stack")
289
+ gr.api(fn=extract_available_technologies, name="extract_available_technologies")
290
 
291
  if __name__ == "__main__":
292
+ demo.launch(mcp_server=True)