Skip to content

Commit aec0b67

Browse files
committed
feat: add canonical schema export
1 parent 56a9789 commit aec0b67

7 files changed

Lines changed: 166 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 0.21.0 - 2026-03-13
4+
5+
- added a canonical JSON Schema export for third-party tool integration
6+
- added a `schema` CLI command to print or write the schema
7+
38
## 0.20.0 - 2026-03-13
49

510
- refreshed README homepage with badges, quick start, and workflow overview

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This project solves that with a portable canonical memory package plus adapter-b
2525

2626
- detect supported source formats automatically
2727
- normalize memory into a canonical package
28+
- publish a JSON Schema for external tool integrations
2829
- convert across agent-specific formats
2930
- run diagnostics with `doctor`
3031
- generate suggestions and safe repairs

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "memory-migrate-plugin"
7-
version = "0.20.0"
7+
version = "0.21.0"
88
description = "Open-source toolkit for migrating memories across AI agent systems."
99
readme = "README.md"
1010
requires-python = ">=3.11"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__all__ = ["__version__"]
22

3-
__version__ = "0.20.0"
3+
__version__ = "0.21.0"

src/memory_migrate_plugin/cli.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from memory_migrate_plugin.registry import build_registry, detect_format
1616
from memory_migrate_plugin.release import run_release
1717
from memory_migrate_plugin.repair import repair_package
18+
from memory_migrate_plugin.schema import build_canonical_package_schema, write_canonical_package_schema
1819
from memory_migrate_plugin.report import build_merge_report, build_package_report
1920
from memory_migrate_plugin.suggest import build_package_suggestions
2021
from memory_migrate_plugin.utils import write_json
@@ -28,6 +29,9 @@ def build_parser() -> argparse.ArgumentParser:
2829
subparsers.add_parser("adapters", help="List supported adapters.")
2930
subparsers.add_parser("profiles", help="List supported export profiles.")
3031

32+
schema_parser = subparsers.add_parser("schema", help="Print or export the canonical JSON Schema.")
33+
schema_parser.add_argument("--output")
34+
3135
detect_parser = subparsers.add_parser("detect", help="Detect the most likely source format for a path.")
3236
detect_parser.add_argument("--input", required=True)
3337

@@ -129,6 +133,15 @@ def command_profiles() -> int:
129133
return 0
130134

131135

