Skip to content

Commit 2d0c88b

Browse files
committed
feat: enhance logging and improve UI navigation
1 parent 2530bc0 commit 2d0c88b

File tree

15 files changed

+1394
-64
lines changed

15 files changed

+1394
-64
lines changed

.gitignore

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ __pycache__/
55

66
# C extensions
77
*.so
8-
TASK.md
9-
TASK2.md
108
# Distribution / packaging
119
.Python
1210
build/
@@ -208,4 +206,9 @@ marimo/_lsp/
208206
__marimo__/
209207

210208
.playwright-mcp
211-
.mcp.json
209+
.mcp.json
210+
211+
TASK.md
212+
TASK2.md
213+
TASK_IN_PROGRESS.md
214+
TASK_DONE.md

app/actions/rules.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ clean-comments:
7070
namespace: "development"
7171
content: |
7272
## Clean Comments
73-
7473
- When updating code, don't reference what is changing
7574
- Avoid keywords like LEGACY, CHANGED, REMOVED
7675
- Focus on comments that document just the functionality of the code

app/main.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,33 @@
33
from fastapi.staticfiles import StaticFiles
44
from fastapi.templating import Jinja2Templates
55
from pathlib import Path
6-
from app.routes import install, actions, recommend
6+
from app.routes import install, actions, recommend, generate
77
from app.services.actions_loader import actions_loader
88
from api_analytics.fastapi import Analytics
99
from fastapi_mcp import FastApiMCP
1010
import os
1111
from dotenv import load_dotenv
12+
from loguru import logger
13+
import sys
1214

1315
# Load environment variables
1416
load_dotenv()
1517

18+
# Configure loguru logger
19+
logger.remove()
20+
logger.add(
21+
sys.stderr,
22+
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
23+
level="INFO"
24+
)
25+
logger.add(
26+
"logs/app.log",
27+
rotation="10 MB",
28+
retention="7 days",
29+
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} - {message}",
30+
level="DEBUG"
31+
)
32+
1633
app = FastAPI(title="Gitrules", version="0.1.0")
1734

1835
# Add API Analytics middleware
@@ -29,6 +46,7 @@
2946
app.include_router(install.router)
3047
app.include_router(actions.router)
3148
app.include_router(recommend.router)
49+
app.include_router(generate.router)
3250

3351
@app.get("/favicon.ico", operation_id="get_favicon")
3452
async def favicon():
@@ -44,6 +62,11 @@ async def select(request: Request):
4462
"""Action selection page with filters"""
4563
return templates.TemplateResponse("select.html", {"request": request})
4664

65+
@app.get("/generate", response_class=HTMLResponse, operation_id="get_generate_page")
66+
async def get_generate_page(request: Request):
67+
"""Generate configuration files from selected actions"""
68+
return templates.TemplateResponse("generate.html", {"request": request})
69+
4770
@app.get("/", response_class=HTMLResponse, operation_id="get_index_page")
4871
async def index(request: Request):
4972
"""Landing page for starting the configuration journey"""

