resklogits 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- resklogits/__init__.py +38 -0
- resklogits/cache.py +269 -0
- resklogits/cli.py +311 -0
- resklogits/config_parser.py +308 -0
- resklogits/data/banned_phrases.json +461 -0
- resklogits/pattern_automata.py +286 -0
- resklogits/py.typed +0 -0
- resklogits/rule_engine.py +311 -0
- resklogits/rule_templates.py +228 -0
- resklogits/shadow_ban_processor.py +213 -0
- resklogits/vectorized_aho_corasick.py +216 -0
- resklogits-0.1.0.dist-info/METADATA +590 -0
- resklogits-0.1.0.dist-info/RECORD +16 -0
- resklogits-0.1.0.dist-info/WHEEL +4 -0
- resklogits-0.1.0.dist-info/entry_points.txt +2 -0
- resklogits-0.1.0.dist-info/licenses/LICENSE +201 -0
resklogits/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ReskLogits - GPU-Accelerated Shadow Ban Logits Processor
|
|
3
|
+
|
|
4
|
+
Ultra-fast vectorized Aho-Corasick pattern matching for LLM safety filtering
|
|
5
|
+
with symbolic rule generation and intelligent caching.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .cache import CachedGenerator, RuleCache
|
|
9
|
+
from .config_parser import ConfigParser, load_rules_from_yaml
|
|
10
|
+
from .pattern_automata import PatternExpander, SynonymGraph
|
|
11
|
+
from .rule_engine import ContainsRule, ExactRule, Rule, RuleEngine, StartsWithRule
|
|
12
|
+
from .rule_templates import Template, TemplateEngine
|
|
13
|
+
from .shadow_ban_processor import MultiLevelShadowBanProcessor, ShadowBanProcessor
|
|
14
|
+
from .vectorized_aho_corasick import VectorizedAhoCorasick
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
__author__ = "RESK Team"
|
|
18
|
+
__all__ = [
|
|
19
|
+
# Core logits processors
|
|
20
|
+
"VectorizedAhoCorasick",
|
|
21
|
+
"ShadowBanProcessor",
|
|
22
|
+
"MultiLevelShadowBanProcessor",
|
|
23
|
+
# Rule generation
|
|
24
|
+
"ConfigParser",
|
|
25
|
+
"load_rules_from_yaml",
|
|
26
|
+
"TemplateEngine",
|
|
27
|
+
"Template",
|
|
28
|
+
"RuleEngine",
|
|
29
|
+
"Rule",
|
|
30
|
+
"ExactRule",
|
|
31
|
+
"StartsWithRule",
|
|
32
|
+
"ContainsRule",
|
|
33
|
+
"PatternExpander",
|
|
34
|
+
"SynonymGraph",
|
|
35
|
+
# Caching
|
|
36
|
+
"RuleCache",
|
|
37
|
+
"CachedGenerator",
|
|
38
|
+
]
|
resklogits/cache.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cache System for Rule Generation
|
|
3
|
+
|
|
4
|
+
Provides hash-based caching to avoid regenerating patterns.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional, cast
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RuleCache:
|
|
16
|
+
"""
|
|
17
|
+
Hash-based cache for generated patterns.
|
|
18
|
+
|
|
19
|
+
Cache structure:
|
|
20
|
+
.resklogits_cache/
|
|
21
|
+
├── index.json # Cache metadata index
|
|
22
|
+
├── <hash>_patterns.json # Generated patterns
|
|
23
|
+
└── <hash>_meta.json # Generation metadata
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, cache_dir: str = ".resklogits_cache"):
|
|
27
|
+
self.cache_dir = Path(cache_dir)
|
|
28
|
+
self.cache_dir.mkdir(exist_ok=True)
|
|
29
|
+
|
|
30
|
+
self.index_file = self.cache_dir / "index.json"
|
|
31
|
+
self.index = self._load_index()
|
|
32
|
+
|
|
33
|
+
def _load_index(self) -> Dict[str, Any]:
|
|
34
|
+
"""Load cache index from disk."""
|
|
35
|
+
if self.index_file.exists():
|
|
36
|
+
with open(self.index_file, "r", encoding="utf-8") as f:
|
|
37
|
+
return cast(Dict[str, Any], json.load(f))
|
|
38
|
+
return {"entries": {}, "version": "1.0"}
|
|
39
|
+
|
|
40
|
+
def _save_index(self):
|
|
41
|
+
"""Save cache index to disk."""
|
|
42
|
+
with open(self.index_file, "w", encoding="utf-8") as f:
|
|
43
|
+
json.dump(self.index, f, indent=2)
|
|
44
|
+
|
|
45
|
+
def compute_hash(self, content: str) -> str:
|
|
46
|
+
"""
|
|
47
|
+
Compute SHA256 hash of content.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
content: Content to hash (usually YAML rules)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Hex hash string
|
|
54
|
+
"""
|
|
55
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()[:16]
|
|
56
|
+
|
|
57
|
+
def exists(self, rule_hash: str) -> bool:
|
|
58
|
+
"""Check if cache entry exists for given hash."""
|
|
59
|
+
pattern_file = self.cache_dir / f"{rule_hash}_patterns.json"
|
|
60
|
+
return pattern_file.exists()
|
|
61
|
+
|
|
62
|
+
def load(self, rule_hash: str) -> List[str]:
|
|
63
|
+
"""
|
|
64
|
+
Load patterns from cache.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
rule_hash: Hash of rules
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
List of cached patterns
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
FileNotFoundError: If cache entry doesn't exist
|
|
74
|
+
"""
|
|
75
|
+
pattern_file = self.cache_dir / f"{rule_hash}_patterns.json"
|
|
76
|
+
|
|
77
|
+
if not pattern_file.exists():
|
|
78
|
+
raise FileNotFoundError(f"Cache entry not found: {rule_hash}")
|
|
79
|
+
|
|
80
|
+
with open(pattern_file, "r", encoding="utf-8") as f:
|
|
81
|
+
data = json.load(f)
|
|
82
|
+
|
|
83
|
+
# Update access time in index
|
|
84
|
+
if rule_hash in self.index["entries"]:
|
|
85
|
+
self.index["entries"][rule_hash]["last_accessed"] = time.time()
|
|
86
|
+
self._save_index()
|
|
87
|
+
|
|
88
|
+
return cast(List[str], data.get("patterns", []))
|
|
89
|
+
|
|
90
|
+
def save(self, rule_hash: str, patterns: List[str], metadata: Optional[Dict[str, Any]] = None):
|
|
91
|
+
"""
|
|
92
|
+
Save patterns to cache.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
rule_hash: Hash of rules
|
|
96
|
+
patterns: Generated patterns
|
|
97
|
+
metadata: Optional metadata about generation
|
|
98
|
+
"""
|
|
99
|
+
# Save patterns
|
|
100
|
+
pattern_file = self.cache_dir / f"{rule_hash}_patterns.json"
|
|
101
|
+
with open(pattern_file, "w", encoding="utf-8") as f:
|
|
102
|
+
json.dump(
|
|
103
|
+
{"patterns": patterns, "count": len(patterns), "created": time.time()}, f, indent=2
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Save metadata
|
|
107
|
+
if metadata:
|
|
108
|
+
meta_file = self.cache_dir / f"{rule_hash}_meta.json"
|
|
109
|
+
with open(meta_file, "w", encoding="utf-8") as f:
|
|
110
|
+
json.dump(metadata, f, indent=2)
|
|
111
|
+
|
|
112
|
+
# Update index
|
|
113
|
+
self.index["entries"][rule_hash] = {
|
|
114
|
+
"created": time.time(),
|
|
115
|
+
"last_accessed": time.time(),
|
|
116
|
+
"pattern_count": len(patterns),
|
|
117
|
+
"has_metadata": metadata is not None,
|
|
118
|
+
}
|
|
119
|
+
self._save_index()
|
|
120
|
+
|
|
121
|
+
def get_metadata(self, rule_hash: str) -> Optional[Dict[str, Any]]:
|
|
122
|
+
"""Load metadata for cached entry."""
|
|
123
|
+
meta_file = self.cache_dir / f"{rule_hash}_meta.json"
|
|
124
|
+
|
|
125
|
+
if not meta_file.exists():
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
with open(meta_file, "r", encoding="utf-8") as f:
|
|
129
|
+
return cast(Optional[Dict[str, Any]], json.load(f))
|
|
130
|
+
|
|
131
|
+
def list_entries(self) -> List[Dict[str, Any]]:
|
|
132
|
+
"""List all cache entries with metadata."""
|
|
133
|
+
entries = []
|
|
134
|
+
for hash_key, info in self.index["entries"].items():
|
|
135
|
+
entry = {
|
|
136
|
+
"hash": hash_key,
|
|
137
|
+
**info,
|
|
138
|
+
"created_str": datetime.fromtimestamp(info["created"]).isoformat(),
|
|
139
|
+
"last_accessed_str": datetime.fromtimestamp(info["last_accessed"]).isoformat(),
|
|
140
|
+
}
|
|
141
|
+
entries.append(entry)
|
|
142
|
+
|
|
143
|
+
# Sort by last accessed (most recent first)
|
|
144
|
+
entries.sort(key=lambda x: x["last_accessed"], reverse=True)
|
|
145
|
+
return entries
|
|
146
|
+
|
|
147
|
+
def clear(self, rule_hash: Optional[str] = None):
|
|
148
|
+
"""
|
|
149
|
+
Clear cache entries.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
rule_hash: Specific hash to clear (None = clear all)
|
|
153
|
+
"""
|
|
154
|
+
if rule_hash:
|
|
155
|
+
# Clear specific entry
|
|
156
|
+
pattern_file = self.cache_dir / f"{rule_hash}_patterns.json"
|
|
157
|
+
meta_file = self.cache_dir / f"{rule_hash}_meta.json"
|
|
158
|
+
|
|
159
|
+
if pattern_file.exists():
|
|
160
|
+
pattern_file.unlink()
|
|
161
|
+
if meta_file.exists():
|
|
162
|
+
meta_file.unlink()
|
|
163
|
+
|
|
164
|
+
# Remove from index
|
|
165
|
+
if rule_hash in self.index["entries"]:
|
|
166
|
+
del self.index["entries"][rule_hash]
|
|
167
|
+
self._save_index()
|
|
168
|
+
else:
|
|
169
|
+
# Clear all entries
|
|
170
|
+
for file in self.cache_dir.glob("*_patterns.json"):
|
|
171
|
+
file.unlink()
|
|
172
|
+
for file in self.cache_dir.glob("*_meta.json"):
|
|
173
|
+
file.unlink()
|
|
174
|
+
|
|
175
|
+
self.index["entries"] = {}
|
|
176
|
+
self._save_index()
|
|
177
|
+
|
|
178
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
179
|
+
"""Get cache statistics."""
|
|
180
|
+
total_entries = len(self.index["entries"])
|
|
181
|
+
total_patterns = sum(
|
|
182
|
+
entry.get("pattern_count", 0) for entry in self.index["entries"].values()
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Calculate total size
|
|
186
|
+
total_size = 0
|
|
187
|
+
for file in self.cache_dir.glob("*.json"):
|
|
188
|
+
total_size += file.stat().st_size
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
"total_entries": total_entries,
|
|
192
|
+
"total_patterns": total_patterns,
|
|
193
|
+
"cache_size_bytes": total_size,
|
|
194
|
+
"cache_size_mb": total_size / (1024 * 1024),
|
|
195
|
+
"cache_dir": str(self.cache_dir),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
def cleanup_old(self, max_age_days: int = 30):
|
|
199
|
+
"""
|
|
200
|
+
Remove cache entries older than specified age.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
max_age_days: Maximum age in days
|
|
204
|
+
"""
|
|
205
|
+
current_time = time.time()
|
|
206
|
+
max_age_seconds = max_age_days * 24 * 60 * 60
|
|
207
|
+
|
|
208
|
+
to_remove = []
|
|
209
|
+
for hash_key, info in self.index["entries"].items():
|
|
210
|
+
age = current_time - info["last_accessed"]
|
|
211
|
+
if age > max_age_seconds:
|
|
212
|
+
to_remove.append(hash_key)
|
|
213
|
+
|
|
214
|
+
for hash_key in to_remove:
|
|
215
|
+
self.clear(hash_key)
|
|
216
|
+
|
|
217
|
+
def __repr__(self):
|
|
218
|
+
stats = self.get_stats()
|
|
219
|
+
return f"RuleCache({stats['total_entries']} entries, {stats['total_patterns']} patterns)"
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class CachedGenerator:
|
|
223
|
+
"""Wrapper that adds caching to any generator function."""
|
|
224
|
+
|
|
225
|
+
def __init__(self, generator_func, cache: Optional[RuleCache] = None):
|
|
226
|
+
"""
|
|
227
|
+
Initialize cached generator.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
generator_func: Function that generates patterns
|
|
231
|
+
cache: RuleCache instance (creates default if None)
|
|
232
|
+
"""
|
|
233
|
+
self.generator_func = generator_func
|
|
234
|
+
self.cache = cache or RuleCache()
|
|
235
|
+
|
|
236
|
+
def generate(self, rules_content: str, force: bool = False, **kwargs) -> List[str]:
|
|
237
|
+
"""
|
|
238
|
+
Generate patterns with caching.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
rules_content: Rule content to hash
|
|
242
|
+
force: Force regeneration even if cached
|
|
243
|
+
**kwargs: Additional arguments for generator function
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Generated patterns
|
|
247
|
+
"""
|
|
248
|
+
# Compute hash
|
|
249
|
+
rule_hash = self.cache.compute_hash(rules_content)
|
|
250
|
+
|
|
251
|
+
# Check cache unless force regeneration
|
|
252
|
+
if not force and self.cache.exists(rule_hash):
|
|
253
|
+
return self.cache.load(rule_hash)
|
|
254
|
+
|
|
255
|
+
# Generate patterns
|
|
256
|
+
patterns = cast(List[str], self.generator_func(rules_content, **kwargs))
|
|
257
|
+
|
|
258
|
+
# Save to cache
|
|
259
|
+
metadata = {
|
|
260
|
+
"generator": self.generator_func.__name__,
|
|
261
|
+
"kwargs": kwargs,
|
|
262
|
+
"timestamp": time.time(),
|
|
263
|
+
}
|
|
264
|
+
self.cache.save(rule_hash, patterns, metadata)
|
|
265
|
+
|
|
266
|
+
return patterns
|
|
267
|
+
|
|
268
|
+
def __repr__(self):
|
|
269
|
+
return f"CachedGenerator({self.generator_func.__name__}, {self.cache})"
|
resklogits/cli.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI Tool for Rule Generation
|
|
3
|
+
|
|
4
|
+
Command-line interface for generating, testing, and managing rules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .cache import RuleCache
|
|
13
|
+
from .config_parser import ConfigParser, load_rules_from_yaml
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def cmd_generate(args):
|
|
17
|
+
"""Generate patterns from YAML rules."""
|
|
18
|
+
print(f"Generating patterns from: {args.rules}")
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
parser = ConfigParser()
|
|
22
|
+
results = parser.generate_all_patterns(
|
|
23
|
+
args.rules, use_synonyms=args.synonyms, use_cache=not args.no_cache, force=args.force
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Flatten patterns
|
|
27
|
+
all_patterns = []
|
|
28
|
+
for _rule_name, patterns in results.items():
|
|
29
|
+
all_patterns.extend(patterns)
|
|
30
|
+
|
|
31
|
+
unique_patterns = list(set(all_patterns))
|
|
32
|
+
|
|
33
|
+
print(f"✓ Generated {len(unique_patterns)} unique patterns from {len(results)} rules")
|
|
34
|
+
|
|
35
|
+
if args.output:
|
|
36
|
+
# Save to JSON
|
|
37
|
+
output_path = Path(args.output)
|
|
38
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
39
|
+
if args.by_category:
|
|
40
|
+
json.dump(results, f, indent=2)
|
|
41
|
+
else:
|
|
42
|
+
json.dump(unique_patterns, f, indent=2)
|
|
43
|
+
print(f"✓ Saved to: {args.output}")
|
|
44
|
+
else:
|
|
45
|
+
# Print to stdout
|
|
46
|
+
if args.by_category:
|
|
47
|
+
print(json.dumps(results, indent=2))
|
|
48
|
+
else:
|
|
49
|
+
for pattern in sorted(unique_patterns)[: args.preview]:
|
|
50
|
+
print(f" - {pattern}")
|
|
51
|
+
if len(unique_patterns) > args.preview:
|
|
52
|
+
print(f" ... and {len(unique_patterns) - args.preview} more")
|
|
53
|
+
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"✗ Error: {e}", file=sys.stderr)
|
|
58
|
+
if args.verbose:
|
|
59
|
+
import traceback
|
|
60
|
+
|
|
61
|
+
traceback.print_exc()
|
|
62
|
+
return 1
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def cmd_test(args):
|
|
66
|
+
"""Test rule matching against text."""
|
|
67
|
+
print(f"Testing rules from: {args.rules}")
|
|
68
|
+
print(f'Test text: "{args.text}"')
|
|
69
|
+
print()
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
patterns = load_rules_from_yaml(args.rules, use_cache=not args.no_cache)
|
|
73
|
+
|
|
74
|
+
matches = [p for p in patterns if p in args.text.lower()]
|
|
75
|
+
|
|
76
|
+
if matches:
|
|
77
|
+
print(f"✓ Found {len(matches)} matches:")
|
|
78
|
+
for match in matches:
|
|
79
|
+
print(f" - {match}")
|
|
80
|
+
else:
|
|
81
|
+
print("✗ No matches found")
|
|
82
|
+
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print(f"✗ Error: {e}", file=sys.stderr)
|
|
87
|
+
return 1
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def cmd_expand(args):
|
|
91
|
+
"""Expand and preview patterns for a specific rule."""
|
|
92
|
+
print(f"Expanding rule: {args.rule}")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
parser = ConfigParser()
|
|
96
|
+
all_rules = parser.parse_file(args.rules)
|
|
97
|
+
|
|
98
|
+
if args.rule not in all_rules:
|
|
99
|
+
print(f"✗ Rule '{args.rule}' not found", file=sys.stderr)
|
|
100
|
+
print(f"Available rules: {', '.join(all_rules.keys())}")
|
|
101
|
+
return 1
|
|
102
|
+
|
|
103
|
+
rule_config = all_rules[args.rule]
|
|
104
|
+
patterns = parser.generate_patterns(rule_config, use_synonyms=not args.no_synonyms)
|
|
105
|
+
|
|
106
|
+
print(f"✓ Generated {len(patterns)} patterns:")
|
|
107
|
+
for pattern in sorted(patterns)[: args.preview]:
|
|
108
|
+
print(f" - {pattern}")
|
|
109
|
+
if len(patterns) > args.preview:
|
|
110
|
+
print(f" ... and {len(patterns) - args.preview} more")
|
|
111
|
+
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(f"✗ Error: {e}", file=sys.stderr)
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def cmd_cache_status(args):
|
|
120
|
+
"""Show cache status."""
|
|
121
|
+
cache = RuleCache(args.cache_dir)
|
|
122
|
+
|
|
123
|
+
stats = cache.get_stats()
|
|
124
|
+
print("Cache Status:")
|
|
125
|
+
print(f" Directory: {stats['cache_dir']}")
|
|
126
|
+
print(f" Entries: {stats['total_entries']}")
|
|
127
|
+
print(f" Total patterns: {stats['total_patterns']}")
|
|
128
|
+
print(f" Size: {stats['cache_size_mb']:.2f} MB")
|
|
129
|
+
print()
|
|
130
|
+
|
|
131
|
+
if args.verbose and stats["total_entries"] > 0:
|
|
132
|
+
print("Entries:")
|
|
133
|
+
for entry in cache.list_entries()[:10]:
|
|
134
|
+
print(
|
|
135
|
+
f" {entry['hash']}: {entry['pattern_count']} patterns (accessed: {entry['last_accessed_str']})"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def cmd_cache_clear(args):
|
|
142
|
+
"""Clear cache."""
|
|
143
|
+
cache = RuleCache(args.cache_dir)
|
|
144
|
+
|
|
145
|
+
if args.hash:
|
|
146
|
+
cache.clear(args.hash)
|
|
147
|
+
print(f"✓ Cleared cache entry: {args.hash}")
|
|
148
|
+
else:
|
|
149
|
+
stats = cache.get_stats()
|
|
150
|
+
if (
|
|
151
|
+
args.yes
|
|
152
|
+
or input(f"Clear all {stats['total_entries']} cache entries? (y/N): ").lower() == "y"
|
|
153
|
+
):
|
|
154
|
+
cache.clear()
|
|
155
|
+
print("✓ Cache cleared")
|
|
156
|
+
else:
|
|
157
|
+
print("Cancelled")
|
|
158
|
+
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def cmd_cache_show(args):
|
|
163
|
+
"""Show cache entry details."""
|
|
164
|
+
cache = RuleCache(args.cache_dir)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
patterns = cache.load(args.hash)
|
|
168
|
+
metadata = cache.get_metadata(args.hash)
|
|
169
|
+
|
|
170
|
+
print(f"Cache Entry: {args.hash}")
|
|
171
|
+
print(f" Patterns: {len(patterns)}")
|
|
172
|
+
|
|
173
|
+
if metadata:
|
|
174
|
+
print(" Metadata:")
|
|
175
|
+
for key, value in metadata.items():
|
|
176
|
+
print(f" {key}: {value}")
|
|
177
|
+
|
|
178
|
+
if args.show_patterns:
|
|
179
|
+
print("\n Patterns:")
|
|
180
|
+
for pattern in patterns[: args.limit]:
|
|
181
|
+
print(f" - {pattern}")
|
|
182
|
+
if len(patterns) > args.limit:
|
|
183
|
+
print(f" ... and {len(patterns) - args.limit} more")
|
|
184
|
+
|
|
185
|
+
return 0
|
|
186
|
+
|
|
187
|
+
except FileNotFoundError:
|
|
188
|
+
print(f"✗ Cache entry not found: {args.hash}", file=sys.stderr)
|
|
189
|
+
return 1
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def cmd_validate(args):
|
|
193
|
+
"""Validate YAML rules file."""
|
|
194
|
+
print(f"Validating: {args.rules}")
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
parser = ConfigParser()
|
|
198
|
+
rules = parser.parse_file(args.rules)
|
|
199
|
+
|
|
200
|
+
print("✓ Valid YAML configuration")
|
|
201
|
+
print(f" Rules: {len(rules)}")
|
|
202
|
+
|
|
203
|
+
for name, config in rules.items():
|
|
204
|
+
print(f" - {name}: severity={config.severity}, penalty={config.penalty}")
|
|
205
|
+
|
|
206
|
+
return 0
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
print(f"✗ Validation failed: {e}", file=sys.stderr)
|
|
210
|
+
return 1
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def main():
|
|
214
|
+
"""Main CLI entry point."""
|
|
215
|
+
parser = argparse.ArgumentParser(
|
|
216
|
+
description="ReskLogits Rule Generator CLI",
|
|
217
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
|
|
221
|
+
|
|
222
|
+
# Generate command
|
|
223
|
+
gen_parser = subparsers.add_parser("generate", help="Generate patterns from rules")
|
|
224
|
+
gen_parser.add_argument("rules", help="Path to YAML rules file")
|
|
225
|
+
gen_parser.add_argument("-o", "--output", help="Output file (JSON)")
|
|
226
|
+
gen_parser.add_argument("--no-synonyms", action="store_true", help="Disable synonym expansion")
|
|
227
|
+
gen_parser.add_argument("--no-cache", action="store_true", help="Disable caching")
|
|
228
|
+
gen_parser.add_argument("--force", action="store_true", help="Force regeneration")
|
|
229
|
+
gen_parser.add_argument(
|
|
230
|
+
"--by-category", action="store_true", help="Group output by rule category"
|
|
231
|
+
)
|
|
232
|
+
gen_parser.add_argument("--preview", type=int, default=20, help="Number of patterns to preview")
|
|
233
|
+
gen_parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
|
|
234
|
+
|
|
235
|
+
# Test command
|
|
236
|
+
test_parser = subparsers.add_parser("test", help="Test rule matching")
|
|
237
|
+
test_parser.add_argument("rules", help="Path to YAML rules file")
|
|
238
|
+
test_parser.add_argument("--text", required=True, help="Text to test against")
|
|
239
|
+
test_parser.add_argument("--no-cache", action="store_true", help="Disable caching")
|
|
240
|
+
|
|
241
|
+
# Expand command
|
|
242
|
+
expand_parser = subparsers.add_parser("expand", help="Expand and preview a specific rule")
|
|
243
|
+
expand_parser.add_argument("rules", help="Path to YAML rules file")
|
|
244
|
+
expand_parser.add_argument("--rule", required=True, help="Rule name to expand")
|
|
245
|
+
expand_parser.add_argument(
|
|
246
|
+
"--no-synonyms", action="store_true", help="Disable synonym expansion"
|
|
247
|
+
)
|
|
248
|
+
expand_parser.add_argument("--preview", type=int, default=50, help="Number of patterns to show")
|
|
249
|
+
|
|
250
|
+
# Cache subcommands
|
|
251
|
+
cache_parser = subparsers.add_parser("cache", help="Cache management")
|
|
252
|
+
cache_subparsers = cache_parser.add_subparsers(dest="cache_command")
|
|
253
|
+
|
|
254
|
+
cache_status_parser = cache_subparsers.add_parser("status", help="Show cache status")
|
|
255
|
+
cache_status_parser.add_argument(
|
|
256
|
+
"--cache-dir", default=".resklogits_cache", help="Cache directory"
|
|
257
|
+
)
|
|
258
|
+
cache_status_parser.add_argument(
|
|
259
|
+
"-v", "--verbose", action="store_true", help="Show detailed info"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
cache_clear_parser = cache_subparsers.add_parser("clear", help="Clear cache")
|
|
263
|
+
cache_clear_parser.add_argument(
|
|
264
|
+
"--cache-dir", default=".resklogits_cache", help="Cache directory"
|
|
265
|
+
)
|
|
266
|
+
cache_clear_parser.add_argument("--hash", help="Clear specific entry")
|
|
267
|
+
cache_clear_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
|
|
268
|
+
|
|
269
|
+
cache_show_parser = cache_subparsers.add_parser("show", help="Show cache entry")
|
|
270
|
+
cache_show_parser.add_argument("hash", help="Cache entry hash")
|
|
271
|
+
cache_show_parser.add_argument(
|
|
272
|
+
"--cache-dir", default=".resklogits_cache", help="Cache directory"
|
|
273
|
+
)
|
|
274
|
+
cache_show_parser.add_argument("--show-patterns", action="store_true", help="Show patterns")
|
|
275
|
+
cache_show_parser.add_argument("--limit", type=int, default=20, help="Pattern limit")
|
|
276
|
+
|
|
277
|
+
# Validate command
|
|
278
|
+
validate_parser = subparsers.add_parser("validate", help="Validate YAML rules file")
|
|
279
|
+
validate_parser.add_argument("rules", help="Path to YAML rules file")
|
|
280
|
+
|
|
281
|
+
args = parser.parse_args()
|
|
282
|
+
|
|
283
|
+
if not args.command:
|
|
284
|
+
parser.print_help()
|
|
285
|
+
return 1
|
|
286
|
+
|
|
287
|
+
# Execute command
|
|
288
|
+
if args.command == "generate":
|
|
289
|
+
return cmd_generate(args)
|
|
290
|
+
elif args.command == "test":
|
|
291
|
+
return cmd_test(args)
|
|
292
|
+
elif args.command == "expand":
|
|
293
|
+
return cmd_expand(args)
|
|
294
|
+
elif args.command == "cache":
|
|
295
|
+
if args.cache_command == "status":
|
|
296
|
+
return cmd_cache_status(args)
|
|
297
|
+
elif args.cache_command == "clear":
|
|
298
|
+
return cmd_cache_clear(args)
|
|
299
|
+
elif args.cache_command == "show":
|
|
300
|
+
return cmd_cache_show(args)
|
|
301
|
+
else:
|
|
302
|
+
cache_parser.print_help()
|
|
303
|
+
return 1
|
|
304
|
+
elif args.command == "validate":
|
|
305
|
+
return cmd_validate(args)
|
|
306
|
+
|
|
307
|
+
return 1
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
if __name__ == "__main__":
|
|
311
|
+
sys.exit(main())
|