136+
def command_schema(output_path: str | None) -> int:
137+
schema = build_canonical_package_schema() if not output_path else write_canonical_package_schema(Path(output_path))
138+
if output_path:
139+
print(f"Wrote schema to {output_path}")
140+
else:
141+
print(json.dumps(schema, indent=2, ensure_ascii=False))
142+
return 0
143+
144+
132145
def command_detect(source_path: Path) -> int:
133146
matches = detect_format(source_path)
134147
if not matches:
@@ -317,6 +330,8 @@ def main() -> int:
317330
return command_adapters()
318331
if args.command == "profiles":
319332
return command_profiles()
333+
if args.command == "schema":
334+
return command_schema(args.output)
320335
if args.command == "detect":
321336
return command_detect(Path(args.input))
322337
if args.command == "inspect":
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from typing import Any
5+
6+
from memory_migrate_plugin.utils import write_json
7+
8+
9+
CANONICAL_SCHEMA_ID = "https://agent-memory-bridge.dev/schema/canonical-memory-package/v1.0"
10+
11+
12+
def build_canonical_package_schema() -> dict[str, Any]:
13+
return {
14+
"$schema": "https://json-schema.org/draft/2020-12/schema",
15+
"$id": CANONICAL_SCHEMA_ID,
16+
"title": "CanonicalMemoryPackage",
17+
"type": "object",
18+
"additionalProperties": False,
19+
"required": [
20+
"schema_version",
21+
"package_id",
22+
"created_at",
23+
"source_formats",
24+
"metadata",
25+
"entries",
26+
],
27+
"properties": {
28+
"schema_version": {
29+
"type": "string",
30+
"description": "Canonical package schema version.",
31+
"default": "1.0",
32+
},
33+
"package_id": {
34+
"type": "string",
35+
"description": "Stable identifier for the package.",
36+
"minLength": 1,
37+
},
38+
"created_at": {
39+
"type": "string",
40+
"description": "UTC creation timestamp in ISO 8601 format.",
41+
"format": "date-time",
42+
},
43+
"source_formats": {
44+
"type": "array",
45+
"description": "Distinct source adapters represented in the package.",
46+
"items": {"type": "string"},
47+
"uniqueItems": True,
48+
},
49+
"metadata": {
50+
"type": "object",
51+
"description": "Package-level metadata and provenance fields.",
52+
"additionalProperties": True,
53+
},
54+
"entries": {
55+
"type": "array",
56+
"description": "Normalized memory entries.",
57+
"items": {"$ref": "#/$defs/memoryEntry"},
58+
},
59+
},
60+
"$defs": {
61+
"memoryEntry": {
62+
"type": "object",
63+
"additionalProperties": False,
64+
"required": [
65+
"id",
66+
"kind",
67+
"title",
68+
"content",
69+
"tags",
70+
"source_format",
71+
"metadata",
72+
],
73+
"properties": {
74+
"id": {
75+
"type": "string",
76+
"description": "Stable entry identifier.",
77+
"minLength": 1,
78+
},
79+
"kind": {
80+
"type": "string",
81+
"description": "Semantic entry category such as note, project, or preference.",
82+
"minLength": 1,
83+
},
84+
"title": {
85+
"type": "string",
86+
"description": "Human-readable entry title.",
87+
"minLength": 1,
88+
},
89+
"content": {
90+
"type": "string",
91+
"description": "Primary body content for the memory entry.",
92+
},
93+
"tags": {
94+
"type": "array",
95+
"description": "Keyword tags used for retrieval and filtering.",
96+
"items": {"type": "string"},
97+
},
98+
"source_format": {
99+
"type": "string",
100+
"description": "Adapter name that produced the entry.",
101+
"minLength": 1,
102+
},
103+
"created_at": {
104+
"type": ["string", "null"],
105+
"description": "Original creation timestamp when available.",
106+
"format": "date-time",
107+
},
108+
"updated_at": {
109+
"type": ["string", "null"],
110+
"description": "Original update timestamp when available.",
111+
"format": "date-time",
112+
},
113+
"metadata": {
114+
"type": "object",
115+
"description": "Entry-level adapter metadata.",
116+
"additionalProperties": True,
117+
},
118+
},
119+
}
120+
},
121+
}
122+
123+
124+
def write_canonical_package_schema(output_path: Path) -> dict[str, Any]:
125+
schema = build_canonical_package_schema()
126+
write_json(output_path, schema)
127+
return schema

tests/test_roundtrip.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from memory_migrate_plugin.suggest import build_package_suggestions
1919
from memory_migrate_plugin.manifest import build_manifest
2020
from memory_migrate_plugin.verify import verify_manifest
21+
from memory_migrate_plugin.schema import build_canonical_package_schema, write_canonical_package_schema
2122

2223

2324
class MemoryMigrateTests(unittest.TestCase):
@@ -53,6 +54,21 @@ def test_generic_json_roundtrip(self) -> None:
5354
package = normalize("generic-json", source)
5455
self.assertEqual(package.entries[0].title, "Editor")
5556

57+
def test_build_canonical_package_schema_describes_required_fields(self) -> None:
58+
schema = build_canonical_package_schema()
59+
self.assertEqual(schema["title"], "CanonicalMemoryPackage")
60+
self.assertIn("entries", schema["required"])
61+
self.assertEqual(schema["properties"]["entries"]["items"]["$ref"], "#/$defs/memoryEntry")
62+
self.assertIn("source_format", schema["$defs"]["memoryEntry"]["required"])
63+
64+
def test_write_canonical_package_schema_outputs_json_file(self) -> None:
65+
with tempfile.TemporaryDirectory() as tmpdir:
66+
output = Path(tmpdir) / "canonical-memory-package.schema.json"
67+
schema = write_canonical_package_schema(output)
68+
written = json.loads(output.read_text(encoding="utf-8"))
69+
self.assertEqual(written["$id"], schema["$id"])
70+
self.assertEqual(written["$schema"], "https://json-schema.org/draft/2020-12/schema")
71+
5672
def test_detect_format_prefers_cline_memory_bank(self) -> None:
5773
with tempfile.TemporaryDirectory() as tmpdir:
5874
source = Path(tmpdir) / "memory-bank"

0 commit comments

Comments
 (0)