memstack-skill-loader 4.15.0__tar.gz → 4.15.1__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.

Potentially problematic release.


This version of memstack-skill-loader might be problematic. Click here for more details.

Files changed (71) hide show
  1. {memstack_skill_loader-4.15.0/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.15.1}/PKG-INFO +11 -5
  2. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/README.md +10 -4
  3. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/pyproject.toml +1 -1
  4. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/__init__.py +1 -1
  5. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/config.py +28 -2
  6. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/server.py +28 -0
  7. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1/src/memstack_skill_loader.egg-info}/PKG-INFO +11 -5
  8. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader.egg-info/SOURCES.txt +1 -0
  9. memstack_skill_loader-4.15.1/tests/test_pro_source_resolution.py +227 -0
  10. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/MANIFEST.in +0 -0
  11. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/setup.cfg +0 -0
  12. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/__main__.py +0 -0
  13. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/agent_runner.py +0 -0
  14. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/categories.py +0 -0
  15. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/compression.py +0 -0
  16. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/dashboard.html +0 -0
  17. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/dashboard.py +0 -0
  18. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/diary_ingest.py +0 -0
  19. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/goal.py +0 -0
  20. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/goal_engine.py +0 -0
  21. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/hooks/__init__.py +0 -0
  22. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/hooks/deny_git.py +0 -0
  23. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/hooks/deny_manager_write.py +0 -0
  24. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/hooks/deny_reviewer_git.py +0 -0
  25. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/hooks/git_policy.py +0 -0
  26. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/index_meta.py +0 -0
  27. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/indexer.py +0 -0
  28. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/license.py +0 -0
  29. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/memory_db.py +0 -0
  30. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/memory_engine.py +0 -0
  31. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/proxy/__init__.py +0 -0
  32. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/proxy/_diag.py +0 -0
  33. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/proxy/body_parser.py +0 -0
  34. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/proxy/compressor.py +0 -0
  35. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/proxy/forwarder.py +0 -0
  36. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/proxy/pro_compressor.py +0 -0
  37. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/proxy/server.py +0 -0
  38. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/proxy/stats_tracker.py +0 -0
  39. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/search.py +0 -0
  40. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/session_context.py +0 -0
  41. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/skill_config.py +0 -0
  42. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/skill_descriptions.json +0 -0
  43. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/stats.py +0 -0
  44. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/tfidf_search.py +0 -0
  45. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader/version_check.py +0 -0
  46. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
  47. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
  48. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
  49. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
  50. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_advisor_strategy.py +0 -0
  51. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_body_parser_metric.py +0 -0
  52. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_builder_scope.py +0 -0
  53. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_code_read_savings.py +0 -0
  54. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_dashscan.py +0 -0
  55. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_diary_ingest.py +0 -0
  56. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_failed_approaches.py +0 -0
  57. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_git_policy.py +0 -0
  58. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_index_meta_guard.py +0 -0
  59. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_license_environment.py +0 -0
  60. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_license_grace.py +0 -0
  61. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_memory_engine.py +0 -0
  62. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_memory_inspector.py +0 -0
  63. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_memory_scoring.py +0 -0
  64. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_memory_tools.py +0 -0
  65. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_pro_compressor.py +0 -0
  66. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_pro_skills_update.py +0 -0
  67. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_project_dir_canonical.py +0 -0
  68. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_search_memory.py +0 -0
  69. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_session_context.py +0 -0
  70. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_skill_drift.py +0 -0
  71. {memstack_skill_loader-4.15.0 → memstack_skill_loader-4.15.1}/tests/test_wheel_contents.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memstack-skill-loader
3
- Version: 4.15.0
3
+ Version: 4.15.1
4
4
  Summary: MCP server that vector-indexes MemStack Pro skills for on-demand loading
5
5
  Project-URL: Homepage, https://memstack.pro
6
6
  Project-URL: Documentation, https://memstack.pro/docs
