memlink-bridge 0.1.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 (38) hide show
  1. memlink_bridge-0.1.0/LICENSE +21 -0
  2. memlink_bridge-0.1.0/PKG-INFO +243 -0
  3. memlink_bridge-0.1.0/README.md +209 -0
  4. memlink_bridge-0.1.0/pyproject.toml +103 -0
  5. memlink_bridge-0.1.0/python/memlink/__init__.py +28 -0
  6. memlink_bridge-0.1.0/python/memlink/_frontmatter.py +28 -0
  7. memlink_bridge-0.1.0/python/memlink/cli.py +418 -0
  8. memlink_bridge-0.1.0/python/memlink/converter.py +510 -0
  9. memlink_bridge-0.1.0/python/memlink/generic_reader.py +214 -0
  10. memlink_bridge-0.1.0/python/memlink/models.py +120 -0
  11. memlink_bridge-0.1.0/python/memlink/ombre_reader.py +184 -0
  12. memlink_bridge-0.1.0/python/memlink/ombre_writer.py +189 -0
  13. memlink_bridge-0.1.0/python/memlink/openclaw_reader.py +300 -0
  14. memlink_bridge-0.1.0/python/memlink/openclaw_writer.py +366 -0
  15. memlink_bridge-0.1.0/python/memlink/plugin.py +90 -0
  16. memlink_bridge-0.1.0/python/memlink/py.typed +0 -0
  17. memlink_bridge-0.1.0/python/memlink/registry.py +114 -0
  18. memlink_bridge-0.1.0/python/memlink/serialization.py +76 -0
  19. memlink_bridge-0.1.0/python/memlink/testing.py +144 -0
  20. memlink_bridge-0.1.0/python/memlink/validators.py +322 -0
  21. memlink_bridge-0.1.0/python/memlink_bridge.egg-info/PKG-INFO +243 -0
  22. memlink_bridge-0.1.0/python/memlink_bridge.egg-info/SOURCES.txt +36 -0
  23. memlink_bridge-0.1.0/python/memlink_bridge.egg-info/dependency_links.txt +1 -0
  24. memlink_bridge-0.1.0/python/memlink_bridge.egg-info/entry_points.txt +11 -0
  25. memlink_bridge-0.1.0/python/memlink_bridge.egg-info/requires.txt +10 -0
  26. memlink_bridge-0.1.0/python/memlink_bridge.egg-info/top_level.txt +1 -0
  27. memlink_bridge-0.1.0/setup.cfg +4 -0
  28. memlink_bridge-0.1.0/tests/test_fuzz.py +121 -0
  29. memlink_bridge-0.1.0/tests/test_generic_reader.py +86 -0
  30. memlink_bridge-0.1.0/tests/test_models.py +107 -0
  31. memlink_bridge-0.1.0/tests/test_ombre_reader.py +73 -0
  32. memlink_bridge-0.1.0/tests/test_ombre_writer.py +170 -0
  33. memlink_bridge-0.1.0/tests/test_openclaw_reader.py +210 -0
  34. memlink_bridge-0.1.0/tests/test_openclaw_writer.py +153 -0
  35. memlink_bridge-0.1.0/tests/test_plugin.py +87 -0
  36. memlink_bridge-0.1.0/tests/test_schema.py +84 -0
  37. memlink_bridge-0.1.0/tests/test_serialization.py +81 -0
  38. memlink_bridge-0.1.0/tests/test_validators.py +67 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 memlink contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: memlink-bridge
3
+ Version: 0.1.0
4
+ Summary: AI Memory Interchange Layer — language-neutral bridge for AI memory systems
5
+ Author: memlink contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/velnori/memlink
8
+ Project-URL: Repository, https://github.com/velnori/memlink
9
+ Project-URL: Issues, https://github.com/velnori/memlink/issues
10
+ Keywords: ai,memory,interchange,canonical,bridge,pandoc
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: pyyaml>=6.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8; extra == "dev"
28
+ Requires-Dist: pytest-cov>=5; extra == "dev"
29
+ Requires-Dist: mypy>=1; extra == "dev"
30
+ Requires-Dist: ruff>=0.6; extra == "dev"
31
+ Provides-Extra: docs
32
+ Requires-Dist: mkdocs-material>=9; extra == "docs"
33
+ Dynamic: license-file
34
+
35
+ # memlink
36
+
37
+ > A language-neutral interchange layer for AI memory systems — similar in spirit to how Pandoc enables document interoperability.
38
+
39
+ [![CI](https://github.com/velnori/memlink/actions/workflows/test.yml/badge.svg)](https://github.com/velnori/memlink/actions/workflows/test.yml)
40
+ [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)](https://www.python.org/)
41
+ [![PyPI](https://img.shields.io/badge/pypi-memlink-blue)](https://pypi.org/project/memlink/)
42
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
43
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-261230)](https://github.com/astral-sh/ruff)
44
+ [![codecov](https://codecov.io/gh/velnori/memlink/branch/main/graph/badge.svg)](https://codecov.io/gh/velnori/memlink)
45
+
46
+ ## Why memlink?
47
+
48
+ **Problem**: 10+ AI memory formats (Ombre, OpenClaw, Mem0, Zep, Letta...), each with its own schema. Converting between them naively requires O(n²) converters.
49
+
50
+ **Solution**: A single Canonical Memory intermediate format. Add a new system = write one Reader + one Writer. O(n) complexity.
51
+
52
+ ```
53
+ Ombre ──┐
54
+ Mem0 ──┼──→ Canonical ──┬──→ OpenClaw
55
+ Zep ──┘ └──→ Your Format
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ```bash
61
+ # Install from PyPI
62
+ pip install memlink-bridge
63
+
64
+ # Or from source
65
+ git clone https://github.com/velnori/memlink.git
66
+ cd memlink
67
+ pip install -e .
68
+
69
+ # Inspect a memory
70
+ memlink inspect tests/fixtures/ombre_samples/dynamic/user/sample.md
71
+
72
+ # Convert Ombre → OpenClaw
73
+ memlink convert --from ombre --to openclaw \
74
+ -s tests/fixtures/ombre_samples \
75
+ -T ./my-memories/
76
+
77
+ # Validate roundtrip
78
+ memlink validate -s tests/fixtures/ombre_samples --level schema
79
+
80
+ # Show installed formats
81
+ memlink formats
82
+
83
+ # Validate roundtrip integrity
84
+ memlink validate --level roundtrip -s ~/.claude/ombre-buckets/
85
+
86
+ # List installed formats
87
+ memlink formats
88
+ ```
89
+
90
+ ## Supported Formats
91
+
92
+ | Plugin | Capability | Covered Apps |
93
+ |--------|-----------|--------------|
94
+ | **Ombre** | Read + Write | [Ombre Brain](https://github.com/P0luz/Ombre-Brain) |
95
+ | **OpenClaw** | Read + Write | OpenClaw core, [basic-memory](https://github.com/basicmachines-co/openclaw-basic-memory), [claw-mem](https://github.com/opensourceclaw/claw-mem) |
96
+ | **Generic** | Read | Obsidian, Logseq, Bear, iA Writer, plain Markdown |
97
+
98
+ **3 plugins, 7+ apps interoperable.** Generic Reader alone covers every app that uses YAML frontmatter + Markdown body — no additional code needed.
99
+
100
+ Add a new format = write one Reader or Writer. Zero changes to core code. That's the O(n) architecture.
101
+
102
+ ## Architecture
103
+
104
+ memlink uses a **Canonical Memory** intermediate format. Each AI memory format gets a plugin with three methods:
105
+
106
+ ```python
107
+ class FormatPlugin:
108
+ def read(path) -> ReadResult # Format → Canonical
109
+ def write(memories, path) -> [] # Canonical → Format
110
+ def validate(path) -> [Issue] # Format-specific checks
111
+ ```
112
+
113
+ ### Canonical Memory Schema
114
+
115
+ ```yaml
116
+ schema_version: "1"
117
+ id: "project-alpha-kickoff"
118
+ name: "Project Alpha Kickoff"
119
+ summary: "Initial planning session"
120
+ body: |
121
+ ## Decisions
122
+ - Use TypeScript for frontend
123
+ - Deploy on AWS
124
+
125
+ kind: dynamic # dynamic | permanent | emotion (open string)
126
+ status: active # active | archived
127
+ tags: [meeting, planning]
128
+ domains: [work, project]
129
+
130
+ created_at: "2024-06-28T10:00:00Z"
131
+ updated_at: "2024-06-28T15:30:00Z"
132
+
133
+ importance_score: 0.8
134
+ importance_label: null
135
+ valence: 0.7
136
+ arousal: 0.6
137
+ pinned: false
138
+ checksum: "sha256:abc123..."
139
+
140
+ source:
141
+ format: ombre
142
+ path: dynamic/work/meeting.md
143
+ uri: ombre://dynamic/work/project-alpha-kickoff
144
+
145
+ metadata:
146
+ memlink: # Roundtrip preservation
147
+ source:
148
+ format: ombre
149
+ version: "1.0"
150
+ original:
151
+ importance: 8
152
+ created_tz: "2024-06-28T18:00:00+08:00"
153
+
154
+ extensions: {} # Third-party pass-through
155
+ relationships: [] # v0 reserved
156
+ ```
157
+
158
+ Full schema spec: [spec/canonical-v1.md](spec/canonical-v1.md) | JSON Schema: [spec/canonical-v1.schema.json](spec/canonical-v1.schema.json)
159
+
160
+ ### Feature Loss Report
161
+
162
+ Conversion warns about capabilities mismatch:
163
+
164
+ ```
165
+ memlink convert --from ombre --to openclaw -s ... -t ...
166
+
167
+ Converted: 128
168
+
169
+ ── Feature Loss ──
170
+ relationships 8 dropped
171
+ emotion 14 dropped
172
+ extensions 5 dropped
173
+ ─────────────────
174
+ Total loss 27 fields across 19 memories
175
+ ```
176
+
177
+ ## What memlink is NOT
178
+
179
+ - ❌ **Synchronization Engine** — v0 is export/import only
180
+ - ❌ **Memory Database** — Works with files, not live memory APIs
181
+ - ❌ **Embedding Store** — No vector search or similarity
182
+ - ❌ **Knowledge Graph** — No graph traversal or inference
183
+ - ❌ **Not opinionated** — Preserves original data structures, no normalization
184
+
185
+ ## Spec Compliance
186
+
187
+ | Canonical v1 feature | Ombre | OpenClaw |
188
+ |---------------------|-------|----------|
189
+ | Core Fields (id, name, body) | ✅ | ✅ |
190
+ | Summary | — | ✅ |
191
+ | Tags & Domains | ✅ | ✅ |
192
+ | Emotion (valence/arousal) | ✅ | — |
193
+ | Importance | ✅ 1-10 | ✅ label/score |
194
+ | Pinned | ✅ | ✅ |
195
+ | Timestamps | ✅ | ✅ |
196
+ | Relationships | ⚠ v0 metadata | ⚠ v0 metadata |
197
+ | Extensions | ⚠ pass-through | ⚠ not preserved |
198
+
199
+ Full spec: [spec/canonical-v1.md](spec/canonical-v1.md)
200
+
201
+ ## Non-Goals
202
+
203
+ - ❌ **Not a sync tool** — v0 is export/import only. Bidirectional sync is future work.
204
+ - ❌ **Not a database** — Works with files, not live memory APIs.
205
+ - ❌ **Not opinionated** — Preserves original data structures. No normalization.
206
+
207
+ ## Limitations
208
+
209
+ ### Lossy Conversions
210
+
211
+ | Direction | What may be lost | Mitigation |
212
+ |-----------|-----------------|------------|
213
+ | OpenClaw → Ombre | Domain if no `metadata.domain` | `--unknown-domain-action default:general` |
214
+ | Ombre → OpenClaw | Local timezone | Stored in `metadata.memlink.original.created_tz` |
215
+
216
+ ### Concurrency
217
+
218
+ ⚠️ v0 does not support concurrent writes to the same target directory. See [#1](https://github.com/velnori/memlink/issues/1).
219
+
220
+ ## Development
221
+
222
+ ```bash
223
+ git clone https://github.com/velnori/memlink.git
224
+ cd memlink
225
+ pip install -e ".[dev]"
226
+
227
+ # Run tests
228
+ pytest tests/ -v
229
+
230
+ # Lint & type check
231
+ ruff check memlink/
232
+ mypy memlink/
233
+ ```
234
+
235
+ ### Adding a New Format
236
+
237
+ 1. Create `memlink/<format>_reader.py` — implement `FormatPlugin.read()`
238
+ 2. Create `memlink/<format>_writer.py` — implement `FormatPlugin.write()`
239
+ 3. Register in `pyproject.toml` under `[project.entry-points."memlink.readers"]` and `writers`
240
+
241
+ ## License
242
+
243
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,209 @@
1
+ # memlink
2
+
3
+ > A language-neutral interchange layer for AI memory systems — similar in spirit to how Pandoc enables document interoperability.
4
+
5
+ [![CI](https://github.com/velnori/memlink/actions/workflows/test.yml/badge.svg)](https://github.com/velnori/memlink/actions/workflows/test.yml)
6
+ [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)](https://www.python.org/)
7
+ [![PyPI](https://img.shields.io/badge/pypi-memlink-blue)](https://pypi.org/project/memlink/)
8
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
9
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-261230)](https://github.com/astral-sh/ruff)
10
+ [![codecov](https://codecov.io/gh/velnori/memlink/branch/main/graph/badge.svg)](https://codecov.io/gh/velnori/memlink)
11
+
12
+ ## Why memlink?
13
+
14
+ **Problem**: 10+ AI memory formats (Ombre, OpenClaw, Mem0, Zep, Letta...), each with its own schema. Converting between them naively requires O(n²) converters.
15
+
16
+ **Solution**: A single Canonical Memory intermediate format. Add a new system = write one Reader + one Writer. O(n) complexity.
17
+
18
+ ```
19
+ Ombre ──┐
20
+ Mem0 ──┼──→ Canonical ──┬──→ OpenClaw
21
+ Zep ──┘ └──→ Your Format
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```bash
27
+ # Install from PyPI
28
+ pip install memlink-bridge
29
+
30
+ # Or from source
31
+ git clone https://github.com/velnori/memlink.git
32
+ cd memlink
33
+ pip install -e .
34
+
35
+ # Inspect a memory
36
+ memlink inspect tests/fixtures/ombre_samples/dynamic/user/sample.md
37
+
38
+ # Convert Ombre → OpenClaw
39
+ memlink convert --from ombre --to openclaw \
40
+ -s tests/fixtures/ombre_samples \
41
+ -T ./my-memories/
42
+
43
+ # Validate roundtrip
44
+ memlink validate -s tests/fixtures/ombre_samples --level schema
45
+
46
+ # Show installed formats
47
+ memlink formats
48
+
49
+ # Validate roundtrip integrity
50
+ memlink validate --level roundtrip -s ~/.claude/ombre-buckets/
51
+
52
+ # List installed formats
53
+ memlink formats
54
+ ```
55
+
56
+ ## Supported Formats
57
+
58
+ | Plugin | Capability | Covered Apps |
59
+ |--------|-----------|--------------|
60
+ | **Ombre** | Read + Write | [Ombre Brain](https://github.com/P0luz/Ombre-Brain) |
61
+ | **OpenClaw** | Read + Write | OpenClaw core, [basic-memory](https://github.com/basicmachines-co/openclaw-basic-memory), [claw-mem](https://github.com/opensourceclaw/claw-mem) |
62
+ | **Generic** | Read | Obsidian, Logseq, Bear, iA Writer, plain Markdown |
63
+
64
+ **3 plugins, 7+ apps interoperable.** Generic Reader alone covers every app that uses YAML frontmatter + Markdown body — no additional code needed.
65
+
66
+ Add a new format = write one Reader or Writer. Zero changes to core code. That's the O(n) architecture.
67
+
68
+ ## Architecture
69
+
70
+ memlink uses a **Canonical Memory** intermediate format. Each AI memory format gets a plugin with three methods:
71
+
72
+ ```python
73
+ class FormatPlugin:
74
+ def read(path) -> ReadResult # Format → Canonical
75
+ def write(memories, path) -> [] # Canonical → Format
76
+ def validate(path) -> [Issue] # Format-specific checks
77
+ ```
78
+
79
+ ### Canonical Memory Schema
80
+
81
+ ```yaml
82
+ schema_version: "1"
83
+ id: "project-alpha-kickoff"
84
+ name: "Project Alpha Kickoff"
85
+ summary: "Initial planning session"
86
+ body: |
87
+ ## Decisions
88
+ - Use TypeScript for frontend
89
+ - Deploy on AWS
90
+
91
+ kind: dynamic # dynamic | permanent | emotion (open string)
92
+ status: active # active | archived
93
+ tags: [meeting, planning]
94
+ domains: [work, project]
95
+
96
+ created_at: "2024-06-28T10:00:00Z"
97
+ updated_at: "2024-06-28T15:30:00Z"
98
+
99
+ importance_score: 0.8
100
+ importance_label: null
101
+ valence: 0.7
102
+ arousal: 0.6
103
+ pinned: false
104
+ checksum: "sha256:abc123..."
105
+
106
+ source:
107
+ format: ombre
108
+ path: dynamic/work/meeting.md
109
+ uri: ombre://dynamic/work/project-alpha-kickoff
110
+
111
+ metadata:
112
+ memlink: # Roundtrip preservation
113
+ source:
114
+ format: ombre
115
+ version: "1.0"
116
+ original:
117
+ importance: 8
118
+ created_tz: "2024-06-28T18:00:00+08:00"
119
+
120
+ extensions: {} # Third-party pass-through
121
+ relationships: [] # v0 reserved
122
+ ```
123
+
124
+ Full schema spec: [spec/canonical-v1.md](spec/canonical-v1.md) | JSON Schema: [spec/canonical-v1.schema.json](spec/canonical-v1.schema.json)
125
+
126
+ ### Feature Loss Report
127
+
128
+ Conversion warns about capabilities mismatch:
129
+
130
+ ```
131
+ memlink convert --from ombre --to openclaw -s ... -t ...
132
+
133
+ Converted: 128
134
+
135
+ ── Feature Loss ──
136
+ relationships 8 dropped
137
+ emotion 14 dropped
138
+ extensions 5 dropped
139
+ ─────────────────
140
+ Total loss 27 fields across 19 memories
141
+ ```
142
+
143
+ ## What memlink is NOT
144
+
145
+ - ❌ **Synchronization Engine** — v0 is export/import only
146
+ - ❌ **Memory Database** — Works with files, not live memory APIs
147
+ - ❌ **Embedding Store** — No vector search or similarity
148
+ - ❌ **Knowledge Graph** — No graph traversal or inference
149
+ - ❌ **Not opinionated** — Preserves original data structures, no normalization
150
+
151
+ ## Spec Compliance
152
+
153
+ | Canonical v1 feature | Ombre | OpenClaw |
154
+ |---------------------|-------|----------|
155
+ | Core Fields (id, name, body) | ✅ | ✅ |
156
+ | Summary | — | ✅ |
157
+ | Tags & Domains | ✅ | ✅ |
158
+ | Emotion (valence/arousal) | ✅ | — |
159
+ | Importance | ✅ 1-10 | ✅ label/score |
160
+ | Pinned | ✅ | ✅ |
161
+ | Timestamps | ✅ | ✅ |
162
+ | Relationships | ⚠ v0 metadata | ⚠ v0 metadata |
163
+ | Extensions | ⚠ pass-through | ⚠ not preserved |
164
+
165
+ Full spec: [spec/canonical-v1.md](spec/canonical-v1.md)
166
+
167
+ ## Non-Goals
168
+
169
+ - ❌ **Not a sync tool** — v0 is export/import only. Bidirectional sync is future work.
170
+ - ❌ **Not a database** — Works with files, not live memory APIs.
171
+ - ❌ **Not opinionated** — Preserves original data structures. No normalization.
172
+
173
+ ## Limitations
174
+
175
+ ### Lossy Conversions
176
+
177
+ | Direction | What may be lost | Mitigation |
178
+ |-----------|-----------------|------------|
179
+ | OpenClaw → Ombre | Domain if no `metadata.domain` | `--unknown-domain-action default:general` |
180
+ | Ombre → OpenClaw | Local timezone | Stored in `metadata.memlink.original.created_tz` |
181
+
182
+ ### Concurrency
183
+
184
+ ⚠️ v0 does not support concurrent writes to the same target directory. See [#1](https://github.com/velnori/memlink/issues/1).
185
+
186
+ ## Development
187
+
188
+ ```bash
189
+ git clone https://github.com/velnori/memlink.git
190
+ cd memlink
191
+ pip install -e ".[dev]"
192
+
193
+ # Run tests
194
+ pytest tests/ -v
195
+
196
+ # Lint & type check
197
+ ruff check memlink/
198
+ mypy memlink/
199
+ ```
200
+
201
+ ### Adding a New Format
202
+
203
+ 1. Create `memlink/<format>_reader.py` — implement `FormatPlugin.read()`
204
+ 2. Create `memlink/<format>_writer.py` — implement `FormatPlugin.write()`
205
+ 3. Register in `pyproject.toml` under `[project.entry-points."memlink.readers"]` and `writers`
206
+
207
+ ## License
208
+
209
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,103 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "memlink-bridge"
7
+ version = "0.1.0"
8
+ description = "AI Memory Interchange Layer — language-neutral bridge for AI memory systems"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "memlink contributors"},
14
+ ]
15
+ keywords = ["ai", "memory", "interchange", "canonical", "bridge", "pandoc"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ "Topic :: Utilities",
28
+ ]
29
+ dependencies = [
30
+ "pyyaml>=6.0",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/velnori/memlink"
35
+ Repository = "https://github.com/velnori/memlink"
36
+ Issues = "https://github.com/velnori/memlink/issues"
37
+
38
+ [project.scripts]
39
+ memlink = "memlink.cli:main"
40
+
41
+ [project.entry-points."memlink.readers"]
42
+ ombre = "memlink.ombre_reader:OmbreReader"
43
+ openclaw = "memlink.openclaw_reader:OpenClawReader"
44
+ generic = "memlink.generic_reader:GenericReader"
45
+
46
+ [project.entry-points."memlink.writers"]
47
+ ombre = "memlink.ombre_writer:OmbreWriter"
48
+ openclaw = "memlink.openclaw_writer:OpenClawWriter"
49
+
50
+ [project.optional-dependencies]
51
+ dev = [
52
+ "pytest>=8",
53
+ "pytest-cov>=5",
54
+ "mypy>=1",
55
+ "ruff>=0.6",
56
+ ]
57
+ docs = [
58
+ "mkdocs-material>=9",
59
+ ]
60
+
61
+ [tool.ruff]
62
+ target-version = "py310"
63
+ line-length = 100
64
+
65
+ [tool.ruff.lint]
66
+ select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
67
+
68
+ [tool.ruff.lint.isort]
69
+ known-first-party = ["memlink"]
70
+
71
+ [tool.ruff.format]
72
+ quote-style = "double"
73
+ indent-style = "space"
74
+ skip-magic-trailing-comma = false
75
+
76
+ [tool.mypy]
77
+ strict = true
78
+ python_version = "3.10"
79
+ ignore_missing_imports = true
80
+
81
+ [tool.pytest.ini_options]
82
+ testpaths = ["tests"]
83
+ python_files = ["test_*.py"]
84
+ pythonpath = ["python"]
85
+ addopts = "-v --tb=short --strict-markers"
86
+ markers = [
87
+ "slow: slow tests (fuzz, performance)",
88
+ ]
89
+
90
+ [tool.coverage.run]
91
+ source = ["memlink"]
92
+ omit = ["tests/*"]
93
+
94
+ [tool.coverage.report]
95
+ fail_under = 85
96
+ exclude_lines = [
97
+ "pragma: no cover",
98
+ "if TYPE_CHECKING:",
99
+ "raise NotImplementedError",
100
+ ]
101
+
102
+ [tool.setuptools.packages.find]
103
+ where = ["python"]
@@ -0,0 +1,28 @@
1
+ """memlink — AI Memory Interchange Layer."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .models import JSONValue, Memory, Relationship, Source
6
+ from .plugin import Capabilities, FormatPlugin, ReadResult, Severity, ValidationIssue
7
+ from .converter import ConversionAnalysis, FeatureImpact, analyze_conversion, compare_memories
8
+ from .registry import get_reader, get_writer, list_formats
9
+
10
+ __all__ = [
11
+ "__version__",
12
+ "Memory",
13
+ "Source",
14
+ "Relationship",
15
+ "JSONValue",
16
+ "FormatPlugin",
17
+ "Capabilities",
18
+ "ReadResult",
19
+ "ValidationIssue",
20
+ "Severity",
21
+ "ConversionAnalysis",
22
+ "FeatureImpact",
23
+ "analyze_conversion",
24
+ "compare_memories",
25
+ "get_reader",
26
+ "get_writer",
27
+ "list_formats",
28
+ ]
@@ -0,0 +1,28 @@
1
+ """Shared YAML frontmatter parser — used by all Readers and validators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import yaml
6
+
7
+
8
+ def parse_frontmatter(text: str) -> tuple[dict, str]:
9
+ """Parse YAML frontmatter from a Markdown file.
10
+
11
+ Returns (frontmatter_dict, body_text).
12
+ If no frontmatter found, returns ({}, text).
13
+ If YAML is invalid, returns ({}, text) — never raises.
14
+ """
15
+ if not text.startswith("---"):
16
+ return {}, text
17
+
18
+ parts = text.split("---", 2)
19
+ if len(parts) < 3:
20
+ return {}, text
21
+
22
+ try:
23
+ fm = yaml.safe_load(parts[1])
24
+ if not isinstance(fm, dict):
25
+ return {}, text
26
+ return fm, parts[2]
27
+ except yaml.YAMLError:
28
+ return {}, text