def rule_resolve(rules_dir, rule_name, visited=None):
    """Recursively resolve a rule with all its base rules merged"""
    if visited is None:
        visited = set()
    
    if rule_name in visited:
        raise ValueError(f"Circular dependency detected: {rule_name}")
    visited.add(rule_name)
    
    rule_file = os.path.join(rules_dir, f"{rule_name}.rule")
    with open(rule_file) as f:
        rule = json.load(f)
    
    # Start with empty merged rule
    merged = {
        'allow_ips': [],
        'allow_ports': [],
        'block_ips': [],
        'block_ports': [],
        'dns_redirect': False
    }
    
    # Merge base rules first (left to right)
    for base_name in rule.get('extends', []):
        base = rule_resolve(rules_dir, base_name, visited.copy())
        merged['allow_ips']    += base.get('allow_ips', [])
        merged['allow_ports']  += base.get('allow_ports', [])
        merged['block_ips']    += base.get('block_ips', [])
        merged['block_ports']  += base.get('block_ports', [])
        if base.get('dns_redirect'):
            merged['dns_redirect'] = True
    
    # Child rule overrides — deduplicate
    merged['allow_ips']   = list(dict.fromkeys(
        merged['allow_ips'] + rule.get('allow_ips', [])))
    merged['allow_ports'] = list(dict.fromkeys(
        merged['allow_ports'] + rule.get('allow_ports', [])))
    merged['block_ips']   = list(dict.fromkeys(
        merged['block_ips'] + rule.get('block_ips', [])))
    merged['block_ports'] = list(dict.fromkeys(
        merged['block_ports'] + rule.get('block_ports', [])))
    if rule.get('dns_redirect'):
        merged['dns_redirect'] = True
    
    merged['name'] = rule['name']
    merged['desc'] = rule.get('desc', '')
    merged['extends'] = rule.get('extends', [])
    
    print(json.dumps(merged))

def find_rule_file(rules_dir, rule_name):
    """Find rule file in rules/ or rules/base/"""
    for path in [
        os.path.join(rules_dir, f"{rule_name}.rule"),
        os.path.join(rules_dir, "base", f"{rule_name}.rule"),
    ]:
        if os.path.exists(path):
            return path
    raise FileNotFoundError(f"Rule not found: {rule_name}")


'rule_resolve':           lambda args: rule_resolve(args[0], args[1], args[2]),
'find_rule_file':         lambda args: find_rule_file(args[0], args[1]),


Still need to add that to json.sh, also let's avoid inline Python, I saw you write it just now on rule::get()