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.
Files changed (90) hide show
  1. skillpool/__init__.py +74 -0
  2. skillpool/__main__.py +6 -0
  3. skillpool/adapters/__init__.py +8 -0
  4. skillpool/adapters/base.py +41 -0
  5. skillpool/adapters/claude_adapter.py +36 -0
  6. skillpool/adapters/codex_adapter.py +92 -0
  7. skillpool/adapters/hermes_adapter.py +38 -0
  8. skillpool/audit/__init__.py +651 -0
  9. skillpool/bridge/__init__.py +16 -0
  10. skillpool/bridge/freeze_detector.py +134 -0
  11. skillpool/bridge/maintenance.py +119 -0
  12. skillpool/bridge/wal_manager.py +136 -0
  13. skillpool/clawmem_client.py +176 -0
  14. skillpool/cli.py +700 -0
  15. skillpool/combiner/__init__.py +31 -0
  16. skillpool/combiner/lifecycle.py +453 -0
  17. skillpool/combiner/models.py +99 -0
  18. skillpool/config.py +34 -0
  19. skillpool/cost/__init__.py +111 -0
  20. skillpool/cost/audit_hash.py +51 -0
  21. skillpool/cost/budget_tracker.py +66 -0
  22. skillpool/cost/dashboard.py +189 -0
  23. skillpool/cost/models.py +129 -0
  24. skillpool/cost/token_governor.py +264 -0
  25. skillpool/cost/trace_ceiling.py +38 -0
  26. skillpool/csdf.py +126 -0
  27. skillpool/evolver/__init__.py +978 -0
  28. skillpool/gain/__init__.py +285 -0
  29. skillpool/gate.py +282 -0
  30. skillpool/gate_policy/__init__.py +31 -0
  31. skillpool/gate_policy/incremental.py +157 -0
  32. skillpool/gate_policy/parser.py +258 -0
  33. skillpool/gate_policy/state_machine.py +432 -0
  34. skillpool/graph/__init__.py +14 -0
  35. skillpool/graph/ppr.py +279 -0
  36. skillpool/health/__init__.py +73 -0
  37. skillpool/health/check.py +85 -0
  38. skillpool/health/degradation.py +90 -0
  39. skillpool/health/models.py +43 -0
  40. skillpool/hooks/__init__.py +4 -0
  41. skillpool/hooks/security_scanner.py +288 -0
  42. skillpool/lifecycle.py +150 -0
  43. skillpool/materializer/__init__.py +124 -0
  44. skillpool/materializer/budget_cropper.py +178 -0
  45. skillpool/materializer/csdf_loader.py +114 -0
  46. skillpool/materializer/lazy_loader.py +265 -0
  47. skillpool/materializer/lifecycle_filter.py +93 -0
  48. skillpool/materializer/mapper.py +178 -0
  49. skillpool/materializer/models.py +66 -0
  50. skillpool/mcp_server.py +2005 -0
  51. skillpool/monitor/__init__.py +576 -0
  52. skillpool/monitor/bug_collector.py +392 -0
  53. skillpool/monitor/defect_classifier.py +218 -0
  54. skillpool/monitor/self_healing.py +530 -0
  55. skillpool/monitor/telemetry_bridge.py +197 -0
  56. skillpool/paradigm/__init__.py +312 -0
  57. skillpool/paradigm/override.py +285 -0
  58. skillpool/profile.py +94 -0
  59. skillpool/quality.py +254 -0
  60. skillpool/registry/__init__.py +509 -0
  61. skillpool/registry/models.py +98 -0
  62. skillpool/resolver/__init__.py +320 -0
  63. skillpool/resolver/cache.py +103 -0
  64. skillpool/resolver/circuit_breaker.py +103 -0
  65. skillpool/resolver/conflict_detector.py +111 -0
  66. skillpool/resolver/health_filter.py +38 -0
  67. skillpool/resolver/models.py +154 -0
  68. skillpool/resolver/rate_limiter.py +48 -0
  69. skillpool/resolver/skill_graph.py +183 -0
  70. skillpool/review/__init__.py +242 -0
  71. skillpool/review/async_queue.py +96 -0
  72. skillpool/review/checkpoint_runner.py +345 -0
  73. skillpool/review/models.py +164 -0
  74. skillpool/review/suspect_marker.py +39 -0
  75. skillpool/review/veto_evaluator.py +94 -0
  76. skillpool/router/__init__.py +481 -0
  77. skillpool/schemas.py +119 -0
  78. skillpool/synergy/__init__.py +240 -0
  79. skillpool/synergy/detector.py +5 -0
  80. skillpool/telemetry.py +126 -0
  81. skillpool/utils/__init__.py +21 -0
  82. skillpool/utils/changelog.py +218 -0
  83. skillpool/utils/logger.py +273 -0
  84. skillpool/utils/runtime_audit.py +163 -0
  85. skillpool/utils/time_utils.py +13 -0
  86. skillpool-4.3.0.dist-info/METADATA +21 -0
  87. skillpool-4.3.0.dist-info/RECORD +90 -0
  88. skillpool-4.3.0.dist-info/WHEEL +5 -0
  89. skillpool-4.3.0.dist-info/entry_points.txt +3 -0
  90. skillpool-4.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,432 @@