@@ -72,7 +72,7 @@ activate_license(key="your-key", email="you@example.com")
72
72
  ```
73
73
  Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://memstack.pro) to unlock all 44 Pro skills (86 free + 44 Pro = 130 total). Then type `list skills` to verify.
74
74
 
75
- > **The first start is slow, and that is normal.** On the very first run after installing or upgrading, the server builds the skill index before it answers anything. Expect roughly a minute, nearly all of it loading the embedding model rather than reading your skills. **The server will look unresponsive for that time.** Let it finish. Every later start reuses the index and is not affected.
75
+ > **The first start is slow, and that is normal.** On the very first run after installing or upgrading, the server builds the skill index before it answers anything. Nearly all of that time is a one-time download of the ~90 MB embedding model, so it tracks your network speed rather than the size of your skill library: indexing 86 skills itself takes about a second. On a fast connection the whole first start is around half a minute; on a slow one it is longer. **The server will look unresponsive for that time.** Let it finish. Every later start reuses the cached model and index and is not affected.
76
76
 
77
77
  > To override the skills path, set `MEMSTACK_SKILLS_DIR=/path/to/your/memstack/skills`.
78
78
 
@@ -80,7 +80,7 @@ Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://
80
80
 
81
81
  ## Troubleshooting
82
82
 
83
- **Nothing responds after a fresh install, and it does not seem to be failing either (during Step 4):** the first start builds the skill index before the server answers anything, which takes roughly a minute. Most of that is loading the embedding model, not reading your skills. There is no progress output, so a working first start and a hung one look identical. Wait it out before changing anything. If it is still silent well past a couple of minutes, then treat it as the interpreter mismatch below. Later starts reuse the index and respond immediately.
83
+ **Nothing responds after a fresh install, and it does not seem to be failing either (during Step 4):** the first start builds the skill index before the server answers anything. Most of that is a one-time ~90 MB embedding-model download, not reading your skills, so the wait depends on your connection: around half a minute on a fast one, longer on a slow one. There is no progress output, so a working first start and a hung one look identical. Wait it out before changing anything. If it is still silent well past a few minutes, then treat it as the interpreter mismatch below. Later starts reuse the cached model and index and respond immediately.
84
84
 
85
85
  **`error: externally-managed-environment` (during Step 2)** — pip refuses to install on a system-managed Python. Add `--break-system-packages` · (in terminal):
86
86
  ```bash
@@ -111,7 +111,7 @@ MCP server that vector-indexes all 130 MemStack™ skills so Claude Code can cal
111
111
 
