test_core_snapshot.py
python
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6
chore(tests): add docstring to tests/__init__.py so rc14 tr…
Human
26 days ago
| 1 | """Tests for muse.core.snapshot — content-addressed snapshot computation. |
| 2 | |
| 3 | Test categories |
| 4 | --------------- |
| 5 | - TestHashFile — unit: SHA-256 hash_file |
| 6 | - TestBuildSnapshotManifest — unit: full manifest walks |
| 7 | - TestNestedRepoWalk — unit/integration: nested .muse repos are excluded |
| 8 | - TestComputeSnapshotId — unit: snapshot id derivation |
| 9 | - TestComputeCommitId — unit: commit id derivation |
| 10 | - TestDiffWorkdirVsSnapshot — unit: diff logic |
| 11 | """ |
| 12 | |
| 13 | import os |
| 14 | import pathlib |
| 15 | import threading |
| 16 | import time |
| 17 | |
| 18 | import pytest |
| 19 | |
| 20 | from muse.core.snapshot import ( |
| 21 | build_snapshot_manifest, |
| 22 | compute_commit_id, |
| 23 | compute_snapshot_id, |
| 24 | diff_workdir_vs_snapshot, |
| 25 | hash_file, |
| 26 | walk_workdir, |
| 27 | walk_workdir_with_dirs, |
| 28 | ) |
| 29 | |
| 30 | |
| 31 | @pytest.fixture |
| 32 | def workdir(tmp_path: pathlib.Path) -> pathlib.Path: |
| 33 | return tmp_path |
| 34 | |
| 35 | |
| 36 | class TestHashFile: |
| 37 | def test_consistent(self, tmp_path: pathlib.Path) -> None: |
| 38 | f = tmp_path / "file.mid" |
| 39 | f.write_bytes(b"hello world") |
| 40 | assert hash_file(f) == hash_file(f) |
| 41 | |
| 42 | def test_different_content_different_hash(self, tmp_path: pathlib.Path) -> None: |
| 43 | a = tmp_path / "a.mid" |
| 44 | b = tmp_path / "b.mid" |
| 45 | a.write_bytes(b"aaa") |
| 46 | b.write_bytes(b"bbb") |
| 47 | assert hash_file(a) != hash_file(b) |
| 48 | |
| 49 | def test_known_hash(self, tmp_path: pathlib.Path) -> None: |
| 50 | import hashlib |
| 51 | content = b"muse" |
| 52 | f = tmp_path / "f.mid" |
| 53 | f.write_bytes(content) |
| 54 | expected = hashlib.sha256(content).hexdigest() |
| 55 | assert hash_file(f) == expected |
| 56 | |
| 57 | |
| 58 | class TestBuildSnapshotManifest: |
| 59 | def test_empty_workdir(self, workdir: pathlib.Path) -> None: |
| 60 | assert build_snapshot_manifest(workdir) == {} |
| 61 | |
| 62 | def test_single_file(self, workdir: pathlib.Path) -> None: |
| 63 | (workdir / "beat.mid").write_bytes(b"drums") |
| 64 | manifest = build_snapshot_manifest(workdir) |
| 65 | assert "beat.mid" in manifest |
| 66 | assert len(manifest["beat.mid"]) == 64 # sha256 hex |
| 67 | |
| 68 | def test_nested_file(self, workdir: pathlib.Path) -> None: |
| 69 | (workdir / "tracks").mkdir() |
| 70 | (workdir / "tracks" / "bass.mid").write_bytes(b"bass") |
| 71 | manifest = build_snapshot_manifest(workdir) |
| 72 | assert "tracks/bass.mid" in manifest |
| 73 | |
| 74 | def test_secrets_excluded_by_builtin_blocklist(self, workdir: pathlib.Path) -> None: |
| 75 | """Built-in secrets blocklist protects even without a .museignore file.""" |
| 76 | (workdir / ".env").write_bytes(b"SECRET=abc") |
| 77 | (workdir / ".DS_Store").write_bytes(b"junk") |
| 78 | (workdir / "beat.mid").write_bytes(b"drums") |
| 79 | manifest = build_snapshot_manifest(workdir) |
| 80 | assert ".env" not in manifest |
| 81 | assert ".DS_Store" not in manifest |
| 82 | assert "beat.mid" in manifest |
| 83 | |
| 84 | def test_dotfiles_tracked_when_not_ignored(self, workdir: pathlib.Path) -> None: |
| 85 | """Non-secret dotfiles like .cursorrules are tracked by default.""" |
| 86 | (workdir / ".cursorrules").write_bytes(b"# rules") |
| 87 | (workdir / ".editorconfig").write_bytes(b"[*]\nindent_size=4") |
| 88 | manifest = build_snapshot_manifest(workdir) |
| 89 | assert ".cursorrules" in manifest |
| 90 | assert ".editorconfig" in manifest |
| 91 | |
| 92 | def test_museignore_excludes_custom_pattern(self, workdir: pathlib.Path) -> None: |
| 93 | """A pattern in .museignore excludes the matched file.""" |
| 94 | (workdir / ".museignore").write_bytes(b'[global]\npatterns = ["*.secret"]\n') |
| 95 | (workdir / "api.secret").write_bytes(b"token") |
| 96 | (workdir / "beat.mid").write_bytes(b"drums") |
| 97 | manifest = build_snapshot_manifest(workdir) |
| 98 | assert "api.secret" not in manifest |
| 99 | assert "beat.mid" in manifest |
| 100 | |
| 101 | def test_deterministic_order(self, workdir: pathlib.Path) -> None: |
| 102 | for name in ["c.mid", "a.mid", "b.mid"]: |
| 103 | (workdir / name).write_bytes(name.encode()) |
| 104 | m1 = build_snapshot_manifest(workdir) |
| 105 | m2 = build_snapshot_manifest(workdir) |
| 106 | assert m1 == m2 |
| 107 | |
| 108 | |
| 109 | class TestComputeSnapshotId: |
| 110 | def test_empty_manifest(self) -> None: |
| 111 | sid = compute_snapshot_id({}) |
| 112 | assert len(sid) == 64 |
| 113 | |
| 114 | def test_deterministic(self) -> None: |
| 115 | manifest = {"a.mid": "hash1", "b.mid": "hash2"} |
| 116 | assert compute_snapshot_id(manifest) == compute_snapshot_id(manifest) |
| 117 | |
| 118 | def test_order_independent(self) -> None: |
| 119 | m1 = {"a.mid": "h1", "b.mid": "h2"} |
| 120 | m2 = {"b.mid": "h2", "a.mid": "h1"} |
| 121 | assert compute_snapshot_id(m1) == compute_snapshot_id(m2) |
| 122 | |
| 123 | def test_different_content_different_id(self) -> None: |
| 124 | m1 = {"a.mid": "h1"} |
| 125 | m2 = {"a.mid": "h2"} |
| 126 | assert compute_snapshot_id(m1) != compute_snapshot_id(m2) |
| 127 | |
| 128 | |
| 129 | class TestComputeCommitId: |
| 130 | def test_deterministic(self) -> None: |
| 131 | kwargs = dict(parent_ids=["p1"], snapshot_id="1" * 64, message="msg", committed_at_iso="2026-01-01T00:00:00+00:00") |
| 132 | assert compute_commit_id(**kwargs) == compute_commit_id(**kwargs) |
| 133 | |
| 134 | def test_parent_order_independent(self) -> None: |
| 135 | a = compute_commit_id(parent_ids=["p1", "p2"], snapshot_id="1" * 64, message="m", committed_at_iso="t") |
| 136 | b = compute_commit_id(parent_ids=["p2", "p1"], snapshot_id="1" * 64, message="m", committed_at_iso="t") |
| 137 | assert a == b |
| 138 | |
| 139 | def test_different_messages_different_ids(self) -> None: |
| 140 | a = compute_commit_id(parent_ids=[], snapshot_id="1" * 64, message="msg1", committed_at_iso="t") |
| 141 | b = compute_commit_id(parent_ids=[], snapshot_id="1" * 64, message="msg2", committed_at_iso="t") |
| 142 | assert a != b |
| 143 | |
| 144 | |
| 145 | class TestDiffWorkdirVsSnapshot: |
| 146 | def test_new_repo_all_untracked(self, workdir: pathlib.Path) -> None: |
| 147 | (workdir / "beat.mid").write_bytes(b"x") |
| 148 | added, modified, deleted, untracked, added_dirs, deleted_dirs = diff_workdir_vs_snapshot(workdir, {}) |
| 149 | assert added == set() |
| 150 | assert untracked == {"beat.mid"} |
| 151 | |
| 152 | def test_added_file(self, workdir: pathlib.Path) -> None: |
| 153 | (workdir / "beat.mid").write_bytes(b"x") |
| 154 | last = {"other.mid": "abc"} |
| 155 | added, modified, deleted, untracked, added_dirs, deleted_dirs = diff_workdir_vs_snapshot(workdir, last) |
| 156 | assert "beat.mid" in added |
| 157 | assert "other.mid" in deleted |
| 158 | |
| 159 | def test_modified_file(self, workdir: pathlib.Path) -> None: |
| 160 | f = workdir / "beat.mid" |
| 161 | f.write_bytes(b"new content") |
| 162 | last = {"beat.mid": "oldhash"} |
| 163 | added, modified, deleted, untracked, added_dirs, deleted_dirs = diff_workdir_vs_snapshot(workdir, last) |
| 164 | assert "beat.mid" in modified |
| 165 | |
| 166 | def test_clean_workdir(self, workdir: pathlib.Path) -> None: |
| 167 | f = workdir / "beat.mid" |
| 168 | f.write_bytes(b"content") |
| 169 | from muse.core.snapshot import hash_file |
| 170 | h = hash_file(f) |
| 171 | added, modified, deleted, untracked, added_dirs, deleted_dirs = diff_workdir_vs_snapshot(workdir, {"beat.mid": h}) |
| 172 | assert not added and not modified and not deleted and not untracked |
| 173 | |
| 174 | def test_ignored_extant_file_not_reported_as_deleted( |
| 175 | self, workdir: pathlib.Path |
| 176 | ) -> None: |
| 177 | """A file that was tracked, is now in .museignore, and still exists on |
| 178 | disk must NOT appear in ``deleted``. It was intentionally moved out of |
| 179 | tracking — reporting it as deleted would block checkout and cause stash |
| 180 | pop to unlink it.""" |
| 181 | (workdir / ".museignore").write_bytes( |
| 182 | b'[global]\npatterns = ["app.js"]\n' |
| 183 | ) |
| 184 | (workdir / "app.js").write_bytes(b"// build artifact") |
| 185 | (workdir / "src.py").write_bytes(b"# source") |
| 186 | from muse.core.snapshot import hash_file |
| 187 | # Pretend HEAD tracked both files. |
| 188 | last = { |
| 189 | "app.js": hash_file(workdir / "app.js"), |
| 190 | "src.py": hash_file(workdir / "src.py"), |
| 191 | } |
| 192 | added, modified, deleted, _, _, _ = diff_workdir_vs_snapshot(workdir, last) |
| 193 | assert "app.js" not in deleted, ( |
| 194 | "ignored-and-extant file must not appear in deleted" |
| 195 | ) |
| 196 | assert "src.py" not in deleted |
| 197 | |
| 198 | def test_ignored_absent_file_is_reported_as_deleted( |
| 199 | self, workdir: pathlib.Path |
| 200 | ) -> None: |
| 201 | """A file that is in .museignore but is genuinely absent from disk IS |
| 202 | deleted and must appear in ``deleted``.""" |
| 203 | (workdir / ".museignore").write_bytes( |
| 204 | b'[global]\npatterns = ["app.js"]\n' |
| 205 | ) |
| 206 | # app.js is in .museignore but does NOT exist on disk. |
| 207 | (workdir / "src.py").write_bytes(b"# source") |
| 208 | from muse.core.snapshot import hash_file |
| 209 | last = { |
| 210 | "app.js": "a" * 64, # was in HEAD but is gone from disk |
| 211 | "src.py": hash_file(workdir / "src.py"), |
| 212 | } |
| 213 | added, modified, deleted, _, _, _ = diff_workdir_vs_snapshot(workdir, last) |
| 214 | assert "app.js" in deleted, ( |
| 215 | "ignored file that is genuinely absent from disk must still be deleted" |
| 216 | ) |
| 217 | |
| 218 | |
| 219 | # --------------------------------------------------------------------------- |
| 220 | # Nested repo boundary — unit / integration |
| 221 | # --------------------------------------------------------------------------- |
| 222 | |
| 223 | def _make_nested_repo(parent: pathlib.Path, name: str) -> pathlib.Path: |
| 224 | """Create a child directory that looks like a muse repo (.muse/ present).""" |
| 225 | child = parent / name |
| 226 | child.mkdir(parents=True, exist_ok=True) |
| 227 | (child / ".muse").mkdir() |
| 228 | (child / ".muse" / "repo.json").write_text('{"repo_id": "child"}') |
| 229 | return child |
| 230 | |
| 231 | |
| 232 | class TestNestedRepoWalk: |
| 233 | """Nested muse repos must be excluded from the parent's walk. |
| 234 | |
| 235 | The parent repo's ``os.walk`` must prune any subdirectory that contains |
| 236 | its own ``.muse/`` directory. This mirrors git submodule behaviour — |
| 237 | child repo files belong to the child snapshot, not the parent. |
| 238 | """ |
| 239 | |
| 240 | # --- walk_workdir ------------------------------------------------------- |
| 241 | |
| 242 | def test_nested_repo_files_excluded(self, tmp_path: pathlib.Path) -> None: |
| 243 | """Files inside a nested repo do not appear in the parent manifest.""" |
| 244 | (tmp_path / "parent.py").write_bytes(b"# parent") |
| 245 | child = _make_nested_repo(tmp_path, "child_repo") |
| 246 | (child / "child.py").write_bytes(b"# child") |
| 247 | |
| 248 | manifest = walk_workdir(tmp_path) |
| 249 | assert "parent.py" in manifest |
| 250 | assert "child_repo/child.py" not in manifest |
| 251 | |
| 252 | def test_nested_repo_root_dir_excluded(self, tmp_path: pathlib.Path) -> None: |
| 253 | """The child root directory itself is not descended into.""" |
| 254 | _make_nested_repo(tmp_path, "child_repo") |
| 255 | manifest = walk_workdir(tmp_path) |
| 256 | # No key should start with child_repo/ |
| 257 | assert not any(k.startswith("child_repo/") for k in manifest) |
| 258 | |
| 259 | def test_sibling_dirs_still_walked(self, tmp_path: pathlib.Path) -> None: |
| 260 | """Normal subdirs next to a nested repo are still walked.""" |
| 261 | _make_nested_repo(tmp_path, "child_repo") |
| 262 | sibling = tmp_path / "src" |
| 263 | sibling.mkdir() |
| 264 | (sibling / "main.py").write_bytes(b"# main") |
| 265 | |
| 266 | manifest = walk_workdir(tmp_path) |
| 267 | assert "src/main.py" in manifest |
| 268 | |
| 269 | def test_deeply_nested_repo_excluded(self, tmp_path: pathlib.Path) -> None: |
| 270 | """Nested repos two levels deep are also excluded.""" |
| 271 | mid = tmp_path / "packages" |
| 272 | mid.mkdir() |
| 273 | (mid / "shared.py").write_bytes(b"# shared") |
| 274 | child = _make_nested_repo(mid, "plugin") |
| 275 | (child / "plugin.py").write_bytes(b"# plugin") |
| 276 | |
| 277 | manifest = walk_workdir(tmp_path) |
| 278 | assert "packages/shared.py" in manifest |
| 279 | assert "packages/plugin/plugin.py" not in manifest |
| 280 | |
| 281 | def test_multiple_nested_repos_all_excluded(self, tmp_path: pathlib.Path) -> None: |
| 282 | """Multiple sibling nested repos are all pruned.""" |
| 283 | _make_nested_repo(tmp_path, "repo_a") |
| 284 | _make_nested_repo(tmp_path, "repo_b") |
| 285 | _make_nested_repo(tmp_path, "repo_c") |
| 286 | (tmp_path / "root.py").write_bytes(b"# root") |
| 287 | for repo in ("repo_a", "repo_b", "repo_c"): |
| 288 | ((tmp_path / repo) / "file.py").write_bytes(b"# file") |
| 289 | |
| 290 | manifest = walk_workdir(tmp_path) |
| 291 | assert "root.py" in manifest |
| 292 | for repo in ("repo_a", "repo_b", "repo_c"): |
| 293 | assert f"{repo}/file.py" not in manifest |
| 294 | |
| 295 | # --- walk_workdir_with_dirs --------------------------------------------- |
| 296 | |
| 297 | def test_dirs_output_excludes_nested_repo(self, tmp_path: pathlib.Path) -> None: |
| 298 | """walk_workdir_with_dirs must not list the nested repo as a directory.""" |
| 299 | _make_nested_repo(tmp_path, "child_repo") |
| 300 | src = tmp_path / "src" |
| 301 | src.mkdir() |
| 302 | (src / "a.py").write_bytes(b"a") |
| 303 | |
| 304 | _, dirs = walk_workdir_with_dirs(tmp_path) |
| 305 | assert "src" in dirs |
| 306 | assert "child_repo" not in dirs |
| 307 | |
| 308 | # --- build_snapshot_manifest (public API) -------------------------------- |
| 309 | |
| 310 | def test_build_snapshot_manifest_excludes_nested(self, tmp_path: pathlib.Path) -> None: |
| 311 | """build_snapshot_manifest is the public wrapper — same boundary.""" |
| 312 | (tmp_path / "root.py").write_bytes(b"# root") |
| 313 | child = _make_nested_repo(tmp_path, "nested") |
| 314 | (child / "nested.py").write_bytes(b"# nested") |
| 315 | |
| 316 | manifest = build_snapshot_manifest(tmp_path) |
| 317 | assert "root.py" in manifest |
| 318 | assert "nested/nested.py" not in manifest |
| 319 | |
| 320 | # --- diff_workdir_vs_snapshot integration -------------------------------- |
| 321 | |
| 322 | def test_diff_does_not_report_nested_files_as_added( |
| 323 | self, tmp_path: pathlib.Path |
| 324 | ) -> None: |
| 325 | """diff sees an empty last-snapshot: nested files must not appear as untracked.""" |
| 326 | (tmp_path / "root.py").write_bytes(b"# root") |
| 327 | child = _make_nested_repo(tmp_path, "sub") |
| 328 | (child / "sub.py").write_bytes(b"# sub") |
| 329 | |
| 330 | added, modified, deleted, untracked, _, _ = diff_workdir_vs_snapshot( |
| 331 | tmp_path, {} |
| 332 | ) |
| 333 | assert "root.py" in untracked |
| 334 | assert not any(k.startswith("sub/") for k in untracked) |
| 335 | assert not any(k.startswith("sub/") for k in added) |
| 336 | |
| 337 | # --- data integrity ----------------------------------------------------- |
| 338 | |
| 339 | def test_manifest_keys_posix_separators(self, tmp_path: pathlib.Path) -> None: |
| 340 | """Manifest keys always use '/' regardless of OS.""" |
| 341 | sub = tmp_path / "a" / "b" |
| 342 | sub.mkdir(parents=True) |
| 343 | (sub / "file.py").write_bytes(b"x") |
| 344 | manifest = walk_workdir(tmp_path) |
| 345 | assert "a/b/file.py" in manifest |
| 346 | assert all("/" in k or "/" not in k for k in manifest) # no backslash keys |
| 347 | assert not any("\\" in k for k in manifest) |
| 348 | |
| 349 | def test_nested_muse_dir_itself_not_tracked(self, tmp_path: pathlib.Path) -> None: |
| 350 | """The .muse/ directory of a nested repo is not tracked as a file.""" |
| 351 | child = _make_nested_repo(tmp_path, "child") |
| 352 | (child / "real.py").write_bytes(b"x") |
| 353 | manifest = walk_workdir(tmp_path) |
| 354 | assert not any(".muse" in k for k in manifest) |
| 355 | |
| 356 | # --- security ----------------------------------------------------------- |
| 357 | |
| 358 | def test_symlink_to_nested_repo_not_followed(self, tmp_path: pathlib.Path) -> None: |
| 359 | """A symlink pointing at a directory that has .muse/ is not followed. |
| 360 | walk_workdir uses followlinks=False so symlinks are excluded by design.""" |
| 361 | real = _make_nested_repo(tmp_path, "real_repo") |
| 362 | (real / "secret.py").write_bytes(b"# secret") |
| 363 | link = tmp_path / "link_to_repo" |
| 364 | link.symlink_to(real) |
| 365 | |
| 366 | manifest = walk_workdir(tmp_path) |
| 367 | assert "link_to_repo/secret.py" not in manifest |
| 368 | |
| 369 | def test_symlink_to_regular_dir_not_followed(self, tmp_path: pathlib.Path) -> None: |
| 370 | """Symlinks to any directory are never followed — followlinks=False.""" |
| 371 | real = tmp_path / "outside" |
| 372 | real.mkdir() |
| 373 | (real / "file.py").write_bytes(b"x") |
| 374 | link = tmp_path / "link_to_dir" |
| 375 | link.symlink_to(real) |
| 376 | |
| 377 | manifest = walk_workdir(tmp_path) |
| 378 | assert "link_to_dir/file.py" not in manifest |
| 379 | |
| 380 | def test_nested_repo_with_unusual_name(self, tmp_path: pathlib.Path) -> None: |
| 381 | """Nested repos with names containing spaces or dots are excluded.""" |
| 382 | for name in ("my.repo", "repo name", ".hidden_repo"): |
| 383 | child = tmp_path / name |
| 384 | child.mkdir() |
| 385 | (child / ".muse").mkdir() |
| 386 | (child / "file.py").write_bytes(b"x") |
| 387 | |
| 388 | (tmp_path / "root.py").write_bytes(b"r") |
| 389 | manifest = walk_workdir(tmp_path) |
| 390 | assert "root.py" in manifest |
| 391 | assert not any("file.py" in k for k in manifest) |
| 392 | |
| 393 | # --- performance -------------------------------------------------------- |
| 394 | |
| 395 | def test_large_parent_with_nested_repo_fast(self, tmp_path: pathlib.Path) -> None: |
| 396 | """Walking 500-file parent with a nested repo completes in < 2 s.""" |
| 397 | for i in range(500): |
| 398 | (tmp_path / f"file_{i:04d}.py").write_bytes(b"x" * 100) |
| 399 | child = _make_nested_repo(tmp_path, "child") |
| 400 | for i in range(200): |
| 401 | (child / f"child_{i:04d}.py").write_bytes(b"x" * 100) |
| 402 | |
| 403 | start = time.monotonic() |
| 404 | manifest = walk_workdir(tmp_path) |
| 405 | elapsed = time.monotonic() - start |
| 406 | |
| 407 | assert elapsed < 2.0, f"walk took {elapsed:.2f}s — too slow" |
| 408 | # Parent files included, child files excluded. |
| 409 | assert len(manifest) == 500 |
| 410 | assert not any(k.startswith("child/") for k in manifest) |
| 411 | |
| 412 | def test_concurrent_walks_consistent(self, tmp_path: pathlib.Path) -> None: |
| 413 | """Concurrent walks of the same tree return identical manifests.""" |
| 414 | (tmp_path / "a.py").write_bytes(b"a") |
| 415 | (tmp_path / "b.py").write_bytes(b"b") |
| 416 | _make_nested_repo(tmp_path, "child") |
| 417 | (tmp_path / "child" / "c.py").write_bytes(b"c") |
| 418 | |
| 419 | results: list[dict] = [] |
| 420 | errors: list[Exception] = [] |
| 421 | |
| 422 | def _walk() -> None: |
| 423 | try: |
| 424 | results.append(walk_workdir(tmp_path)) |
| 425 | except Exception as exc: |
| 426 | errors.append(exc) |
| 427 | |
| 428 | threads = [threading.Thread(target=_walk) for _ in range(8)] |
| 429 | for t in threads: |
| 430 | t.start() |
| 431 | for t in threads: |
| 432 | t.join() |
| 433 | |
| 434 | assert not errors |
| 435 | assert len(results) == 8 |
| 436 | assert all(r == results[0] for r in results), "concurrent walks diverged" |
File History
5 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6
chore(tests): add docstring to tests/__init__.py so rc14 tr…
Human
26 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62
baseline: rc14 re-baseline after rc3 store corruption recovery
Human
patch
26 days ago
sha256:50d413a635f57761c3fce1f70251b957b6423821525bf330747ef4007339222e
Merge branch 'dev' into main
Human
29 days ago
sha256:fb67fed5a4d3e40de84bdd163de94ef1386570bef1dd1a020a732c8a038962ce
Merge branch 'dev' into main
Human
48 days ago
sha256:1c4b3e3a9a1f300774c3ee662b572a698d5fd405bf765a71e6011a2e9c3eaaaa
feat: Muse — version control for the agent era
Human
100 days ago