def rule_list_data(rules_dir, meta_dir):
    """Return all rule data including base rules and extends"""
    import glob

    # Count peers per rule from meta files
    rule_peer_counts = {}
    for f in glob.glob(f"{meta_dir}/*.meta"):
        try:
            with open(f) as mf:
                meta = json.load(mf)
            rule = meta.get('rule', '')
            if rule:
                rule_peer_counts[rule] = rule_peer_counts.get(rule, 0) + 1
        except:
            pass

    # Read all rules — both normal and base
    rule_files = (
        sorted(glob.glob(f"{rules_dir}/*.rule")) +
        sorted(glob.glob(f"{rules_dir}/base/*.rule"))
    )

    for rule_file in rule_files:
        is_base = '/base/' in rule_file
        try:
            with open(rule_file) as f:
                r = json.load(f)
            name     = r.get('name', '')
            desc     = r.get('desc', '')
            group    = r.get('group', '')
            extends  = ','.join(r.get('extends', []))
            # Use resolved rule for counts
            resolved = _rule_resolve_internal(rules_dir, name)
            n_allows = len(resolved.get('allow_ips', [])) + \
                       len(resolved.get('allow_ports', []))
            n_blocks = len(resolved.get('block_ips', [])) + \
                       len(resolved.get('block_ports', []))
            peer_count = rule_peer_counts.get(name, 0)
            rule_files_data.sort(key=lambda x: (x['is_base'], x['group'] == '', x['group'], x['name']))
            print(f"{name}|{desc}|{n_allows}|{n_blocks}|{peer_count}|{extends}|{is_base}|{group}")
        except:
            pass