memstack-skill-loader 4.3.0__tar.gz → 4.4.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.
Files changed (43) hide show
  1. {memstack_skill_loader-4.3.0/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.4.0}/PKG-INFO +1 -1
  2. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/README.md +21 -17
  3. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/pyproject.toml +1 -1
  4. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/__init__.py +1 -1
  5. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/agent_runner.py +107 -24
  6. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/dashboard.html +522 -59
  7. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/dashboard.py +265 -23
  8. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/license.py +2 -2
  9. memstack_skill_loader-4.4.0/src/memstack_skill_loader/proxy/_diag.py +83 -0
  10. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/proxy/body_parser.py +62 -20
  11. memstack_skill_loader-4.4.0/src/memstack_skill_loader/proxy/compressor.py +26 -0
  12. memstack_skill_loader-4.4.0/src/memstack_skill_loader/proxy/pro_compressor.py +767 -0
  13. memstack_skill_loader-4.4.0/src/memstack_skill_loader/proxy/server.py +294 -0
  14. memstack_skill_loader-4.4.0/src/memstack_skill_loader/proxy/stats_tracker.py +214 -0
  15. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/server.py +328 -10
  16. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/stats.py +18 -6
  17. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
  18. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader.egg-info/SOURCES.txt +4 -1
  19. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/tests/test_pro_compressor.py +3 -1
  20. memstack_skill_loader-4.4.0/tests/test_pro_skills_update.py +281 -0
  21. memstack_skill_loader-4.4.0/tests/test_skill_drift.py +36 -0
  22. memstack_skill_loader-4.3.0/src/memstack_skill_loader/proxy/compressor.py +0 -77
  23. memstack_skill_loader-4.3.0/src/memstack_skill_loader/proxy/pro_compressor.py +0 -312
  24. memstack_skill_loader-4.3.0/src/memstack_skill_loader/proxy/server.py +0 -287
  25. memstack_skill_loader-4.3.0/src/memstack_skill_loader/proxy/stats_tracker.py +0 -125
  26. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/MANIFEST.in +0 -0
  27. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/setup.cfg +0 -0
  28. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/__main__.py +0 -0
  29. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/categories.py +0 -0
  30. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/compression.py +0 -0
  31. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/config.py +0 -0
  32. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/indexer.py +0 -0
  33. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/memory_db.py +0 -0
  34. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/proxy/__init__.py +0 -0
  35. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/proxy/forwarder.py +0 -0
  36. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/search.py +0 -0
  37. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/skill_config.py +0 -0
  38. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/tfidf_search.py +0 -0
  39. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader/version_check.py +0 -0
  40. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
  41. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
  42. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
  43. {memstack_skill_loader-4.3.0 → memstack_skill_loader-4.4.0}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memstack-skill-loader
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: MCP server that vector-indexes MemStack Pro skills for on-demand loading
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: mcp>=1.0.0
@@ -1,22 +1,26 @@
1
1
  # MemStack™ Skill Loader
2
2
 
3
- **127 skills for Claude Code** 85 free + 42 Pro exclusive. Vector-indexed so CC loads only the skill it needs, saving your context window.
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
 
7
- 1. **Install from PyPI:**
7
+ 1. **Get the free skills (in Claude Code):** Installs the plugin that provides the 84 free skills.
8
+ ```
9
+ /plugin marketplace add cwinvestments/memstack
10
+ /plugin install memstack@cwinvestments-memstack
11
+ ```
12
+
13
+ 2. **Install the engine (in your terminal):** Vector-indexes the skills so Claude Code loads only what it needs.
8
14
  ```bash
9
15
  pip install memstack-skill-loader
10
16
  ```
11
17
 
12
- 2. **Register with Claude Code:**
18
+ 3. **Register the engine with Claude Code (in your terminal):**
13
19
  ```bash
14
20
  claude mcp add --scope user memstack-skills -- python -m memstack_skill_loader
15
21
  ```
16
22
 