app/routes/generate.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
from fastapi import APIRouter, HTTPException
2+
from pydantic import BaseModel
3+
from typing import List, Dict, Any, Optional
4+
import json
5+
from app.services.actions_loader import actions_loader
6+
7+
router = APIRouter(prefix="/api/v2", tags=["generate"])
8+
9+
class GenerateRequest(BaseModel):
10+
action_ids: List[str]
11+
formats: List[str] = ["claude"] # claude, cursor, agents
12+
source: str = "scratch" # "repo", "template", or "scratch"
13+
repo_url: Optional[str] = None # For tracking the source repo when source="repo"
14+
15+
class GenerateResponse(BaseModel):
16+
files: Dict[str, str]
17+
patch: str
18+
source: str
19+
20+
@router.post("/generate", operation_id="generate_configuration")
21+
async def generate_configuration(request: GenerateRequest) -> GenerateResponse:
22+
"""Generate configuration files from selected action IDs"""
23+
24+
files = {}
25+
26+
# Load action details
27+
selected_agents = []
28+
selected_rules = []
29+
selected_mcps = []
30+
31+
for action_id in request.action_ids:
32+
# Try to find the action in different categories
33+
34+
# Check agents
35+
agent = actions_loader.get_agent(action_id)
36+
if agent:
37+
selected_agents.append(agent)
38+
continue
39+
40+
# Check rules
41+
rule = actions_loader.get_rule(action_id)
42+
if rule:
43+
selected_rules.append(rule)
44+
continue
45+
46+
# Check MCPs
47+
mcp = actions_loader.get_mcp(action_id)
48+
if mcp:
49+
selected_mcps.append(mcp)
50+
continue
51+
52+
# Generate files based on selected formats
53+
for format_type in request.formats:
54+
if format_type == "claude":
55+
# Generate CLAUDE.md if there are rules
56+
if selected_rules:
57+
claude_content = ""
58+
for rule in selected_rules:
59+
if rule.get('content'):
60+
claude_content += rule['content'].strip() + "\n\n"
61+
62+
if claude_content:
63+
files['CLAUDE.md'] = claude_content.strip()
64+
65+
# Generate agent files for Claude format
66+
for agent in selected_agents:
67+
if agent.get('content'):
68+
filename = agent.get('filename', f"{agent['name']}.md")
69+
files[f".claude/agents/{filename}"] = agent['content']
70+
71+
elif format_type == "cursor":
72+
# Generate .cursorrules file
73+
if selected_rules:
74+
cursor_content = ""
75+
for rule in selected_rules:
76+
if rule.get('content'):
77+
cursor_content += rule['content'].strip() + "\n\n"
78+
79+
if cursor_content:
80+
files['.cursorrules'] = cursor_content.strip()
81+
82+
elif format_type == "agents":
83+
# Generate AGENTS.md file with rules (copy of CLAUDE.md)
84+
if selected_rules:
85+
agents_content = ""
86+
for rule in selected_rules:
87+
if rule.get('content'):
88+
agents_content += rule['content'].strip() + "\n\n"
89+
90+
if agents_content:
91+
files['AGENTS.md'] = agents_content.strip()
92+
93+
# Generate .mcp.json if there are MCPs
94+
if selected_mcps:
95+
mcp_config = {"mcpServers": {}}
96+
for mcp in selected_mcps:
97+
if mcp.get('config'):
98+
mcp_config["mcpServers"][mcp['name']] = mcp['config']
99+
100+
if mcp_config["mcpServers"]:
101+
files['.mcp.json'] = json.dumps(mcp_config, indent=2)
102+
103+
# Generate patch file
104+
patch = generate_patch(files, request.source, request.repo_url)
105+
106+
return GenerateResponse(files=files, patch=patch, source=request.source)
107+
108+
def generate_patch(files: Dict[str, str], source: str = "scratch", repo_url: str = None) -> str:
109+
"""
110+
Generate a unified diff patch from the files.
111+
112+
Args:
113+
files: Dictionary of file paths and their contents
114+
source: Source of the generation ("repo", "template", or "scratch")
115+
repo_url: URL of source repository if source is "repo"
116+
117+
Returns:
118+
Unified diff patch string that can be applied with patch command
119+
"""
120+
patch_lines = []
121+
122+
# Add a comment header explaining the patch
123+
if source == "repo" and repo_url:
124+
patch_lines.append(f"# Gitrules configuration patch generated from repository: {repo_url}")
125+
patch_lines.append("# Apply with: git apply <this-patch>")
126+
use_git_format = True
127+
elif source == "template":
128+
patch_lines.append("# Gitrules configuration patch generated from template")
129+
patch_lines.append("# Apply with: patch -p0 < <this-patch>")
130+
use_git_format = False
131+
else:
132+
patch_lines.append("# Gitrules configuration patch generated from scratch")
133+
patch_lines.append("# Apply with: patch -p0 < <this-patch>")
134+
use_git_format = False
135+
136+
patch_lines.append("")
137+
138+
for filepath, content in files.items():
139+
if use_git_format:
140+
# Git format
141+
patch_lines.append(f"diff --git a/{filepath} b/{filepath}")
142+
patch_lines.append("new file mode 100644")
143+
patch_lines.append("index 0000000..1234567")
144+
patch_lines.append("--- /dev/null")
145+
patch_lines.append(f"+++ b/{filepath}")
146+
else:
147+
# Standard patch format
148+
patch_lines.append(f"--- /dev/null")
149+
patch_lines.append(f"+++ {filepath}")
150+
151+
lines = content.split('\n')
152+
if lines and lines[-1] == '':
153+
lines.pop() # Remove empty last line if present
154+
155+
patch_lines.append(f"@@ -0,0 +1,{len(lines)} @@")
156+
157+
for line in lines:
158+
patch_lines.append(f"+{line}")
159+
160+
patch_lines.append("") # Empty line between files
161+
162+
return '\n'.join(patch_lines)

app/routes/recommend.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
call_llm_for_reco,
1414
parse_and_validate
1515
)
16+
from loguru import logger
1617

1718
router = APIRouter(prefix="/api", tags=["recommend"])
1819

@@ -53,17 +54,17 @@ async def recommend_tools(request: RecommendRequest):
5354
status_code=400,
5455
detail="Either repo_url or context must be provided"
5556
)
56-
print(f"Getting context for {request.repo_url}")
57+
logger.info(f"Getting context for {request.repo_url}")
5758
# Step 1: Get context (ingest if needed)
5859
if request.context:
59-
print(f"Using provided context")
60+
logger.info("Using provided context")
6061
context = request.context
6162
else:
6263
# Ingest the repository
63-
print(f"Ingesting repository {request.repo_url}")
64+
logger.info(f"Ingesting repository {request.repo_url}")
6465
context = await use_gitingest(request.repo_url)
6566
context_size = len(context)
66-
print(f"Context size: {context_size}")
67+
logger.info(f"Context size: {context_size}")
6768

6869
# Step 2: Build catalog
6970
catalog = build_tools_catalog()

0 commit comments

Comments
 (0)