memplex 3.2.0__py3-none-any.whl

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 (83) hide show
  1. memnex/__init__.py +31 -0
  2. memnex/__main__.py +6 -0
  3. memnex/_plugin/.claude-plugin/plugin.json +24 -0
  4. memnex/_plugin/.mcp.json +9 -0
  5. memnex/_plugin/__init__.py +0 -0
  6. memnex/_plugin/hooks/hooks.json +43 -0
  7. memnex/_plugin/scripts/hook-runner.py +166 -0
  8. memnex/_plugin/skills/mem-explore/SKILL.md +83 -0
  9. memnex/_plugin/skills/mem-manage/SKILL.md +92 -0
  10. memnex/_plugin/skills/mem-search/SKILL.md +85 -0
  11. memnex/_plugin/skills/mem-write/SKILL.md +78 -0
  12. memnex/adapters/__init__.py +14 -0
  13. memnex/adapters/claude_skill.py +169 -0
  14. memnex/adapters/cli.py +525 -0
  15. memnex/adapters/http_api.py +314 -0
  16. memnex/adapters/mcp_server.py +448 -0
  17. memnex/compaction.py +563 -0
  18. memnex/config.py +366 -0
  19. memnex/core/__init__.py +13 -0
  20. memnex/core/associator/__init__.py +8 -0
  21. memnex/core/associator/domain_classifier.py +75 -0
  22. memnex/core/associator/entity_aligner.py +127 -0
  23. memnex/core/associator/ref_linker.py +197 -0
  24. memnex/core/associator/term_mapper.py +77 -0
  25. memnex/core/dictionaries/__init__.py +50 -0
  26. memnex/core/engine.py +667 -0
  27. memnex/core/extractors/__init__.py +15 -0
  28. memnex/core/extractors/docx.py +97 -0
  29. memnex/core/extractors/image.py +233 -0
  30. memnex/core/extractors/markdown.py +139 -0
  31. memnex/core/extractors/pdf.py +133 -0
  32. memnex/core/extractors/vision_mapper.py +131 -0
  33. memnex/core/handlers/__init__.py +7 -0
  34. memnex/core/handlers/clipboard.py +40 -0
  35. memnex/core/handlers/file_handler.py +62 -0
  36. memnex/core/handlers/url_handler.py +132 -0
  37. memnex/llm/__init__.py +25 -0
  38. memnex/llm/enhancer.py +226 -0
  39. memnex/llm/fallback_chain.py +87 -0
  40. memnex/llm/injection_guard.py +178 -0
  41. memnex/llm/provider.py +130 -0
  42. memnex/llm/providers/__init__.py +22 -0
  43. memnex/llm/providers/anthropic.py +135 -0
  44. memnex/llm/providers/local.py +135 -0
  45. memnex/llm/providers/rule_based.py +68 -0
  46. memnex/llm/sanitizer.py +67 -0
  47. memnex/models/__init__.py +68 -0
  48. memnex/models/feedback.py +42 -0
  49. memnex/models/graph.py +33 -0
  50. memnex/models/memory.py +102 -0
  51. memnex/models/misc.py +185 -0
  52. memnex/models/paragraph.py +45 -0
  53. memnex/models/search.py +51 -0
  54. memnex/models/source.py +23 -0
  55. memnex/models/task.py +62 -0
  56. memnex/processing/__init__.py +1 -0
  57. memnex/processing/graph_builder.py +278 -0
  58. memnex/processing/merger/__init__.py +6 -0
  59. memnex/processing/merger/confidence_calculator.py +127 -0
  60. memnex/processing/merger/conflict_resolver.py +116 -0
  61. memnex/retrieval/__init__.py +1 -0
  62. memnex/retrieval/dedup.py +386 -0
  63. memnex/retrieval/embedding.py +289 -0
  64. memnex/retrieval/reranker.py +299 -0
  65. memnex/service.py +902 -0
  66. memnex/storage/__init__.py +65 -0
  67. memnex/storage/base.py +132 -0
  68. memnex/storage/changelog.py +106 -0
  69. memnex/storage/feedback.py +486 -0
  70. memnex/storage/lite/__init__.py +5 -0
  71. memnex/storage/lite/store.py +606 -0
  72. memnex/storage/vector.py +265 -0
  73. memnex/wiki/__init__.py +11 -0
  74. memnex/wiki/community.py +221 -0
  75. memnex/wiki/compiler.py +545 -0
  76. memnex/wiki/generator.py +270 -0
  77. memnex/wiki/search.py +282 -0
  78. memnex/worker.py +412 -0
  79. memplex-3.2.0.dist-info/METADATA +37 -0
  80. memplex-3.2.0.dist-info/RECORD +83 -0
  81. memplex-3.2.0.dist-info/WHEEL +5 -0
  82. memplex-3.2.0.dist-info/entry_points.txt +2 -0
  83. memplex-3.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,65 @@
1
+ """MemNex storage layer -- MemoryStore interface and backends."""
2
+
3
+ from memnex.storage.base import MemoryStore
4
+ from memnex.storage.lite.store import LiteMemoryStore
5
+ from memnex.storage.changelog import ChangelogStore
6
+ from memnex.storage.vector import (
7
+ VectorStore,
8
+ InMemoryVectorStore,
9
+ ChromaVectorStore,
10
+ create_vector_store,
11
+ )
12
+ from memnex.storage.feedback import (
13
+ FeedbackStore,
14
+ LiteFeedbackStore,
15
+ SQLiteFeedbackStore,
16
+ PostgresFeedbackStore,
17
+ create_feedback_store,
18
+ )
19
+
20
+ from typing import Optional
21
+
22
+
23
+ def create_store(
24
+ config=None,
25
+ **kwargs,
26
+ ) -> MemoryStore:
27
+ """Factory: create a MemoryStore.
28
+
29
+ Accepts either a MemNexConfig object or a backend string.
30
+ Falls back to 'lite' for unsupported backends.
31
+ """
32
+ if config is not None and hasattr(config, 'storage'):
33
+ backend = config.storage.backend
34
+ storage_path = config.storage.path
35
+ else:
36
+ backend = config if isinstance(config, str) else kwargs.get('backend', 'lite')
37
+ storage_path = kwargs.get('path')
38
+
39
+ if backend in ("lite", "standard", "enterprise"):
40
+ from pathlib import Path
41
+ path = Path(storage_path).expanduser() / "memory.json" if storage_path else None
42
+ try:
43
+ return LiteMemoryStore(path=path)
44
+ except Exception:
45
+ return LiteMemoryStore()
46
+ raise ValueError(
47
+ f"Unknown storage backend: {backend!r}. Supported: 'lite'."
48
+ )
49
+
50
+
51
+ __all__ = [
52
+ "MemoryStore",
53
+ "LiteMemoryStore",
54
+ "ChangelogStore",
55
+ "VectorStore",
56
+ "InMemoryVectorStore",
57
+ "ChromaVectorStore",
58
+ "create_vector_store",
59
+ "FeedbackStore",
60
+ "LiteFeedbackStore",
61
+ "SQLiteFeedbackStore",
62
+ "PostgresFeedbackStore",
63
+ "create_feedback_store",
64
+ "create_store",
65
+ ]
memnex/storage/base.py ADDED
@@ -0,0 +1,132 @@
1
+ """MemoryStore abstract base class -- unified data access layer.
2
+
3
+ Pure CRUD + basic retrieval. No orchestration logic (that belongs in
4
+ MemNexService). Every concrete backend (Lite, Standard, Enterprise)
5
+ implements this interface.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import List, Optional
12
+
13
+ from memnex.models import (
14
+ BatchResult,
15
+ ChangelogEvent,
16
+ Function,
17
+ GraphData,
18
+ MergeResult,
19
+ Observation,
20
+ SearchFilters,
21
+ SearchResult,
22
+ SourceDocument,
23
+ )
24
+
25
+
26
+ class MemoryStore(ABC):
27
+ """Pure data access layer -- CRUD + basic retrieval, no orchestration."""
28
+
29
+ # ── Write operations ────────────────────────────────────────────
30
+
31
+ @abstractmethod
32
+ def add(self, func: Function, source: SourceDocument) -> None:
33
+ """Add a Function. If a Function with the same *name_normalized*
34
+ already exists, its FieldValues are merged instead.
35
+
36
+ Concurrency (Lite backend): single-threaded, optimistic lock skipped.
37
+ """
38
+
39
+ @abstractmethod
40
+ def add_batch(
41
+ self,
42
+ funcs: List[Function],
43
+ sources: List[SourceDocument],
44
+ ) -> BatchResult:
45
+ """Batch add. Calls :meth:`add` per item; single-item failure does
46
+ **not** abort the rest. Failed items are recorded in
47
+ ``BatchResult.failed_items``.
48
+ """
49
+
50
+ @abstractmethod
51
+ def add_observation(self, observation: Observation) -> None:
52
+ """Persist an Observation event."""
53
+
54
+ @abstractmethod
55
+ def increment_access(self, func_id: str) -> None:
56
+ """Atomically increment ``access_count`` and update
57
+ ``last_accessed_at``. Must not depend on a prior ``get()`` to
58
+ avoid read-modify-write races.
59
+ """
60
+
61
+ # ── Retrieval ───────────────────────────────────────────────────
62
+
63
+ @abstractmethod
64
+ def vector_search(self, text: str, top_k: int = 5) -> List[SearchResult]:
65
+ """Semantic / vector similarity search."""
66
+
67
+ @abstractmethod
68
+ def fts_search(self, text: str, top_k: int = 10) -> List[SearchResult]:
69
+ """Full-text / keyword search."""
70
+
71
+ @abstractmethod
72
+ def filter(self, filters: SearchFilters) -> List[Function]:
73
+ """Structured filter over stored Functions."""
74
+
75
+ # ── Read operations ─────────────────────────────────────────────
76
+
77
+ @abstractmethod
78
+ def get(self, func_id: str) -> Optional[Function]:
79
+ """Retrieve a single Function by ID, or ``None``."""
80
+
81
+ @abstractmethod
82
+ def get_neighbors(
83
+ self,
84
+ func_id: str,
85
+ edge_types: Optional[List[str]] = None,
86
+ max_hops: int = 1,
87
+ ) -> List[Function]:
88
+ """Return neighbour Functions reachable within *max_hops* edges,
89
+ optionally restricted to *edge_types*.
90
+ """
91
+
92
+ @abstractmethod
93
+ def get_graph(
94
+ self,
95
+ func_ids: Optional[List[str]] = None,
96
+ ) -> GraphData:
97
+ """Return sub-graph. If *func_ids* is ``None`` the full graph is
98
+ returned.
99
+ """
100
+
101
+ @abstractmethod
102
+ def get_timeline(
103
+ self,
104
+ func_id: str,
105
+ limit: int = 20,
106
+ ) -> List[ChangelogEvent]:
107
+ """Return recent ChangelogEvents for a Function."""
108
+
109
+ @abstractmethod
110
+ def list_functions(
111
+ self,
112
+ offset: int = 0,
113
+ limit: int = 1000,
114
+ owner: Optional[str] = None,
115
+ ) -> List[Function]:
116
+ """Paginated listing, optionally filtered by *owner*."""
117
+
118
+ # ── Delete / merge / clear ──────────────────────────────────────
119
+
120
+ @abstractmethod
121
+ def delete(self, func_id: str) -> None:
122
+ """Soft-delete a Function and its associated graph edges."""
123
+
124
+ @abstractmethod
125
+ def merge(self, sub_graph: GraphData) -> MergeResult:
126
+ """Incrementally fuse *sub_graph* into the main graph. Existing
127
+ nodes are merged; new ones are inserted.
128
+ """
129
+
130
+ @abstractmethod
131
+ def clear(self) -> None:
132
+ """Remove all stored data (Functions, edges, changelog)."""
@@ -0,0 +1,106 @@
1
+ """ChangelogStore -- lightweight append-only event log.
2
+
3
+ Lite implementation: in-memory list persisted to a JSON file.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import logging
10
+ import tempfile
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import List, Optional
14
+
15
+ from memnex.models import ChangelogEvent
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class ChangelogStore:
21
+ """Append-only changelog with JSON persistence.
22
+
23
+ Data path: ``~/.memnex/changelog.json``
24
+ """
25
+
26
+ def __init__(self, path: Optional[Path] = None) -> None:
27
+ self._path = path or Path("~/.memnex/changelog.json").expanduser()
28
+ self._events: List[ChangelogEvent] = []
29
+ self._load()
30
+
31
+ # ── Public API ──────────────────────────────────────────────────
32
+
33
+ def append(self, event: ChangelogEvent) -> None:
34
+ """Append an event and persist."""
35
+ self._events.append(event)
36
+ self._save()
37
+
38
+ def get_timeline(
39
+ self,
40
+ func_id: str,
41
+ limit: int = 20,
42
+ ) -> List[ChangelogEvent]:
43
+ """Return the most recent events for *func_id*, newest first."""
44
+ matching = [e for e in self._events if e.func_id == func_id]
45
+ matching.sort(key=lambda e: e.timestamp, reverse=True)
46
+ return matching[:limit]
47
+
48
+ def clear(self) -> None:
49
+ """Remove all events."""
50
+ self._events.clear()
51
+ self._save()
52
+
53
+ # ── Persistence helpers ─────────────────────────────────────────
54
+
55
+ @staticmethod
56
+ def _serialize_event(event: ChangelogEvent) -> dict:
57
+ return {
58
+ "func_id": event.func_id,
59
+ "timestamp": (
60
+ event.timestamp.isoformat()
61
+ if isinstance(event.timestamp, datetime)
62
+ else event.timestamp
63
+ ),
64
+ "event_type": event.event_type,
65
+ "description": event.description,
66
+ "source": event.source,
67
+ "actor": event.actor,
68
+ }
69
+
70
+ @staticmethod
71
+ def _deserialize_event(data: dict) -> ChangelogEvent:
72
+ ts = data["timestamp"]
73
+ if isinstance(ts, str):
74
+ ts = datetime.fromisoformat(ts)
75
+ return ChangelogEvent(
76
+ func_id=data["func_id"],
77
+ timestamp=ts,
78
+ event_type=data["event_type"],
79
+ description=data["description"],
80
+ source=data["source"],
81
+ actor=data["actor"],
82
+ )
83
+
84
+ def _load(self) -> None:
85
+ if not self._path.exists():
86
+ return
87
+ try:
88
+ raw = json.loads(self._path.read_text(encoding="utf-8"))
89
+ self._events = [self._deserialize_event(d) for d in raw]
90
+ except Exception:
91
+ logger.warning("Failed to load changelog from %s", self._path)
92
+
93
+ def _save(self) -> None:
94
+ self._path.parent.mkdir(parents=True, exist_ok=True)
95
+ data = [self._serialize_event(e) for e in self._events]
96
+ # Atomic write: write to temp then rename
97
+ tmp_fd, tmp_path = tempfile.mkstemp(
98
+ dir=str(self._path.parent), suffix=".tmp"
99
+ )
100
+ try:
101
+ with open(tmp_fd, "w", encoding="utf-8") as fh:
102
+ json.dump(data, fh, ensure_ascii=False, indent=2)
103
+ Path(tmp_path).replace(self._path)
104
+ except Exception:
105
+ Path(tmp_path).unlink(missing_ok=True)
106
+ raise