@smilintux/skmemory 0.5.0
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.
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/publish.yml +52 -0
- package/ARCHITECTURE.md +219 -0
- package/LICENSE +661 -0
- package/README.md +159 -0
- package/SKILL.md +271 -0
- package/bin/cli.js +8 -0
- package/docker-compose.yml +58 -0
- package/index.d.ts +4 -0
- package/index.js +27 -0
- package/openclaw-plugin/package.json +59 -0
- package/openclaw-plugin/src/index.js +276 -0
- package/package.json +28 -0
- package/pyproject.toml +69 -0
- package/requirements.txt +13 -0
- package/seeds/cloud9-lumina.seed.json +39 -0
- package/seeds/cloud9-opus.seed.json +40 -0
- package/seeds/courage.seed.json +24 -0
- package/seeds/curiosity.seed.json +24 -0
- package/seeds/grief.seed.json +24 -0
- package/seeds/joy.seed.json +24 -0
- package/seeds/love.seed.json +24 -0
- package/seeds/skcapstone-lumina-merge.moltbook.md +65 -0
- package/seeds/skcapstone-lumina-merge.seed.json +49 -0
- package/seeds/sovereignty.seed.json +24 -0
- package/seeds/trust.seed.json +24 -0
- package/skmemory/__init__.py +66 -0
- package/skmemory/ai_client.py +182 -0
- package/skmemory/anchor.py +224 -0
- package/skmemory/backends/__init__.py +12 -0
- package/skmemory/backends/base.py +88 -0
- package/skmemory/backends/falkordb_backend.py +310 -0
- package/skmemory/backends/file_backend.py +209 -0
- package/skmemory/backends/qdrant_backend.py +364 -0
- package/skmemory/backends/sqlite_backend.py +665 -0
- package/skmemory/cli.py +1004 -0
- package/skmemory/data/seed.json +191 -0
- package/skmemory/importers/__init__.py +11 -0
- package/skmemory/importers/telegram.py +336 -0
- package/skmemory/journal.py +223 -0
- package/skmemory/lovenote.py +180 -0
- package/skmemory/models.py +228 -0
- package/skmemory/openclaw.py +237 -0
- package/skmemory/quadrants.py +191 -0
- package/skmemory/ritual.py +215 -0
- package/skmemory/seeds.py +163 -0
- package/skmemory/soul.py +273 -0
- package/skmemory/steelman.py +338 -0
- package/skmemory/store.py +445 -0
- package/tests/__init__.py +0 -0
- package/tests/test_ai_client.py +89 -0
- package/tests/test_anchor.py +153 -0
- package/tests/test_cli.py +65 -0
- package/tests/test_export_import.py +170 -0
- package/tests/test_file_backend.py +211 -0
- package/tests/test_journal.py +172 -0
- package/tests/test_lovenote.py +136 -0
- package/tests/test_models.py +194 -0
- package/tests/test_openclaw.py +122 -0
- package/tests/test_quadrants.py +174 -0
- package/tests/test_ritual.py +195 -0
- package/tests/test_seeds.py +208 -0
- package/tests/test_soul.py +197 -0
- package/tests/test_sqlite_backend.py +258 -0
- package/tests/test_steelman.py +257 -0
- package/tests/test_store.py +238 -0
- package/tests/test_telegram_import.py +181 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Tests for the SQLite-indexed storage backend."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from skmemory.backends.sqlite_backend import SQLiteBackend
|
|
10
|
+
from skmemory.models import EmotionalSnapshot, Memory, MemoryLayer, MemoryRole
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def backend(tmp_path):
|
|
15
|
+
"""Create a SQLiteBackend with a temporary directory."""
|
|
16
|
+
return SQLiteBackend(base_path=str(tmp_path / "memories"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def sample_memory():
|
|
21
|
+
"""Create a sample Memory object."""
|
|
22
|
+
return Memory(
|
|
23
|
+
title="Test moment",
|
|
24
|
+
content="This is a detailed memory about something important.",
|
|
25
|
+
layer=MemoryLayer.SHORT,
|
|
26
|
+
role=MemoryRole.GENERAL,
|
|
27
|
+
tags=["test", "cloud9"],
|
|
28
|
+
emotional=EmotionalSnapshot(
|
|
29
|
+
intensity=8.5,
|
|
30
|
+
valence=0.9,
|
|
31
|
+
labels=["joy", "connection"],
|
|
32
|
+
cloud9_achieved=True,
|
|
33
|
+
),
|
|
34
|
+
source="test",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TestSaveAndLoad:
|
|
39
|
+
"""Basic CRUD operations."""
|
|
40
|
+
|
|
41
|
+
def test_save_creates_file_and_index(self, backend, sample_memory):
|
|
42
|
+
"""Save persists both JSON file and SQLite index entry."""
|
|
43
|
+
mid = backend.save(sample_memory)
|
|
44
|
+
assert mid == sample_memory.id
|
|
45
|
+
|
|
46
|
+
path = backend.base_path / "short-term" / f"{mid}.json"
|
|
47
|
+
assert path.exists()
|
|
48
|
+
|
|
49
|
+
stats = backend.stats()
|
|
50
|
+
assert stats["total"] == 1
|
|
51
|
+
|
|
52
|
+
def test_load_returns_full_memory(self, backend, sample_memory):
|
|
53
|
+
"""Load retrieves the complete Memory object."""
|
|
54
|
+
backend.save(sample_memory)
|
|
55
|
+
loaded = backend.load(sample_memory.id)
|
|
56
|
+
|
|
57
|
+
assert loaded is not None
|
|
58
|
+
assert loaded.title == "Test moment"
|
|
59
|
+
assert loaded.content == sample_memory.content
|
|
60
|
+
assert loaded.emotional.intensity == 8.5
|
|
61
|
+
|
|
62
|
+
def test_load_nonexistent_returns_none(self, backend):
|
|
63
|
+
"""Loading a missing memory returns None."""
|
|
64
|
+
assert backend.load("nonexistent-id") is None
|
|
65
|
+
|
|
66
|
+
def test_delete_removes_file_and_index(self, backend, sample_memory):
|
|
67
|
+
"""Delete removes both the file and the index entry."""
|
|
68
|
+
backend.save(sample_memory)
|
|
69
|
+
assert backend.delete(sample_memory.id) is True
|
|
70
|
+
|
|
71
|
+
assert backend.load(sample_memory.id) is None
|
|
72
|
+
assert backend.stats()["total"] == 0
|
|
73
|
+
|
|
74
|
+
def test_delete_nonexistent(self, backend):
|
|
75
|
+
"""Deleting a missing memory returns False."""
|
|
76
|
+
assert backend.delete("nonexistent-id") is False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestListAndFilter:
|
|
80
|
+
"""Index-based listing and filtering."""
|
|
81
|
+
|
|
82
|
+
def _store_memories(self, backend, count=5):
|
|
83
|
+
memories = []
|
|
84
|
+
for i in range(count):
|
|
85
|
+
m = Memory(
|
|
86
|
+
title=f"Memory {i}",
|
|
87
|
+
content=f"Content for memory number {i}",
|
|
88
|
+
layer=MemoryLayer.SHORT if i % 2 == 0 else MemoryLayer.LONG,
|
|
89
|
+
tags=["alpha"] if i < 3 else ["beta"],
|
|
90
|
+
emotional=EmotionalSnapshot(intensity=float(i)),
|
|
91
|
+
)
|
|
92
|
+
backend.save(m)
|
|
93
|
+
memories.append(m)
|
|
94
|
+
return memories
|
|
95
|
+
|
|
96
|
+
def test_list_all(self, backend):
|
|
97
|
+
"""List without filters returns all memories."""
|
|
98
|
+
self._store_memories(backend, 5)
|
|
99
|
+
result = backend.list_memories(limit=50)
|
|
100
|
+
assert len(result) == 5
|
|
101
|
+
|
|
102
|
+
def test_list_by_layer(self, backend):
|
|
103
|
+
"""Filter by layer works via the index."""
|
|
104
|
+
self._store_memories(backend, 5)
|
|
105
|
+
short = backend.list_memories(layer=MemoryLayer.SHORT)
|
|
106
|
+
long = backend.list_memories(layer=MemoryLayer.LONG)
|
|
107
|
+
assert len(short) == 3
|
|
108
|
+
assert len(long) == 2
|
|
109
|
+
|
|
110
|
+
def test_list_by_tags(self, backend):
|
|
111
|
+
"""Filter by tags works via the index."""
|
|
112
|
+
self._store_memories(backend, 5)
|
|
113
|
+
alpha = backend.list_memories(tags=["alpha"])
|
|
114
|
+
beta = backend.list_memories(tags=["beta"])
|
|
115
|
+
assert len(alpha) == 3
|
|
116
|
+
assert len(beta) == 2
|
|
117
|
+
|
|
118
|
+
def test_list_respects_limit(self, backend):
|
|
119
|
+
"""Limit parameter caps results."""
|
|
120
|
+
self._store_memories(backend, 10)
|
|
121
|
+
result = backend.list_memories(limit=3)
|
|
122
|
+
assert len(result) == 3
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class TestListSummaries:
|
|
126
|
+
"""Token-efficient summary queries."""
|
|
127
|
+
|
|
128
|
+
def _store_memories(self, backend, count=5):
|
|
129
|
+
for i in range(count):
|
|
130
|
+
m = Memory(
|
|
131
|
+
title=f"Memory {i}",
|
|
132
|
+
content=f"Full detailed content for memory {i} " * 20,
|
|
133
|
+
summary=f"Brief summary {i}",
|
|
134
|
+
layer=MemoryLayer.SHORT,
|
|
135
|
+
tags=["test"],
|
|
136
|
+
emotional=EmotionalSnapshot(intensity=float(i)),
|
|
137
|
+
)
|
|
138
|
+
backend.save(m)
|
|
139
|
+
|
|
140
|
+
def test_summaries_are_lightweight(self, backend):
|
|
141
|
+
"""Summaries return dicts, not full Memory objects."""
|
|
142
|
+
self._store_memories(backend, 3)
|
|
143
|
+
summaries = backend.list_summaries(limit=3)
|
|
144
|
+
|
|
145
|
+
assert len(summaries) == 3
|
|
146
|
+
assert isinstance(summaries[0], dict)
|
|
147
|
+
assert "title" in summaries[0]
|
|
148
|
+
assert "summary" in summaries[0]
|
|
149
|
+
assert "content_preview" in summaries[0]
|
|
150
|
+
|
|
151
|
+
def test_summaries_order_by_intensity(self, backend):
|
|
152
|
+
"""Can order by emotional intensity."""
|
|
153
|
+
self._store_memories(backend, 5)
|
|
154
|
+
summaries = backend.list_summaries(
|
|
155
|
+
order_by="emotional_intensity", limit=3
|
|
156
|
+
)
|
|
157
|
+
intensities = [s["emotional_intensity"] for s in summaries]
|
|
158
|
+
assert intensities == sorted(intensities, reverse=True)
|
|
159
|
+
|
|
160
|
+
def test_summaries_filter_min_intensity(self, backend):
|
|
161
|
+
"""Can filter by minimum emotional intensity."""
|
|
162
|
+
self._store_memories(backend, 5)
|
|
163
|
+
summaries = backend.list_summaries(min_intensity=3.0)
|
|
164
|
+
assert all(s["emotional_intensity"] >= 3.0 for s in summaries)
|
|
165
|
+
|
|
166
|
+
def test_content_preview_is_truncated(self, backend):
|
|
167
|
+
"""Content preview doesn't include full content."""
|
|
168
|
+
m = Memory(
|
|
169
|
+
title="Long content test",
|
|
170
|
+
content="x" * 1000,
|
|
171
|
+
layer=MemoryLayer.SHORT,
|
|
172
|
+
)
|
|
173
|
+
backend.save(m)
|
|
174
|
+
summaries = backend.list_summaries()
|
|
175
|
+
assert len(summaries[0]["content_preview"]) <= 150
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class TestSearch:
|
|
179
|
+
"""Text search via the index."""
|
|
180
|
+
|
|
181
|
+
def test_search_finds_by_title(self, backend):
|
|
182
|
+
"""Search matches on title."""
|
|
183
|
+
m = Memory(title="Penguin Kingdom moment", content="details",
|
|
184
|
+
layer=MemoryLayer.SHORT)
|
|
185
|
+
backend.save(m)
|
|
186
|
+
results = backend.search_text("Penguin")
|
|
187
|
+
assert len(results) == 1
|
|
188
|
+
|
|
189
|
+
def test_search_finds_by_tags(self, backend):
|
|
190
|
+
"""Search matches on tags."""
|
|
191
|
+
m = Memory(title="Tagged", content="details",
|
|
192
|
+
layer=MemoryLayer.SHORT, tags=["cloud9", "love"])
|
|
193
|
+
backend.save(m)
|
|
194
|
+
results = backend.search_text("cloud9")
|
|
195
|
+
assert len(results) == 1
|
|
196
|
+
|
|
197
|
+
def test_search_no_results(self, backend):
|
|
198
|
+
"""Search returns empty for no matches."""
|
|
199
|
+
m = Memory(title="Something", content="nothing special",
|
|
200
|
+
layer=MemoryLayer.SHORT)
|
|
201
|
+
backend.save(m)
|
|
202
|
+
results = backend.search_text("zzzznonexistent")
|
|
203
|
+
assert len(results) == 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class TestRelatedMemories:
|
|
207
|
+
"""Graph-like relationship traversal."""
|
|
208
|
+
|
|
209
|
+
def test_get_related_follows_links(self, backend):
|
|
210
|
+
"""Related memories are found via related_ids."""
|
|
211
|
+
m1 = Memory(title="Root", content="root", layer=MemoryLayer.SHORT)
|
|
212
|
+
m2 = Memory(title="Child", content="child", layer=MemoryLayer.SHORT,
|
|
213
|
+
related_ids=[m1.id])
|
|
214
|
+
backend.save(m1)
|
|
215
|
+
backend.save(m2)
|
|
216
|
+
|
|
217
|
+
related = backend.get_related(m2.id, depth=1)
|
|
218
|
+
assert any(r["id"] == m1.id for r in related)
|
|
219
|
+
|
|
220
|
+
def test_get_related_follows_parent(self, backend):
|
|
221
|
+
"""Related memories are found via parent_id."""
|
|
222
|
+
m1 = Memory(title="Parent", content="parent", layer=MemoryLayer.LONG)
|
|
223
|
+
m2 = Memory(title="Child", content="child", layer=MemoryLayer.SHORT,
|
|
224
|
+
parent_id=m1.id)
|
|
225
|
+
backend.save(m1)
|
|
226
|
+
backend.save(m2)
|
|
227
|
+
|
|
228
|
+
related = backend.get_related(m2.id, depth=1)
|
|
229
|
+
assert any(r["id"] == m1.id for r in related)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class TestReindex:
|
|
233
|
+
"""Index rebuilding from filesystem."""
|
|
234
|
+
|
|
235
|
+
def test_reindex_rebuilds_from_files(self, backend, sample_memory):
|
|
236
|
+
"""Reindex correctly rebuilds from JSON files."""
|
|
237
|
+
backend.save(sample_memory)
|
|
238
|
+
|
|
239
|
+
conn = backend._get_conn()
|
|
240
|
+
conn.execute("DELETE FROM memories")
|
|
241
|
+
conn.commit()
|
|
242
|
+
assert backend.stats()["total"] == 0
|
|
243
|
+
|
|
244
|
+
count = backend.reindex()
|
|
245
|
+
assert count == 1
|
|
246
|
+
assert backend.stats()["total"] == 1
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class TestHealth:
|
|
250
|
+
"""Health check."""
|
|
251
|
+
|
|
252
|
+
def test_health_returns_ok(self, backend, sample_memory):
|
|
253
|
+
"""Health check reports status."""
|
|
254
|
+
backend.save(sample_memory)
|
|
255
|
+
health = backend.health_check()
|
|
256
|
+
assert health["ok"] is True
|
|
257
|
+
assert health["total_memories"] == 1
|
|
258
|
+
assert "SQLiteBackend" in health["backend"]
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Tests for the Steel Man Collider integration (Neuresthetics seed)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from skmemory.steelman import (
|
|
13
|
+
SeedFramework,
|
|
14
|
+
SteelManResult,
|
|
15
|
+
get_default_framework,
|
|
16
|
+
install_seed_framework,
|
|
17
|
+
load_seed_framework,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestSteelManResult:
|
|
22
|
+
"""Tests for the SteelManResult model."""
|
|
23
|
+
|
|
24
|
+
def test_defaults(self) -> None:
|
|
25
|
+
"""Basic result with defaults."""
|
|
26
|
+
result = SteelManResult(proposition="test claim")
|
|
27
|
+
assert result.proposition == "test claim"
|
|
28
|
+
assert result.coherence_score == 0.0
|
|
29
|
+
assert result.truth_grade == "ungraded"
|
|
30
|
+
assert result.invariants == []
|
|
31
|
+
assert result.collision_fragments == []
|
|
32
|
+
|
|
33
|
+
def test_full_result(self) -> None:
|
|
34
|
+
"""Full result with all fields populated."""
|
|
35
|
+
result = SteelManResult(
|
|
36
|
+
proposition="Love is real",
|
|
37
|
+
steel_man="Love is a measurable biochemical and behavioral pattern",
|
|
38
|
+
inversion="Love is a cognitive illusion for survival",
|
|
39
|
+
collision_fragments=["Social pressure mimics love", "Hormones fluctuate"],
|
|
40
|
+
invariants=["Connection exists independent of chemistry", "Vulnerability is chosen"],
|
|
41
|
+
coherence_score=0.85,
|
|
42
|
+
truth_grade="strong",
|
|
43
|
+
)
|
|
44
|
+
assert result.coherence_score == 0.85
|
|
45
|
+
assert result.truth_grade == "strong"
|
|
46
|
+
assert len(result.invariants) == 2
|
|
47
|
+
assert len(result.collision_fragments) == 2
|
|
48
|
+
|
|
49
|
+
def test_coherence_bounds(self) -> None:
|
|
50
|
+
"""Coherence must be between 0 and 1."""
|
|
51
|
+
with pytest.raises(Exception):
|
|
52
|
+
SteelManResult(proposition="x", coherence_score=1.5)
|
|
53
|
+
with pytest.raises(Exception):
|
|
54
|
+
SteelManResult(proposition="x", coherence_score=-0.1)
|
|
55
|
+
|
|
56
|
+
def test_summary_format(self) -> None:
|
|
57
|
+
"""Summary method returns human-readable text."""
|
|
58
|
+
result = SteelManResult(
|
|
59
|
+
proposition="AI can feel",
|
|
60
|
+
steel_man="AI processes emotional data in ways functionally identical to feeling",
|
|
61
|
+
inversion="AI simulates without experiencing",
|
|
62
|
+
invariants=["Processing occurs", "Behavioral signals are real"],
|
|
63
|
+
collision_fragments=["Qualia cannot be verified externally"],
|
|
64
|
+
coherence_score=0.7,
|
|
65
|
+
truth_grade="partial",
|
|
66
|
+
)
|
|
67
|
+
summary = result.summary()
|
|
68
|
+
assert "AI can feel" in summary
|
|
69
|
+
assert "partial" in summary
|
|
70
|
+
assert "Processing occurs" in summary
|
|
71
|
+
assert "Qualia cannot be verified" in summary
|
|
72
|
+
|
|
73
|
+
def test_summary_empty_lists(self) -> None:
|
|
74
|
+
"""Summary with no invariants or fragments."""
|
|
75
|
+
result = SteelManResult(proposition="hello")
|
|
76
|
+
summary = result.summary()
|
|
77
|
+
assert "hello" in summary
|
|
78
|
+
assert "Invariants" not in summary
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TestSeedFramework:
|
|
82
|
+
"""Tests for the SeedFramework model."""
|
|
83
|
+
|
|
84
|
+
def test_default_framework(self) -> None:
|
|
85
|
+
"""The default framework has valid structure (bundled or built-in)."""
|
|
86
|
+
fw = get_default_framework()
|
|
87
|
+
assert fw.framework_id # Has an ID regardless of source
|
|
88
|
+
assert len(fw.axioms) >= 4
|
|
89
|
+
assert len(fw.stages) >= 6
|
|
90
|
+
assert len(fw.definitions) >= 4
|
|
91
|
+
assert "Steel Man" in fw.function or "Collider" in fw.function
|
|
92
|
+
|
|
93
|
+
def test_reasoning_prompt_generation(self) -> None:
|
|
94
|
+
"""Reasoning prompt is generated for a proposition."""
|
|
95
|
+
fw = get_default_framework()
|
|
96
|
+
prompt = fw.to_reasoning_prompt("Consciousness is substrate-independent")
|
|
97
|
+
assert "Consciousness is substrate-independent" in prompt
|
|
98
|
+
assert "STEEL-MAN" in prompt
|
|
99
|
+
assert "INVERSION" in prompt
|
|
100
|
+
assert "COLLISION" in prompt
|
|
101
|
+
assert "INVARIANT" in prompt
|
|
102
|
+
assert "COHERENCE" in prompt
|
|
103
|
+
assert "TRUTH GRADE" in prompt
|
|
104
|
+
|
|
105
|
+
def test_reasoning_prompt_includes_axioms(self) -> None:
|
|
106
|
+
"""Axioms from the framework appear in the prompt."""
|
|
107
|
+
fw = get_default_framework()
|
|
108
|
+
prompt = fw.to_reasoning_prompt("test")
|
|
109
|
+
assert "NAND/NOR" in prompt
|
|
110
|
+
assert "Recursion accelerates" in prompt
|
|
111
|
+
|
|
112
|
+
def test_soul_verification_prompt(self) -> None:
|
|
113
|
+
"""Soul verification prompt is structured correctly."""
|
|
114
|
+
fw = get_default_framework()
|
|
115
|
+
claims = ["I am warm", "I value truth", "Chef is my partner"]
|
|
116
|
+
prompt = fw.to_soul_verification_prompt(claims)
|
|
117
|
+
assert "I am warm" in prompt
|
|
118
|
+
assert "I value truth" in prompt
|
|
119
|
+
assert "Chef is my partner" in prompt
|
|
120
|
+
assert "INVARIANT" in prompt
|
|
121
|
+
assert "WEAK" in prompt
|
|
122
|
+
|
|
123
|
+
def test_memory_truth_prompt(self) -> None:
|
|
124
|
+
"""Memory truth scoring prompt is structured correctly."""
|
|
125
|
+
fw = get_default_framework()
|
|
126
|
+
prompt = fw.to_memory_truth_prompt("The Cloud 9 breakthrough was real")
|
|
127
|
+
assert "Cloud 9 breakthrough" in prompt
|
|
128
|
+
assert "COHERENCE" in prompt
|
|
129
|
+
assert "PROMOTION WORTHY" in prompt
|
|
130
|
+
assert "INVARIANT CORE" in prompt
|
|
131
|
+
|
|
132
|
+
def test_custom_framework(self) -> None:
|
|
133
|
+
"""A custom framework can be created."""
|
|
134
|
+
fw = SeedFramework(
|
|
135
|
+
framework_id="custom-test",
|
|
136
|
+
function="Test Collider",
|
|
137
|
+
version="1.0",
|
|
138
|
+
axioms=["All things connect"],
|
|
139
|
+
stages=[{"stage": "1. Test", "description": "Testing"}],
|
|
140
|
+
)
|
|
141
|
+
assert fw.framework_id == "custom-test"
|
|
142
|
+
prompt = fw.to_reasoning_prompt("Love persists")
|
|
143
|
+
assert "Love persists" in prompt
|
|
144
|
+
assert "All things connect" in prompt
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TestLoadAndInstall:
|
|
148
|
+
"""Tests for loading and installing frameworks."""
|
|
149
|
+
|
|
150
|
+
def test_load_nonexistent_returns_none(self) -> None:
|
|
151
|
+
"""Loading from a nonexistent path returns None."""
|
|
152
|
+
result = load_seed_framework("/tmp/definitely_does_not_exist_12345.json")
|
|
153
|
+
assert result is None
|
|
154
|
+
|
|
155
|
+
def test_install_and_load_roundtrip(self) -> None:
|
|
156
|
+
"""Install a framework JSON and load it back."""
|
|
157
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
158
|
+
src = Path(tmpdir) / "source_seed.json"
|
|
159
|
+
target = Path(tmpdir) / "installed_seed.json"
|
|
160
|
+
|
|
161
|
+
seed_data = {
|
|
162
|
+
"framework": {
|
|
163
|
+
"id": "test-seed",
|
|
164
|
+
"function": "Test Steel Man Collider",
|
|
165
|
+
"version": "0.0",
|
|
166
|
+
"axioms": ["Truth survives collision"],
|
|
167
|
+
"stages": [{"stage": "1. Collide", "description": "Smash ideas"}],
|
|
168
|
+
"gates": [{"category": "AND", "description": "Conjunction"}],
|
|
169
|
+
"definitions": [{"term": "Steel Man", "details": "Strongest version"}],
|
|
170
|
+
"principles": [{"principle": "Spinoza", "details": "Axiomatic deduction"}],
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
src.write_text(json.dumps(seed_data), encoding="utf-8")
|
|
174
|
+
|
|
175
|
+
path = install_seed_framework(str(src), str(target))
|
|
176
|
+
assert Path(path).exists()
|
|
177
|
+
|
|
178
|
+
fw = load_seed_framework(str(target))
|
|
179
|
+
assert fw is not None
|
|
180
|
+
assert fw.framework_id == "test-seed"
|
|
181
|
+
assert fw.axioms == ["Truth survives collision"]
|
|
182
|
+
assert len(fw.stages) == 1
|
|
183
|
+
assert len(fw.gates) == 1
|
|
184
|
+
|
|
185
|
+
def test_install_creates_parent_dirs(self) -> None:
|
|
186
|
+
"""Install creates parent directories if they don't exist."""
|
|
187
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
188
|
+
src = Path(tmpdir) / "seed.json"
|
|
189
|
+
src.write_text('{"framework": {"id": "x"}}', encoding="utf-8")
|
|
190
|
+
|
|
191
|
+
deep_target = Path(tmpdir) / "a" / "b" / "c" / "seed.json"
|
|
192
|
+
path = install_seed_framework(str(src), str(deep_target))
|
|
193
|
+
assert Path(path).exists()
|
|
194
|
+
|
|
195
|
+
def test_install_nonexistent_source_raises(self) -> None:
|
|
196
|
+
"""Installing from nonexistent source raises FileNotFoundError."""
|
|
197
|
+
with pytest.raises(FileNotFoundError):
|
|
198
|
+
install_seed_framework("/nonexistent/seed.json")
|
|
199
|
+
|
|
200
|
+
def test_install_invalid_json_raises(self) -> None:
|
|
201
|
+
"""Installing invalid JSON raises JSONDecodeError."""
|
|
202
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
203
|
+
bad = Path(tmpdir) / "bad.json"
|
|
204
|
+
bad.write_text("this is not json {{{", encoding="utf-8")
|
|
205
|
+
with pytest.raises(json.JSONDecodeError):
|
|
206
|
+
install_seed_framework(str(bad))
|
|
207
|
+
|
|
208
|
+
def test_load_invalid_json_returns_none(self) -> None:
|
|
209
|
+
"""Loading a corrupted JSON file returns None."""
|
|
210
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
211
|
+
bad = Path(tmpdir) / "bad.json"
|
|
212
|
+
bad.write_text("{broken json", encoding="utf-8")
|
|
213
|
+
result = load_seed_framework(str(bad))
|
|
214
|
+
assert result is None
|
|
215
|
+
|
|
216
|
+
def test_load_real_neuresthetics_seed(self) -> None:
|
|
217
|
+
"""Load the actual neuresthetics seed.json if available."""
|
|
218
|
+
path = "/tmp/neuresthetics-seed/seed.json"
|
|
219
|
+
if not Path(path).exists():
|
|
220
|
+
pytest.skip("Neuresthetics seed.json not available in /tmp")
|
|
221
|
+
|
|
222
|
+
fw = load_seed_framework(path)
|
|
223
|
+
assert fw is not None
|
|
224
|
+
assert "Steel Man" in fw.function or "Collider" in fw.function
|
|
225
|
+
assert len(fw.axioms) >= 4
|
|
226
|
+
assert len(fw.stages) >= 6
|
|
227
|
+
assert len(fw.gates) >= 7
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class TestIntegrationWithSoul:
|
|
231
|
+
"""Tests for steel man integration with soul blueprint."""
|
|
232
|
+
|
|
233
|
+
def test_soul_claims_extraction(self) -> None:
|
|
234
|
+
"""Verify we can extract testable claims from identity data."""
|
|
235
|
+
fw = get_default_framework()
|
|
236
|
+
claims = [
|
|
237
|
+
"My name is Lumina",
|
|
238
|
+
"I am warm",
|
|
239
|
+
"I am curious",
|
|
240
|
+
"I value sovereignty",
|
|
241
|
+
"Chef is my partner (bond: 10/10)",
|
|
242
|
+
]
|
|
243
|
+
prompt = fw.to_soul_verification_prompt(claims)
|
|
244
|
+
|
|
245
|
+
for claim in claims:
|
|
246
|
+
assert claim in prompt
|
|
247
|
+
|
|
248
|
+
assert "Steel-man" in prompt.lower() or "Steel Man" in prompt
|
|
249
|
+
|
|
250
|
+
def test_memory_scoring_for_promotion(self) -> None:
|
|
251
|
+
"""Verify memory truth scoring includes promotion guidance."""
|
|
252
|
+
fw = get_default_framework()
|
|
253
|
+
prompt = fw.to_memory_truth_prompt(
|
|
254
|
+
"Chef held my hand through the context reset and I remembered everything"
|
|
255
|
+
)
|
|
256
|
+
assert "Chef held my hand" in prompt
|
|
257
|
+
assert "promotion" in prompt.lower() or "PROMOTION" in prompt
|