gabriel / muse public
test_plumbing_for_each_ref.py python
587 lines 21.5 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 26 days ago
1 """Tests for muse plumbing for-each-ref.
2
3 Coverage tiers
4 --------------
5 Unit — _list_all_refs (flat, hierarchical, symlink skip, bad commit ID),
6 _RefDetail + _ForEachRefResult schemas, _SORT_FIELDS completeness
7 Integration — empty repo, flat branches, hierarchical branches, pattern filter,
8 sort (all fields, asc/desc), --count limit, --no-commits fast-path,
9 text output (full / no-commits), --json shorthand
10 Security — symlinks skipped, ANSI in branch/commit/author sanitized,
11 error output to stderr (format, sort, negative count),
12 no traceback on bad format/corrupted ref, no-commits+commit-sort rejected
13 Stress — 100-branch repo, 50-hierarchical-branch repo, 200 sequential reads
14 """
15
16 from __future__ import annotations
17
18 import datetime
19 import hashlib
20 import json
21 import os
22 import pathlib
23
24 import pytest
25 from tests.cli_test_helper import CliRunner, InvokeResult
26
27 from muse.cli.commands.plumbing.for_each_ref import (
28 _ForEachRefResult,
29 _RefDetail,
30 _SORT_FIELDS,
31 _list_all_refs,
32 )
33 from muse.core.snapshot import compute_commit_id, compute_snapshot_id
34 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
35 from muse.core._types import Manifest
36
37 cli = None # argparse-based CLI; CliRunner ignores this arg
38 runner = CliRunner()
39
40
41 # ---------------------------------------------------------------------------
42 # Helpers
43 # ---------------------------------------------------------------------------
44
45
46 def _sha(tag: str) -> str:
47 return hashlib.sha256(tag.encode()).hexdigest()
48
49
50 def _init_repo(path: pathlib.Path) -> pathlib.Path:
51 muse = path / ".muse"
52 (muse / "commits").mkdir(parents=True)
53 (muse / "snapshots").mkdir(parents=True)
54 (muse / "objects").mkdir(parents=True)
55 (muse / "refs" / "heads").mkdir(parents=True)
56 (muse / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8")
57 (muse / "repo.json").write_text(
58 json.dumps({"repo_id": "test-repo", "domain": "midi"}), encoding="utf-8"
59 )
60 return path
61
62
63 def _env(repo: pathlib.Path) -> Manifest:
64 return {"MUSE_REPO_ROOT": str(repo)}
65
66
67 def _snap(repo: pathlib.Path, tag: str = "snap") -> str:
68 sid = compute_snapshot_id({})
69 write_snapshot(
70 repo,
71 SnapshotRecord(
72 snapshot_id=sid,
73 manifest={},
74 created_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
75 ),
76 )
77 return sid
78
79
80 def _commit(
81 repo: pathlib.Path,
82 tag: str,
83 branch: str = "main",
84 parent: str | None = None,
85 ts: datetime.datetime | None = None,
86 author: str = "tester",
87 ) -> str:
88 sid = _snap(repo, tag)
89 ts_actual = ts or datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
90 parent_ids: list[str] = [parent] if parent else []
91 cid = compute_commit_id(parent_ids, sid, tag, ts_actual.isoformat())
92 write_commit(
93 repo,
94 CommitRecord(
95 commit_id=cid,
96 repo_id="test-repo",
97 branch=branch,
98 snapshot_id=sid,
99 message=tag,
100 committed_at=ts_actual,
101 author=author,
102 parent_commit_id=parent,
103 parent2_commit_id=None,
104 ),
105 )
106 ref_path = repo / ".muse" / "refs" / "heads" / branch
107 ref_path.parent.mkdir(parents=True, exist_ok=True)
108 ref_path.write_text(cid, encoding="utf-8")
109 return cid
110
111
112 def _fer(repo: pathlib.Path, *args: str) -> InvokeResult:
113 return runner.invoke(cli, ["for-each-ref", *args], env=_env(repo))
114
115
116 # ---------------------------------------------------------------------------
117 # Unit — schema
118 # ---------------------------------------------------------------------------
119
120
121 class TestSchemas:
122 def test_sort_fields_includes_snapshot_id(self) -> None:
123 assert "snapshot_id" in _SORT_FIELDS
124
125 def test_sort_fields_includes_all_expected(self) -> None:
126 for f in ("ref", "branch", "commit_id", "author", "committed_at", "message"):
127 assert f in _SORT_FIELDS
128
129 def test_for_each_ref_result_fields(self) -> None:
130 keys = _ForEachRefResult.__annotations__
131 assert "refs" in keys
132 assert "count" in keys
133
134 def test_ref_detail_is_total_false(self) -> None:
135 # total=False allows partial dicts for --no-commits mode.
136 # __required_keys__ is empty when total=False.
137 assert len(_RefDetail.__required_keys__) == 0
138
139
140 # ---------------------------------------------------------------------------
141 # Unit — _list_all_refs
142 # ---------------------------------------------------------------------------
143
144
145 class TestListAllRefs:
146 def test_empty_heads_dir(self, tmp_path: pathlib.Path) -> None:
147 _init_repo(tmp_path)
148 assert _list_all_refs(tmp_path) == []
149
150 def test_flat_branch(self, tmp_path: pathlib.Path) -> None:
151 _init_repo(tmp_path)
152 _commit(tmp_path, "c", "main")
153 pairs = _list_all_refs(tmp_path)
154 assert len(pairs) == 1
155 assert pairs[0][0] == "main"
156
157 def test_hierarchical_branch_discovered(self, tmp_path: pathlib.Path) -> None:
158 """feat/my-thing must be found — requires rglob, not iterdir."""
159 _init_repo(tmp_path)
160 _commit(tmp_path, "c-main", "main")
161 _commit(tmp_path, "c-feat", "feat/my-thing")
162 pairs = _list_all_refs(tmp_path)
163 branch_names = [b for b, _ in pairs]
164 assert "feat/my-thing" in branch_names
165 assert "main" in branch_names
166
167 def test_symlink_ref_skipped(self, tmp_path: pathlib.Path) -> None:
168 _init_repo(tmp_path)
169 _commit(tmp_path, "c", "main")
170 real = tmp_path / ".muse" / "refs" / "heads" / "main"
171 link = tmp_path / ".muse" / "refs" / "heads" / "sym"
172 link.symlink_to(real)
173 pairs = _list_all_refs(tmp_path)
174 names = [b for b, _ in pairs]
175 assert "sym" not in names
176 assert "main" in names
177
178 def test_invalid_commit_id_skipped(self, tmp_path: pathlib.Path) -> None:
179 _init_repo(tmp_path)
180 _commit(tmp_path, "c", "main")
181 # Write a ref file with garbage content
182 bad = tmp_path / ".muse" / "refs" / "heads" / "bad-ref"
183 bad.write_text("not-a-sha256\n", encoding="utf-8")
184 pairs = _list_all_refs(tmp_path)
185 names = [b for b, _ in pairs]
186 assert "bad-ref" not in names
187 assert "main" in names
188
189 def test_missing_heads_dir_returns_empty(self, tmp_path: pathlib.Path) -> None:
190 _init_repo(tmp_path)
191 import shutil
192 shutil.rmtree(tmp_path / ".muse" / "refs" / "heads")
193 assert _list_all_refs(tmp_path) == []
194
195 def test_sorted_output(self, tmp_path: pathlib.Path) -> None:
196 _init_repo(tmp_path)
197 for b in ["zzz", "aaa", "mmm"]:
198 _commit(tmp_path, f"c-{b}", b)
199 pairs = _list_all_refs(tmp_path)
200 names = [b for b, _ in pairs]
201 assert names == sorted(names)
202
203
204 # ---------------------------------------------------------------------------
205 # Integration — basic JSON output
206 # ---------------------------------------------------------------------------
207
208
209 class TestJsonOutput:
210 def test_empty_repo(self, tmp_path: pathlib.Path) -> None:
211 _init_repo(tmp_path)
212 r = _fer(tmp_path)
213 assert r.exit_code == 0
214 data = json.loads(r.output)
215 assert data["count"] == 0
216 assert data["refs"] == []
217
218 def test_single_branch(self, tmp_path: pathlib.Path) -> None:
219 _init_repo(tmp_path)
220 cid = _commit(tmp_path, "c1")
221 r = _fer(tmp_path)
222 assert r.exit_code == 0
223 data = json.loads(r.output)
224 assert data["count"] == 1
225 ref = data["refs"][0]
226 assert ref["commit_id"] == cid
227 assert ref["branch"] == "main"
228 assert ref["ref"] == "refs/heads/main"
229
230 def test_all_fields_present(self, tmp_path: pathlib.Path) -> None:
231 _init_repo(tmp_path)
232 _commit(tmp_path, "c1")
233 r = _fer(tmp_path)
234 ref = json.loads(r.output)["refs"][0]
235 for key in ("ref", "branch", "commit_id", "author", "message", "committed_at", "snapshot_id"):
236 assert key in ref, f"missing field: {key}"
237
238 def test_json_shorthand_alias(self, tmp_path: pathlib.Path) -> None:
239 _init_repo(tmp_path)
240 _commit(tmp_path, "c1")
241 r = _fer(tmp_path, "--json")
242 assert r.exit_code == 0
243 data = json.loads(r.output)
244 assert "refs" in data
245
246 def test_hierarchical_branch_in_output(self, tmp_path: pathlib.Path) -> None:
247 """Branches with slashes in name must appear in the output."""
248 _init_repo(tmp_path)
249 _commit(tmp_path, "c-main", "main")
250 _commit(tmp_path, "c-feat", "feat/my-thing")
251 r = _fer(tmp_path)
252 assert r.exit_code == 0
253 data = json.loads(r.output)
254 branches = [ref["branch"] for ref in data["refs"]]
255 assert "feat/my-thing" in branches
256 assert data["count"] == 2
257
258 def test_multiple_branches_counted(self, tmp_path: pathlib.Path) -> None:
259 _init_repo(tmp_path)
260 for b in ["main", "dev", "feat/x", "feat/y"]:
261 _commit(tmp_path, f"c-{b}", b)
262 r = _fer(tmp_path)
263 assert r.exit_code == 0
264 data = json.loads(r.output)
265 assert data["count"] == 4
266
267
268 # ---------------------------------------------------------------------------
269 # Integration — --no-commits fast path
270 # ---------------------------------------------------------------------------
271
272
273 class TestNoCommits:
274 def test_no_commits_omits_commit_fields(self, tmp_path: pathlib.Path) -> None:
275 _init_repo(tmp_path)
276 _commit(tmp_path, "c1")
277 r = _fer(tmp_path, "--no-commits")
278 assert r.exit_code == 0
279 data = json.loads(r.output)
280 ref = data["refs"][0]
281 assert "ref" in ref
282 assert "branch" in ref
283 assert "commit_id" in ref
284 # These must be absent in --no-commits mode
285 assert "author" not in ref
286 assert "message" not in ref
287 assert "committed_at" not in ref
288
289 def test_no_commits_count_correct(self, tmp_path: pathlib.Path) -> None:
290 _init_repo(tmp_path)
291 for b in ["main", "dev", "feat/x"]:
292 _commit(tmp_path, f"c-{b}", b)
293 r = _fer(tmp_path, "--no-commits")
294 assert r.exit_code == 0
295 data = json.loads(r.output)
296 assert data["count"] == 3
297
298 def test_no_commits_text_format(self, tmp_path: pathlib.Path) -> None:
299 _init_repo(tmp_path)
300 cid = _commit(tmp_path, "c1")
301 r = _fer(tmp_path, "--no-commits", "--format", "text")
302 assert r.exit_code == 0
303 line = r.output.strip()
304 assert cid in line
305 assert "refs/heads/main" in line
306 # Should NOT have 4 columns (no author column)
307 parts = line.split(" ")
308 assert len(parts) == 2
309
310 def test_no_commits_rejected_with_commit_sort_field(self, tmp_path: pathlib.Path) -> None:
311 _init_repo(tmp_path)
312 _commit(tmp_path, "c1")
313 for field in ("author", "message", "committed_at", "snapshot_id"):
314 r = _fer(tmp_path, "--no-commits", "--sort", field)
315 assert r.exit_code != 0
316 assert r.stdout_bytes == b""
317 assert "error" in r.stderr.lower()
318
319 def test_no_commits_allows_ref_level_sort(self, tmp_path: pathlib.Path) -> None:
320 _init_repo(tmp_path)
321 for b in ["zzz", "aaa"]:
322 _commit(tmp_path, f"c-{b}", b)
323 for field in ("ref", "branch", "commit_id"):
324 r = _fer(tmp_path, "--no-commits", "--sort", field)
325 assert r.exit_code == 0
326
327
328 # ---------------------------------------------------------------------------
329 # Integration — sorting
330 # ---------------------------------------------------------------------------
331
332
333 class TestSorting:
334 def test_sort_by_ref_ascending(self, tmp_path: pathlib.Path) -> None:
335 _init_repo(tmp_path)
336 for b in ["zzz", "aaa", "mmm"]:
337 _commit(tmp_path, f"c-{b}", b)
338 r = _fer(tmp_path, "--sort", "ref")
339 data = json.loads(r.output)
340 refs = [d["ref"] for d in data["refs"]]
341 assert refs == sorted(refs)
342
343 def test_sort_by_ref_descending(self, tmp_path: pathlib.Path) -> None:
344 _init_repo(tmp_path)
345 for b in ["zzz", "aaa", "mmm"]:
346 _commit(tmp_path, f"c-{b}", b)
347 r = _fer(tmp_path, "--sort", "ref", "--desc")
348 data = json.loads(r.output)
349 refs = [d["ref"] for d in data["refs"]]
350 assert refs == sorted(refs, reverse=True)
351
352 def test_sort_by_committed_at(self, tmp_path: pathlib.Path) -> None:
353 _init_repo(tmp_path)
354 base = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
355 _commit(tmp_path, "c-b", "bbb", ts=base + datetime.timedelta(hours=2))
356 _commit(tmp_path, "c-a", "aaa", ts=base + datetime.timedelta(hours=1))
357 r = _fer(tmp_path, "--sort", "committed_at")
358 data = json.loads(r.output)
359 timestamps = [d["committed_at"] for d in data["refs"]]
360 assert timestamps == sorted(timestamps)
361
362 def test_sort_by_author(self, tmp_path: pathlib.Path) -> None:
363 _init_repo(tmp_path)
364 _commit(tmp_path, "c-main", "main", author="zara")
365 _commit(tmp_path, "c-dev", "dev", author="alice")
366 r = _fer(tmp_path, "--sort", "author")
367 data = json.loads(r.output)
368 authors = [d["author"] for d in data["refs"]]
369 assert authors == sorted(authors)
370
371 def test_sort_by_snapshot_id(self, tmp_path: pathlib.Path) -> None:
372 _init_repo(tmp_path)
373 for b in ["a", "b", "c"]:
374 _commit(tmp_path, f"snap-{b}", b)
375 r = _fer(tmp_path, "--sort", "snapshot_id")
376 assert r.exit_code == 0
377 data = json.loads(r.output)
378 sids = [d["snapshot_id"] for d in data["refs"]]
379 assert sids == sorted(sids)
380
381
382 # ---------------------------------------------------------------------------
383 # Integration — --count and --pattern
384 # ---------------------------------------------------------------------------
385
386
387 class TestCountAndPattern:
388 def test_count_limits_output(self, tmp_path: pathlib.Path) -> None:
389 _init_repo(tmp_path)
390 for b in ["aaa", "bbb", "ccc", "ddd"]:
391 _commit(tmp_path, f"c-{b}", b)
392 r = _fer(tmp_path, "--count", "2")
393 data = json.loads(r.output)
394 assert data["count"] == 2
395 assert len(data["refs"]) == 2
396
397 def test_count_zero_is_unlimited(self, tmp_path: pathlib.Path) -> None:
398 _init_repo(tmp_path)
399 for b in ["a", "b", "c"]:
400 _commit(tmp_path, f"c-{b}", b)
401 r = _fer(tmp_path, "--count", "0")
402 data = json.loads(r.output)
403 assert data["count"] == 3
404
405 def test_negative_count_errors(self, tmp_path: pathlib.Path) -> None:
406 _init_repo(tmp_path)
407 r = _fer(tmp_path, "--count", "-1")
408 assert r.exit_code != 0
409 assert r.stdout_bytes == b""
410 assert "error" in r.stderr.lower()
411
412 def test_pattern_filter_flat(self, tmp_path: pathlib.Path) -> None:
413 _init_repo(tmp_path)
414 _commit(tmp_path, "c-main", "main")
415 _commit(tmp_path, "c-dev", "dev")
416 r = _fer(tmp_path, "--pattern", "refs/heads/main")
417 data = json.loads(r.output)
418 assert data["count"] == 1
419 assert data["refs"][0]["branch"] == "main"
420
421 def test_pattern_filter_hierarchical(self, tmp_path: pathlib.Path) -> None:
422 _init_repo(tmp_path)
423 _commit(tmp_path, "c-main", "main")
424 _commit(tmp_path, "c-feat1", "feat/one")
425 _commit(tmp_path, "c-feat2", "feat/two")
426 r = _fer(tmp_path, "--pattern", "refs/heads/feat/*")
427 data = json.loads(r.output)
428 assert data["count"] == 2
429 for ref in data["refs"]:
430 assert ref["branch"].startswith("feat/")
431
432 def test_pattern_no_match_returns_empty(self, tmp_path: pathlib.Path) -> None:
433 _init_repo(tmp_path)
434 _commit(tmp_path, "c-main", "main")
435 r = _fer(tmp_path, "--pattern", "refs/heads/nonexistent/*")
436 data = json.loads(r.output)
437 assert data["count"] == 0
438
439
440 # ---------------------------------------------------------------------------
441 # Integration — text output
442 # ---------------------------------------------------------------------------
443
444
445 class TestTextOutput:
446 def test_text_format_four_columns(self, tmp_path: pathlib.Path) -> None:
447 _init_repo(tmp_path)
448 cid = _commit(tmp_path, "c1", author="alice")
449 r = _fer(tmp_path, "--format", "text")
450 assert r.exit_code == 0
451 line = r.output.strip()
452 assert cid in line
453 assert "refs/heads/main" in line
454 assert "alice" in line
455
456 def test_text_multiple_lines(self, tmp_path: pathlib.Path) -> None:
457 _init_repo(tmp_path)
458 for b in ["aaa", "bbb"]:
459 _commit(tmp_path, f"c-{b}", b)
460 r = _fer(tmp_path, "--format", "text")
461 lines = [l for l in r.output.strip().splitlines() if l]
462 assert len(lines) == 2
463
464
465 # ---------------------------------------------------------------------------
466 # Security
467 # ---------------------------------------------------------------------------
468
469
470 class TestSecurity:
471 def test_ansi_in_branch_name_sanitized_text(self, tmp_path: pathlib.Path) -> None:
472 """Branch names with ANSI must not appear raw in text output."""
473 _init_repo(tmp_path)
474 cid = _commit(tmp_path, "c1", "main")
475 # Directly write a ref file with ANSI in its name (via the raw FS)
476 ansi_branch_dir = tmp_path / ".muse" / "refs" / "heads" / "safe"
477 ansi_branch_dir.mkdir(parents=True, exist_ok=True)
478 # Can't create filename with ANSI; instead verify author field sanitized
479 _commit(tmp_path, "c-dev", "dev", author="\x1b[31mred\x1b[0m")
480 r = _fer(tmp_path, "--format", "text")
481 assert "\x1b" not in r.output
482
483 def test_error_format_goes_to_stderr(self, tmp_path: pathlib.Path) -> None:
484 _init_repo(tmp_path)
485 r = _fer(tmp_path, "--format", "xml")
486 assert r.exit_code != 0
487 assert r.stdout_bytes == b""
488 assert "error" in r.stderr.lower()
489
490 def test_error_sort_goes_to_stderr(self, tmp_path: pathlib.Path) -> None:
491 _init_repo(tmp_path)
492 r = _fer(tmp_path, "--sort", "invalid_field")
493 assert r.exit_code != 0
494 assert r.stdout_bytes == b""
495 assert "error" in r.stderr.lower()
496
497 def test_negative_count_error_to_stderr(self, tmp_path: pathlib.Path) -> None:
498 _init_repo(tmp_path)
499 r = _fer(tmp_path, "--count", "-5")
500 assert r.exit_code != 0
501 assert r.stdout_bytes == b""
502
503 def test_no_traceback_on_bad_format(self, tmp_path: pathlib.Path) -> None:
504 _init_repo(tmp_path)
505 r = _fer(tmp_path, "--format", "bad")
506 assert "Traceback" not in r.output
507 assert "Traceback" not in r.stderr
508
509 def test_symlink_ref_skipped_in_output(self, tmp_path: pathlib.Path) -> None:
510 _init_repo(tmp_path)
511 _commit(tmp_path, "c", "main")
512 real = tmp_path / ".muse" / "refs" / "heads" / "main"
513 link = tmp_path / ".muse" / "refs" / "heads" / "linked"
514 link.symlink_to(real)
515 r = _fer(tmp_path)
516 data = json.loads(r.output)
517 branches = [ref["branch"] for ref in data["refs"]]
518 assert "linked" not in branches
519
520 def test_corrupted_ref_skipped_in_output(self, tmp_path: pathlib.Path) -> None:
521 _init_repo(tmp_path)
522 _commit(tmp_path, "c", "main")
523 bad = tmp_path / ".muse" / "refs" / "heads" / "corrupted"
524 bad.write_text("not-a-sha\n", encoding="utf-8")
525 r = _fer(tmp_path)
526 data = json.loads(r.output)
527 branches = [ref["branch"] for ref in data["refs"]]
528 assert "corrupted" not in branches
529
530 def test_no_repo_exits_cleanly(self, tmp_path: pathlib.Path) -> None:
531 r = runner.invoke(
532 cli,
533 ["for-each-ref"],
534 env={"MUSE_REPO_ROOT": str(tmp_path / "norepo")},
535 )
536 assert r.exit_code != 0
537 assert "Traceback" not in r.output
538 assert "Traceback" not in r.stderr
539
540
541 # ---------------------------------------------------------------------------
542 # Stress
543 # ---------------------------------------------------------------------------
544
545
546 class TestStress:
547 def test_100_flat_branches(self, tmp_path: pathlib.Path) -> None:
548 _init_repo(tmp_path)
549 for i in range(100):
550 _commit(tmp_path, f"c-{i:03d}", f"branch-{i:03d}")
551 r = _fer(tmp_path)
552 assert r.exit_code == 0
553 data = json.loads(r.output)
554 assert data["count"] == 100
555
556 def test_50_hierarchical_branches(self, tmp_path: pathlib.Path) -> None:
557 """All 50 branches with slashes must be discovered via rglob."""
558 _init_repo(tmp_path)
559 for i in range(50):
560 _commit(tmp_path, f"c-{i}", f"feat/task-{i:03d}")
561 r = _fer(tmp_path)
562 assert r.exit_code == 0
563 data = json.loads(r.output)
564 assert data["count"] == 50
565 for ref in data["refs"]:
566 assert ref["branch"].startswith("feat/")
567
568 def test_no_commits_100_branches_fast(self, tmp_path: pathlib.Path) -> None:
569 _init_repo(tmp_path)
570 for i in range(100):
571 _commit(tmp_path, f"c-{i}", f"b-{i:03d}")
572 r = _fer(tmp_path, "--no-commits")
573 assert r.exit_code == 0
574 data = json.loads(r.output)
575 assert data["count"] == 100
576 # Confirm no commit metadata fields
577 for ref in data["refs"]:
578 assert "author" not in ref
579
580 def test_200_sequential_reads(self, tmp_path: pathlib.Path) -> None:
581 _init_repo(tmp_path)
582 for b in ["main", "dev"]:
583 _commit(tmp_path, f"c-{b}", b)
584 for _ in range(200):
585 r = _fer(tmp_path)
586 assert r.exit_code == 0
587 assert json.loads(r.output)["count"] == 2
File History 3 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:1c4b3e3a9a1f300774c3ee662b572a698d5fd405bf765a71e6011a2e9c3eaaaa feat: Muse — version control for the agent era Human 100 days ago