1
+ """Gate State Machine — 7-state FSM for 4D paradigm phase transitions.
2
+
3
+ Error Codes:
4
+ GP003: Illegal phase transition
5
+ GP004: Missing required artifact for gate
6
+ GP006: gate.json read/write failure
7
+
8
+ States: IDLE, ASSESSING, DOCSDD, SDD, BDD, TDD, REVIEW, COMPLETE
9
+
10
+ Contracts:
11
+ - gate.json persists across process restarts (B04)
12
+ - All transitions logged to phase_history (B13)
13
+ - Atomic writes via temp file + os.replace()
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import logging
20
+ import os
21
+ import tempfile
22
+ from pathlib import Path
23
+
24
+ from pydantic import BaseModel, Field
25
+
26
+ from skillpool.gate_policy.parser import (
27
+ GatePolicyConfig,
28
+ GatePolicyError,
29
+ resolve_level_for_path,
30
+ )
31
+ from skillpool.utils.time_utils import utc_now
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Pydantic Models
38
+ # ---------------------------------------------------------------------------
39
+
40
+
41
+ class PhaseTransition(BaseModel):
42
+ """Single phase transition record."""
43
+
44
+ from_phase: str
45
+ to_phase: str
46
+ timestamp: str
47
+ reason: str = ""
48
+
49
+
50
+ class ReviewCheckpoint(BaseModel):
51
+ """Review checkpoint state."""
52
+
53
+ triggered: bool = False
54
+ checkpoint_level: str | None = None
55
+ review_result: str | None = None
56
+ veto_status: str | None = None
57
+
58
+
59
+ class GateMetadata(BaseModel):
60
+ """Gate state metadata."""
61
+
62
+ created_at: str | None = None
63
+ updated_at: str | None = None
64
+ session_id: str | None = None
65
+ task_description: str | None = None
66
+
67
+
68
+ class GateStateFile(BaseModel):
69
+ """gate.json runtime state."""
70
+
71
+ incremental_mode: bool = True
72
+ current_phase: str = "IDLE"
73
+ assessed_level: str | None = None
74
+ assessed_at: str | None = None
75
+ changed_files: list[str] = Field(default_factory=list)
76
+ phase_history: list[PhaseTransition] = Field(default_factory=list)
77
+ gate_checks: dict[str, str | None] = Field(default_factory=dict)
78
+ artifacts: dict[str, str | None] = Field(default_factory=dict)
79
+ review_checkpoint: ReviewCheckpoint = Field(default_factory=ReviewCheckpoint)
80
+ metadata: GateMetadata = Field(default_factory=GateMetadata)
81
+
82
+
83
+ class GateCheckResult(BaseModel):
84
+ """Result of a gate check between phases."""
85
+
86
+ passed: bool
87
+ missing_artifacts: list[str] = Field(default_factory=list)
88
+ validation_message: str = ""
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Legal Transitions Table
93
+ # ---------------------------------------------------------------------------
94
+
95
+ _LEGAL_TRANSITIONS: dict[str, set[str]] = {
96
+ "IDLE": {"ASSESSING"},
97
+ "ASSESSING": {"COMPLETE", "DOCSDD", "SDD"},
98
+ "DOCSDD": {"SDD"},
99
+ "SDD": {"BDD", "TDD"},
100
+ "BDD": {"TDD"},
101
+ "TDD": {"REVIEW", "COMPLETE"},
102
+ "REVIEW": {"COMPLETE"},
103
+ "COMPLETE": set(),
104
+ }
105
+
106
+ # Conditional transition rules: (from, to) → required assessed_level
107
+ _TRANSITION_LEVEL_CONDITIONS: dict[tuple[str, str], set[str]] = {
108
+ ("ASSESSING", "COMPLETE"): {"L0"},
109
+ ("ASSESSING", "DOCSDD"): {"L2", "L3+L2+"},
110
+ ("ASSESSING", "SDD"): {"L1", "L2", "L3+L2+"},
111
+ ("SDD", "BDD"): {"L2", "L3+L2+"},
112
+ ("SDD", "TDD"): {"L1"},
113
+ ("TDD", "REVIEW"): {"L3+L2+"},
114
+ ("TDD", "COMPLETE"): {"L0", "L1", "L2"},
115
+ }
116
+
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # GateStateMachine
120
+ # ---------------------------------------------------------------------------
121
+
122
+
123
+ class GateStateMachine:
124
+ """7-state FSM for 4D paradigm phase transitions.
125
+
126
+ Args:
127
+ state_path: Path to gate.json file for persistence.
128
+
129
+ Contract:
130
+ - Loads state from gate.json on init (B04).
131
+ - If gate.json missing/corrupt, starts at IDLE.
132
+ - All transitions logged to phase_history (B13).
133
+ """
134
+
135
+ def __init__(self, state_path: Path) -> None:
136
+ self._state_path = state_path
137
+ self._state = self._load_state()
138
+ self._policy: GatePolicyConfig | None = None
139
+
140
+ @property
141
+ def state(self) -> GateStateFile:
142
+ """Current gate state."""
143
+ return self._state
144
+
145
+ def set_policy(self, policy: GatePolicyConfig) -> None:
146
+ """Set policy for gate check validation."""
147
+ self._policy = policy
148
+
149
+ def assess(
150
+ self,
151
+ task_description: str,
152
+ changed_files: list[str],
153
+ policy: GatePolicyConfig | None = None,
154
+ ) -> str:
155
+ """Assess complexity and set assessed_level.
156
+
157
+ Args:
158
+ task_description: Natural language task description.
159
+ changed_files: List of changed file paths.
160
+ policy: Optional policy for path-based level resolution.
161
+
162
+ Returns:
163
+ Assessed complexity level string (L0/L1/L2/L3+L2+).
164
+
165
+ Side effects:
166
+ - Transitions IDLE → ASSESSING.
167
+ - Sets assessed_level in gate.json.
168
+ - If L0, immediately transitions to COMPLETE.
169
+
170
+ Raises:
171
+ GatePolicyError: GP003 if not in IDLE state.
172
+ """
173
+ if self._state.current_phase != "IDLE":
174
+ raise GatePolicyError(
175
+ "GP003",
176
+ f"assess() can only be called from IDLE, current: {self._state.current_phase}",
177
+ )
178
+
179
+ # Transition IDLE → ASSESSING
180
+ self._do_transition("ASSESSING", reason="assess called")
181
+
182
+ # Determine level from changed files if policy provided
183
+ if policy and changed_files:
184
+ # Use highest level across all files
185
+ levels = set()
186
+ for f in changed_files:
187
+ resolution = resolve_level_for_path(f, policy)
188
+ levels.add(resolution.level)
189
+ # Pick highest
190
+ level_order = {"L0": 0, "L1": 1, "L2": 2, "L3+L2+": 3}
191
+ assessed = max(levels, key=lambda lvl: level_order.get(lvl, 0))
192
+ else:
193
+ # Simple keyword-based assessment as fallback
194
+ assessed = _keyword_assess(task_description)
195
+
196
+ self._state.assessed_level = assessed
197
+ self._state.assessed_at = utc_now().isoformat()
198
+ self._state.changed_files = list(changed_files)
199
+ self._state.metadata.task_description = task_description
200
+ self._persist()
201
+
202
+ # Auto-transition if L0
203
+ if assessed == "L0":
204
+ self._do_transition("COMPLETE", reason="L0 auto-complete")
205
+
206
+ return assessed
207
+
208
+ def transition(self, target_phase: str) -> GateStateFile:
209
+ """Transition to target phase.
210
+
211
+ Args:
212
+ target_phase: One of the 7 valid phase names.
213
+
214
+ Returns:
215
+ Updated GateStateFile after transition.
216
+
217
+ Raises:
218
+ GatePolicyError: GP003 if transition is illegal (strict mode).
219
+ """
220
+ current = self._state.current_phase
221
+
222
+ # Determine enforcement mode
223
+ mode = "strict"
224
+ if self._policy:
225
+ mode = self._policy.enforcement.mode
226
+
227
+ # Check if transition is in legal table
228
+ illegal = target_phase not in _LEGAL_TRANSITIONS.get(current, set())
229
+
230
+ # Check level conditions
231
+ level_violation = False
232
+ key = (current, target_phase)
233
+ if key in _TRANSITION_LEVEL_CONDITIONS:
234
+ required_levels = _TRANSITION_LEVEL_CONDITIONS[key]
235
+ assessed = self._state.assessed_level
236
+ if assessed not in required_levels:
237
+ level_violation = True
238
+
239
+ if illegal or level_violation:
240
+ if mode == "strict":
241
+ reason = f"Illegal transition: {current} → {target_phase}"
242
+ if level_violation:
243
+ reason = (
244
+ f"Transition {current} → {target_phase} requires assessed_level "
245
+ f"in {_TRANSITION_LEVEL_CONDITIONS[key]}, got {self._state.assessed_level}"
246
+ )
247
+ raise GatePolicyError("GP003", reason)
248
+ elif mode == "permissive":
249
+ logger.warning(
250
+ "GP003 (permissive): Illegal transition %s → %s blocked but not raised",
251
+ current,
252
+ target_phase,
253
+ )
254
+ return self._state # No transition, no exception
255
+ # disabled mode: proceed regardless
256
+ logger.warning(
257
+ "GP003 (disabled): Illegal transition %s → %s allowed by disabled enforcement",
258
+ current,
259
+ target_phase,
260
+ )
261
+
262
+ self._do_transition(target_phase, reason=f"transition to {target_phase}")
263
+ return self._state
264
+
265
+ def check_gate(
266
+ self,
267
+ from_phase: str,
268
+ to_phase: str,
269
+ artifacts: dict[str, str | None],
270
+ policy: GatePolicyConfig | None = None,
271
+ ) -> GateCheckResult:
272
+ """Check if gate transition is allowed given current artifacts.
273
+
274
+ Does NOT modify state. Pure check function.
275
+ """
276
+ gate_key = f"{from_phase.lower()}_to_{to_phase.lower()}"
277
+ missing: list[str] = []
278
+
279
+ # Check policy phase_gates
280
+ effective_policy = policy or self._policy
281
+ if effective_policy and gate_key in effective_policy.phase_gates:
282
+ gate = effective_policy.phase_gates[gate_key]
283
+ for artifact_name in gate.required_artifacts:
284
+ if artifacts.get(artifact_name) is None:
285
+ missing.append(artifact_name)
286
+
287
+ # Check emergency bypass — only active if override file exists
288
+ if effective_policy and effective_policy.emergency_bypass.enabled:
289
+ bypass_path = self._state_path.parent / effective_policy.emergency_bypass.config_file
290
+ bypass_file_exists = bypass_path.exists()
291
+ bypass_expired = self._check_bypass_expiry(effective_policy) if bypass_file_exists else False
292
+ if bypass_file_exists and not bypass_expired:
293
+ # Bypass is active
294
+ if to_phase in effective_policy.emergency_bypass.allowed_phases:
295
+ return GateCheckResult(passed=True, validation_message="Emergency bypass active")
296
+ else:
297
+ missing.append(f"emergency_bypass:phase_{to_phase}_not_allowed")
298
+
299
+ return GateCheckResult(
300
+ passed=len(missing) == 0,
301
+ missing_artifacts=missing,
302
+ validation_message="All artifacts present" if not missing else f"Missing: {missing}",
303
+ )
304
+
305
+ def update_artifact(self, name: str, value: str) -> None:
306
+ """Update artifact status in gate.json."""
307
+ self._state.artifacts[name] = value
308
+ self._persist()
309
+
310
+ def reset(self) -> GateStateFile:
311
+ """Reset state machine to IDLE.
312
+
313
+ Returns:
314
+ GateStateFile with current_phase="IDLE", cleared history.
315
+
316
+ Contract:
317
+ - Clears assessed_level, changed_files, phase_history, gate_checks, artifacts, review_checkpoint.
318
+ - Persists reset state to gate.json.
319
+ - Does NOT clear metadata.created_at.
320
+ - Gate key format: ``{from_phase.lower()}_to_{to_phase.lower()}``
321
+ (e.g. "sdd_to_bdd", "tdd_to_review"). After reset, all gate_checks are cleared.
322
+ """
323
+ created_at = self._state.metadata.created_at
324
+ self._state = GateStateFile()
325
+ self._state.metadata.created_at = created_at
326
+ self._state.metadata.updated_at = utc_now().isoformat()
327
+ self._persist()
328
+ return self._state
329
+
330
+ # -----------------------------------------------------------------------
331
+ # Internal
332
+ # -----------------------------------------------------------------------
333
+
334
+ def _check_bypass_expiry(self, policy: GatePolicyConfig) -> bool:
335
+ """Check if emergency bypass has expired.
336
+
337
+ Returns True if bypass has expired (should enforce normal rules).
338
+ Returns False if bypass is still active or no override file exists.
339
+ """
340
+ bypass_path = self._state_path.parent / policy.emergency_bypass.config_file
341
+ if not bypass_path.exists():
342
+ return False # No override file → bypass not activated
343
+ try:
344
+ data = json.loads(bypass_path.read_text())
345
+ expires_at = data.get("expires_at")
346
+ if not expires_at:
347
+ return False # No expiry set → bypass active indefinitely
348
+ from datetime import datetime
349
+
350
+ expiry = datetime.fromisoformat(expires_at)
351
+ now = utc_now()
352
+ if now >= expiry:
353
+ return True # Bypass expired
354
+ return False
355
+ except Exception:
356
+ return False # Corrupt file → treat as bypass active (safer default)
357
+
358
+ def _do_transition(self, target: str, reason: str = "") -> None:
359
+ """Execute a transition and log it."""
360
+ transition = PhaseTransition(
361
+ from_phase=self._state.current_phase,
362
+ to_phase=target,
363
+ timestamp=utc_now().isoformat(),
364
+ reason=reason,
365
+ )
366
+ self._state.phase_history.append(transition)
367
+ self._state.current_phase = target
368
+ self._state.metadata.updated_at = utc_now().isoformat()
369
+ # Auto-set review_checkpoint when entering REVIEW
370
+ if target == "REVIEW":
371
+ self._state.review_checkpoint.triggered = True
372
+ self._state.review_checkpoint.checkpoint_level = self._state.assessed_level
373
+ self._persist()
374
+
375
+ def _load_state(self) -> GateStateFile:
376
+ """Load state from gate.json. Returns IDLE on any failure (GP006)."""
377
+ if not self._state_path.exists():
378
+ state = GateStateFile()
379
+ state.metadata.created_at = utc_now().isoformat()
380
+ return state
381
+ try:
382
+ data = json.loads(self._state_path.read_text())
383
+ return GateStateFile.model_validate(data)
384
+ except Exception:
385
+ # GP006: corrupt file, reset to IDLE
386
+ state = GateStateFile()
387
+ state.metadata.created_at = utc_now().isoformat()
388
+ return state
389
+
390
+ def _persist(self) -> None:
391
+ """Atomic write of gate.json via temp file + os.replace()."""
392
+ self._state_path.parent.mkdir(parents=True, exist_ok=True)
393
+ data = self._state.model_dump(mode="json")
394
+
395
+ try:
396
+ fd, tmp_path = tempfile.mkstemp(
397
+ dir=str(self._state_path.parent),
398
+ suffix=".tmp",
399
+ )
400
+ with os.fdopen(fd, "w") as f:
401
+ json.dump(data, f, indent=2)
402
+ os.replace(tmp_path, self._state_path)
403
+ except Exception:
404
+ # GP006: write failure, try to clean up temp file
405
+ try:
406
+ os.unlink(tmp_path)
407
+ except Exception:
408
+ pass
409
+ raise
410
+
411
+
412
+ # ---------------------------------------------------------------------------
413
+ # Helper: Keyword-based complexity assessment (fallback)
414
+ # ---------------------------------------------------------------------------
415
+
416
+ _KEYWORD_LEVELS: dict[str, list[str]] = {
417
+ "L0": ["typo", "comment", "log", "style", "rename", "whitespace", "formatting"],
418
+ "L1": ["config", "flag", "param", "validation", "default", "simple fix", "parameter"],
419
+ "L2": ["feature", "module", "refactor", "api", "integration", "new feature"],
420
+ "L3+L2+": ["subsystem", "migration", "breaking", "security", "architecture", "performance"],
421
+ }
422
+
423
+
424
+ def _keyword_assess(task_description: str) -> str:
425
+ """Assess complexity from task description keywords."""
426
+ desc_lower = task_description.lower()
427
+ # Check from highest to lowest
428
+ for level in ["L3+L2+", "L2", "L1", "L0"]:
429
+ for keyword in _KEYWORD_LEVELS[level]:
430
+ if keyword in desc_lower:
431
+ return level
432
+ return "L2" # Default
@@ -0,0 +1,14 @@
1
+ """Graph module — Skill graph algorithms."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__ = ["personalized_pagerank", "reverse_ppr"]
6
+
7
+
8
+ def __getattr__(name: str):
9
+ """Lazy import — numpy/scipy are optional dependencies."""
10
+ if name in ("personalized_pagerank", "reverse_ppr"):
11
+ from skillpool.graph.ppr import personalized_pagerank, reverse_ppr # noqa: F401
12
+
13
+ return locals().get(name)
14
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")