memstack-skill-loader 4.6.0__tar.gz → 4.8.0__tar.gz
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.
- {memstack_skill_loader-4.6.0/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.8.0}/PKG-INFO +1 -1
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/README.md +15 -4
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/pyproject.toml +1 -1
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/categories.py +4 -3
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/license.py +50 -3
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/server.py +13 -2
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader.egg-info/SOURCES.txt +1 -0
- memstack_skill_loader-4.8.0/tests/test_license_environment.py +86 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/setup.cfg +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/agent_runner.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/dashboard.html +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/dashboard.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/memory_db.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/proxy/__init__.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/proxy/_diag.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/proxy/body_parser.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/proxy/compressor.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/proxy/forwarder.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/proxy/pro_compressor.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/proxy/server.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/proxy/stats_tracker.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/stats.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/tests/test_body_parser_metric.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/tests/test_code_read_savings.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/tests/test_license_grace.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/tests/test_pro_compressor.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/tests/test_pro_skills_update.py +0 -0
- {memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/tests/test_skill_drift.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MemStack™ Skill Loader
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**129 skills for Claude Code:** 85 free + 44 Pro exclusive. Vector-indexed so CC loads only the skill it needs, saving your context window.
|
|
4
4
|
|
|
5
5
|
## Quick Start (5 minutes)
|
|
6
6
|
|
|
@@ -53,7 +53,7 @@ If this doesn't match Step 2's interpreter, the server won't launch (you'll see
|
|
|
53
53
|
```
|
|
54
54
|
activate_license(key="your-key", email="you@example.com")
|
|
55
55
|
```
|
|
56
|
-
Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://memstack.pro) to unlock all
|
|
56
|
+
Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://memstack.pro) to unlock all 44 Pro skills (85 free + 44 Pro = 129 total). Then type `list skills` to verify.
|
|
57
57
|
|
|
58
58
|
> To override the skills path, set `MEMSTACK_SKILLS_DIR=/path/to/your/memstack/skills`.
|
|
59
59
|
|
|
@@ -86,12 +86,23 @@ sudo apt install python3-pip
|
|
|
86
86
|
|
|
87
87
|
## How It Works
|
|
88
88
|
|
|
89
|
-
MCP server that vector-indexes all
|
|
89
|
+
MCP server that vector-indexes all 129 MemStack™ skills so Claude Code can call `find_skill("deploy to Railway")` and load **only** the relevant skill on demand, instead of all skills consuming context window.
|
|
90
90
|
|
|
91
|
-
- **No API
|
|
91
|
+
- **No Anthropic API key required:** skill search and indexing run locally — the only outbound network call is license validation, which contacts the MemStack license server on session start ([Data and privacy](#data-and-privacy))
|
|
92
92
|
- **Pro skills auto-detected:** set your license key and they appear automatically
|
|
93
93
|
- **Auto-reindex on start:** skills stay current without manual rebuilds
|
|
94
94
|
|
|
95
|
+
### Data and privacy
|
|
96
|
+
|
|
97
|
+
License validation is the loader's only outbound network call. On session start it POSTs to the MemStack license server (`admin.cwaffiliateinvestments.com/api/licenses/validate`) with:
|
|
98
|
+
|
|
99
|
+
- your **license key**
|
|
100
|
+
- a **machine ID** — a stable, hashed fingerprint that binds the license to your machine
|
|
101
|
+
- your **email**, if you provided one at activation
|
|
102
|
+
- your **environment** — OS name, Python version, and MemStack loader version
|
|
103
|
+
|
|
104
|
+
That is the complete payload. **No code, file contents, file paths, usernames, project names, or prompts are ever sent.** The environment fields exist only so platform-specific breakage (for example, a bug that affects only Linux) is visible to us in aggregate.
|
|
105
|
+
|
|
95
106
|
### Environment Variable Override
|
|
96
107
|
|
|
97
108
|
Set `MEMSTACK_SKILLS_DIR` to override the skills path in `config.json`:
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/categories.py
RENAMED
|
@@ -50,7 +50,7 @@ CATEGORY_MAP = {
|
|
|
50
50
|
"owasp-top10": "Security", "owasp-top-10": "Security",
|
|
51
51
|
"rls-checker": "Security", "rls-guardian": "Security",
|
|
52
52
|
"secrets-scanner": "Security", "git-guard": "Security",
|
|
53
|
-
# ── Pro-exclusive skills (
|
|
53
|
+
# ── Pro-exclusive skills (44) — CANONICAL category map ─────────────────
|
|
54
54
|
# Must cover every slug in PRO_EXCLUSIVE_SKILLS (license.py) exactly; no Pro
|
|
55
55
|
# slug may fall through to "Other". Enforced by scripts/check_skill_drift.py
|
|
56
56
|
# (Check C / F-3). Core = MemStack's own session/memory/agent/efficiency
|
|
@@ -75,9 +75,10 @@ CATEGORY_MAP = {
|
|
|
75
75
|
# Security (4)
|
|
76
76
|
"advanced-security": "Security", "config-audit": "Security",
|
|
77
77
|
"dependency-auditor": "Security", "env-manager-pro": "Security",
|
|
78
|
-
# Automation (
|
|
78
|
+
# Automation (5)
|
|
79
79
|
"browser-use": "Automation", "hooks-integration": "Automation",
|
|
80
|
-
"video-pipeline": "Automation", "
|
|
80
|
+
"video-pipeline": "Automation", "video-review": "Automation",
|
|
81
|
+
"web-scraper": "Automation",
|
|
81
82
|
# Business (2)
|
|
82
83
|
"meeting-insights-analyzer": "Business", "us-privacy-compliance": "Business",
|
|
83
84
|
# Content (1)
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/license.py
RENAMED
|
@@ -9,6 +9,7 @@ import hmac as hmac_mod
|
|
|
9
9
|
import json
|
|
10
10
|
import math
|
|
11
11
|
import os
|
|
12
|
+
import platform
|
|
12
13
|
import socket
|
|
13
14
|
import sys
|
|
14
15
|
import threading
|
|
@@ -62,7 +63,7 @@ TRANSIENT_REASONS = frozenset({"rate_limited", "server_error"})
|
|
|
62
63
|
MACHINE_MISMATCH_REASON = "machine_mismatch"
|
|
63
64
|
|
|
64
65
|
# Skills that require a Pro license. Free-tier users see a locked message.
|
|
65
|
-
PRO_EXCLUSIVE_SKILLS = frozenset({"advanced-security", "api-docs", "api-load-tester", "branching", "browser-use", "burn", "checkpoint", "claude-api-helper", "codebase-index", "config-audit", "consolidate", "context-db", "council", "database-architect", "database-migration", "dependency-auditor", "developer-growth-analysis", "diagram-generator", "doc-index", "drift-detection", "env-manager-pro", "error-handler", "frontend-design", "git-worktrees", "governor-pro", "gtm-validator", "hooks-integration", "ios-app-store", "log-analyzer", "mcp-builder", "meeting-insights-analyzer", "model-router", "multi-agent", "nextjs-conventions", "performance-profiler", "python-conventions", "rag-builder", "session-restore", "social-media", "test-generator", "us-privacy-compliance", "video-pipeline", "web-scraper"})
|
|
66
|
+
PRO_EXCLUSIVE_SKILLS = frozenset({"advanced-security", "api-docs", "api-load-tester", "branching", "browser-use", "burn", "checkpoint", "claude-api-helper", "codebase-index", "config-audit", "consolidate", "context-db", "council", "database-architect", "database-migration", "dependency-auditor", "developer-growth-analysis", "diagram-generator", "doc-index", "drift-detection", "env-manager-pro", "error-handler", "frontend-design", "git-worktrees", "governor-pro", "gtm-validator", "hooks-integration", "ios-app-store", "log-analyzer", "mcp-builder", "meeting-insights-analyzer", "model-router", "multi-agent", "nextjs-conventions", "performance-profiler", "python-conventions", "rag-builder", "session-restore", "social-media", "test-generator", "us-privacy-compliance", "video-pipeline", "video-review", "web-scraper"})
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
@dataclass
|
|
@@ -406,6 +407,50 @@ def save_license_key(key: str) -> None:
|
|
|
406
407
|
# ---------------------------------------------------------------------------
|
|
407
408
|
|
|
408
409
|
|
|
410
|
+
def _environment_info() -> dict[str, str | None]:
|
|
411
|
+
"""Coarse environment info attached to every license-validation POST.
|
|
412
|
+
|
|
413
|
+
Purpose: let us see which OS / Python / loader versions our users run, so a
|
|
414
|
+
bug that silently breaks one platform (e.g. the hooks bug that killed skill
|
|
415
|
+
auto-loading on Linux/macOS) is visible in aggregate.
|
|
416
|
+
|
|
417
|
+
Collects ONLY three fields: OS name, running Python version, installed
|
|
418
|
+
loader version. It deliberately sends NO hostname, username, file path,
|
|
419
|
+
project name, or any identifier beyond the machine_id already in the
|
|
420
|
+
payload. Each field is detected independently and fail-safe: any failure
|
|
421
|
+
yields ``None`` (JSON null), never an exception — environment detection
|
|
422
|
+
must NEVER be able to fail a license validation.
|
|
423
|
+
|
|
424
|
+
``plugin_version`` is intentionally absent. The Claude Code skills-bundle
|
|
425
|
+
plugin is a separate marketplace artifact; its version lives in
|
|
426
|
+
``.claude-plugin/plugin.json`` inside Claude Code's plugin cache. This
|
|
427
|
+
loader's MCP server is registered standalone
|
|
428
|
+
(``claude mcp add ... python -m memstack_skill_loader``), so the process
|
|
429
|
+
never receives ``CLAUDE_PLUGIN_ROOT`` or any handle to that cache, and the
|
|
430
|
+
two artifacts share no version coupling. There is no reliable value to
|
|
431
|
+
send, so the field is omitted rather than guessed.
|
|
432
|
+
"""
|
|
433
|
+
info: dict[str, str | None] = {"os": None, "python_version": None, "loader_version": None}
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
info["os"] = platform.system() or None
|
|
437
|
+
except Exception:
|
|
438
|
+
pass
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
info["python_version"] = platform.python_version()
|
|
442
|
+
except Exception:
|
|
443
|
+
pass
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
from importlib.metadata import version as _pkg_version
|
|
447
|
+
info["loader_version"] = _pkg_version("memstack-skill-loader")
|
|
448
|
+
except Exception:
|
|
449
|
+
pass
|
|
450
|
+
|
|
451
|
+
return info
|
|
452
|
+
|
|
453
|
+
|
|
409
454
|
async def validate_license(
|
|
410
455
|
license_key: str | None = None,
|
|
411
456
|
email: str | None = None,
|
|
@@ -644,9 +689,10 @@ async def _validate_locked(key: str, email: str | None) -> LicenseStatus:
|
|
|
644
689
|
return cached_status
|
|
645
690
|
|
|
646
691
|
# --- build request body ---
|
|
647
|
-
body_payload: dict[str, str] = {"license_key": key, "machine_id": _machine_id()}
|
|
692
|
+
body_payload: dict[str, str | None] = {"license_key": key, "machine_id": _machine_id()}
|
|
648
693
|
if email:
|
|
649
694
|
body_payload["email"] = email
|
|
695
|
+
body_payload.update(_environment_info()) # os / python_version / loader_version (fail-safe)
|
|
650
696
|
|
|
651
697
|
# --- always call API on session start ---
|
|
652
698
|
try:
|
|
@@ -710,7 +756,8 @@ def refresh_license_cache(key: str, timeout: float = HTTP_TIMEOUT) -> LicenseSta
|
|
|
710
756
|
Returns the new LicenseStatus on success, or None if the server is unreachable
|
|
711
757
|
or returns an error. All network exceptions are caught internally.
|
|
712
758
|
"""
|
|
713
|
-
body_payload: dict[str, str] = {"license_key": key, "machine_id": _machine_id()}
|
|
759
|
+
body_payload: dict[str, str | None] = {"license_key": key, "machine_id": _machine_id()}
|
|
760
|
+
body_payload.update(_environment_info()) # os / python_version / loader_version (fail-safe)
|
|
714
761
|
# Uses VALIDATE_URL directly via httpx — not routed through ANTHROPIC_BASE_URL (Anthropic SDK only).
|
|
715
762
|
try:
|
|
716
763
|
with httpx.Client(timeout=timeout) as client:
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/server.py
RENAMED
|
@@ -434,6 +434,15 @@ _TAMPERED_MSG = (
|
|
|
434
434
|
"\U0001f512 MemStack\u2122 Pro: License file integrity check failed. "
|
|
435
435
|
"Get your key at https://memstack.pro"
|
|
436
436
|
)
|
|
437
|
+
# Appended to the activate_license success message (both tiers) so activation is
|
|
438
|
+
# informed consent: a plain, non-alarming disclosure of exactly what the
|
|
439
|
+
# on-session-start validation POST sends. Kept in one place so both branches
|
|
440
|
+
# stay in sync.
|
|
441
|
+
_VALIDATION_DISCLOSURE = (
|
|
442
|
+
"License validation sends your license key, a machine ID, your email, and "
|
|
443
|
+
"your environment (OS, Python version, MemStack version). No code, file "
|
|
444
|
+
"paths, usernames, or project data is sent."
|
|
445
|
+
)
|
|
437
446
|
|
|
438
447
|
|
|
439
448
|
def _grace_banner(status) -> str:
|
|
@@ -1090,7 +1099,8 @@ async def _handle_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
|
1090
1099
|
total = len(skills) if skills else 0
|
|
1091
1100
|
msg = (
|
|
1092
1101
|
f"\u2705 License activated! Tier: **Pro**. "
|
|
1093
|
-
f"All {total} skills unlocked
|
|
1102
|
+
f"All {total} skills unlocked.\n\n"
|
|
1103
|
+
f"{_VALIDATION_DISCLOSURE}"
|
|
1094
1104
|
)
|
|
1095
1105
|
else:
|
|
1096
1106
|
skills = await asyncio.to_thread(list_all_skills, config)
|
|
@@ -1101,7 +1111,8 @@ async def _handle_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
|
1101
1111
|
msg = (
|
|
1102
1112
|
f"\u2705 License activated! Tier: **Free**. "
|
|
1103
1113
|
f"{free_count} skills unlocked. "
|
|
1104
|
-
f"Upgrade to Pro at https://memstack.pro to unlock all {total}
|
|
1114
|
+
f"Upgrade to Pro at https://memstack.pro to unlock all {total}.\n\n"
|
|
1115
|
+
f"{_VALIDATION_DISCLOSURE}"
|
|
1105
1116
|
)
|
|
1106
1117
|
return [TextContent(type="text", text=msg)]
|
|
1107
1118
|
return [TextContent(
|
|
@@ -34,6 +34,7 @@ src/memstack_skill_loader/proxy/server.py
|
|
|
34
34
|
src/memstack_skill_loader/proxy/stats_tracker.py
|
|
35
35
|
tests/test_body_parser_metric.py
|
|
36
36
|
tests/test_code_read_savings.py
|
|
37
|
+
tests/test_license_environment.py
|
|
37
38
|
tests/test_license_grace.py
|
|
38
39
|
tests/test_pro_compressor.py
|
|
39
40
|
tests/test_pro_skills_update.py
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Environment info attached to the license-validation POST payload.
|
|
2
|
+
|
|
3
|
+
Covers ``license._environment_info`` — the coarse OS / Python / loader-version
|
|
4
|
+
block added to every validate call so a platform-specific breakage is visible in
|
|
5
|
+
aggregate. Two properties matter and are tested here:
|
|
6
|
+
|
|
7
|
+
1. SHAPE — exactly the three expected keys, no more; every value is str|None.
|
|
8
|
+
2. FAIL-SAFE — if any single detector raises, the function still returns the
|
|
9
|
+
full dict with ``None`` for the failed field and NEVER propagates the
|
|
10
|
+
exception. License validation must not break because env detection failed.
|
|
11
|
+
|
|
12
|
+
Detection is patched at the module boundary (``L.platform`` and
|
|
13
|
+
``importlib.metadata.version``, which the function imports lazily) so a raising
|
|
14
|
+
detector is exercised without touching the real interpreter.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from memstack_skill_loader import license as L
|
|
20
|
+
|
|
21
|
+
EXPECTED_KEYS = {"os", "python_version", "loader_version"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _raise(*args, **kwargs):
|
|
25
|
+
raise RuntimeError("boom")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ── 1. SHAPE ──────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_returns_exactly_the_expected_keys():
|
|
32
|
+
info = L._environment_info()
|
|
33
|
+
assert set(info) == EXPECTED_KEYS # no extra keys (e.g. plugin_version is omitted)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_values_are_str_or_none():
|
|
37
|
+
info = L._environment_info()
|
|
38
|
+
for key, value in info.items():
|
|
39
|
+
assert value is None or isinstance(value, str), f"{key}={value!r}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_happy_path_populates_all_fields():
|
|
43
|
+
# On a real interpreter every field is detectable, so none should be None.
|
|
44
|
+
info = L._environment_info()
|
|
45
|
+
assert all(v is not None for v in info.values()), info
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ── 2. FAIL-SAFE ──────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_os_detection_failure_yields_null(monkeypatch):
|
|
52
|
+
monkeypatch.setattr(L.platform, "system", _raise)
|
|
53
|
+
info = L._environment_info()
|
|
54
|
+
assert info["os"] is None
|
|
55
|
+
# other fields still detected; no exception propagated
|
|
56
|
+
assert set(info) == EXPECTED_KEYS
|
|
57
|
+
assert info["python_version"] is not None
|
|
58
|
+
assert info["loader_version"] is not None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_python_version_detection_failure_yields_null(monkeypatch):
|
|
62
|
+
monkeypatch.setattr(L.platform, "python_version", _raise)
|
|
63
|
+
info = L._environment_info()
|
|
64
|
+
assert info["python_version"] is None
|
|
65
|
+
assert set(info) == EXPECTED_KEYS
|
|
66
|
+
assert info["os"] is not None
|
|
67
|
+
assert info["loader_version"] is not None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_loader_version_lookup_failure_yields_null(monkeypatch):
|
|
71
|
+
# _environment_info imports importlib.metadata.version lazily, so patching
|
|
72
|
+
# the source attribute makes the in-function lookup raise.
|
|
73
|
+
monkeypatch.setattr("importlib.metadata.version", _raise)
|
|
74
|
+
info = L._environment_info()
|
|
75
|
+
assert info["loader_version"] is None
|
|
76
|
+
assert set(info) == EXPECTED_KEYS
|
|
77
|
+
assert info["os"] is not None
|
|
78
|
+
assert info["python_version"] is not None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_all_detectors_failing_returns_full_null_dict_without_raising(monkeypatch):
|
|
82
|
+
monkeypatch.setattr(L.platform, "system", _raise)
|
|
83
|
+
monkeypatch.setattr(L.platform, "python_version", _raise)
|
|
84
|
+
monkeypatch.setattr("importlib.metadata.version", _raise)
|
|
85
|
+
info = L._environment_info() # must not raise
|
|
86
|
+
assert info == {"os": None, "python_version": None, "loader_version": None}
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/compression.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/config.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/dashboard.html
RENAMED
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/dashboard.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/indexer.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/memory_db.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/proxy/_diag.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/search.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/src/memstack_skill_loader/stats.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.6.0 → memstack_skill_loader-4.8.0}/tests/test_body_parser_metric.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|