17
- 3. **Restart Claude Code**, then type `list skills` to verify.
18
-
19
- 4. **Activate Pro** (if purchased at [memstack.pro](https://memstack.pro)):
23
+ 4. **Restart Claude Code, then activate (in Claude Code):** Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://memstack.pro). Then type `list skills` to verify.
20
24
  ```
21
25
  activate_license(key="your-key-here", email="you@example.com")
22
26
  ```
@@ -27,11 +31,11 @@
27
31
 
28
32
  ## How It Works
29
33
 
30
- MCP server that vector-indexes all 127 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.
34
+ MCP server that vector-indexes all 127 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.
31
35
 
32
- - **No API keys required** everything runs locally
33
- - **Pro skills auto-detected** set your license key and they appear automatically
34
- - **Auto-reindex on start** skills stay current without manual rebuilds
36
+ - **No API keys required:** everything runs locally
37
+ - **Pro skills auto-detected:** set your license key and they appear automatically
38
+ - **Auto-reindex on start:** skills stay current without manual rebuilds
35
39
 
36
40
  ### Environment Variable Override
37
41
 
@@ -99,7 +103,7 @@ The `config.json` file controls where skills are loaded from:
99
103
  }
100
104
  ```
101
105
 
102
- Pro skills are **auto-detected** when `MEMSTACK_PRO_LICENSE_KEY` is set no need to add them to `config.json`.
106
+ Pro skills are **auto-detected** when `MEMSTACK_PRO_LICENSE_KEY` is set, no need to add them to `config.json`.
103
107
 
104
108
  Add entries to `skill_sources` to index skills from multiple directories:
105
109
 
@@ -123,8 +127,8 @@ Add entries to `skill_sources` to index skills from multiple directories:
123
127
  ```
124
128
 
125
129
  The `pattern` field controls how skills are discovered:
126
- - `**/SKILL.md` Subdirectory structure (e.g., `category/skill-name/SKILL.md`)
127
- - `*.md` Flat directory (each `.md` file is a skill)
130
+ - `**/SKILL.md`: Subdirectory structure (e.g., `category/skill-name/SKILL.md`)
131
+ - `*.md`: Flat directory (each `.md` file is a skill)
128
132
 
129
133
  ## Release Notes
130
134
 
@@ -133,16 +137,16 @@ The `pattern` field controls how skills are discovered:
133
137
  - Dashboard: 6-page localhost dashboard (Overview, Skills Manager, Burn Report, Memory Browser, Agent Monitor, Settings)
134
138
  - Agent Runner: 3-agent orchestration (Manager/Builder/Reviewer) with per-agent model selection
135
139
  - 17 MCP tools
136
- - 127 skills (85 free + 42 Pro)
140
+ - 127 skills (84 free + 43 Pro)
137
141
  - Real-time context window monitoring per agent
138
142
  - Session diary with AI-authored markdown narratives
139
143
  - Safe git staging (prevents accidental commits of secrets/runtime data)
140
144
  - Task completion notifications (browser, tab flash, audio)
141
145
  - Token usage tracking with estimated costs
142
- - Headroom proxy integration (~35-40% token savings)
146
+ - TokenStack™ proxy integration (~35-40% token savings)
143
147
 
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)
148
+ **[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
149
 
146
150
  ## License
147
151
 
148
- Proprietary Part of MemStack™ Pro by CW Affiliate Investments LLC.
152
+ Proprietary. Part of MemStack™ Pro by CW Affiliate Investments LLC.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memstack-skill-loader"
7
- version = "4.3.0"
7
+ version = "4.4.0"
8
8
  description = "MCP server that vector-indexes MemStack Pro skills for on-demand loading"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -1,3 +1,3 @@
1
1
  """MemStack Skill Loader — MCP server for semantic skill search."""
2
2
 
3
- __version__ = "4.2.0"
3
+ __version__ = "4.4.0"
@@ -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,12 +62,12 @@ 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 = 2
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"
68
69
  OAUTH_TOKEN_FILE = Path.home() / ".memstack" / "oauth_token"
69
- API_DEFAULT_MODEL = "claude-sonnet-4-20250514"
70
+ API_DEFAULT_MODEL = "claude-sonnet-4-6"
70
71
  API_MAX_TOKENS = 16000
71
72
 
72
73
  # API key loading moved to _load_api_key() — called on-demand per agent invocation.
@@ -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
- elif task_hint:
544
- for _line in task_hint.splitlines():
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
- subtask_results.append(f"Subtask {idx}: PASSED - {title}")
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
- subtask_results.append(f"Subtask {idx}: FAILED (max iterations) - {title}")
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): {reviewer_output[:500]}"
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 {MAX_ITERATIONS} iterations (max reached)"
2006
+ session.result = f"Completed after {session.iteration} iteration(s)"
1924
2007
  session._save_state()
1925
2008
 
1926
2009
  except subprocess.TimeoutExpired: