memstack-skill-loader 4.9.0__tar.gz → 4.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {memstack_skill_loader-4.9.0/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.10.0}/PKG-INFO +1 -1
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/README.md +4 -4
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/pyproject.toml +1 -1
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/agent_runner.py +94 -1
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/categories.py +1 -0
- memstack_skill_loader-4.10.0/src/memstack_skill_loader/goal.py +118 -0
- memstack_skill_loader-4.10.0/src/memstack_skill_loader/goal_engine.py +326 -0
- memstack_skill_loader-4.10.0/src/memstack_skill_loader/hooks/__init__.py +5 -0
- memstack_skill_loader-4.10.0/src/memstack_skill_loader/hooks/deny_git.py +21 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader.egg-info/SOURCES.txt +4 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/setup.cfg +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/dashboard.html +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/dashboard.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/license.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/memory_db.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/proxy/__init__.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/proxy/_diag.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/proxy/body_parser.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/proxy/compressor.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/proxy/forwarder.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/proxy/pro_compressor.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/proxy/server.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/proxy/stats_tracker.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/server.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/stats.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_body_parser_metric.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_code_read_savings.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_license_environment.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_license_grace.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_pro_compressor.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_pro_skills_update.py +0 -0
- {memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_skill_drift.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MemStack™ Skill Loader
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**130 skills for Claude Code:** 86 free + 44 Pro exclusive. Vector-indexed so CC loads only the skill it needs, saving your context window.
|
|
4
4
|
|
|
5
5
|
## Quick Start (5 minutes)
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ MemStack installs in two parts: the **skills** (via the Claude Code plugin marke
|
|
|
11
11
|
/plugin marketplace add cwinvestments/memstack
|
|
12
12
|
/plugin install memstack@cwinvestments-memstack
|
|
13
13
|
```
|
|
14
|
-
Run both commands. This unlocks the
|
|
14
|
+
Run both commands. This unlocks the 86 free skills right away.
|
|
15
15
|
|
|
16
16
|
> **SSH error?** ("Host key verification failed" on a fresh server that's never used GitHub over SSH.)
|
|
17
17
|
> **Default fix · (in terminal)** — rewrite GitHub to HTTPS, then retry Step 1:
|
|
@@ -53,7 +53,7 @@ If this doesn't match Step 2's interpreter, the server won't launch (you'll see
|
|
|
53
53
|
```
|
|
54
54
|
activate_license(key="your-key", email="you@example.com")
|
|
55
55
|
```
|
|
56
|
-
Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://memstack.pro) to unlock all 44 Pro skills (
|
|
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
58
|
> To override the skills path, set `MEMSTACK_SKILLS_DIR=/path/to/your/memstack/skills`.
|
|
59
59
|
|
|
@@ -86,7 +86,7 @@ sudo apt install python3-pip
|
|
|
86
86
|
|
|
87
87
|
## How It Works
|
|
88
88
|
|
|
89
|
-
MCP server that vector-indexes all
|
|
89
|
+
MCP server that vector-indexes all 130 MemStack™ skills so Claude Code can call `find_skill("deploy to Railway")` and load **only** the relevant skill on demand, instead of all skills consuming context window.
|
|
90
90
|
|
|
91
91
|
- **No Anthropic API key required:** skill search and indexing run locally — the only outbound network call is license validation, which contacts the MemStack license server on session start ([Data and privacy](#data-and-privacy))
|
|
92
92
|
- **Pro skills auto-detected:** set your license key and they appear automatically
|
|
@@ -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.10.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 = [
|
|
@@ -1026,7 +1026,8 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
1026
1026
|
model: str = "", disallowed_tools: Optional[list[str]] = None,
|
|
1027
1027
|
session: Optional["Session"] = None,
|
|
1028
1028
|
display_name: str = "",
|
|
1029
|
-
execution_mode: str = "subscription"
|
|
1029
|
+
execution_mode: str = "subscription",
|
|
1030
|
+
extra_settings: Optional[str] = None) -> tuple[str, int, int, int]:
|
|
1030
1031
|
"""Run a single claude interactive streaming invocation and return the output."""
|
|
1031
1032
|
claude_bin = shutil.which("claude")
|
|
1032
1033
|
if not claude_bin:
|
|
@@ -1040,6 +1041,10 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
1040
1041
|
if disallowed_tools:
|
|
1041
1042
|
patterns = ",".join(f"mcp__{srv}__*" for srv in disallowed_tools)
|
|
1042
1043
|
cmd.extend(["--disallowedTools", patterns])
|
|
1044
|
+
if extra_settings:
|
|
1045
|
+
# Inline --settings JSON (e.g. Builder git-deny PreToolUse hook). Only
|
|
1046
|
+
# passed by Builder call sites; Manager/Reviewer never provide this.
|
|
1047
|
+
cmd.extend(["--settings", extra_settings])
|
|
1043
1048
|
|
|
1044
1049
|
if log_path:
|
|
1045
1050
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -1385,10 +1390,53 @@ def _parse_subtasks(text: str) -> list[tuple[str, str]]:
|
|
|
1385
1390
|
return []
|
|
1386
1391
|
|
|
1387
1392
|
|
|
1393
|
+
def _build_builder_hook_settings() -> str:
|
|
1394
|
+
"""Inline --settings JSON that registers the Builder git-deny PreToolUse hook.
|
|
1395
|
+
|
|
1396
|
+
The hook script ships inside this package (hooks/deny_git.py). Its absolute
|
|
1397
|
+
path MUST use forward slashes: backslashes get stripped when the settings
|
|
1398
|
+
command string is executed on Windows, which mangles the path and breaks
|
|
1399
|
+
the hook (see smoke test)."""
|
|
1400
|
+
hook_script = os.path.join(os.path.dirname(__file__), "hooks", "deny_git.py").replace("\\", "/")
|
|
1401
|
+
return json.dumps({
|
|
1402
|
+
"hooks": {"PreToolUse": [{"matcher": "Bash",
|
|
1403
|
+
"hooks": [{"type": "command", "command": f"python {hook_script}"}]}]}
|
|
1404
|
+
})
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
def _git_head(working_dir: str) -> Optional[str]:
|
|
1408
|
+
"""Return the current HEAD commit SHA, or None if it can't be read."""
|
|
1409
|
+
try:
|
|
1410
|
+
r = subprocess.run(
|
|
1411
|
+
["git", "rev-parse", "HEAD"],
|
|
1412
|
+
cwd=working_dir, capture_output=True, text=True, timeout=30,
|
|
1413
|
+
)
|
|
1414
|
+
return r.stdout.strip() if r.returncode == 0 else None
|
|
1415
|
+
except Exception:
|
|
1416
|
+
return None
|
|
1417
|
+
|
|
1418
|
+
|
|
1419
|
+
def _git_reset_soft(working_dir: str, ref: str) -> bool:
|
|
1420
|
+
"""Best-effort `git reset --soft <ref>` to move HEAD back without touching
|
|
1421
|
+
the working tree. Used to undo a Builder commit so the orchestrator remains
|
|
1422
|
+
the single committer. Returns True on success."""
|
|
1423
|
+
try:
|
|
1424
|
+
r = subprocess.run(
|
|
1425
|
+
["git", "reset", "--soft", ref],
|
|
1426
|
+
cwd=working_dir, capture_output=True, text=True, timeout=30,
|
|
1427
|
+
)
|
|
1428
|
+
return r.returncode == 0
|
|
1429
|
+
except Exception:
|
|
1430
|
+
return False
|
|
1431
|
+
|
|
1432
|
+
|
|
1388
1433
|
def _orchestrate(session: Session) -> None:
|
|
1389
1434
|
"""Run the Manager → Builder → Reviewer loop."""
|
|
1390
1435
|
session._start_monotonic = time.monotonic()
|
|
1391
1436
|
session_log_dir = STATE_DIR / "sessions" / session.session_id
|
|
1437
|
+
# Builder-only git-deny hook (Layer 1). Built once, passed to Builder
|
|
1438
|
+
# _invoke_agent calls via extra_settings; never to Manager/Reviewer.
|
|
1439
|
+
builder_hook_settings = _build_builder_hook_settings()
|
|
1392
1440
|
|
|
1393
1441
|
try:
|
|
1394
1442
|
# Load execution modes and custom display names for this run
|
|
@@ -1512,6 +1560,9 @@ def _orchestrate(session: Session) -> None:
|
|
|
1512
1560
|
)
|
|
1513
1561
|
if session.user_name:
|
|
1514
1562
|
builder_prompt = f"The user's name is {session.user_name}.\n\n" + builder_prompt
|
|
1563
|
+
# Layer 2 backstop: snapshot HEAD before the Builder runs so
|
|
1564
|
+
# we can detect any git activity that slips past the hook.
|
|
1565
|
+
pre_head = _git_head(session.working_dir)
|
|
1515
1566
|
try:
|
|
1516
1567
|
if agent_modes["builder"] == "subscription":
|
|
1517
1568
|
builder_output, b_in, b_out, b_ctx = _invoke_agent(
|
|
@@ -1524,6 +1575,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
1524
1575
|
session=session,
|
|
1525
1576
|
display_name=display_names["builder"],
|
|
1526
1577
|
execution_mode=agent_modes["builder"],
|
|
1578
|
+
extra_settings=builder_hook_settings,
|
|
1527
1579
|
)
|
|
1528
1580
|
else:
|
|
1529
1581
|
builder_output, b_in, b_out = _invoke_api_agent(
|
|
@@ -1565,6 +1617,24 @@ def _orchestrate(session: Session) -> None:
|
|
|
1565
1617
|
if session.status == "stopped":
|
|
1566
1618
|
break
|
|
1567
1619
|
|
|
1620
|
+
# Layer 2 backstop: if the Builder moved HEAD (commit, reset,
|
|
1621
|
+
# checkout, etc.) it violated the no-git policy. Soft-undo and
|
|
1622
|
+
# fail the subtask rather than trusting its work.
|
|
1623
|
+
post_head = _git_head(session.working_dir)
|
|
1624
|
+
if pre_head and post_head and post_head != pre_head:
|
|
1625
|
+
undone = _git_reset_soft(session.working_dir, pre_head)
|
|
1626
|
+
reason = (
|
|
1627
|
+
f"Builder ran git and moved HEAD (pre={pre_head[:8]} "
|
|
1628
|
+
f"post={post_head[:8]}) - blocked by policy"
|
|
1629
|
+
+ ("; HEAD soft-reset to pre-state" if undone else "")
|
|
1630
|
+
)
|
|
1631
|
+
print(f"[agent-runner] POLICY VIOLATION: {reason}", file=sys.stderr)
|
|
1632
|
+
session.add_message("system", "all", f"Subtask {idx} of {len(subtasks)} failed: {title} - {reason}")
|
|
1633
|
+
subtasks_failed += 1
|
|
1634
|
+
subtask_results.append(f"Subtask {idx}: FAILED - {title} ({reason})")
|
|
1635
|
+
session._save_state()
|
|
1636
|
+
break
|
|
1637
|
+
|
|
1568
1638
|
if not builder_output or len(builder_output.strip()) < 20:
|
|
1569
1639
|
try:
|
|
1570
1640
|
diff_result = subprocess.run(
|
|
@@ -1784,6 +1854,9 @@ def _orchestrate(session: Session) -> None:
|
|
|
1784
1854
|
)
|
|
1785
1855
|
if session.user_name:
|
|
1786
1856
|
builder_prompt = f"The user's name is {session.user_name}.\n\n" + builder_prompt
|
|
1857
|
+
# Layer 2 backstop: snapshot HEAD before the Builder runs so we can
|
|
1858
|
+
# detect any git activity that slips past the hook.
|
|
1859
|
+
pre_head = _git_head(session.working_dir)
|
|
1787
1860
|
try:
|
|
1788
1861
|
if agent_modes["builder"] == "subscription":
|
|
1789
1862
|
builder_output, b_in, b_out, b_ctx = _invoke_agent(
|
|
@@ -1796,6 +1869,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
1796
1869
|
session=session,
|
|
1797
1870
|
display_name=display_names["builder"],
|
|
1798
1871
|
execution_mode=agent_modes["builder"],
|
|
1872
|
+
extra_settings=builder_hook_settings,
|
|
1799
1873
|
)
|
|
1800
1874
|
else:
|
|
1801
1875
|
builder_output, b_in, b_out = _invoke_api_agent(
|
|
@@ -1836,6 +1910,25 @@ def _orchestrate(session: Session) -> None:
|
|
|
1836
1910
|
session._save_state()
|
|
1837
1911
|
return
|
|
1838
1912
|
|
|
1913
|
+
# Layer 2 backstop: if the Builder moved HEAD (commit, reset,
|
|
1914
|
+
# checkout, etc.) it violated the no-git policy. Soft-undo and fail
|
|
1915
|
+
# the task rather than trusting its work.
|
|
1916
|
+
post_head = _git_head(session.working_dir)
|
|
1917
|
+
if pre_head and post_head and post_head != pre_head:
|
|
1918
|
+
undone = _git_reset_soft(session.working_dir, pre_head)
|
|
1919
|
+
reason = (
|
|
1920
|
+
f"Builder ran git and moved HEAD (pre={pre_head[:8]} "
|
|
1921
|
+
f"post={post_head[:8]}) - blocked by policy"
|
|
1922
|
+
+ ("; HEAD soft-reset to pre-state" if undone else "")
|
|
1923
|
+
)
|
|
1924
|
+
print(f"[agent-runner] POLICY VIOLATION: {reason}", file=sys.stderr)
|
|
1925
|
+
session.agents["builder"]["status"] = "crashed"
|
|
1926
|
+
session.status = "error"
|
|
1927
|
+
session.result = reason
|
|
1928
|
+
session.add_message("system", "all", f"Task failed: {reason}")
|
|
1929
|
+
session._save_state()
|
|
1930
|
+
return
|
|
1931
|
+
|
|
1839
1932
|
if not builder_output or len(builder_output.strip()) < 20:
|
|
1840
1933
|
try:
|
|
1841
1934
|
diff_result = subprocess.run(
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/categories.py
RENAMED
|
@@ -31,6 +31,7 @@ CATEGORY_MAP = {
|
|
|
31
31
|
"performance-audit": "Development", "refactor-planner": "Development", "test-writer": "Development",
|
|
32
32
|
"changelog-generator": "Development", "mentor": "Development", "webapp-testing": "Development",
|
|
33
33
|
# Core
|
|
34
|
+
"goal": "Core",
|
|
34
35
|
"compress": "Core", "diary": "Core", "echo": "Core", "grimoire": "Core",
|
|
35
36
|
"sight": "Core", "token-optimization": "Core",
|
|
36
37
|
# Marketing
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# MemStack Goal CLI - thin `python -m memstack_skill_loader.goal` wrapper over goal_engine
|
|
2
|
+
"""Command-line front door to the goal engine (see goal_engine.py).
|
|
3
|
+
|
|
4
|
+
This module reimplements NOTHING: every subcommand delegates to the engine's
|
|
5
|
+
existing functions. It exists so the /goal skill and the Architect stage can
|
|
6
|
+
shell out to a stable interface and, critically, gate on an EXIT CODE:
|
|
7
|
+
|
|
8
|
+
template -> print the blank five-field scaffold (exit 0)
|
|
9
|
+
validate -> parse + validate a drafted goal; print problems.
|
|
10
|
+
exit 0 when the goal is valid (warnings allowed),
|
|
11
|
+
exit 2 when it has errors (missing TASK/OUTCOME/
|
|
12
|
+
VERIFICATION). This exit code is the enforcement point.
|
|
13
|
+
format -> parse + re-emit the clean labeled template (exit 0)
|
|
14
|
+
|
|
15
|
+
Input for validate/format resolves in this order: --text, else --file, else
|
|
16
|
+
STDIN — so a caller can pipe a goal in or pass it as an argument.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
from .goal_engine import (
|
|
25
|
+
format_goal,
|
|
26
|
+
goal_is_valid,
|
|
27
|
+
goal_template,
|
|
28
|
+
parse_goal,
|
|
29
|
+
validate_goal,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Exit code returned by `validate` when the goal has hard errors. Callers
|
|
33
|
+
# (skill, Architect) branch on this to refuse an unverifiable goal.
|
|
34
|
+
EXIT_INVALID = 2
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_input(args: argparse.Namespace) -> str:
|
|
38
|
+
"""Resolve the goal text from --text, else --file, else STDIN.
|
|
39
|
+
|
|
40
|
+
Returns the raw text unparsed. An empty result is left to the engine's
|
|
41
|
+
validator to report (it will surface the missing required fields).
|
|
42
|
+
"""
|
|
43
|
+
if args.text is not None:
|
|
44
|
+
return args.text
|
|
45
|
+
if args.file is not None:
|
|
46
|
+
with open(args.file, "r", encoding="utf-8") as fh:
|
|
47
|
+
return fh.read()
|
|
48
|
+
return sys.stdin.read()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _add_input_args(parser: argparse.ArgumentParser) -> None:
|
|
52
|
+
"""Attach the shared --text / --file options to a subparser."""
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"--text", default=None,
|
|
55
|
+
help="Goal text passed directly on the command line.",
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--file", default=None,
|
|
59
|
+
help="Path to a file containing the drafted goal.",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cmd_template(_args: argparse.Namespace) -> int:
|
|
64
|
+
"""Print the blank five-field template."""
|
|
65
|
+
print(goal_template())
|
|
66
|
+
return 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cmd_validate(args: argparse.Namespace) -> int:
|
|
70
|
+
"""Validate a drafted goal; print problems, return 0 (valid) or EXIT_INVALID.
|
|
71
|
+
|
|
72
|
+
Problems are printed one per line in the engine's order (errors first, then
|
|
73
|
+
``WARNING:`` lines). Warnings do not affect the exit code.
|
|
74
|
+
"""
|
|
75
|
+
goal = parse_goal(read_input(args))
|
|
76
|
+
for problem in validate_goal(goal):
|
|
77
|
+
print(problem)
|
|
78
|
+
return 0 if goal_is_valid(goal) else EXIT_INVALID
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def cmd_format(args: argparse.Namespace) -> int:
|
|
82
|
+
"""Parse a goal and re-emit it via the engine's clean template."""
|
|
83
|
+
goal = parse_goal(read_input(args))
|
|
84
|
+
print(format_goal(goal))
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main() -> None:
|
|
89
|
+
"""CLI entry point. Dispatches to a subcommand and exits with its code."""
|
|
90
|
+
parser = argparse.ArgumentParser(
|
|
91
|
+
prog="python -m memstack_skill_loader.goal",
|
|
92
|
+
description="Structure and validate a task as a goal with a required, "
|
|
93
|
+
"checkable VERIFICATION criterion.",
|
|
94
|
+
)
|
|
95
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
96
|
+
|
|
97
|
+
p_template = sub.add_parser("template", help="Print the blank five-field template.")
|
|
98
|
+
p_template.set_defaults(func=cmd_template)
|
|
99
|
+
|
|
100
|
+
p_validate = sub.add_parser(
|
|
101
|
+
"validate",
|
|
102
|
+
help="Validate a drafted goal (stdin/--file/--text). Exit 2 on errors.",
|
|
103
|
+
)
|
|
104
|
+
_add_input_args(p_validate)
|
|
105
|
+
p_validate.set_defaults(func=cmd_validate)
|
|
106
|
+
|
|
107
|
+
p_format = sub.add_parser(
|
|
108
|
+
"format", help="Re-emit a drafted goal (stdin/--file/--text) as the clean template.",
|
|
109
|
+
)
|
|
110
|
+
_add_input_args(p_format)
|
|
111
|
+
p_format.set_defaults(func=cmd_format)
|
|
112
|
+
|
|
113
|
+
args = parser.parse_args()
|
|
114
|
+
sys.exit(args.func(args))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
main()
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# MemStack Goal Engine - structures a task into five fields with a required, checkable VERIFICATION criterion
|
|
2
|
+
"""Goal-structuring engine shared by the /goal skill and the Architect stage.
|
|
3
|
+
|
|
4
|
+
A "goal" is a task decomposed into five fields:
|
|
5
|
+
|
|
6
|
+
TASK what to do
|
|
7
|
+
WHY why it matters / who it's for (optional)
|
|
8
|
+
OUTCOME the exact finished result / definition of done
|
|
9
|
+
CONSTRAINTS what must or must not be done (optional, list)
|
|
10
|
+
VERIFICATION how to prove the OUTCOME is complete (REQUIRED)
|
|
11
|
+
|
|
12
|
+
VERIFICATION is the load-bearing field and the whole reason this module exists.
|
|
13
|
+
The dominant failure mode this engine is built to prevent is a goal that reads
|
|
14
|
+
as "done" without a concrete, checkable way to prove it: an agent (or a person)
|
|
15
|
+
declares success against a vague target and ships something unverified. A goal
|
|
16
|
+
without a specific verification criterion is treated as invalid here — not a
|
|
17
|
+
style nit, but a hard rule enforced by ``validate_goal``. A verification that
|
|
18
|
+
exists but only says "it works" / "tests pass" is flagged as a warning so the
|
|
19
|
+
author is nudged toward a concrete, observable check.
|
|
20
|
+
|
|
21
|
+
Pure standard library. No project imports, no external dependencies.
|
|
22
|
+
|
|
23
|
+
Validation contract (see ``validate_goal``): the returned list mixes two
|
|
24
|
+
severities distinguished by prefix —
|
|
25
|
+
* ERRORS — plain messages (e.g. "TASK is required"). A goal with any error
|
|
26
|
+
is invalid and must not proceed.
|
|
27
|
+
* WARNINGS — messages prefixed with the literal ``"WARNING: "``. Advisory
|
|
28
|
+
only; a goal with warnings but no errors is still valid.
|
|
29
|
+
Use ``goal_has_errors`` / ``goal_is_valid`` to branch without string-matching.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import re
|
|
35
|
+
from dataclasses import dataclass, field
|
|
36
|
+
|
|
37
|
+
# The five recognized field labels, in canonical display order. Parsing is
|
|
38
|
+
# case-insensitive and whitespace-tolerant; this ordering drives format output.
|
|
39
|
+
_LABELS = ("TASK", "WHY", "OUTCOME", "CONSTRAINTS", "VERIFICATION")
|
|
40
|
+
|
|
41
|
+
# Matches a label line like "OUTCOME: ...", tolerating leading whitespace and
|
|
42
|
+
# any casing. Group 1 = the (upper-cased) label, group 2 = the inline value.
|
|
43
|
+
_LABEL_RE = re.compile(
|
|
44
|
+
r"^\s*(" + "|".join(_LABELS) + r")\s*:\s*(.*)$",
|
|
45
|
+
re.IGNORECASE,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Marker prepended to advisory messages so callers can separate them from
|
|
49
|
+
# hard errors without re-deriving the rules.
|
|
50
|
+
_WARNING_PREFIX = "WARNING: "
|
|
51
|
+
|
|
52
|
+
# A verification shorter than this (after normalization) is assumed too terse
|
|
53
|
+
# to name a concrete, checkable criterion.
|
|
54
|
+
_VAGUE_MIN_LEN = 15
|
|
55
|
+
|
|
56
|
+
# Vacuous verification phrasings that may exceed the length floor yet still
|
|
57
|
+
# assert nothing observable. Compared against the normalized (lower-cased,
|
|
58
|
+
# punctuation-stripped, whitespace-collapsed) verification text.
|
|
59
|
+
_VAGUE_PHRASES = frozenset({
|
|
60
|
+
"it works",
|
|
61
|
+
"works",
|
|
62
|
+
"it is done",
|
|
63
|
+
"its done",
|
|
64
|
+
"done",
|
|
65
|
+
"finished",
|
|
66
|
+
"complete",
|
|
67
|
+
"completed",
|
|
68
|
+
"tests pass",
|
|
69
|
+
"all tests pass",
|
|
70
|
+
"the tests pass",
|
|
71
|
+
"passes",
|
|
72
|
+
"it passes",
|
|
73
|
+
"ok",
|
|
74
|
+
"looks good",
|
|
75
|
+
"lgtm",
|
|
76
|
+
"works as expected",
|
|
77
|
+
"it works as expected",
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class Goal:
|
|
83
|
+
"""A task structured into five fields.
|
|
84
|
+
|
|
85
|
+
``task``, ``outcome`` and ``verification`` carry the meaning; ``why`` and
|
|
86
|
+
``constraints`` are optional context. Only ``task`` is a positional
|
|
87
|
+
requirement of the dataclass — the required-ness of ``outcome`` and
|
|
88
|
+
``verification`` is a *validation* rule (see ``validate_goal``), not a
|
|
89
|
+
construction rule, so partial goals can be built and then reported on.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
task: str
|
|
93
|
+
why: str = ""
|
|
94
|
+
outcome: str = ""
|
|
95
|
+
constraints: list[str] = field(default_factory=list)
|
|
96
|
+
verification: str = ""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _normalize(text: str) -> str:
|
|
100
|
+
"""Lower-case, strip surrounding punctuation, and collapse inner whitespace.
|
|
101
|
+
|
|
102
|
+
Used to compare a verification string against ``_VAGUE_PHRASES`` so that
|
|
103
|
+
"It works." and " it works " both reduce to "it works".
|
|
104
|
+
"""
|
|
105
|
+
lowered = text.strip().lower()
|
|
106
|
+
lowered = re.sub(r"\s+", " ", lowered)
|
|
107
|
+
return lowered.strip(" \t\r\n.!?,;:-*")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _split_constraints(raw_lines: list[str]) -> list[str]:
|
|
111
|
+
"""Turn accumulated CONSTRAINTS text into a clean list.
|
|
112
|
+
|
|
113
|
+
Splits on both newlines and commas, strips common bullet markers
|
|
114
|
+
("-", "*", "•") and surrounding whitespace, and drops empties.
|
|
115
|
+
"""
|
|
116
|
+
items: list[str] = []
|
|
117
|
+
for line in raw_lines:
|
|
118
|
+
for part in re.split(r"[,\n]", line):
|
|
119
|
+
cleaned = part.strip().lstrip("-*• \t").strip()
|
|
120
|
+
if cleaned:
|
|
121
|
+
items.append(cleaned)
|
|
122
|
+
return items
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def parse_goal(text: str) -> Goal:
|
|
126
|
+
"""Parse labeled ``LABEL: value`` text into a :class:`Goal`.
|
|
127
|
+
|
|
128
|
+
Recognizes the five labels (TASK, WHY, OUTCOME, CONSTRAINTS, VERIFICATION)
|
|
129
|
+
case-insensitively and with tolerant whitespace. A field's value may span
|
|
130
|
+
multiple lines: everything after a label line, up to the next label line,
|
|
131
|
+
belongs to that field. Scalar fields are whitespace-joined; CONSTRAINTS is
|
|
132
|
+
split into a list (see :func:`_split_constraints`).
|
|
133
|
+
|
|
134
|
+
Missing fields become empty (``""`` or ``[]``). Parsing never raises for
|
|
135
|
+
missing required fields — that is reported by :func:`validate_goal`.
|
|
136
|
+
"""
|
|
137
|
+
# Accumulate raw text lines per field as we walk the input.
|
|
138
|
+
buckets: dict[str, list[str]] = {label: [] for label in _LABELS}
|
|
139
|
+
current: str | None = None
|
|
140
|
+
|
|
141
|
+
for line in text.splitlines():
|
|
142
|
+
match = _LABEL_RE.match(line)
|
|
143
|
+
if match:
|
|
144
|
+
current = match.group(1).upper()
|
|
145
|
+
inline = match.group(2).strip()
|
|
146
|
+
if inline:
|
|
147
|
+
buckets[current].append(inline)
|
|
148
|
+
elif current is not None:
|
|
149
|
+
# Continuation line for the field currently being read.
|
|
150
|
+
stripped = line.strip()
|
|
151
|
+
if stripped:
|
|
152
|
+
buckets[current].append(stripped)
|
|
153
|
+
|
|
154
|
+
def scalar(label: str) -> str:
|
|
155
|
+
return " ".join(buckets[label]).strip()
|
|
156
|
+
|
|
157
|
+
return Goal(
|
|
158
|
+
task=scalar("TASK"),
|
|
159
|
+
why=scalar("WHY"),
|
|
160
|
+
outcome=scalar("OUTCOME"),
|
|
161
|
+
constraints=_split_constraints(buckets["CONSTRAINTS"]),
|
|
162
|
+
verification=scalar("VERIFICATION"),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _verification_is_vague(verification: str) -> bool:
|
|
167
|
+
"""True when a non-empty verification names no concrete, checkable criterion.
|
|
168
|
+
|
|
169
|
+
Two triggers: it is shorter than ``_VAGUE_MIN_LEN`` after normalization, or
|
|
170
|
+
it matches one of the known vacuous phrasings in ``_VAGUE_PHRASES``.
|
|
171
|
+
"""
|
|
172
|
+
normalized = _normalize(verification)
|
|
173
|
+
if not normalized:
|
|
174
|
+
return False # empty is an ERROR case, handled by validate_goal
|
|
175
|
+
if len(normalized) < _VAGUE_MIN_LEN:
|
|
176
|
+
return True
|
|
177
|
+
return normalized in _VAGUE_PHRASES
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def validate_goal(goal: Goal) -> list[str]:
|
|
181
|
+
"""Return a list of problems with *goal*; an empty list means fully valid.
|
|
182
|
+
|
|
183
|
+
Errors (plain messages — presence of any makes the goal invalid):
|
|
184
|
+
* "TASK is required"
|
|
185
|
+
* "OUTCOME is required (define what done looks like)"
|
|
186
|
+
* "VERIFICATION is required (state how you'll prove it's complete)"
|
|
187
|
+
|
|
188
|
+
Warnings (prefixed with ``"WARNING: "`` — advisory, do not invalidate):
|
|
189
|
+
* a present-but-vacuous VERIFICATION.
|
|
190
|
+
|
|
191
|
+
Errors are listed before warnings. Use :func:`goal_has_errors` to test
|
|
192
|
+
validity without inspecting the prefix yourself.
|
|
193
|
+
"""
|
|
194
|
+
errors: list[str] = []
|
|
195
|
+
warnings: list[str] = []
|
|
196
|
+
|
|
197
|
+
if not goal.task.strip():
|
|
198
|
+
errors.append("TASK is required")
|
|
199
|
+
if not goal.outcome.strip():
|
|
200
|
+
errors.append("OUTCOME is required (define what done looks like)")
|
|
201
|
+
if not goal.verification.strip():
|
|
202
|
+
errors.append(
|
|
203
|
+
"VERIFICATION is required (state how you'll prove it's complete)"
|
|
204
|
+
)
|
|
205
|
+
elif _verification_is_vague(goal.verification):
|
|
206
|
+
warnings.append(
|
|
207
|
+
_WARNING_PREFIX
|
|
208
|
+
+ "VERIFICATION is vague - state a concrete, checkable criterion"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return errors + warnings
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def goal_has_errors(problems: list[str]) -> bool:
|
|
215
|
+
"""True if *problems* (from :func:`validate_goal`) contains any hard error.
|
|
216
|
+
|
|
217
|
+
A hard error is any message NOT prefixed with ``"WARNING: "``.
|
|
218
|
+
"""
|
|
219
|
+
return any(not p.startswith(_WARNING_PREFIX) for p in problems)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def goal_is_valid(goal: Goal) -> bool:
|
|
223
|
+
"""Convenience: True when *goal* has no hard errors (warnings are allowed)."""
|
|
224
|
+
return not goal_has_errors(validate_goal(goal))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def format_goal(goal: Goal) -> str:
|
|
228
|
+
"""Render *goal* back into the clean labeled template for display/output.
|
|
229
|
+
|
|
230
|
+
Optional empty fields (WHY, CONSTRAINTS) are still emitted as labels so the
|
|
231
|
+
output shape is stable and copy-editable. Constraints render as a bulleted
|
|
232
|
+
list; a single constraint renders inline.
|
|
233
|
+
"""
|
|
234
|
+
lines = [f"TASK: {goal.task}".rstrip()]
|
|
235
|
+
lines.append(f"WHY: {goal.why}".rstrip())
|
|
236
|
+
lines.append(f"OUTCOME: {goal.outcome}".rstrip())
|
|
237
|
+
|
|
238
|
+
if not goal.constraints:
|
|
239
|
+
lines.append("CONSTRAINTS:")
|
|
240
|
+
elif len(goal.constraints) == 1:
|
|
241
|
+
lines.append(f"CONSTRAINTS: {goal.constraints[0]}")
|
|
242
|
+
else:
|
|
243
|
+
lines.append("CONSTRAINTS:")
|
|
244
|
+
lines.extend(f" - {item}" for item in goal.constraints)
|
|
245
|
+
|
|
246
|
+
lines.append(f"VERIFICATION: {goal.verification}".rstrip())
|
|
247
|
+
return "\n".join(lines)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def goal_template() -> str:
|
|
251
|
+
"""Return the empty five-field template with a one-line hint per label.
|
|
252
|
+
|
|
253
|
+
Handed to a user (or the Architect) starting from scratch, so every field —
|
|
254
|
+
especially VERIFICATION — comes with a prompt for what belongs there.
|
|
255
|
+
"""
|
|
256
|
+
return (
|
|
257
|
+
"TASK: (what to do)\n"
|
|
258
|
+
"WHY: (why it matters / who it's for - optional)\n"
|
|
259
|
+
"OUTCOME: (the exact finished result - what 'done' looks like)\n"
|
|
260
|
+
"CONSTRAINTS: (what must or must not be done - optional, one per line)\n"
|
|
261
|
+
"VERIFICATION: (how you'll prove the outcome is complete - "
|
|
262
|
+
"a concrete, checkable criterion)"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
# Small self-test exercising the four cases: valid, missing-verification
|
|
268
|
+
# (error), vague-verification (warning), and template output.
|
|
269
|
+
print("=" * 64)
|
|
270
|
+
print("CASE 1 — complete valid goal")
|
|
271
|
+
print("=" * 64)
|
|
272
|
+
valid = parse_goal(
|
|
273
|
+
"""
|
|
274
|
+
TASK: Add a --json flag to the report command
|
|
275
|
+
WHY: CI needs machine-readable output for the dashboard
|
|
276
|
+
OUTCOME: `report --json` prints a valid JSON object to stdout and exits 0
|
|
277
|
+
CONSTRAINTS: no new dependencies
|
|
278
|
+
must not change the default human output
|
|
279
|
+
VERIFICATION: run `report --json | python -m json.tool` and confirm it
|
|
280
|
+
parses and contains the "total" key
|
|
281
|
+
"""
|
|
282
|
+
)
|
|
283
|
+
print(format_goal(valid))
|
|
284
|
+
problems = validate_goal(valid)
|
|
285
|
+
print("\nproblems:", problems)
|
|
286
|
+
print("has_errors:", goal_has_errors(problems), "| is_valid:", goal_is_valid(valid))
|
|
287
|
+
print("constraints parsed:", valid.constraints)
|
|
288
|
+
assert problems == [], "expected no problems for a complete goal"
|
|
289
|
+
assert goal_is_valid(valid)
|
|
290
|
+
assert valid.constraints == ["no new dependencies", "must not change the default human output"]
|
|
291
|
+
|
|
292
|
+
print("\n" + "=" * 64)
|
|
293
|
+
print("CASE 2 — missing VERIFICATION (error)")
|
|
294
|
+
print("=" * 64)
|
|
295
|
+
missing = parse_goal(
|
|
296
|
+
"TASK: Refactor the parser\n"
|
|
297
|
+
"OUTCOME: parser split into tokenizer and builder modules"
|
|
298
|
+
)
|
|
299
|
+
problems = validate_goal(missing)
|
|
300
|
+
print("problems:", problems)
|
|
301
|
+
print("has_errors:", goal_has_errors(problems), "| is_valid:", goal_is_valid(missing))
|
|
302
|
+
assert goal_has_errors(problems), "missing verification must be an error"
|
|
303
|
+
assert any("VERIFICATION is required" in p for p in problems)
|
|
304
|
+
assert not goal_is_valid(missing)
|
|
305
|
+
|
|
306
|
+
print("\n" + "=" * 64)
|
|
307
|
+
print("CASE 3 — vague VERIFICATION (warning, still valid)")
|
|
308
|
+
print("=" * 64)
|
|
309
|
+
vague = parse_goal(
|
|
310
|
+
"TASK: Fix the login bug\n"
|
|
311
|
+
"OUTCOME: users can log in again\n"
|
|
312
|
+
"VERIFICATION: it works"
|
|
313
|
+
)
|
|
314
|
+
problems = validate_goal(vague)
|
|
315
|
+
print("problems:", problems)
|
|
316
|
+
print("has_errors:", goal_has_errors(problems), "| is_valid:", goal_is_valid(vague))
|
|
317
|
+
assert not goal_has_errors(problems), "vague verification is a warning, not an error"
|
|
318
|
+
assert any(p.startswith("WARNING: ") for p in problems)
|
|
319
|
+
assert goal_is_valid(vague), "a goal with only warnings is still valid"
|
|
320
|
+
|
|
321
|
+
print("\n" + "=" * 64)
|
|
322
|
+
print("CASE 4 — empty template")
|
|
323
|
+
print("=" * 64)
|
|
324
|
+
print(goal_template())
|
|
325
|
+
|
|
326
|
+
print("\nAll self-test assertions passed.")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""PreToolUse hook: denies any Bash git invocation. Injected only into the
|
|
3
|
+
Builder agent subprocess via --settings, so the Builder cannot run git
|
|
4
|
+
(commit, push, reset, checkout, etc.). Fail-closed: on any error, deny."""
|
|
5
|
+
import sys, json, re
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
data = json.load(sys.stdin)
|
|
9
|
+
cmd = data.get("tool_input", {}).get("command", "")
|
|
10
|
+
except Exception:
|
|
11
|
+
# Fail closed: if we can't parse, deny to be safe.
|
|
12
|
+
print("BLOCKED: Builder git-guard could not parse tool input; denying.", file=sys.stderr)
|
|
13
|
+
sys.exit(2)
|
|
14
|
+
|
|
15
|
+
# Match git as a command token in any form: git, /usr/bin/git, sh -c "git",
|
|
16
|
+
# env VAR=x git, chained "&& git", "; git", "| git", GIT_DIR=... git, etc.
|
|
17
|
+
if re.search(r"(^|[\s;&|/])git(\s|$)", cmd) or re.search(r"\bgit\b", cmd):
|
|
18
|
+
print("BLOCKED: Builder may not run git. Git operations are performed by the orchestrator, not the Builder.", file=sys.stderr)
|
|
19
|
+
sys.exit(2)
|
|
20
|
+
|
|
21
|
+
sys.exit(0)
|
|
@@ -9,6 +9,8 @@ src/memstack_skill_loader/compression.py
|
|
|
9
9
|
src/memstack_skill_loader/config.py
|
|
10
10
|
src/memstack_skill_loader/dashboard.html
|
|
11
11
|
src/memstack_skill_loader/dashboard.py
|
|
12
|
+
src/memstack_skill_loader/goal.py
|
|
13
|
+
src/memstack_skill_loader/goal_engine.py
|
|
12
14
|
src/memstack_skill_loader/indexer.py
|
|
13
15
|
src/memstack_skill_loader/license.py
|
|
14
16
|
src/memstack_skill_loader/memory_db.py
|
|
@@ -24,6 +26,8 @@ src/memstack_skill_loader.egg-info/dependency_links.txt
|
|
|
24
26
|
src/memstack_skill_loader.egg-info/entry_points.txt
|
|
25
27
|
src/memstack_skill_loader.egg-info/requires.txt
|
|
26
28
|
src/memstack_skill_loader.egg-info/top_level.txt
|
|
29
|
+
src/memstack_skill_loader/hooks/__init__.py
|
|
30
|
+
src/memstack_skill_loader/hooks/deny_git.py
|
|
27
31
|
src/memstack_skill_loader/proxy/__init__.py
|
|
28
32
|
src/memstack_skill_loader/proxy/_diag.py
|
|
29
33
|
src/memstack_skill_loader/proxy/body_parser.py
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/config.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/dashboard.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/indexer.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/license.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/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
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/search.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/server.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/src/memstack_skill_loader/stats.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_body_parser_metric.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_code_read_savings.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_license_environment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.9.0 → memstack_skill_loader-4.10.0}/tests/test_pro_skills_update.py
RENAMED
|
File without changes
|
|
File without changes
|