memstack-skill-loader 4.3.0__tar.gz → 4.4.0rc1__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.3.0/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.4.0rc1}/PKG-INFO +1 -1
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/README.md +3 -3
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/pyproject.toml +1 -1
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/agent_runner.py +106 -23
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/dashboard.html +468 -56
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/dashboard.py +182 -4
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/license.py +2 -2
- memstack_skill_loader-4.4.0rc1/src/memstack_skill_loader/proxy/_diag.py +83 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/proxy/body_parser.py +62 -20
- memstack_skill_loader-4.4.0rc1/src/memstack_skill_loader/proxy/compressor.py +26 -0
- memstack_skill_loader-4.4.0rc1/src/memstack_skill_loader/proxy/pro_compressor.py +767 -0
- memstack_skill_loader-4.4.0rc1/src/memstack_skill_loader/proxy/server.py +293 -0
- memstack_skill_loader-4.4.0rc1/src/memstack_skill_loader/proxy/stats_tracker.py +205 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/server.py +1 -1
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/stats.py +18 -6
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader.egg-info/SOURCES.txt +1 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/tests/test_pro_compressor.py +3 -1
- memstack_skill_loader-4.3.0/src/memstack_skill_loader/proxy/compressor.py +0 -77
- memstack_skill_loader-4.3.0/src/memstack_skill_loader/proxy/pro_compressor.py +0 -312
- memstack_skill_loader-4.3.0/src/memstack_skill_loader/proxy/server.py +0 -287
- memstack_skill_loader-4.3.0/src/memstack_skill_loader/proxy/stats_tracker.py +0 -125
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/setup.cfg +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/categories.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/memory_db.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/proxy/__init__.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/proxy/forwarder.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0rc1}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MemStack™ Skill Loader
|
|
2
2
|
|
|
3
|
-
**127 skills for Claude Code** —
|
|
3
|
+
**127 skills for Claude Code** — 84 free + 43 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
|
|
|
@@ -133,13 +133,13 @@ The `pattern` field controls how skills are discovered:
|
|
|
133
133
|
- Dashboard: 6-page localhost dashboard (Overview, Skills Manager, Burn Report, Memory Browser, Agent Monitor, Settings)
|
|
134
134
|
- Agent Runner: 3-agent orchestration (Manager/Builder/Reviewer) with per-agent model selection
|
|
135
135
|
- 17 MCP tools
|
|
136
|
-
- 127 skills (
|
|
136
|
+
- 127 skills (84 free + 43 Pro)
|
|
137
137
|
- Real-time context window monitoring per agent
|
|
138
138
|
- Session diary with AI-authored markdown narratives
|
|
139
139
|
- Safe git staging (prevents accidental commits of secrets/runtime data)
|
|
140
140
|
- Task completion notifications (browser, tab flash, audio)
|
|
141
141
|
- Token usage tracking with estimated costs
|
|
142
|
-
-
|
|
142
|
+
- TokenStack™ proxy integration (~35-40% token savings)
|
|
143
143
|
|
|
144
144
|
**[v3.4.0](https://github.com/cwinvestments/memstack-skill-loader/releases/tag/v3.4.0)** — 100 Skills Milestone (18 new Pro skills, auto-detection, display name fixes)
|
|
145
145
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memstack-skill-loader"
|
|
7
|
-
version = "4.
|
|
7
|
+
version = "4.4.0rc1"
|
|
8
8
|
description = "MCP server that vector-indexes MemStack Pro skills for on-demand loading"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
@@ -25,6 +25,7 @@ MODEL_PRICING = {
|
|
|
25
25
|
"claude-sonnet-4-6": {"input": 3.0, "output": 15.0},
|
|
26
26
|
"claude-opus-4-6": {"input": 5.0, "output": 25.0},
|
|
27
27
|
"claude-opus-4-7": {"input": 5.0, "output": 25.0},
|
|
28
|
+
"claude-opus-4-8": {"input": 5.0, "output": 25.0},
|
|
28
29
|
"claude-haiku-4-5": {"input": 1.0, "output": 5.0},
|
|
29
30
|
}
|
|
30
31
|
# Cache read: 90% discount on input rate
|
|
@@ -61,7 +62,7 @@ STATE_FILE = STATE_DIR / "state.json"
|
|
|
61
62
|
|
|
62
63
|
AGENT_TIMEOUT = 3600 # seconds per agent invocation (default 60 minutes)
|
|
63
64
|
INACTIVITY_TIMEOUT = 900 # seconds without stdout before killing agent (15 minutes)
|
|
64
|
-
MAX_ITERATIONS =
|
|
65
|
+
MAX_ITERATIONS = 9999
|
|
65
66
|
|
|
66
67
|
ANTHROPIC_BASE_URL = os.environ.get("ANTHROPIC_BASE_URL", "")
|
|
67
68
|
API_KEY_FILE = Path.home() / ".memstack" / "api_key"
|
|
@@ -430,6 +431,48 @@ def _summarize_subtask_titles(task_hint: str, _re) -> str:
|
|
|
430
431
|
)
|
|
431
432
|
titles = [m.group(1).strip() for m in numbered_pat.finditer(task_hint) if m.group(1).strip()]
|
|
432
433
|
|
|
434
|
+
# Fallback 3: grab any numbered list items (1. Do X, 2. Do Y)
|
|
435
|
+
if not titles:
|
|
436
|
+
list_pat = _re.compile(
|
|
437
|
+
r'^\s*\d+[.)]\s+(.{10,})',
|
|
438
|
+
_re.MULTILINE,
|
|
439
|
+
)
|
|
440
|
+
raw = [m.group(1).strip() for m in list_pat.finditer(task_hint)]
|
|
441
|
+
boilerplate = _re.compile(
|
|
442
|
+
r'^(?:Read\s|Run\s|Branch:|Working directory:|Do NOT\b|Do not\b|Report\s|---)',
|
|
443
|
+
_re.IGNORECASE,
|
|
444
|
+
)
|
|
445
|
+
titles = [t for t in raw if not boilerplate.match(t)][:5]
|
|
446
|
+
|
|
447
|
+
# Fallback 4: grab dash/bullet list items (- Do X, * Do Y)
|
|
448
|
+
if not titles:
|
|
449
|
+
bullet_pat = _re.compile(
|
|
450
|
+
r'^\s*[-*]\s+(.{10,})',
|
|
451
|
+
_re.MULTILINE,
|
|
452
|
+
)
|
|
453
|
+
raw = [m.group(1).strip() for m in bullet_pat.finditer(task_hint)]
|
|
454
|
+
boilerplate = _re.compile(
|
|
455
|
+
r'^(?:Read\s|Run\s|Branch:|Working directory:|Do NOT\b|Do not\b|Report\s|---|\s*src/|\s*\./)',
|
|
456
|
+
_re.IGNORECASE,
|
|
457
|
+
)
|
|
458
|
+
titles = [t for t in raw if not boilerplate.match(t)][:5]
|
|
459
|
+
|
|
460
|
+
# Fallback 5: if still nothing, take the first non-boilerplate line over 15 chars as a single title
|
|
461
|
+
if not titles:
|
|
462
|
+
boilerplate = _re.compile(
|
|
463
|
+
r'^(?:Read\s|Run\s|Branch:|Working directory:|Do NOT\b|Do not\b|Report\s|---|#{1,4}\s|'
|
|
464
|
+
r'STEP\s|Audit\s|src/|\./',
|
|
465
|
+
_re.IGNORECASE,
|
|
466
|
+
)
|
|
467
|
+
for _line in task_hint.splitlines():
|
|
468
|
+
_s = _line.strip()
|
|
469
|
+
if not _s or len(_s) < 15:
|
|
470
|
+
continue
|
|
471
|
+
if boilerplate.match(_s):
|
|
472
|
+
continue
|
|
473
|
+
titles = [_s]
|
|
474
|
+
break
|
|
475
|
+
|
|
433
476
|
if not titles:
|
|
434
477
|
return "multiple fixes"
|
|
435
478
|
|
|
@@ -470,6 +513,55 @@ def _summarize_subtask_titles(task_hint: str, _re) -> str:
|
|
|
470
513
|
return combined
|
|
471
514
|
|
|
472
515
|
|
|
516
|
+
def _describe_from_diff(changed_files: list, stat_output: str, working_dir: str, _re) -> str:
|
|
517
|
+
"""Generate a short commit description by analyzing the actual git diff content."""
|
|
518
|
+
try:
|
|
519
|
+
git_kw = dict(capture_output=True, text=True, cwd=working_dir, timeout=30)
|
|
520
|
+
diff_r = subprocess.run(["git", "diff", "--cached", "-U0"], **git_kw)
|
|
521
|
+
diff_text = diff_r.stdout if diff_r.returncode == 0 else ""
|
|
522
|
+
if not diff_text:
|
|
523
|
+
diff_r = subprocess.run(["git", "diff", "-U0"], **git_kw)
|
|
524
|
+
diff_text = diff_r.stdout if diff_r.returncode == 0 else ""
|
|
525
|
+
except Exception:
|
|
526
|
+
diff_text = ""
|
|
527
|
+
|
|
528
|
+
added_funcs = []
|
|
529
|
+
removed_funcs = []
|
|
530
|
+
if diff_text:
|
|
531
|
+
for line in diff_text.splitlines():
|
|
532
|
+
m = _re.match(r'^\+(?!\+\+)\s*(?:def|async def|class)\s+(\w+)', line)
|
|
533
|
+
if m:
|
|
534
|
+
added_funcs.append(m.group(1))
|
|
535
|
+
m = _re.match(r'^-(?!--)\s*(?:def|async def|class)\s+(\w+)', line)
|
|
536
|
+
if m:
|
|
537
|
+
removed_funcs.append(m.group(1))
|
|
538
|
+
|
|
539
|
+
if len(changed_files) == 1:
|
|
540
|
+
fname = changed_files[0].replace("\\", "/").split("/")[-1]
|
|
541
|
+
if added_funcs and not removed_funcs:
|
|
542
|
+
names = ", ".join(added_funcs[:2])
|
|
543
|
+
desc = f"add {names} to {fname}"
|
|
544
|
+
elif removed_funcs and not added_funcs:
|
|
545
|
+
names = ", ".join(removed_funcs[:2])
|
|
546
|
+
desc = f"remove {names} from {fname}"
|
|
547
|
+
elif added_funcs or removed_funcs:
|
|
548
|
+
desc = f"update {fname} functions"
|
|
549
|
+
else:
|
|
550
|
+
desc = f"update {fname}"
|
|
551
|
+
return desc[:60]
|
|
552
|
+
|
|
553
|
+
if added_funcs and not removed_funcs:
|
|
554
|
+
names = ", ".join(added_funcs[:2])
|
|
555
|
+
desc = f"add {names}"
|
|
556
|
+
return desc[:60]
|
|
557
|
+
if removed_funcs and not added_funcs:
|
|
558
|
+
names = ", ".join(removed_funcs[:2])
|
|
559
|
+
desc = f"remove {names}"
|
|
560
|
+
return desc[:60]
|
|
561
|
+
|
|
562
|
+
return ""
|
|
563
|
+
|
|
564
|
+
|
|
473
565
|
def _build_commit_message_from_diff(working_dir: str, task_hint: str = "") -> str:
|
|
474
566
|
"""Build a commit message from actual staged/unstaged git changes.
|
|
475
567
|
|
|
@@ -540,23 +632,8 @@ def _build_commit_message_from_diff(working_dir: str, task_hint: str = "") -> st
|
|
|
540
632
|
task_desc = ""
|
|
541
633
|
if has_subtasks:
|
|
542
634
|
task_desc = _summarize_subtask_titles(task_hint, _re)
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
_stripped = _line.strip()
|
|
546
|
-
if not _stripped:
|
|
547
|
-
continue
|
|
548
|
-
if _BOILERPLATE.match(_stripped):
|
|
549
|
-
continue
|
|
550
|
-
if _FILE_PATH_LINE.match(_stripped):
|
|
551
|
-
continue
|
|
552
|
-
if _SECTION_HEADER.match(_stripped):
|
|
553
|
-
continue
|
|
554
|
-
if _NUMBERED_FIX.match(_stripped):
|
|
555
|
-
continue
|
|
556
|
-
cleaned = _PREFIX_STRIP.sub('', _stripped).strip()
|
|
557
|
-
if len(cleaned) >= 10:
|
|
558
|
-
task_desc = cleaned.lower()[:60]
|
|
559
|
-
break
|
|
635
|
+
else:
|
|
636
|
+
task_desc = _describe_from_diff(changed_files, stat_output, working_dir, _re)
|
|
560
637
|
|
|
561
638
|
def _short_name(fpath: str) -> str:
|
|
562
639
|
parts = fpath.replace("\\", "/").split("/")
|
|
@@ -1589,7 +1666,8 @@ def _orchestrate(session: Session) -> None:
|
|
|
1589
1666
|
if "APPROVED" in reviewer_output.upper():
|
|
1590
1667
|
subtask_passed = True
|
|
1591
1668
|
subtasks_completed += 1
|
|
1592
|
-
|
|
1669
|
+
builder_snippet = builder_output or ""
|
|
1670
|
+
subtask_results.append(f"Subtask {idx}: PASSED - {title}\n{builder_snippet}")
|
|
1593
1671
|
session.add_message("system", "all", f"Subtask {idx} of {len(subtasks)} approved: {title}")
|
|
1594
1672
|
break
|
|
1595
1673
|
|
|
@@ -1601,7 +1679,11 @@ def _orchestrate(session: Session) -> None:
|
|
|
1601
1679
|
else:
|
|
1602
1680
|
if not subtask_passed:
|
|
1603
1681
|
subtasks_failed += 1
|
|
1604
|
-
|
|
1682
|
+
reviewer_snippet = (reviewer_output or "") if reviewer_output else ""
|
|
1683
|
+
fail_entry = f"Subtask {idx}: FAILED (max iterations) - {title}"
|
|
1684
|
+
if reviewer_snippet:
|
|
1685
|
+
fail_entry += f"\n{reviewer_snippet}"
|
|
1686
|
+
subtask_results.append(fail_entry)
|
|
1605
1687
|
session.add_message("system", "all", f"Subtask {idx} of {len(subtasks)} failed: {title} - max iterations reached")
|
|
1606
1688
|
|
|
1607
1689
|
summary = f"Completed {subtasks_completed}/{len(subtasks)} subtasks"
|
|
@@ -1668,6 +1750,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
1668
1750
|
|
|
1669
1751
|
session.status = "completed"
|
|
1670
1752
|
session.result = summary
|
|
1753
|
+
session.add_message("system", "all", f"Task completed: {summary[:5000]}")
|
|
1671
1754
|
session._save_state()
|
|
1672
1755
|
return
|
|
1673
1756
|
|
|
@@ -1846,7 +1929,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
1846
1929
|
session.add_message("reviewer", "builder", reviewer_output)
|
|
1847
1930
|
|
|
1848
1931
|
if "APPROVED" in reviewer_output.upper():
|
|
1849
|
-
result_text = f"Approved after {iteration} iteration(s)
|
|
1932
|
+
result_text = f"Approved after {iteration} iteration(s).\n\nWhat was done:\n{builder_output or ''}"
|
|
1850
1933
|
if session.auto_commit:
|
|
1851
1934
|
try:
|
|
1852
1935
|
import re as _re
|
|
@@ -1908,6 +1991,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
1908
1991
|
result_text += f"\n\n--- Auto-commit skipped: {exc} ---"
|
|
1909
1992
|
session.status = "completed"
|
|
1910
1993
|
session.result = result_text
|
|
1994
|
+
session.add_message("system", "all", f"Task completed: {result_text[:5000]}")
|
|
1911
1995
|
session._save_state()
|
|
1912
1996
|
return
|
|
1913
1997
|
|
|
@@ -1918,9 +2002,8 @@ def _orchestrate(session: Session) -> None:
|
|
|
1918
2002
|
"Please address the reviewer's feedback."
|
|
1919
2003
|
)
|
|
1920
2004
|
|
|
1921
|
-
# Max iterations reached
|
|
1922
2005
|
session.status = "completed"
|
|
1923
|
-
session.result = f"Completed after {
|
|
2006
|
+
session.result = f"Completed after {session.iteration} iteration(s)"
|
|
1924
2007
|
session._save_state()
|
|
1925
2008
|
|
|
1926
2009
|
except subprocess.TimeoutExpired:
|