112
112
  - **No Anthropic API key required:** skill search and indexing run locally and never leave your machine. Three small calls happen on their own at session start — license validation, a Pro-bundle version probe, and a PyPI update check — and every other outbound call is one you trigger ([Data and privacy](#data-and-privacy))
113
113
  - **Pro skills auto-detected:** once activated, the Pro skills directory is added to your sources automatically, with no `config.json` edit
114
- - **Auto-reindex on start:** skills stay current without manual rebuilds. Only startup ever rebuilds, never a search. The first start pays a one-time build of roughly a minute; after that a rebuild happens in the background while the existing index keeps serving, so later starts are not delayed
114
+ - **Auto-reindex on start:** skills stay current without manual rebuilds. Only startup ever rebuilds, never a search. The first start pays a one-time build dominated by the embedding-model download (see [Quick Start](#quick-start-5-minutes)); after that a rebuild happens in the background while the existing index keeps serving, so later starts are not delayed
115
115
  - **Memory Engine:** an append-only, versioned fact store with query-time recall scoring — facts decay with age and decay more slowly the more they are recalled. Facts flow in from the diary's `## FACTS` block and are exposed to Claude through five `mem_*` tools (see [Available Tools](#available-tools))
116
116
  - **Living memory at session start:** the SessionStart hook injects a compact, token-budgeted digest of the project's top recall-scored facts into context. It fails open — a missing, empty, or unreachable store never blocks startup
117
117
  - **8-page localhost dashboard:** Overview, Skills Manager, Burn Report, Memory Browser (project-aware diary reader), Memory Inspector (read-only Memory Engine fact views), Agent Monitor, Referrals, and Settings
@@ -248,9 +248,15 @@ The `pattern` field controls how skills are discovered:
248
248
 
249
249
  ## Release Notes
250
250
 
251
+ ### 4.15.1 (August 2026)
252
+
253
+ - **Fixed: activating a Pro license on a PyPI install downloaded the Pro skills and never indexed them.** The skills landed in `~/.memstack/pro-skills`, activation reported success, and the catalog still showed only the 86 free skills. Setting `MEMSTACK_PRO_LICENSE_KEY` was the only workaround. Both of the paths that decide whether to attach the Pro source now share one predicate.
254
+ - **Activation now takes effect immediately.** The reindex that follows an activation rebuilds with the Pro skills included, and the confirmation reports the post-activation count, so a restart is no longer needed.
255
+ - First-start timing in these docs was reworded: it is dominated by a one-time embedding-model download and therefore varies with network speed, rather than being the fixed "roughly a minute" previously quoted.
256
+
251
257
  ### 4.15.0 (August 2026)
252
258
 
253
- - The skill index moved to the canonical state root, so a relative `vector_db_path` no longer depends on which directory the server happened to start in. **The first start after installing or upgrading rebuilds the index once, which takes roughly a minute**, nearly all of it loading the embedding model. Later starts are unaffected.
259
+ - The skill index moved to the canonical state root, so a relative `vector_db_path` no longer depends on which directory the server happened to start in. **The first start after installing or upgrading rebuilds the index once**, nearly all of that time being a one-time embedding-model download. Later starts are unaffected.
254
260
  - New index guard classifies the index as current, drift, stale, absent, or rebuilding, and reports that verdict at startup and in the dashboard. Only startup may rebuild on its own, so no search request ever blocks on one.
255
261
  - Agent Runner git policy rewritten as an argv-level classifier, replacing a substring match that could not tell `git status` from `git push`.
256
262
 
@@ -55,7 +55,7 @@ activate_license(key="your-key", email="you@example.com")
55
55
  ```
56
56
  Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://memstack.pro) to unlock all 44 Pro skills (86 free + 44 Pro = 130 total). Then type `list skills` to verify.
57
57
 
58
- > **The first start is slow, and that is normal.** On the very first run after installing or upgrading, the server builds the skill index before it answers anything. Expect roughly a minute, nearly all of it loading the embedding model rather than reading your skills. **The server will look unresponsive for that time.** Let it finish. Every later start reuses the index and is not affected.
58
+ > **The first start is slow, and that is normal.** On the very first run after installing or upgrading, the server builds the skill index before it answers anything. Nearly all of that time is a one-time download of the ~90 MB embedding model, so it tracks your network speed rather than the size of your skill library: indexing 86 skills itself takes about a second. On a fast connection the whole first start is around half a minute; on a slow one it is longer. **The server will look unresponsive for that time.** Let it finish. Every later start reuses the cached model and index and is not affected.
59
59
 
60
60
  > To override the skills path, set `MEMSTACK_SKILLS_DIR=/path/to/your/memstack/skills`.
61
61
 
@@ -63,7 +63,7 @@ Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://
63
63
 
64
64
  ## Troubleshooting
65
65
 
66
- **Nothing responds after a fresh install, and it does not seem to be failing either (during Step 4):** the first start builds the skill index before the server answers anything, which takes roughly a minute. Most of that is loading the embedding model, not reading your skills. There is no progress output, so a working first start and a hung one look identical. Wait it out before changing anything. If it is still silent well past a couple of minutes, then treat it as the interpreter mismatch below. Later starts reuse the index and respond immediately.
66
+ **Nothing responds after a fresh install, and it does not seem to be failing either (during Step 4):** the first start builds the skill index before the server answers anything. Most of that is a one-time ~90 MB embedding-model download, not reading your skills, so the wait depends on your connection: around half a minute on a fast one, longer on a slow one. There is no progress output, so a working first start and a hung one look identical. Wait it out before changing anything. If it is still silent well past a few minutes, then treat it as the interpreter mismatch below. Later starts reuse the cached model and index and respond immediately.
67
67
 
68
68
  **`error: externally-managed-environment` (during Step 2)** — pip refuses to install on a system-managed Python. Add `--break-system-packages` · (in terminal):
69
69
  ```bash
@@ -94,7 +94,7 @@ MCP server that vector-indexes all 130 MemStack™ skills so Claude Code can cal
94
94
 
95
95
  - **No Anthropic API key required:** skill search and indexing run locally and never leave your machine. Three small calls happen on their own at session start — license validation, a Pro-bundle version probe, and a PyPI update check — and every other outbound call is one you trigger ([Data and privacy](#data-and-privacy))
96
96
  - **Pro skills auto-detected:** once activated, the Pro skills directory is added to your sources automatically, with no `config.json` edit
97
- - **Auto-reindex on start:** skills stay current without manual rebuilds. Only startup ever rebuilds, never a search. The first start pays a one-time build of roughly a minute; after that a rebuild happens in the background while the existing index keeps serving, so later starts are not delayed
97
+ - **Auto-reindex on start:** skills stay current without manual rebuilds. Only startup ever rebuilds, never a search. The first start pays a one-time build dominated by the embedding-model download (see [Quick Start](#quick-start-5-minutes)); after that a rebuild happens in the background while the existing index keeps serving, so later starts are not delayed
98
98
  - **Memory Engine:** an append-only, versioned fact store with query-time recall scoring — facts decay with age and decay more slowly the more they are recalled. Facts flow in from the diary's `## FACTS` block and are exposed to Claude through five `mem_*` tools (see [Available Tools](#available-tools))
99
99
  - **Living memory at session start:** the SessionStart hook injects a compact, token-budgeted digest of the project's top recall-scored facts into context. It fails open — a missing, empty, or unreachable store never blocks startup
100
100
  - **8-page localhost dashboard:** Overview, Skills Manager, Burn Report, Memory Browser (project-aware diary reader), Memory Inspector (read-only Memory Engine fact views), Agent Monitor, Referrals, and Settings
@@ -231,9 +231,15 @@ The `pattern` field controls how skills are discovered:
231
231
 
232
232
  ## Release Notes
233
233
 
234
+ ### 4.15.1 (August 2026)
235
+
236
+ - **Fixed: activating a Pro license on a PyPI install downloaded the Pro skills and never indexed them.** The skills landed in `~/.memstack/pro-skills`, activation reported success, and the catalog still showed only the 86 free skills. Setting `MEMSTACK_PRO_LICENSE_KEY` was the only workaround. Both of the paths that decide whether to attach the Pro source now share one predicate.
237
+ - **Activation now takes effect immediately.** The reindex that follows an activation rebuilds with the Pro skills included, and the confirmation reports the post-activation count, so a restart is no longer needed.
238
+ - First-start timing in these docs was reworded: it is dominated by a one-time embedding-model download and therefore varies with network speed, rather than being the fixed "roughly a minute" previously quoted.
239
+
234
240
  ### 4.15.0 (August 2026)
235
241
 
236
- - The skill index moved to the canonical state root, so a relative `vector_db_path` no longer depends on which directory the server happened to start in. **The first start after installing or upgrading rebuilds the index once, which takes roughly a minute**, nearly all of it loading the embedding model. Later starts are unaffected.
242
+ - The skill index moved to the canonical state root, so a relative `vector_db_path` no longer depends on which directory the server happened to start in. **The first start after installing or upgrading rebuilds the index once**, nearly all of that time being a one-time embedding-model download. Later starts are unaffected.
237
243
  - New index guard classifies the index as current, drift, stale, absent, or rebuilding, and reports that verdict at startup and in the dashboard. Only startup may rebuild on its own, so no search request ever blocks on one.
238
244
  - Agent Runner git policy rewritten as an argv-level classifier, replacing a substring match that could not tell `git status` from `git push`.
239
245
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memstack-skill-loader"
7
- version = "4.15.0"
7
+ version = "4.15.1"
8
8
  description = "MCP server that vector-indexes MemStack Pro skills for on-demand loading"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """MemStack Skill Loader — MCP server for semantic skill search."""
2
2
 
3
- __version__ = "4.15.0"
3
+ __version__ = "4.15.1"
@@ -126,6 +126,32 @@ def _auto_discover_skills() -> SkillSource | None:
126
126
  return None
127
127
 
128
128
 
129
+ def _pro_license_present() -> bool:
130
+ """Return True if this installation has a Pro license on either supported path.
131
+
132
+ Both call sites in load_config must ask this question the same way. They did
133
+ not: the env-var-only form was written once, copied into the auto-discovery
134
+ branch, and then fixed in only one of the two copies (07676eb, 2026-05-20).
135
+ For the two and a half months after that, every PyPI install (which always
136
+ takes the auto-discovery branch, because a wheel has no config.json beside
137
+ the package) downloaded Pro skills on activation and never indexed them.
138
+ Keep this predicate the single source of truth so a third copy cannot drift.
139
+
140
+ The env var and license.json are both first-class: license.json is the
141
+ documented primary path (activate_license writes it), and the env var is the
142
+ documented alternative. This mirrors get_license_key() in license.py, which
143
+ has always resolved both.
144
+
145
+ Note this answers "is a license present", not "is it valid". Entitlement is
146
+ enforced at serve time against a live validate_license() call, so a stale
147
+ license.json here only affects what gets indexed, never what a caller is
148
+ allowed to read.
149
+ """
150
+ if os.environ.get("MEMSTACK_PRO_LICENSE_KEY"):
151
+ return True
152
+ return (Path.home() / ".memstack" / "license.json").exists()
153
+
154
+
129
155
  def load_config(config_path: Path | None = None) -> Config:
130
156
  """Load config from JSON file. Falls back to auto-discovery if not found."""
131
157
  if config_path is None:
@@ -138,7 +164,7 @@ def load_config(config_path: Path | None = None) -> Config:
138
164
  auto_reindex_on_start=True,
139
165
  config_source="auto-discovery",
140
166
  )
141
- if os.environ.get("MEMSTACK_PRO_LICENSE_KEY"):
167
+ if _pro_license_present():
142
168
  config = config.with_pro_skills()
143
169
  return config
144
170
 
@@ -184,7 +210,7 @@ def load_config(config_path: Path | None = None) -> Config:
184
210
  )
185
211
 
186
212
  # Auto-detect pro-skills if license key is set (env var or license.json)
187
- if os.environ.get("MEMSTACK_PRO_LICENSE_KEY") or (Path.home() / ".memstack" / "license.json").exists():
213
+ if _pro_license_present():
188
214
  config = config.with_pro_skills()
189
215
 
190
216
  return config
@@ -465,6 +465,25 @@ def _get_config():
465
465
  return _config
466
466
 
467
467
 
468
+ def _reset_config():
469
+ """Drop the cached config, re-resolve it, and return the new one.
470
+
471
+ The config is resolved once per process, which is correct for everything
472
+ except activation: activate_license writes license.json and downloads the
473
+ Pro skills *after* startup has already decided whether this installation
474
+ has a Pro source. Without re-resolving, the reindex that follows an
475
+ activation rebuilds from the pre-activation source list and the customer is
476
+ told a pre-activation skill count, then has to restart to actually get what
477
+ they just paid for.
478
+
479
+ Callers must rebind their own local: _handle_tool binds ``config`` once at
480
+ entry, so mutating the global alone would not change what that frame sees.
481
+ """
482
+ global _config
483
+ _config = None
484
+ return _get_config()
485
+
486
+
468
487
  _INDEX_ABSENT = "absent"
469
488
  _INDEX_UNREADABLE = "unreadable"
470
489
  _INDEX_EMPTY = "empty"
@@ -1336,6 +1355,15 @@ async def _handle_tool(name: str, arguments: dict) -> list[TextContent]:
1336
1355
  "Please try again or contact support@memstack.pro",
1337
1356
  file=sys.stderr,
1338
1357
  )
1358
+ # save_license_key and the download above changed what
1359
+ # load_config would answer, but this frame's `config` was
1360
+ # resolved at startup, before either had happened. Re-resolve
1361
+ # before indexing so the rebuild below picks up the Pro source
1362
+ # and the total reported back is the post-activation one.
1363
+ # Safe on the download-failure path too: with_pro_skills is
1364
+ # gated on the directory existing, so a failed download simply
1365
+ # yields the same config as before rather than a false count.
1366
+ config = _reset_config()
1339
1367
  from .indexer import build_index
1340
1368
  await asyncio.to_thread(build_index, config)
1341
1369
  reset_cache()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memstack-skill-loader
3
- Version: 4.15.0
3
+ Version: 4.15.1
4
4
  Summary: MCP server that vector-indexes MemStack Pro skills for on-demand loading
5
5
  Project-URL: Homepage, https://memstack.pro
6
6
  Project-URL: Documentation, https://memstack.pro/docs
@@ -72,7 +72,7 @@ activate_license(key="your-key", email="you@example.com")
72
72
  ```
73
73
  Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://memstack.pro) to unlock all 44 Pro skills (86 free + 44 Pro = 130 total). Then type `list skills` to verify.
74
74
 
75
- > **The first start is slow, and that is normal.** On the very first run after installing or upgrading, the server builds the skill index before it answers anything. Expect roughly a minute, nearly all of it loading the embedding model rather than reading your skills. **The server will look unresponsive for that time.** Let it finish. Every later start reuses the index and is not affected.
75
+ > **The first start is slow, and that is normal.** On the very first run after installing or upgrading, the server builds the skill index before it answers anything. Nearly all of that time is a one-time download of the ~90 MB embedding model, so it tracks your network speed rather than the size of your skill library: indexing 86 skills itself takes about a second. On a fast connection the whole first start is around half a minute; on a slow one it is longer. **The server will look unresponsive for that time.** Let it finish. Every later start reuses the cached model and index and is not affected.
76
76
 
77
77
  > To override the skills path, set `MEMSTACK_SKILLS_DIR=/path/to/your/memstack/skills`.
78
78
 
@@ -80,7 +80,7 @@ Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://
80
80
 
81
81
  ## Troubleshooting
82
82
 
83
- **Nothing responds after a fresh install, and it does not seem to be failing either (during Step 4):** the first start builds the skill index before the server answers anything, which takes roughly a minute. Most of that is loading the embedding model, not reading your skills. There is no progress output, so a working first start and a hung one look identical. Wait it out before changing anything. If it is still silent well past a couple of minutes, then treat it as the interpreter mismatch below. Later starts reuse the index and respond immediately.
83
+ **Nothing responds after a fresh install, and it does not seem to be failing either (during Step 4):** the first start builds the skill index before the server answers anything. Most of that is a one-time ~90 MB embedding-model download, not reading your skills, so the wait depends on your connection: around half a minute on a fast one, longer on a slow one. There is no progress output, so a working first start and a hung one look identical. Wait it out before changing anything. If it is still silent well past a few minutes, then treat it as the interpreter mismatch below. Later starts reuse the cached model and index and respond immediately.
84
84
 
85
85
  **`error: externally-managed-environment` (during Step 2)** — pip refuses to install on a system-managed Python. Add `--break-system-packages` · (in terminal):
86
86
  ```bash
@@ -111,7 +111,7 @@ MCP server that vector-indexes all 130 MemStack™ skills so Claude Code can cal
111
111
 
112
112
  - **No Anthropic API key required:** skill search and indexing run locally and never leave your machine. Three small calls happen on their own at session start — license validation, a Pro-bundle version probe, and a PyPI update check — and every other outbound call is one you trigger ([Data and privacy](#data-and-privacy))
113
113
  - **Pro skills auto-detected:** once activated, the Pro skills directory is added to your sources automatically, with no `config.json` edit
114
- - **Auto-reindex on start:** skills stay current without manual rebuilds. Only startup ever rebuilds, never a search. The first start pays a one-time build of roughly a minute; after that a rebuild happens in the background while the existing index keeps serving, so later starts are not delayed
114
+ - **Auto-reindex on start:** skills stay current without manual rebuilds. Only startup ever rebuilds, never a search. The first start pays a one-time build dominated by the embedding-model download (see [Quick Start](#quick-start-5-minutes)); after that a rebuild happens in the background while the existing index keeps serving, so later starts are not delayed
115
115
  - **Memory Engine:** an append-only, versioned fact store with query-time recall scoring — facts decay with age and decay more slowly the more they are recalled. Facts flow in from the diary's `## FACTS` block and are exposed to Claude through five `mem_*` tools (see [Available Tools](#available-tools))
116
116
  - **Living memory at session start:** the SessionStart hook injects a compact, token-budgeted digest of the project's top recall-scored facts into context. It fails open — a missing, empty, or unreachable store never blocks startup
117
117
  - **8-page localhost dashboard:** Overview, Skills Manager, Burn Report, Memory Browser (project-aware diary reader), Memory Inspector (read-only Memory Engine fact views), Agent Monitor, Referrals, and Settings
@@ -248,9 +248,15 @@ The `pattern` field controls how skills are discovered:
248
248
 
249
249
  ## Release Notes
250
250
 
251
+ ### 4.15.1 (August 2026)
252
+
253
+ - **Fixed: activating a Pro license on a PyPI install downloaded the Pro skills and never indexed them.** The skills landed in `~/.memstack/pro-skills`, activation reported success, and the catalog still showed only the 86 free skills. Setting `MEMSTACK_PRO_LICENSE_KEY` was the only workaround. Both of the paths that decide whether to attach the Pro source now share one predicate.
254
+ - **Activation now takes effect immediately.** The reindex that follows an activation rebuilds with the Pro skills included, and the confirmation reports the post-activation count, so a restart is no longer needed.
255
+ - First-start timing in these docs was reworded: it is dominated by a one-time embedding-model download and therefore varies with network speed, rather than being the fixed "roughly a minute" previously quoted.
256
+
251
257
  ### 4.15.0 (August 2026)
252
258
 
253
- - The skill index moved to the canonical state root, so a relative `vector_db_path` no longer depends on which directory the server happened to start in. **The first start after installing or upgrading rebuilds the index once, which takes roughly a minute**, nearly all of it loading the embedding model. Later starts are unaffected.
259
+ - The skill index moved to the canonical state root, so a relative `vector_db_path` no longer depends on which directory the server happened to start in. **The first start after installing or upgrading rebuilds the index once**, nearly all of that time being a one-time embedding-model download. Later starts are unaffected.
254
260
  - New index guard classifies the index as current, drift, stale, absent, or rebuilding, and reports that verdict at startup and in the dashboard. Only startup may rebuild on its own, so no search request ever blocks on one.
255
261
  - Agent Runner git policy rewritten as an argv-level classifier, replacing a substring match that could not tell `git status` from `git push`.
256
262
 
@@ -61,6 +61,7 @@ tests/test_memory_scoring.py
61
61
  tests/test_memory_tools.py
62
62
  tests/test_pro_compressor.py
63
63
  tests/test_pro_skills_update.py
64
+ tests/test_pro_source_resolution.py
64
65
  tests/test_project_dir_canonical.py
65
66
  tests/test_search_memory.py
66
67
  tests/test_session_context.py
@@ -0,0 +1,227 @@
1
+ """Pro skill source resolution must be identical on both load_config branches.
2
+
3
+ ``load_config`` has two exits that each decide whether to attach the Pro skill
4
+ source, and they must answer that question the same way:
5
+
6
+ * the **config.json** branch, taken by a dev checkout (a config.json sits
7
+ beside the package), and
8
+ * the **auto-discovery** branch, taken by every PyPI install (a wheel has no
9
+ config.json three levels up from the module).
10
+
11
+ They drifted. The env-var-only form was written once, copied into the
12
+ auto-discovery branch, and then fixed in only one of the two copies by 07676eb
13
+ (2026-05-20, "startup checks license.json for Pro skills, not just env var").
14
+ For the next two and a half months a paying customer on a PyPI install could run
15
+ ``activate_license``, watch the Pro skills download, and never see them indexed,
16
+ because their branch still required an environment variable that the product
17
+ documents as the *alternative* to the license file. 4.15.0 shipped that way.
18
+
19
+ These tests pin the invariant rather than the implementation: whatever the
20
+ answer is, **both branches must give it**. Every case is parametrised over both,
21
+ so a future third copy of the gate cannot pass here while diverging in practice.
22
+
23
+ Isolation: HOME/USERPROFILE are redirected to tmp_path and
24
+ MEMSTACK_PRO_LICENSE_KEY is removed, so nothing reads the real ~/.memstack.
25
+ """
26
+
27
+ import json
28
+
29
+ import pytest
30
+
31
+ from memstack_skill_loader.config import _pro_license_present, load_config
32
+
33
+ PRO_LABEL = "MemStack Pro"
34
+
35
+
36
+ # ── fixtures ──────────────────────────────────────────────────────────────────
37
+
38
+
39
+ @pytest.fixture
40
+ def fake_home(tmp_path, monkeypatch):
41
+ """Redirect Path.home() to tmp_path and guarantee no license env var.
42
+
43
+ Both HOME and USERPROFILE are set because Path.home() reads USERPROFILE on
44
+ Windows and HOME on POSIX; setting one only would make this test pass on one
45
+ platform and silently no-op on the other.
46
+ """
47
+ home = tmp_path / "home"
48
+ home.mkdir()
49
+ monkeypatch.setenv("HOME", str(home))
50
+ monkeypatch.setenv("USERPROFILE", str(home))
51
+ monkeypatch.delenv("MEMSTACK_PRO_LICENSE_KEY", raising=False)
52
+ monkeypatch.delenv("MEMSTACK_SKILLS_DIR", raising=False)
53
+ return home
54
+
55
+
56
+ def _write_license(home):
57
+ """Create ~/.memstack/license.json the way save_license_key would."""
58
+ memstack = home / ".memstack"
59
+ memstack.mkdir(parents=True, exist_ok=True)
60
+ (memstack / "license.json").write_text(json.dumps({"license_key": "TEST-KEY"}))
61
+
62
+
63
+ def _write_pro_skills(home):
64
+ """Create a downloaded ~/.memstack/pro-skills tree with its .complete marker.
65
+
66
+ The marker matters: Config.pro_skills_dir only prefers the customer
67
+ directory when .complete is present, and otherwise falls back to a bundled
68
+ directory beside the package, which exists in a dev checkout and would make
69
+ an assertion about *which* directory got attached meaningless.
70
+ """
71
+ pro = home / ".memstack" / "pro-skills" / "example-pro-skill"
72
+ pro.mkdir(parents=True, exist_ok=True)
73
+ (pro / "SKILL.md").write_text("---\nname: example-pro-skill\n---\nbody\n")
74
+ (home / ".memstack" / "pro-skills" / ".complete").write_text("")
75
+ return home / ".memstack" / "pro-skills"
76
+
77
+
78
+ def _write_free_skills(home):
79
+ """Create the plugin-marketplace tree that _auto_discover_skills looks for."""
80
+ skills = (home / ".claude" / "plugins" / "marketplaces"
81
+ / "cwinvestments-memstack" / "skills" / "example-free-skill")
82
+ skills.mkdir(parents=True, exist_ok=True)
83
+ (skills / "SKILL.md").write_text("---\nname: example-free-skill\n---\nbody\n")
84
+ return skills.parent
85
+
86
+
87
+ def _load(branch, tmp_path, home):
88
+ """Load config through the requested branch of load_config."""
89
+ if branch == "auto-discovery":
90
+ # A path that does not exist forces the auto-discovery exit, which is
91
+ # what a wheel install always gets.
92
+ return load_config(tmp_path / "no-such-config.json")
93
+
94
+ config_path = tmp_path / "config.json"
95
+ config_path.write_text(json.dumps({
96
+ "skill_sources": [{
97
+ "type": "local",
98
+ "path": str(_write_free_skills(home)),
99
+ "pattern": "**/SKILL.md",
100
+ "label": "MemStack",
101
+ }],
102
+ }))
103
+ return load_config(config_path)
104
+
105
+
106
+ def _has_pro(config):
107
+ return any(s.label == PRO_LABEL for s in config.skill_sources)
108
+
109
+
110
+ BRANCHES = ["auto-discovery", "config.json"]
111
+
112
+
113
+ # ── 1. the regression: license.json alone must attach the Pro source ──────────
114
+
115
+
116
+ @pytest.mark.parametrize("branch", BRANCHES)
117
+ def test_license_json_alone_attaches_pro_source(branch, tmp_path, fake_home):
118
+ """A license file and no env var is the documented primary path.
119
+
120
+ This is the 4.15.0 bug. It passed on config.json and failed on
121
+ auto-discovery, which is the branch every paying PyPI customer takes.
122
+ """
123
+ _write_license(fake_home)
124
+ _write_free_skills(fake_home)
125
+ pro_dir = _write_pro_skills(fake_home)
126
+
127
+ config = _load(branch, tmp_path, fake_home)
128
+
129
+ assert _has_pro(config), (
130
+ f"{branch} branch did not attach the Pro source with license.json present "
131
+ f"and no MEMSTACK_PRO_LICENSE_KEY set"
132
+ )
133
+ attached = next(s for s in config.skill_sources if s.label == PRO_LABEL)
134
+ assert str(pro_dir) == attached.path, "attached the wrong pro-skills directory"
135
+
136
+
137
+ # ── 2. the env var remains a first-class alternative ─────────────────────────
138
+
139
+
140
+ @pytest.mark.parametrize("branch", BRANCHES)
141
+ def test_env_var_alone_attaches_pro_source(branch, tmp_path, fake_home, monkeypatch):
142
+ """No license.json, env var set: the documented alternative for advanced users."""
143
+ monkeypatch.setenv("MEMSTACK_PRO_LICENSE_KEY", "TEST-KEY")
144
+ _write_free_skills(fake_home)
145
+ _write_pro_skills(fake_home)
146
+
147
+ config = _load(branch, tmp_path, fake_home)
148
+
149
+ assert not (fake_home / ".memstack" / "license.json").exists()
150
+ assert _has_pro(config), f"{branch} branch ignored MEMSTACK_PRO_LICENSE_KEY"
151
+
152
+
153
+ # ── 3. no license of any kind must NOT attach it ─────────────────────────────
154
+
155
+
156
+ @pytest.mark.parametrize("branch", BRANCHES)
157
+ def test_no_license_does_not_attach_pro_source(branch, tmp_path, fake_home):
158
+ """Absent both signals, the Pro source stays off: the fix must not over-attach."""
159
+ _write_free_skills(fake_home)
160
+ _write_pro_skills(fake_home)
161
+
162
+ config = _load(branch, tmp_path, fake_home)
163
+
164
+ assert not _has_pro(config), (
165
+ f"{branch} branch attached the Pro source with no license present at all"
166
+ )
167
+
168
+
169
+ # ── 4. the two branches must never disagree ──────────────────────────────────
170
+
171
+
172
+ @pytest.mark.parametrize(
173
+ "with_license, with_env",
174
+ [(True, False), (False, True), (True, True), (False, False)],
175
+ )
176
+ def test_both_branches_always_agree(with_license, with_env, tmp_path, fake_home, monkeypatch):
177
+ """The invariant the drift violated, stated directly.
178
+
179
+ Whatever the gate decides, an installed wheel and a dev checkout must decide
180
+ it identically. This is the assertion that would have failed in May.
181
+ """
182
+ if with_license:
183
+ _write_license(fake_home)
184
+ if with_env:
185
+ monkeypatch.setenv("MEMSTACK_PRO_LICENSE_KEY", "TEST-KEY")
186
+ _write_free_skills(fake_home)
187
+ _write_pro_skills(fake_home)
188
+
189
+ auto = _has_pro(_load("auto-discovery", tmp_path, fake_home))
190
+ cfg = _has_pro(_load("config.json", tmp_path, fake_home))
191
+
192
+ assert auto == cfg, (
193
+ f"branches disagree with license={with_license} env={with_env}: "
194
+ f"auto-discovery={auto} config.json={cfg}"
195
+ )
196
+ assert auto is (with_license or with_env)
197
+
198
+
199
+ # ── 5. the shared predicate itself ───────────────────────────────────────────
200
+
201
+
202
+ def test_predicate_false_when_nothing_present(fake_home):
203
+ assert _pro_license_present() is False
204
+
205
+
206
+ def test_predicate_true_on_license_json(fake_home):
207
+ _write_license(fake_home)
208
+ assert _pro_license_present() is True
209
+
210
+
211
+ def test_predicate_true_on_env_var(fake_home, monkeypatch):
212
+ monkeypatch.setenv("MEMSTACK_PRO_LICENSE_KEY", "TEST-KEY")
213
+ assert _pro_license_present() is True
214
+
215
+
216
+ def test_predicate_does_not_validate_the_license(fake_home):
217
+ """Presence, not validity: entitlement is enforced at serve time.
218
+
219
+ Pinned deliberately: a stale license.json still attaches the Pro source, and
220
+ that is safe only because find_skill/list_skills/get_skill each re-check the
221
+ live license tier before returning Pro content. If someone ever makes this
222
+ predicate validate, that is a design change, not a tidy-up.
223
+ """
224
+ memstack = fake_home / ".memstack"
225
+ memstack.mkdir(parents=True, exist_ok=True)
226
+ (memstack / "license.json").write_text("this is not even valid json")
227
+ assert _pro_license_present() is True