skillpool 4.3.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.
- skillpool/__init__.py +74 -0
- skillpool/__main__.py +6 -0
- skillpool/adapters/__init__.py +8 -0
- skillpool/adapters/base.py +41 -0
- skillpool/adapters/claude_adapter.py +36 -0
- skillpool/adapters/codex_adapter.py +92 -0
- skillpool/adapters/hermes_adapter.py +38 -0
- skillpool/audit/__init__.py +651 -0
- skillpool/bridge/__init__.py +16 -0
- skillpool/bridge/freeze_detector.py +134 -0
- skillpool/bridge/maintenance.py +119 -0
- skillpool/bridge/wal_manager.py +136 -0
- skillpool/clawmem_client.py +176 -0
- skillpool/cli.py +700 -0
- skillpool/combiner/__init__.py +31 -0
- skillpool/combiner/lifecycle.py +453 -0
- skillpool/combiner/models.py +99 -0
- skillpool/config.py +34 -0
- skillpool/cost/__init__.py +111 -0
- skillpool/cost/audit_hash.py +51 -0
- skillpool/cost/budget_tracker.py +66 -0
- skillpool/cost/dashboard.py +189 -0
- skillpool/cost/models.py +129 -0
- skillpool/cost/token_governor.py +264 -0
- skillpool/cost/trace_ceiling.py +38 -0
- skillpool/csdf.py +126 -0
- skillpool/evolver/__init__.py +978 -0
- skillpool/gain/__init__.py +285 -0
- skillpool/gate.py +282 -0
- skillpool/gate_policy/__init__.py +31 -0
- skillpool/gate_policy/incremental.py +157 -0
- skillpool/gate_policy/parser.py +258 -0
- skillpool/gate_policy/state_machine.py +432 -0
- skillpool/graph/__init__.py +14 -0
- skillpool/graph/ppr.py +279 -0
- skillpool/health/__init__.py +73 -0
- skillpool/health/check.py +85 -0
- skillpool/health/degradation.py +90 -0
- skillpool/health/models.py +43 -0
- skillpool/hooks/__init__.py +4 -0
- skillpool/hooks/security_scanner.py +288 -0
- skillpool/lifecycle.py +150 -0
- skillpool/materializer/__init__.py +124 -0
- skillpool/materializer/budget_cropper.py +178 -0
- skillpool/materializer/csdf_loader.py +114 -0
- skillpool/materializer/lazy_loader.py +265 -0
- skillpool/materializer/lifecycle_filter.py +93 -0
- skillpool/materializer/mapper.py +178 -0
- skillpool/materializer/models.py +66 -0
- skillpool/mcp_server.py +2005 -0
- skillpool/monitor/__init__.py +576 -0
- skillpool/monitor/bug_collector.py +392 -0
- skillpool/monitor/defect_classifier.py +218 -0
- skillpool/monitor/self_healing.py +530 -0
- skillpool/monitor/telemetry_bridge.py +197 -0
- skillpool/paradigm/__init__.py +312 -0
- skillpool/paradigm/override.py +285 -0
- skillpool/profile.py +94 -0
- skillpool/quality.py +254 -0
- skillpool/registry/__init__.py +509 -0
- skillpool/registry/models.py +98 -0
- skillpool/resolver/__init__.py +320 -0
- skillpool/resolver/cache.py +103 -0
- skillpool/resolver/circuit_breaker.py +103 -0
- skillpool/resolver/conflict_detector.py +111 -0
- skillpool/resolver/health_filter.py +38 -0
- skillpool/resolver/models.py +154 -0
- skillpool/resolver/rate_limiter.py +48 -0
- skillpool/resolver/skill_graph.py +183 -0
- skillpool/review/__init__.py +242 -0
- skillpool/review/async_queue.py +96 -0
- skillpool/review/checkpoint_runner.py +345 -0
- skillpool/review/models.py +164 -0
- skillpool/review/suspect_marker.py +39 -0
- skillpool/review/veto_evaluator.py +94 -0
- skillpool/router/__init__.py +481 -0
- skillpool/schemas.py +119 -0
- skillpool/synergy/__init__.py +240 -0
- skillpool/synergy/detector.py +5 -0
- skillpool/telemetry.py +126 -0
- skillpool/utils/__init__.py +21 -0
- skillpool/utils/changelog.py +218 -0
- skillpool/utils/logger.py +273 -0
- skillpool/utils/runtime_audit.py +163 -0
- skillpool/utils/time_utils.py +13 -0
- skillpool-4.3.0.dist-info/METADATA +21 -0
- skillpool-4.3.0.dist-info/RECORD +90 -0
- skillpool-4.3.0.dist-info/WHEEL +5 -0
- skillpool-4.3.0.dist-info/entry_points.txt +3 -0
- skillpool-4.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""SkillPoolLogger — structlog-style structured logging without external dependencies.
|
|
2
|
+
|
|
3
|
+
Processor chain: add_timestamp -> add_trace_id -> add_skill_context -> format_json
|
|
4
|
+
Context binding: bind_contextvars(skill_id=..., checkpoint=..., gate_result=...)
|
|
5
|
+
Canonical log lines: single JSON line with all context
|
|
6
|
+
Two renderers: JSONRenderer (prod) and ConsoleRenderer (dev, colored)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ConsoleRenderer",
|
|
13
|
+
"ContextVarsBinding",
|
|
14
|
+
"JSONRenderer",
|
|
15
|
+
"SkillPoolLogger",
|
|
16
|
+
"get_skillpool_logger",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
from contextvars import ContextVar
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from skillpool.utils.time_utils import utc_now
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ── Context Variables ──
|
|
28
|
+
|
|
29
|
+
_context_vars: dict[str, ContextVar[Any]] = {}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get_context_var(key: str) -> ContextVar[Any]:
|
|
33
|
+
"""Get or create a ContextVar for the given key."""
|
|
34
|
+
if key not in _context_vars:
|
|
35
|
+
_context_vars[key] = ContextVar(f"skillpool_log_{key}", default=None)
|
|
36
|
+
return _context_vars[key]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ContextVarsBinding:
|
|
40
|
+
"""Thread-safe + asyncio-safe context variable binding.
|
|
41
|
+
|
|
42
|
+
Uses Python's contextvars module for automatic propagation across
|
|
43
|
+
asyncio tasks and thread pools.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def bind(**kwargs: Any) -> None:
|
|
48
|
+
"""Bind context variables. These will be included in all subsequent log entries."""
|
|
49
|
+
for key, value in kwargs.items():
|
|
50
|
+
var = _get_context_var(key)
|
|
51
|
+
var.set(value)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def unbind(*keys: str) -> None:
|
|
55
|
+
"""Unbind context variables by resetting them to None."""
|
|
56
|
+
for key in keys:
|
|
57
|
+
var = _get_context_var(key)
|
|
58
|
+
var.set(None)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def get() -> dict[str, Any]:
|
|
62
|
+
"""Get all currently bound context variables as a dict."""
|
|
63
|
+
result: dict[str, Any] = {}
|
|
64
|
+
for key, var in _context_vars.items():
|
|
65
|
+
value = var.get(None)
|
|
66
|
+
if value is not None:
|
|
67
|
+
result[key] = value
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ── Processors ──
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def add_timestamp(logger: str, method: str, event_dict: dict[str, Any]) -> dict[str, Any]:
|
|
75
|
+
"""Add ISO 8601 UTC timestamp to the event dict."""
|
|
76
|
+
if "timestamp" not in event_dict:
|
|
77
|
+
event_dict["timestamp"] = utc_now().isoformat()
|
|
78
|
+
return event_dict
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def add_trace_id(logger: str, method: str, event_dict: dict[str, Any]) -> dict[str, Any]:
|
|
82
|
+
"""Add trace_id from context vars if not already present."""
|
|
83
|
+
if "trace_id" not in event_dict:
|
|
84
|
+
ctx = ContextVarsBinding.get()
|
|
85
|
+
if "trace_id" in ctx:
|
|
86
|
+
event_dict["trace_id"] = ctx["trace_id"]
|
|
87
|
+
return event_dict
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def add_skill_context(logger: str, method: str, event_dict: dict[str, Any]) -> dict[str, Any]:
|
|
91
|
+
"""Add skill_id, checkpoint, gate_result from context vars."""
|
|
92
|
+
ctx = ContextVarsBinding.get()
|
|
93
|
+
for key in ("skill_id", "checkpoint", "gate_result"):
|
|
94
|
+
if key not in event_dict and key in ctx:
|
|
95
|
+
event_dict[key] = ctx[key]
|
|
96
|
+
return event_dict
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def format_json(logger: str, method: str, event_dict: dict[str, Any]) -> dict[str, Any]:
|
|
100
|
+
"""Ensure event_dict is JSON-serializable. No-op processor for consistency."""
|
|
101
|
+
return event_dict
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ── Renderers ──
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class JSONRenderer:
|
|
108
|
+
"""Render log events as single JSON lines (production)."""
|
|
109
|
+
|
|
110
|
+
def __call__(self, logger: str, method: str, event_dict: dict[str, Any]) -> str:
|
|
111
|
+
"""Render event dict to a JSON string."""
|
|
112
|
+
return json.dumps(event_dict, sort_keys=True, ensure_ascii=False, default=str)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ConsoleRenderer:
|
|
116
|
+
"""Render log events as colored console output (development).
|
|
117
|
+
|
|
118
|
+
Color codes:
|
|
119
|
+
- DEBUG: grey
|
|
120
|
+
- INFO: green
|
|
121
|
+
- WARNING: yellow
|
|
122
|
+
- ERROR: red
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
_COLORS: dict[str, str] = {
|
|
126
|
+
"debug": "\033[90m", # grey
|
|
127
|
+
"info": "\033[32m", # green
|
|
128
|
+
"warning": "\033[33m", # yellow
|
|
129
|
+
"error": "\033[31m", # red
|
|
130
|
+
}
|
|
131
|
+
_RESET = "\033[0m"
|
|
132
|
+
|
|
133
|
+
def __init__(self, stream: Any | None = None) -> None:
|
|
134
|
+
self._stream = stream or sys.stderr
|
|
135
|
+
self._is_tty = hasattr(self._stream, "isatty") and self._stream.isatty()
|
|
136
|
+
|
|
137
|
+
def __call__(self, logger: str, method: str, event_dict: dict[str, Any]) -> str:
|
|
138
|
+
"""Render event dict to a colored console line."""
|
|
139
|
+
level = method.upper()
|
|
140
|
+
timestamp = event_dict.pop("timestamp", "")
|
|
141
|
+
message = event_dict.pop("event", "")
|
|
142
|
+
|
|
143
|
+
# Build context suffix from remaining fields
|
|
144
|
+
context_parts = []
|
|
145
|
+
for k, v in sorted(event_dict.items()):
|
|
146
|
+
context_parts.append(f"{k}={v}")
|
|
147
|
+
context_str = " ".join(context_parts)
|
|
148
|
+
|
|
149
|
+
if self._is_tty:
|
|
150
|
+
color = self._COLORS.get(method, "")
|
|
151
|
+
line = f"{color}{level:<8}{self._RESET} {timestamp} {message}"
|
|
152
|
+
if context_str:
|
|
153
|
+
line += f" {context_str}"
|
|
154
|
+
else:
|
|
155
|
+
line = f"{level:<8} {timestamp} {message}"
|
|
156
|
+
if context_str:
|
|
157
|
+
line += f" {context_str}"
|
|
158
|
+
|
|
159
|
+
# Restore popped keys so the dict is not mutated for other processors
|
|
160
|
+
if timestamp:
|
|
161
|
+
event_dict["timestamp"] = timestamp
|
|
162
|
+
event_dict["event"] = message
|
|
163
|
+
|
|
164
|
+
return line
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ── Default processor chain ──
|
|
168
|
+
|
|
169
|
+
DEFAULT_PROCESSORS = [
|
|
170
|
+
add_timestamp,
|
|
171
|
+
add_trace_id,
|
|
172
|
+
add_skill_context,
|
|
173
|
+
format_json,
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
DEFAULT_RENDERER = JSONRenderer()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_skillpool_logger(name: str) -> SkillPoolLogger:
|
|
180
|
+
"""Factory: return a SkillPoolLogger with the appropriate renderer.
|
|
181
|
+
|
|
182
|
+
In prod (SKILLPOOL_LOG_LEVEL=PROD or when not a TTY), uses JSONRenderer.
|
|
183
|
+
Otherwise uses ConsoleRenderer for readable dev output.
|
|
184
|
+
"""
|
|
185
|
+
import os as _os
|
|
186
|
+
|
|
187
|
+
_log_level = _os.environ.get("SKILLPOOL_LOG_LEVEL", "INFO").upper()
|
|
188
|
+
renderer: Any = ConsoleRenderer() if sys.stderr.isatty() else JSONRenderer()
|
|
189
|
+
return SkillPoolLogger(name=name, renderer=renderer)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ── Logger ──
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class SkillPoolLogger:
|
|
196
|
+
"""structlog-style structured logger.
|
|
197
|
+
|
|
198
|
+
Processor chain: add_timestamp -> add_trace_id -> add_skill_context -> format_json
|
|
199
|
+
Each log entry goes through the processor chain, then gets rendered.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
name: Logger name (usually module path).
|
|
203
|
+
processors: List of processor callables. Each takes (logger, method, event_dict) -> event_dict.
|
|
204
|
+
renderer: Final renderer callable. Takes (logger, method, event_dict) -> str.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def __init__(
|
|
208
|
+
self,
|
|
209
|
+
name: str,
|
|
210
|
+
processors: list | None = None,
|
|
211
|
+
renderer: Any | None = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
self._name = name
|
|
214
|
+
self._processors = processors or list(DEFAULT_PROCESSORS)
|
|
215
|
+
self._renderer = renderer or DEFAULT_RENDERER
|
|
216
|
+
self._bound: dict[str, Any] = {}
|
|
217
|
+
|
|
218
|
+
def bind(self, **kwargs: Any) -> SkillPoolLogger:
|
|
219
|
+
"""Return a new logger with additional bound context.
|
|
220
|
+
|
|
221
|
+
The original logger is not modified.
|
|
222
|
+
"""
|
|
223
|
+
new_logger = SkillPoolLogger(
|
|
224
|
+
name=self._name,
|
|
225
|
+
processors=self._processors,
|
|
226
|
+
renderer=self._renderer,
|
|
227
|
+
)
|
|
228
|
+
new_logger._bound = {**self._bound, **kwargs}
|
|
229
|
+
return new_logger
|
|
230
|
+
|
|
231
|
+
def _log(self, level: str, message: str, **kwargs: Any) -> None:
|
|
232
|
+
"""Core logging method: build event dict, run processors, render, emit."""
|
|
233
|
+
event_dict: dict[str, Any] = {
|
|
234
|
+
"event": message,
|
|
235
|
+
"logger": self._name,
|
|
236
|
+
"level": level,
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# Merge bound context
|
|
240
|
+
event_dict.update(self._bound)
|
|
241
|
+
|
|
242
|
+
# Merge call-site kwargs (override bound)
|
|
243
|
+
event_dict.update(kwargs)
|
|
244
|
+
|
|
245
|
+
# Run processor chain
|
|
246
|
+
for processor in self._processors:
|
|
247
|
+
event_dict = processor(self._name, level, event_dict)
|
|
248
|
+
|
|
249
|
+
# Render
|
|
250
|
+
output = self._renderer(self._name, level, event_dict)
|
|
251
|
+
|
|
252
|
+
# Emit to stderr
|
|
253
|
+
try:
|
|
254
|
+
sys.stderr.write(output + "\n")
|
|
255
|
+
sys.stderr.flush()
|
|
256
|
+
except (ValueError, OSError):
|
|
257
|
+
pass # stderr closed or broken — don't crash
|
|
258
|
+
|
|
259
|
+
def debug(self, message: str, **kwargs: Any) -> None:
|
|
260
|
+
"""Log at DEBUG level."""
|
|
261
|
+
self._log("debug", message, **kwargs)
|
|
262
|
+
|
|
263
|
+
def info(self, message: str, **kwargs: Any) -> None:
|
|
264
|
+
"""Log at INFO level."""
|
|
265
|
+
self._log("info", message, **kwargs)
|
|
266
|
+
|
|
267
|
+
def warning(self, message: str, **kwargs: Any) -> None:
|
|
268
|
+
"""Log at WARNING level."""
|
|
269
|
+
self._log("warning", message, **kwargs)
|
|
270
|
+
|
|
271
|
+
def error(self, message: str, **kwargs: Any) -> None:
|
|
272
|
+
"""Log at ERROR level."""
|
|
273
|
+
self._log("error", message, **kwargs)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Runtime Audit Hook — Security monitoring via sys.addaudithook (PEP 578).
|
|
2
|
+
|
|
3
|
+
Tracks security-sensitive operations: exec, compile, open, subprocess.Popen,
|
|
4
|
+
socket.connect. Cannot be removed once registered (by design).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"RuntimeAuditHook",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Callable
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from skillpool.config import get_data_dir
|
|
22
|
+
|
|
23
|
+
_DEFAULT_LOG_DIR = get_data_dir() / "logs"
|
|
24
|
+
_DEFAULT_LOG_FILE = _DEFAULT_LOG_DIR / "runtime_audit.jsonl"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RuntimeAuditHook:
|
|
28
|
+
"""Monitor security-sensitive operations via sys.addaudithook.
|
|
29
|
+
|
|
30
|
+
Tracks: exec, compile, open, subprocess.Popen, socket.connect.
|
|
31
|
+
Cannot be removed once registered (by design — PEP 578 constraint).
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
hook = RuntimeAuditHook()
|
|
35
|
+
hook.install()
|
|
36
|
+
# ... code runs, events are logged ...
|
|
37
|
+
events = hook.get_events()
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
MONITORED_EVENTS: frozenset[str] = frozenset(
|
|
41
|
+
{
|
|
42
|
+
"exec",
|
|
43
|
+
"compile",
|
|
44
|
+
"open",
|
|
45
|
+
"subprocess.Popen",
|
|
46
|
+
"socket.connect",
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
callback: Callable[[str, tuple[Any, ...]], None] | None = None,
|
|
53
|
+
log_file: Path | None = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Initialize the audit hook.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
callback: Optional custom callback receiving (event_name, args).
|
|
59
|
+
If None, events are logged to the default JSONL file.
|
|
60
|
+
log_file: Override the default log file path.
|
|
61
|
+
"""
|
|
62
|
+
self._callback = callback
|
|
63
|
+
self._log_file = log_file or _DEFAULT_LOG_FILE
|
|
64
|
+
self._installed = False
|
|
65
|
+
self._events: list[dict[str, Any]] = []
|
|
66
|
+
self._in_handler = False # Reentrancy guard
|
|
67
|
+
|
|
68
|
+
def install(self, force: bool = False) -> None:
|
|
69
|
+
"""Register the audit hook via sys.addaudithook.
|
|
70
|
+
|
|
71
|
+
Safe to call multiple times — subsequent calls are no-ops.
|
|
72
|
+
The hook cannot be removed once registered (PEP 578 design).
|
|
73
|
+
|
|
74
|
+
By default, only installs in production environments
|
|
75
|
+
(SKILLPOOL_ENV=prod). In dev/test, the I/O overhead of
|
|
76
|
+
monitoring every open/exec/subprocess event is prohibitive.
|
|
77
|
+
Use force=True to override this check.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
force: Install regardless of environment (for tests).
|
|
81
|
+
"""
|
|
82
|
+
if self._installed:
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
# Skip in non-production environments unless forced
|
|
86
|
+
env = os.environ.get("SKILLPOOL_ENV", "dev")
|
|
87
|
+
if env != "prod" and not force:
|
|
88
|
+
self._installed = True # Mark as installed to prevent retries
|
|
89
|
+
return
|
|
90
|
+
if self._installed:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
def _audit_handler(event_name: str, args: tuple[Any, ...]) -> None:
|
|
94
|
+
if event_name not in self.MONITORED_EVENTS:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
# Reentrancy guard: prevent infinite recursion when
|
|
98
|
+
# _log_to_file triggers its own 'open' audit event
|
|
99
|
+
if self._in_handler:
|
|
100
|
+
return
|
|
101
|
+
self._in_handler = True
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
entry = {
|
|
105
|
+
"event": event_name,
|
|
106
|
+
"args": _serialize_args(args),
|
|
107
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
self._events.append(entry)
|
|
111
|
+
|
|
112
|
+
if self._callback:
|
|
113
|
+
self._callback(event_name, args)
|
|
114
|
+
else:
|
|
115
|
+
self._log_to_file(entry)
|
|
116
|
+
finally:
|
|
117
|
+
self._in_handler = False
|
|
118
|
+
|
|
119
|
+
sys.addaudithook(_audit_handler)
|
|
120
|
+
self._installed = True
|
|
121
|
+
|
|
122
|
+
def get_events(self) -> list[dict[str, Any]]:
|
|
123
|
+
"""Retrieve all logged events since hook installation.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of event dicts with keys: event, args, timestamp.
|
|
127
|
+
"""
|
|
128
|
+
return list(self._events)
|
|
129
|
+
|
|
130
|
+
def is_installed(self) -> bool:
|
|
131
|
+
"""Check if the audit hook has been registered."""
|
|
132
|
+
return self._installed
|
|
133
|
+
|
|
134
|
+
def _log_to_file(self, entry: dict[str, Any]) -> None:
|
|
135
|
+
"""Append an event entry to the JSONL log file."""
|
|
136
|
+
try:
|
|
137
|
+
self._log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
with open(self._log_file, "a", encoding="utf-8") as f:
|
|
139
|
+
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
140
|
+
except OSError:
|
|
141
|
+
# Silently fail — audit hook must not raise (PEP 578 constraint)
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _serialize_args(args: tuple[Any, ...]) -> list[Any]:
|
|
146
|
+
"""Convert audit hook args to JSON-serializable form.
|
|
147
|
+
|
|
148
|
+
Args may contain arbitrary objects; we convert what we can and
|
|
149
|
+
stringify the rest.
|
|
150
|
+
"""
|
|
151
|
+
result: list[Any] = []
|
|
152
|
+
for arg in args:
|
|
153
|
+
if isinstance(arg, (str, int, float, bool, type(None))):
|
|
154
|
+
result.append(arg)
|
|
155
|
+
elif isinstance(arg, bytes):
|
|
156
|
+
result.append(arg.decode("utf-8", errors="replace"))
|
|
157
|
+
elif isinstance(arg, (list, tuple, set, frozenset)):
|
|
158
|
+
result.append(_serialize_args(tuple(arg)))
|
|
159
|
+
elif isinstance(arg, dict):
|
|
160
|
+
result.append({str(k): str(v) for k, v in arg.items()})
|
|
161
|
+
else:
|
|
162
|
+
result.append(str(arg))
|
|
163
|
+
return result
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Time utilities — timezone-aware UTC helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def utc_now() -> datetime:
|
|
9
|
+
"""Return the current UTC datetime (timezone-aware).
|
|
10
|
+
|
|
11
|
+
Replaces datetime.utcnow() which is deprecated and returns naive datetimes.
|
|
12
|
+
"""
|
|
13
|
+
return datetime.now(timezone.utc)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: skillpool
|
|
3
|
+
Version: 4.3.0
|
|
4
|
+
Summary: Skill Pool V4.3 — AI Agent Skill Governance & Delivery Platform
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: click>=8.0
|
|
8
|
+
Requires-Dist: pydantic>=2.0
|
|
9
|
+
Requires-Dist: fastmcp>=0.2.0
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
11
|
+
Requires-Dist: httpx>=0.24.0
|
|
12
|
+
Provides-Extra: graph
|
|
13
|
+
Requires-Dist: numpy>=1.24; extra == "graph"
|
|
14
|
+
Requires-Dist: scipy>=1.10; extra == "graph"
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-xdist>=3.0; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest-bdd>=7.0; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
skillpool/__init__.py,sha256=iqCqRbDt8qM961d-QyQePsf7lrGV5ge4LeGFEAoqW9I,2155
|
|
2
|
+
skillpool/__main__.py,sha256=4NyWBZ_yt1y4GxeCVehQv4XF3_7LZAUiTwkH0ms-wMY,125
|
|
3
|
+
skillpool/clawmem_client.py,sha256=7sQmBP6Jp6jb9dSNe-uQv97tBueqYnaurcixOgxZg1w,5973
|
|
4
|
+
skillpool/cli.py,sha256=k2bzzgJPzGOsQBL5F0soQQis5E1earcatrwImBwXcUI,26937
|
|
5
|
+
skillpool/config.py,sha256=_-wlKZjggznLD-i1KETzukeUmfeHsABp1t805wI2jLg,1109
|
|
6
|
+
skillpool/csdf.py,sha256=xtq9r3imOFzl-tw3Oj74McX-ILjWq-pYIZIRZ6No4yU,4103
|
|
7
|
+
skillpool/gate.py,sha256=cWadO5l-HYSk5dIO7VfvGfRIfjShBVjU-PSeS5unPKI,9190
|
|
8
|
+
skillpool/lifecycle.py,sha256=6CskpHbouOozUrwpUCZH7mZUtasa-jy5nQR2upISYR4,4869
|
|
9
|
+
skillpool/mcp_server.py,sha256=BfFEDzQtDGzUj59DtqtheTyI-tbndRi4NbV5FrJ1STs,73378
|
|
10
|
+
skillpool/profile.py,sha256=5Qr0s65IBgshazu4UCnqYyhOI-9Qd1DR5AZz7l4AOJA,3466
|
|
11
|
+
skillpool/quality.py,sha256=BHt5Uoq2VlVJF-zSLeFqqfwlmwy544qLpebwlnQi5Vs,8893
|
|
12
|
+
skillpool/schemas.py,sha256=CL0fY5_uUrH_LF8izJs7IXq_0_gN14C7MJPuTytrbpo,3837
|
|
13
|
+
skillpool/telemetry.py,sha256=8cNH7iBqP8cghkWj_-y2UFEsa44z1lSLpixaPxqP4g8,4104
|
|
14
|
+
skillpool/adapters/__init__.py,sha256=xkyEmCrHeLa5pZjkMBnBxEmSTfVv75z7RBYrBu3stVU,318
|
|
15
|
+
skillpool/adapters/base.py,sha256=-RaOTYdJelPlajkvf9AAhEuSZda0SnBbgldaQdVsN44,1518
|
|
16
|
+
skillpool/adapters/claude_adapter.py,sha256=CQ0AMtPWrj-EfDvbEpuhpH0SFL87UhXpCsQpvhh1jkg,1528
|
|
17
|
+
skillpool/adapters/codex_adapter.py,sha256=Zmaee0KAUCLTbRThBGKvi58s6DoZS-rhv0CaHpUPzpU,3903
|
|
18
|
+
skillpool/adapters/hermes_adapter.py,sha256=4d7LUSzRKb-1XtJ6h6XlcslSi8cHvk8xfbZRKOw2L0c,1658
|
|
19
|
+
skillpool/audit/__init__.py,sha256=VqifTn0OAmsaMxh1ovjL04I9AedpoEjOdXjcZpzRlhc,23726
|
|
20
|
+
skillpool/bridge/__init__.py,sha256=XDAu38vD3tCXt44IlyBFKAPA2mnQ4NRhUY_cNHSCnro,462
|
|
21
|
+
skillpool/bridge/freeze_detector.py,sha256=-_Dagt8vRyA1GBpZxVXBXTpjrmHo3LuvZWOt2XH9quw,4659
|
|
22
|
+
skillpool/bridge/maintenance.py,sha256=KdX58HDbee4EAPhe2L5aRjlfml57W6AkQWDS8vYlaYk,4271
|
|
23
|
+
skillpool/bridge/wal_manager.py,sha256=Zqa_s5y0-RsbvJuBStLTw-Hw1kufZwscLl73aumYIF4,4758
|
|
24
|
+
skillpool/combiner/__init__.py,sha256=IC8fH_6KqbgrkIq0IhuT5qhinyMwBnBBtVqim-YNjuM,1025
|
|
25
|
+
skillpool/combiner/lifecycle.py,sha256=2lJiRbs3ySsyJ__hdlMV9AjVZD7iWE_s6LcS8XL3My4,17167
|
|
26
|
+
skillpool/combiner/models.py,sha256=0K_W5TM1xWo9JRY3LiMMysPYam4wzjf0Ydd_gSZN0Cs,3912
|
|
27
|
+
skillpool/cost/__init__.py,sha256=3msWWd8zDJdC5nzmzSqRajzeisyQtMjtTYz7m2Lq8uU,4031
|
|
28
|
+
skillpool/cost/audit_hash.py,sha256=1kznujWw-ejUz94xKv9ahOtP-X0SxIKuUby8YZJsgHI,1797
|
|
29
|
+
skillpool/cost/budget_tracker.py,sha256=0GRHyxYlhgB2HVMXk3JhCtDGlE2SKeYnaKhLQHG4fgQ,2615
|
|
30
|
+
skillpool/cost/dashboard.py,sha256=PXOcKbcdbbg-xGlMcGjYufera5OQ3UArG7vvt1NiC80,7124
|
|
31
|
+
skillpool/cost/models.py,sha256=6NJU44FbWMn_i3DAD8b9vjHggQw3aggN5Kkd0YzPmlM,4491
|
|
32
|
+
skillpool/cost/token_governor.py,sha256=TXdCDd3T-iJZcLl9UbFx9-QiD0-GgtEQMW9ZXRE_h3o,10588
|
|
33
|
+
skillpool/cost/trace_ceiling.py,sha256=-SCYu5tnVjWf7niUJlug2gcHIc4zD1fvXFjAYPxXW6Y,1410
|
|
34
|
+
skillpool/evolver/__init__.py,sha256=DAoM0QhHtV8CBmJ6lkGsAv2f6ToDRBXo9POw9I41XoE,35972
|
|
35
|
+
skillpool/gain/__init__.py,sha256=4oIjzdwHPvhQ6-BA48T_rhLnPRB9VGH_GLTjm2GWC7Q,10464
|
|
36
|
+
skillpool/gate_policy/__init__.py,sha256=xfOoI5zbNcPqXyuDgwcTzJFTuwEWnRvlZS1CVlUM8cw,708
|
|
37
|
+
skillpool/gate_policy/incremental.py,sha256=s-DfM4KbTfEthaQfCxlbKJoki2p_brKGiSVr1xiDAho,4982
|
|
38
|
+
skillpool/gate_policy/parser.py,sha256=ZvpYRfpb0kh0tDqaquTAw6YhoQT2cImsbQ1dFcoUexo,8346
|
|
39
|
+
skillpool/gate_policy/state_machine.py,sha256=QCUlTVlgsfYSdy1HMwujzlBz-tzTBtRJ3DKnaSEMMyk,15592
|
|
40
|
+
skillpool/graph/__init__.py,sha256=V3gQE11svLSlFIKP_po6hCDHt8r4_PQg25fvvtFvpKM,486
|
|
41
|
+
skillpool/graph/ppr.py,sha256=-PKarRL0yU1t9s5YQTMp5X5sODOtkSCs9aNJtRUx9h0,7961
|
|
42
|
+
skillpool/health/__init__.py,sha256=ZeOs9tJGhRXOAideatmOkFP2Fqz-Q19MJJ7VF6ZeF6E,2748
|
|
43
|
+
skillpool/health/check.py,sha256=rtH2NK3_wPENYJ4_-R9fn2PxY2vL7xpyLPcq1SNhl8U,2693
|
|
44
|
+
skillpool/health/degradation.py,sha256=8rJCbTyH2JkN5Ob9hTDdwEYGjmuIi833ePNfJrHd7kQ,3722
|
|
45
|
+
skillpool/health/models.py,sha256=iZT4LcDlAHspRBsxz159myuQadUiilDUwmD2nZdeQbg,1410
|
|
46
|
+
skillpool/hooks/__init__.py,sha256=d7xaAwAcSmipiT9MRlaKOygFvtUCuXmycPTkL45X0vg,145
|
|
47
|
+
skillpool/hooks/security_scanner.py,sha256=sdhj3scNGolaUX7GAUInJBcayPgdFqbg7wH2VEUY_fk,11059
|
|
48
|
+
skillpool/materializer/__init__.py,sha256=lK2iQcHj8S63Ue58ANRNInaCPIFMNsuS0mOSABE0AhM,3883
|
|
49
|
+
skillpool/materializer/budget_cropper.py,sha256=2BPx-K_oh4O2UpKG28sePfllx7oWev5S499nNxZDGIw,6465
|
|
50
|
+
skillpool/materializer/csdf_loader.py,sha256=Tt-RC1XQrT6ap6eg2J5ir_EXPQfylDxPvgSatiSJIUo,3636
|
|
51
|
+
skillpool/materializer/lazy_loader.py,sha256=dtpHPuyeqdFe2MnQ_EaD6fHKfzB0Nmx3dPHCEMlwW0I,9922
|
|
52
|
+
skillpool/materializer/lifecycle_filter.py,sha256=tVtnTRptoP58yF0elovtZLSqlrXYaC1QCUU8ajyf538,3569
|
|
53
|
+
skillpool/materializer/mapper.py,sha256=8PZg1EXDAURmLSKtNLF6ePFFmS2VwpiRnv4DJFE9cg4,6021
|
|
54
|
+
skillpool/materializer/models.py,sha256=AgflQMn-9KHzeEQ9UDvmDSoCD7B2Xn0oHXrglRKgfEw,1998
|
|
55
|
+
skillpool/monitor/__init__.py,sha256=nsBkbNw1JUTijdyoPzKs3pWgL2rYNrhGaRvniHaSuUo,19319
|
|
56
|
+
skillpool/monitor/bug_collector.py,sha256=bJzWWJOevr6-HHE_lgvnPLcpCH8iWGWFJxLTr1HQa-Y,13779
|
|
57
|
+
skillpool/monitor/defect_classifier.py,sha256=yYSz5TBQaAkbSddfjUqJnLavbHDNWnXA3VWXa2pGHRk,8135
|
|
58
|
+
skillpool/monitor/self_healing.py,sha256=mNrlRCEsI4YSWNQBixk_HKKlyGZFUgAb8cKdtQG0CUo,19195
|
|
59
|
+
skillpool/monitor/telemetry_bridge.py,sha256=9vRxzoohO1i4oH6fi9VHUmCnN4zyCZ9g8m01f2aIuB4,6113
|
|
60
|
+
skillpool/paradigm/__init__.py,sha256=zXZBydz9yDryR4YWawrKnRX4B-mBR-lKQjB1CvkQHg4,11301
|
|
61
|
+
skillpool/paradigm/override.py,sha256=oOv788wwYhR_NZ5tmBIwtIjrIXFfNDlFkqnBHxv5l6c,9031
|
|
62
|
+
skillpool/registry/__init__.py,sha256=h5TjYzWCkSRJgAMgeeVVfUeu14HxBxqvySZVU_1Z-NE,17443
|
|
63
|
+
skillpool/registry/models.py,sha256=mTV6R1YxJ7tUKlLp3NWWYOdP_Bq2c-1ywjaoA_d85sg,2192
|
|
64
|
+
skillpool/resolver/__init__.py,sha256=Qkq4CATCmDel40S_G4rJqDzsWDMASAiVtg_MMCcsbqE,11410
|
|
65
|
+
skillpool/resolver/cache.py,sha256=uYIUBBTOwnzILQyKBvqiJFYfzfOn1Q7lY1V2juDAoSQ,3505
|
|
66
|
+
skillpool/resolver/circuit_breaker.py,sha256=bt-V3WL6fV1LkwcMgU0uA3kL9mEK8hiPfzEHsyFhlWw,3324
|
|
67
|
+
skillpool/resolver/conflict_detector.py,sha256=XUo7AdbvJUoNLZR1xjNBMhCqnbDotyWH880i7G8ZLRc,4212
|
|
68
|
+
skillpool/resolver/health_filter.py,sha256=qJmhrpET0WRQMdNeCQHtC8hGgZXUEfE1tBC8E_5MhkQ,1042
|
|
69
|
+
skillpool/resolver/models.py,sha256=1LzE4v6-vlW1EsF1eCymeY0cpI5EwEbLZlvVYBT8U7k,5451
|
|
70
|
+
skillpool/resolver/rate_limiter.py,sha256=P_asznbAVQT7U7EC6Cl8sdGb7G0gEBF-M4e2It7diWI,1474
|
|
71
|
+
skillpool/resolver/skill_graph.py,sha256=29xadJeFtAz46N2e7j6CskLlqxDObLp-uznpuHGD6Hk,6031
|
|
72
|
+
skillpool/review/__init__.py,sha256=u9wR2u7AwCGr3W7kAqXh9t7UX0IoXre6pvqAK5Q_NjE,9254
|
|
73
|
+
skillpool/review/async_queue.py,sha256=F9nqVSyHFMCikiHGDqar7EBv-gXY95Lwenza7_7-Gfs,3524
|
|
74
|
+
skillpool/review/checkpoint_runner.py,sha256=3cD_V8rip7Dtqr6BNuNdabdeOsbNHopc_g0eXRSkB44,12061
|
|
75
|
+
skillpool/review/models.py,sha256=HIL_1jXo58RnuDmYaSJ2_KBcmrMES4VTJcYn9M5mvMU,5147
|
|
76
|
+
skillpool/review/suspect_marker.py,sha256=SRp3nbNJIJZYEe0RgFmZoBJr5wDB43TdS3R7t0DG1Mw,1254
|
|
77
|
+
skillpool/review/veto_evaluator.py,sha256=Q6SEyWQuuvtwnlDrejGrNTQyhyYfG9JzDKu8nE4OLMo,3399
|
|
78
|
+
skillpool/router/__init__.py,sha256=CnS53PQ81IAkBAGgBnEgtxs3r_MQt0j5_z9_d5jIoLY,19540
|
|
79
|
+
skillpool/synergy/__init__.py,sha256=ggki1UfjGk4e0qT21Y97rRBFWUSZt1GL-oAthUb4_Qk,9695
|
|
80
|
+
skillpool/synergy/detector.py,sha256=2PXH_ImR-RHU4jYbwlZ2MWwA_m2JkquxH3UtM1x2oKk,216
|
|
81
|
+
skillpool/utils/__init__.py,sha256=28bgrDSBhCFHRpxlmq0qhj5GGkDKyriS4zeoMHhQ4x8,445
|
|
82
|
+
skillpool/utils/changelog.py,sha256=tIMSnJwsHKPI2Wig970hSbJxJ-qGlianN3PVepz128Y,7326
|
|
83
|
+
skillpool/utils/logger.py,sha256=uPlNPD0OQ9Ncthb4NDkKK5bZoLeMIP8FV0rTeVR1Rs4,8594
|
|
84
|
+
skillpool/utils/runtime_audit.py,sha256=D4irlvJ5NBKzF0gEtuCSyW9tDbrrQqzUWodAqQuK2ws,5274
|
|
85
|
+
skillpool/utils/time_utils.py,sha256=QUoDWFSQUEei-U6QfQOaASA5J-4obleWqickg008hdA,343
|
|
86
|
+
skillpool-4.3.0.dist-info/METADATA,sha256=hzNizCDwF59B7WjxlpQos37BWgHqWCkNNDQfYSnDR6E,717
|
|
87
|
+
skillpool-4.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
88
|
+
skillpool-4.3.0.dist-info/entry_points.txt,sha256=BHsoSynXvvIDkxBXgjdv-sO0_IpWT3m1D5BkFEzLl8o,91
|
|
89
|
+
skillpool-4.3.0.dist-info/top_level.txt,sha256=ne3n_NFhpOKwQk8NODbsGyod7FIuqDczdDHv8S9xKzs,10
|
|
90
|
+
skillpool-4.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
skillpool
|