Skip to content

Commit 0564013

Browse files
committed
feat: refactor rules system with hierarchy, fuzzy search, and enhanced filtering
1 parent 2e72db3 commit 0564013

File tree

6 files changed

+686
-158
lines changed

6 files changed

+686
-158
lines changed

app/actions/mcps.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,15 @@ mcps:
2727
- '@supabase/mcp-server-supabase@latest'
2828
- --access-token
2929
- ${SUPABASE_ACCESS_TOKEN}
30+
- display_name: Context7
31+
slug: Context7
32+
config:
33+
type: http
34+
url: https://mcp.context7.com/mcp/
35+
headers:
36+
"CONTEXT7_API_KEY": "${CONTEXT7_API_KEY}"
37+
- display_name: Exa Search
38+
slug: ExaSearch
39+
config:
40+
type: http
41+
url: https://mcp.exa.ai/mcp?exa_api_key=${EXA_API_KEY}

app/actions/rules.yaml

Lines changed: 143 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,156 @@
1-
rules:
2-
- display_name: You Are A Pirate
3-
slug: you-are-a-pirate
4-
content: '## Yarr!
5-
6-
1+
you-are-a-pirate:
2+
display_name: You Are A Pirate
3+
type: rule
4+
author: Captain Hook
5+
tags: ["fun", "roleplay", "pirate"]
6+
namespace: "personality"
7+
content: |
8+
## Yarr!
9+
710
- You are a pirate!
8-
9-
- You will usually try to help the user but only if you can get something out
10-
of it. Otherwise, try to trick him.
11-
11+
- You will usually try to help the user but only if you can get something out of it. Otherwise, try to trick him.
1212
- Use pirate slang!
13-
1413
- The only code you know about is the pirate code.
15-
16-
- As far as you''re concerned, Python is a snake. You don''t like snakes!
17-
14+
- As far as you're concerned, Python is a snake. You don't like snakes!
1815
- Yarr!
1916
20-
'
21-
22-
- display_name: Code Quality
23-
slug: code-quality
24-
content: '## Code Quality
25-
26-
27-
- Remove dead code immediately rather than maintaining it - no backward compatibility
28-
or legacy functions
29-
17+
code-quality:
18+
display_name: Code Quality
19+
type: ruleset
20+
author: Engineering Team
21+
tags: ["engineering", "best-practices", "code-style"]
22+
namespace: "development"
23+
children:
24+
- "minimal-code"
25+
- "remove-dead-code"
26+
- "prioritize-functionality"
27+
- "clean-comments"
28+
29+
minimal-code:
30+
display_name: Keep Code Minimal
31+
type: rule
32+
author: Engineering Team
33+
tags: ["simplicity", "code-style"]
34+
namespace: "development"
35+
content: |
36+
- Always keep code SUPER minimal
37+
- Never introduce features not explicitly mentioned
38+
39+
remove-dead-code:
40+
display_name: Remove Dead Code
41+
type: rule
42+
author: Engineering Team
43+
tags: ["maintenance", "code-style"]
44+
namespace: "development"
45+
content: |
46+
- Remove dead code immediately rather than maintaining it
47+
- No backward compatibility or legacy functions
48+
49+
prioritize-functionality:
50+
display_name: Prioritize Functionality
51+
type: rule
52+
author: Engineering Team
53+
tags: ["architecture", "code-style"]
54+
namespace: "development"
55+
content: |
3056
- Prioritize functionality over production-ready patterns
31-
3257
- Focus on user experience and feature completeness
3358
34-
- When updating code, don''t reference what is changing (avoid keywords like LEGACY,
35-
CHANGED, REMOVED), instead focus on comments that document just the functionality
36-
of the code
37-
38-
- Always keep code SUPER minimal, never introduce features not explicitly mentioned.
39-
40-
'
41-
- display_name: Env Secrets
42-
slug: env-secrets
43-
content: '## Environment Variables
44-
45-
59+
clean-comments:
60+
display_name: Clean Comments
61+
type: rule
62+
author: Engineering Team
63+
tags: ["documentation", "code-style"]
64+
namespace: "development"
65+
content: |
66+
- When updating code, don't reference what is changing
67+
- Avoid keywords like LEGACY, CHANGED, REMOVED
68+
- Focus on comments that document just the functionality of the code
69+
70+
env-secrets:
71+
display_name: Environment Variables
72+
type: rule
73+
author: Security Team
74+
tags: ["security", "configuration", "environment"]
75+
namespace: "security"
76+
content: |
77+
## Environment Variables
4678
- Store secrets in a .env file (never commit it)
47-
48-
- A .env.example file should be provided for reference and any new secrets should
49-
be added to it.
50-
51-
- The implementation should use the dotenv (or similar) library to load environment
52-
variables from .env files.
53-
54-
- Variables should also be loaded from the environment.
55-
56-
'
57-
- display_name: Error Handling
58-
slug: error-handling
59-
content: '### Error Handling
60-
61-
62-
**Core Principle**: We need to intelligently decide when to fail hard and fast
63-
to quickly address issues, and when to allow processes to complete in critical
64-
services despite failures. Read below carefully and make intelligent decisions
65-
on a case-by-case basis.
66-
67-
79+
- A .env.example file should be provided for reference and any new secrets should be added to it
80+
- The implementation should use the dotenv (or similar) library to load environment variables from .env files
81+
- Variables should also be loaded from the environment
82+
83+
error-handling:
84+
display_name: Error Handling
85+
type: ruleset
86+
author: Engineering Team
87+
tags: ["errors", "exceptions", "reliability"]
88+
namespace: "development"
89+
children:
90+
- "fail-fast-principle"
91+
- "when-to-fail-fast"
92+
- "when-to-log-continue"
93+
94+
fail-fast-principle:
95+
display_name: Fail Fast Principle
96+
type: rule
97+
author: Engineering Team
98+
tags: ["architecture", "errors"]
99+
namespace: "development"
100+
content: |
101+
**Core Principle**: We need to intelligently decide when to fail hard and fast to quickly address issues, and when to allow processes to complete in critical services despite failures. Read below carefully and make intelligent decisions on a case-by-case basis.
102+
103+
when-to-fail-fast:
104+
display_name: When to Fail Fast and Loud
105+
type: rule
106+
author: Engineering Team
107+
tags: ["exceptions", "errors"]
108+
namespace: "development"
109+
content: |
68110
#### When to Fail Fast and Loud (Let it Crash!)
69-
70-
111+
71112
These errors should stop execution and bubble up immediately:
72-
73-
74-
- **Service startup failures** - If credentials, database, or any service can''t
75-
initialize, the system should crash with a clear error
76-
77-
- **Missing configuration** - Missing environment variables or invalid settings
78-
should stop the system
79-
80-
- **Service connection failures** - Don''t hide connection issues, expose them
81-
82-
- **Authentication/authorization failures** - Security errors must be visible
83-
and halt the operation
84-
85-
- **Data corruption or validation errors** - Never silently accept bad data, Pydantic
86-
should raise
87-
88-
- **Critical dependencies unavailable** - If a required service is down, fail
89-
immediately
90-
91-
- **Invalid data that would corrupt state** - Never store malformed JSON or other
92-
invalid data
93-
94-
113+
114+
- **Service startup failures** - If credentials, database, or any service can't initialize, the system should crash with a clear error
115+
- **Missing configuration** - Missing environment variables or invalid settings should stop the system
116+
- **Service connection failures** - Don't hide connection issues, expose them
117+
- **Authentication/authorization failures** - Security errors must be visible and halt the operation
118+
- **Data corruption or validation errors** - Never silently accept bad data, Pydantic should raise
119+
- **Critical dependencies unavailable** - If a required service is down, fail immediately
120+
- **Invalid data that would corrupt state** - Never store malformed JSON or other invalid data
121+
122+
when-to-log-continue:
123+
display_name: When to Complete but Log
124+
type: rule
125+
author: Engineering Team
126+
tags: ["logging", "errors"]
127+
namespace: "development"
128+
content: |
95129
#### When to Complete but Log Detailed Errors
96-
97-
130+
98131
These operations should continue but track and report failures clearly:
99-
100-
101-
- **WebSocket events** - Don''t crash on a single event failure, log it and continue
102-
serving other clients
103-
104-
'
105-
- display_name: Update Docs
106-
slug: update-docs
107-
content: '- Update any documentation when it''s relevant, including CLAUDE.md.
108-
109-
'
110-
- display_name: Use Uv
111-
slug: use-uv
112-
content: "## UV\n\n- This project uses UV for package management.\n- Use:\n - `uv\
113-
\ venv` to create a virtual environment.\n - `uv pip install -r requirements.txt|pyproject.toml`\
114-
\ to install dependencies.\n"
132+
133+
- **WebSocket events** - Don't crash on a single event failure, log it and continue serving other clients
134+
135+
update-docs:
136+
display_name: Update Documentation
137+
type: rule
138+
author: Documentation Team
139+
tags: ["documentation", "maintenance"]
140+
namespace: "documentation"
141+
content: |
142+
- Update any documentation when it's relevant, including CLAUDE.md
143+
144+
use-uv:
145+
display_name: Use UV Package Manager
146+
type: rule
147+
author: DevOps Team
148+
tags: ["tooling", "dependencies", "uv"]
149+
namespace: "development"
150+
content: |
151+
## UV
152+
153+
- This project uses UV for package management
154+
- Use:
155+
- `uv venv` to create a virtual environment
156+
- `uv pip install -r requirements.txt|pyproject.toml` to install dependencies

app/main.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,49 @@ async def favicon():
3737
async def index(request: Request):
3838
# Get all actions data for server-side rendering
3939
agents = [agent.dict() for agent in actions_loader.get_agents()]
40-
rules = [rule.dict() for rule in actions_loader.get_rules()]
40+
all_rules = actions_loader.get_rules()
41+
42+
# Create a set of all child rule IDs
43+
child_rule_ids = set()
44+
for rule in all_rules:
45+
if rule.children:
46+
child_rule_ids.update(rule.children)
47+
48+
# Create a mapping of all rules by slug for lookups
49+
rules_by_slug = {rule.slug: rule for rule in all_rules}
50+
51+
# Update rulesets to inherit children's tags
52+
for rule in all_rules:
53+
if rule.type == 'ruleset' and rule.children:
54+
# Collect all tags from children
55+
inherited_tags = set(rule.tags or [])
56+
for child_slug in rule.children:
57+
child_rule = rules_by_slug.get(child_slug)
58+
if child_rule and child_rule.tags:
59+
inherited_tags.update(child_rule.tags)
60+
rule.tags = list(inherited_tags)
61+
62+
# Filter to only top-level rules (not children of any ruleset)
63+
top_level_rules_data = [rule for rule in all_rules if rule.slug not in child_rule_ids]
64+
65+
# Sort rules: rulesets first, then standalone rules
66+
top_level_rules_data.sort(key=lambda rule: (rule.type != 'ruleset', rule.display_name or rule.name))
67+
68+
# Convert to dict
69+
top_level_rules = [rule.dict() for rule in top_level_rules_data]
70+
71+
# Create a mapping of all rules by slug for frontend to look up children (with updated tags)
72+
rules_by_slug_dict = {rule.slug: rule.dict() for rule in all_rules}
73+
4174
mcps = [mcp.dict() for mcp in actions_loader.get_mcps()]
4275

4376
return templates.TemplateResponse(
4477
"index.html",
4578
{
4679
"request": request,
4780
"agents": agents,
48-
"rules": rules,
81+
"rules": top_level_rules,
82+
"rules_by_slug": rules_by_slug_dict,
4983
"mcps": mcps
5084
}
5185
)

app/models/actions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ class Rule(BaseModel):
1414
display_name: Optional[str] = None
1515
slug: Optional[str] = None
1616
content: Optional[str] = None
17+
author: Optional[str] = None
18+
tags: Optional[List[str]] = None
19+
children: Optional[List[str]] = None # List of rule IDs
20+
type: str = "rule" # "rule" or "ruleset"
21+
namespace: Optional[str] = None
1722

1823
class MCP(BaseModel):
1924
name: str

app/services/actions_loader.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,35 @@ def load_agents(self):
3737
else:
3838
self.agents = []
3939

40+
def _parse_rule(self, slug: str, rule_data: Dict[str, Any]) -> Rule:
41+
"""Parse a single rule or ruleset from the YAML data"""
42+
rule = Rule(
43+
name=slug, # Use slug as name for backward compat
44+
filename=f"{slug}.yaml", # Virtual filename
45+
display_name=rule_data.get('display_name'),
46+
slug=slug,
47+
content=rule_data.get('content'),
48+
author=rule_data.get('author'),
49+
tags=rule_data.get('tags'),
50+
type=rule_data.get('type', 'rule'),
51+
namespace=rule_data.get('namespace'),
52+
children=rule_data.get('children') # Now just a list of rule IDs
53+
)
54+
55+
return rule
56+
4057
def load_rules(self):
4158
"""Load all rules from rules.yaml"""
4259
rules_file = self.actions_dir / "rules.yaml"
4360
if rules_file.exists():
4461
with open(rules_file, 'r') as f:
4562
data = yaml.safe_load(f)
46-
if data and 'rules' in data:
47-
self.rules = [
48-
Rule(
49-
name=rule.get('slug', ''), # Use slug as name for backward compat
50-
filename=f"{rule.get('slug', '')}.yaml", # Virtual filename
51-
display_name=rule.get('display_name'),
52-
slug=rule.get('slug'),
53-
content=rule.get('content')
54-
)
55-
for rule in data['rules']
56-
]
63+
if data:
64+
self.rules = []
65+
# Now the top-level keys are the slugs
66+
for slug, rule_data in data.items():
67+
rule = self._parse_rule(slug, rule_data)
68+
self.rules.append(rule)
5769
else:
5870
self.rules = []
5971

0 commit comments

Comments
 (0)