memstack-skill-loader 4.13.0__tar.gz → 4.13.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.
- {memstack_skill_loader-4.13.0/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.13.1}/PKG-INFO +1 -1
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/pyproject.toml +1 -1
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/diary_ingest.py +22 -2
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_diary_ingest.py +69 -1
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/README.md +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/setup.cfg +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/agent_runner.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/categories.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/dashboard.html +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/dashboard.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/goal.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/goal_engine.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/hooks/__init__.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/hooks/deny_git.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/hooks/deny_manager_write.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/license.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/memory_db.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/memory_engine.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/proxy/__init__.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/proxy/_diag.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/proxy/body_parser.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/proxy/compressor.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/proxy/forwarder.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/proxy/pro_compressor.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/proxy/server.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/proxy/stats_tracker.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/server.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/session_context.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/skill_descriptions.json +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/stats.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader.egg-info/SOURCES.txt +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_advisor_strategy.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_body_parser_metric.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_builder_scope.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_code_read_savings.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_failed_approaches.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_license_environment.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_license_grace.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_memory_engine.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_memory_inspector.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_memory_scoring.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_memory_tools.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_pro_compressor.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_pro_skills_update.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_search_memory.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_session_context.py +0 -0
- {memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_skill_drift.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memstack-skill-loader"
|
|
7
|
-
version = "4.13.
|
|
7
|
+
version = "4.13.1"
|
|
8
8
|
description = "MCP server that vector-indexes MemStack Pro skills for on-demand loading"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
@@ -7,7 +7,8 @@ module by path, exactly like the devlog-webhook:
|
|
|
7
7
|
python -m memstack_skill_loader.diary_ingest "<markdown-backup-path>"
|
|
8
8
|
|
|
9
9
|
Each fact line is stored through :class:`MemoryStore` with ``source_type='diary'``
|
|
10
|
-
and ``source_ref`` set to the diary file path
|
|
10
|
+
and ``source_ref`` set to the diary file path, canonicalized to an absolute
|
|
11
|
+
forward-slash path so the same file re-ingested from any cwd/arg form dedupes.
|
|
11
12
|
|
|
12
13
|
FAIL-OPEN CONTRACT (do not "improve" away)
|
|
13
14
|
------------------------------------------
|
|
@@ -141,6 +142,25 @@ def _project_from_path(path: str) -> str:
|
|
|
141
142
|
return slug or "default"
|
|
142
143
|
|
|
143
144
|
|
|
145
|
+
def _canonical_source_ref(path: str) -> str:
|
|
146
|
+
"""Canonicalize a diary path into a stable dedupe key.
|
|
147
|
+
|
|
148
|
+
The Memory Engine dedupe key is ``(source_ref, subject, claim)`` compared as
|
|
149
|
+
raw strings. A diary saved once via a relative path and once via an absolute
|
|
150
|
+
path therefore produced two different ``source_ref`` strings and re-stored
|
|
151
|
+
every fact as a duplicate live row. Resolving to a real absolute path
|
|
152
|
+
(collapsing ``..`` segments and following symlinks) and emitting it with
|
|
153
|
+
forward slashes makes every path form of the same file collapse to one key.
|
|
154
|
+
|
|
155
|
+
Fail-open: this runs in the never-break-the-diary-save path, so if resolution
|
|
156
|
+
fails for any reason, fall back to the raw string rather than raise.
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
return Path(path).resolve().as_posix()
|
|
160
|
+
except OSError:
|
|
161
|
+
return str(path)
|
|
162
|
+
|
|
163
|
+
|
|
144
164
|
def _warn(message: str) -> None:
|
|
145
165
|
print(f"{_STDERR_PREFIX} {message}", file=sys.stderr)
|
|
146
166
|
|
|
@@ -169,7 +189,7 @@ def ingest_diary_file(path: str, store: MemoryStore | None = None) -> dict:
|
|
|
169
189
|
if store is None:
|
|
170
190
|
store = MemoryStore()
|
|
171
191
|
project = _project_from_path(path)
|
|
172
|
-
source_ref =
|
|
192
|
+
source_ref = _canonical_source_ref(path)
|
|
173
193
|
|
|
174
194
|
ingested = 0
|
|
175
195
|
duplicates = 0
|
|
@@ -116,9 +116,13 @@ def test_ingest_stores_facts_with_diary_provenance(tmp_path):
|
|
|
116
116
|
store = MemoryStore()
|
|
117
117
|
rows = store.list_facts(project="demoproj", limit=50)
|
|
118
118
|
assert len(rows) == 4
|
|
119
|
+
# source_ref is stored in canonical form: an absolute, forward-slash path
|
|
120
|
+
# (resolve + as_posix), so re-ingests from any cwd/arg form dedupe.
|
|
121
|
+
expected_ref = Path(p).resolve().as_posix()
|
|
119
122
|
for r in rows:
|
|
120
123
|
assert r["source_type"] == "diary"
|
|
121
|
-
assert r["source_ref"] ==
|
|
124
|
+
assert r["source_ref"] == expected_ref
|
|
125
|
+
assert "\\" not in r["source_ref"]
|
|
122
126
|
# entities landed
|
|
123
127
|
supa = [r for r in rows if r["subject"] == "adminstack.portal.auth"][0]
|
|
124
128
|
assert set(store.get_entities(supa["id"])) == {"supabase", "auth"}
|
|
@@ -198,6 +202,70 @@ def test_fact_exists_dedupe_key(tmp_path):
|
|
|
198
202
|
assert store.fact_exists("s.x", "the claim", "/other.md") is False
|
|
199
203
|
|
|
200
204
|
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
# source_ref canonicalization: the SAME file ingested via different path forms
|
|
207
|
+
# (relative, absolute, ../-laden, symlinked) must dedupe to ONE fact. Regression
|
|
208
|
+
# guard for the relative-vs-absolute duplicate-fact gap (4 dupes in the real
|
|
209
|
+
# store on 2026-07-23). Genuinely different files must still stay separate.
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
def test_canonical_source_ref_is_absolute_forward_slash(tmp_path, monkeypatch):
|
|
213
|
+
p = _diary(tmp_path, "placeholder")
|
|
214
|
+
monkeypatch.chdir(tmp_path)
|
|
215
|
+
canon = diary_ingest._canonical_source_ref(p.name) # relative input
|
|
216
|
+
assert canon == Path(p).resolve().as_posix()
|
|
217
|
+
assert Path(canon).is_absolute()
|
|
218
|
+
assert "\\" not in canon # forward-slash only
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_relative_and_absolute_path_dedupe_to_one_fact(tmp_path, monkeypatch):
|
|
222
|
+
p = _diary(tmp_path, CANONICAL_EXAMPLES)
|
|
223
|
+
monkeypatch.chdir(tmp_path)
|
|
224
|
+
first = diary_ingest.ingest_diary_file(p.name) # relative path
|
|
225
|
+
second = diary_ingest.ingest_diary_file(str(p.resolve())) # absolute path
|
|
226
|
+
assert first["ingested"] == 4
|
|
227
|
+
assert second["ingested"] == 0
|
|
228
|
+
assert second["duplicates"] == 4
|
|
229
|
+
assert MemoryStore().stats(project="demoproj")["total"] == 4
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def test_dotdot_segments_canonicalize_to_same_fact(tmp_path):
|
|
233
|
+
p = _diary(tmp_path, CANONICAL_EXAMPLES)
|
|
234
|
+
sub = tmp_path / "sub"
|
|
235
|
+
sub.mkdir()
|
|
236
|
+
detour = sub / ".." / p.name # resolves back to the same file
|
|
237
|
+
first = diary_ingest.ingest_diary_file(str(p))
|
|
238
|
+
second = diary_ingest.ingest_diary_file(str(detour))
|
|
239
|
+
assert first["ingested"] == 4
|
|
240
|
+
assert second["duplicates"] == 4
|
|
241
|
+
assert MemoryStore().stats(project="demoproj")["total"] == 4
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_symlinked_path_canonicalizes_to_same_fact(tmp_path):
|
|
245
|
+
p = _diary(tmp_path, CANONICAL_EXAMPLES)
|
|
246
|
+
link = tmp_path / "2026-07-20-demoproj-link.md"
|
|
247
|
+
try:
|
|
248
|
+
link.symlink_to(p)
|
|
249
|
+
except (OSError, NotImplementedError):
|
|
250
|
+
pytest.skip("symlink creation not permitted on this platform")
|
|
251
|
+
first = diary_ingest.ingest_diary_file(str(p))
|
|
252
|
+
second = diary_ingest.ingest_diary_file(str(link)) # follows to real path
|
|
253
|
+
assert first["ingested"] == 4
|
|
254
|
+
assert second["duplicates"] == 4
|
|
255
|
+
assert MemoryStore().stats(project="demoproj")["total"] == 4
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_two_different_diaries_store_as_separate_facts(tmp_path):
|
|
259
|
+
p1 = _diary(tmp_path, CANONICAL_EXAMPLES, name="2026-07-20-demoproj.md")
|
|
260
|
+
p2 = _diary(tmp_path, CANONICAL_EXAMPLES, name="2026-07-21-demoproj.md")
|
|
261
|
+
a = diary_ingest.ingest_diary_file(str(p1))
|
|
262
|
+
b = diary_ingest.ingest_diary_file(str(p2))
|
|
263
|
+
assert a["ingested"] == 4
|
|
264
|
+
assert b["ingested"] == 4 # different source_ref => not dupes
|
|
265
|
+
assert b["duplicates"] == 0
|
|
266
|
+
assert MemoryStore().stats(project="demoproj")["total"] == 8
|
|
267
|
+
|
|
268
|
+
|
|
201
269
|
# ---------------------------------------------------------------------------
|
|
202
270
|
# cross-repo drift guard: the diary SKILL.md examples must parse to exactly the
|
|
203
271
|
# facts CANONICAL_EXAMPLES parses to. The skill lives in a SIBLING repo
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/config.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/dashboard.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/goal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/indexer.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/license.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/memory_db.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/search.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/src/memstack_skill_loader/server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/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
|
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_advisor_strategy.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_body_parser_metric.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_code_read_savings.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_failed_approaches.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_license_environment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_memory_inspector.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.13.0 → memstack_skill_loader-4.13.1}/tests/test_pro_skills_update.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|