test_dir_lifecycle.py
python
sha256:a154bc65916614c833d5a40a10d81ba3eae0d0495b0afddd34dc34f18d5e91b8
fix: test suite alignment and typing audit — zero violations
Sonnet 4.6
minor
⚠ breaking
23 days ago
| 1 | """Comprehensive directory lifecycle tests — all states × all commands. |
| 2 | |
| 3 | State matrix: |
| 4 | U — untracked: on disk, not in HEAD, not staged |
| 5 | SA — staged added: on disk, not in HEAD, staged A |
| 6 | C — clean: on disk, in HEAD, not staged |
| 7 | UD — unstaged del: not on disk, in HEAD, not staged |
| 8 | SD — staged deleted: not on disk, in HEAD, staged D |
| 9 | SR — staged renamed: muse mv produced D(old)+A(new)+rename_map |
| 10 | |
| 11 | Commands covered: muse status (text+json), muse diff, muse code add, muse mv, |
| 12 | muse code reset, muse commit. |
| 13 | """ |
| 14 | from __future__ import annotations |
| 15 | |
| 16 | import datetime |
| 17 | import json |
| 18 | import os |
| 19 | import pathlib |
| 20 | import shutil |
| 21 | from collections.abc import Mapping |
| 22 | |
| 23 | import pytest |
| 24 | |
| 25 | from tests.cli_test_helper import CliRunner |
| 26 | from muse.core.paths import muse_dir, ref_path |
| 27 | from muse.core.object_store import write_object |
| 28 | from muse.core.ids import hash_commit, hash_snapshot |
| 29 | from muse.core.commits import CommitRecord, write_commit |
| 30 | from muse.core.snapshots import SnapshotRecord, write_snapshot |
| 31 | from muse.core.types import blob_id |
| 32 | |
| 33 | runner = CliRunner() |
| 34 | |
| 35 | |
| 36 | # --------------------------------------------------------------------------- |
| 37 | # Shared helpers |
| 38 | # --------------------------------------------------------------------------- |
| 39 | |
| 40 | def _make_repo(path: pathlib.Path, dirs: list[str] | None = None) -> pathlib.Path: |
| 41 | """Init a minimal code-domain repo with one committed file.""" |
| 42 | dot = muse_dir(path) |
| 43 | for d in ("commits", "snapshots", "objects", "refs/heads", "code"): |
| 44 | (dot / d).mkdir(parents=True, exist_ok=True) |
| 45 | (dot / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8") |
| 46 | (dot / "repo.json").write_text( |
| 47 | json.dumps({"repo_id": "test", "domain": "code"}), encoding="utf-8" |
| 48 | ) |
| 49 | content = b"readme\n" |
| 50 | oid = blob_id(content) |
| 51 | write_object(path, oid, content) |
| 52 | manifest = {"readme.md": oid} |
| 53 | snap_id = hash_snapshot(manifest, dirs or []) |
| 54 | write_snapshot(path, SnapshotRecord( |
| 55 | snapshot_id=snap_id, manifest=manifest, directories=dirs or [] |
| 56 | )) |
| 57 | now = datetime.datetime.now(datetime.timezone.utc) |
| 58 | cid = hash_commit( |
| 59 | parent_ids=[], snapshot_id=snap_id, |
| 60 | message="init", committed_at_iso=now.isoformat(), |
| 61 | ) |
| 62 | write_commit(path, CommitRecord( |
| 63 | commit_id=cid, branch="main", snapshot_id=snap_id, |
| 64 | message="init", committed_at=now, parent_commit_id=None, |
| 65 | )) |
| 66 | ref_path(path, "main").write_text(cid, encoding="utf-8") |
| 67 | (path / "readme.md").write_bytes(content) |
| 68 | return path |
| 69 | |
| 70 | |
| 71 | def _env(root: pathlib.Path) -> Mapping[str, str]: |
| 72 | return {"MUSE_REPO_ROOT": str(root)} |
| 73 | |
| 74 | |
| 75 | def _status_json(root: pathlib.Path) -> Mapping[str, object]: |
| 76 | return json.loads(runner.invoke(None, ["status", "--json"], env=_env(root)).output) |
| 77 | |
| 78 | |
| 79 | def _diff_json(root: pathlib.Path) -> Mapping[str, object]: |
| 80 | return json.loads(runner.invoke(None, ["diff", "--json"], env=_env(root)).output) |
| 81 | |
| 82 | |
| 83 | # --------------------------------------------------------------------------- |
| 84 | # State U — untracked empty directory |
| 85 | # --------------------------------------------------------------------------- |
| 86 | |
| 87 | class TestStateUntracked: |
| 88 | """Empty dir exists on disk, not in HEAD, not staged.""" |
| 89 | |
| 90 | def test_status_json_shows_untracked(self, tmp_path: pathlib.Path) -> None: |
| 91 | root = _make_repo(tmp_path) |
| 92 | (root / "newdir").mkdir() |
| 93 | d = _status_json(root) |
| 94 | assert "newdir/" in d["untracked"] |
| 95 | assert d["clean"] is False |
| 96 | |
| 97 | def test_status_text_labels_untracked_directory(self, tmp_path: pathlib.Path) -> None: |
| 98 | root = _make_repo(tmp_path) |
| 99 | (root / "newdir").mkdir() |
| 100 | out = runner.invoke(None, ["status"], env=_env(root)).output |
| 101 | assert "untracked directory" in out |
| 102 | assert "newdir/" in out |
| 103 | |
| 104 | def test_not_in_added_or_deleted(self, tmp_path: pathlib.Path) -> None: |
| 105 | root = _make_repo(tmp_path) |
| 106 | (root / "newdir").mkdir() |
| 107 | d = _status_json(root) |
| 108 | assert "newdir/" not in d["added"] |
| 109 | assert "newdir/" not in d["deleted"] |
| 110 | |
| 111 | def test_diff_does_not_show_untracked(self, tmp_path: pathlib.Path) -> None: |
| 112 | """Untracked dirs are invisible to diff (same as git).""" |
| 113 | root = _make_repo(tmp_path) |
| 114 | (root / "newdir").mkdir() |
| 115 | d = _diff_json(root) |
| 116 | assert d["has_changes"] is False |
| 117 | |
| 118 | |
| 119 | # --------------------------------------------------------------------------- |
| 120 | # State SA — staged added (new empty dir, staged via muse code add) |
| 121 | # --------------------------------------------------------------------------- |
| 122 | |
| 123 | class TestStateStagedAdded: |
| 124 | """Empty dir on disk, not in HEAD, staged as A sentinel.""" |
| 125 | |
| 126 | def test_shows_in_staged_added(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 127 | root = _make_repo(tmp_path) |
| 128 | (root / "newdir").mkdir() |
| 129 | monkeypatch.chdir(root) |
| 130 | runner.invoke(None, ["code", "add", "newdir/"], env=_env(root)) |
| 131 | d = _status_json(root) |
| 132 | assert "newdir/" in d["staged"]["added"] |
| 133 | assert "newdir/" in d["added"] |
| 134 | assert "newdir/" not in d["untracked"] |
| 135 | |
| 136 | def test_status_text_shows_new_directory(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 137 | root = _make_repo(tmp_path) |
| 138 | (root / "newdir").mkdir() |
| 139 | monkeypatch.chdir(root) |
| 140 | runner.invoke(None, ["code", "add", "newdir/"], env=_env(root)) |
| 141 | out = runner.invoke(None, ["status"], env=_env(root)).output |
| 142 | assert "new directory" in out |
| 143 | assert "newdir/" in out |
| 144 | |
| 145 | def test_diff_shows_as_added(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 146 | root = _make_repo(tmp_path) |
| 147 | (root / "newdir").mkdir() |
| 148 | monkeypatch.chdir(root) |
| 149 | runner.invoke(None, ["code", "add", "newdir/"], env=_env(root)) |
| 150 | d = _diff_json(root) |
| 151 | assert "newdir/" in d["added"] |
| 152 | |
| 153 | def test_reset_clears_back_to_untracked(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 154 | root = _make_repo(tmp_path) |
| 155 | (root / "newdir").mkdir() |
| 156 | monkeypatch.chdir(root) |
| 157 | runner.invoke(None, ["code", "add", "newdir/"], env=_env(root)) |
| 158 | runner.invoke(None, ["code", "reset"], env=_env(root)) |
| 159 | d = _status_json(root) |
| 160 | assert "newdir/" in d["untracked"] |
| 161 | assert d["staged"]["added"] == [] |
| 162 | |
| 163 | def test_commit_includes_directory(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 164 | root = _make_repo(tmp_path) |
| 165 | (root / "newdir").mkdir() |
| 166 | monkeypatch.chdir(root) |
| 167 | runner.invoke(None, ["code", "add", "newdir/"], env=_env(root)) |
| 168 | runner.invoke(None, ["commit", "-m", "add dir"], env=_env(root)) |
| 169 | # After commit, dir is clean |
| 170 | d = _status_json(root) |
| 171 | assert d["clean"] is True |
| 172 | |
| 173 | |
| 174 | # --------------------------------------------------------------------------- |
| 175 | # State C — clean (committed dir still on disk) |
| 176 | # --------------------------------------------------------------------------- |
| 177 | |
| 178 | class TestStateClean: |
| 179 | """Empty dir on disk, in HEAD dirs, not staged → nothing to report.""" |
| 180 | |
| 181 | def test_clean_when_committed_dir_unchanged(self, tmp_path: pathlib.Path) -> None: |
| 182 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 183 | (root / "mydir").mkdir() |
| 184 | d = _status_json(root) |
| 185 | assert d["clean"] is True |
| 186 | assert d["untracked"] == [] |
| 187 | assert d["added"] == [] |
| 188 | assert d["deleted"] == [] |
| 189 | |
| 190 | def test_status_text_says_nothing_to_commit(self, tmp_path: pathlib.Path) -> None: |
| 191 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 192 | (root / "mydir").mkdir() |
| 193 | out = runner.invoke(None, ["status"], env=_env(root)).output |
| 194 | assert "working tree clean" in out |
| 195 | |
| 196 | |
| 197 | # --------------------------------------------------------------------------- |
| 198 | # State UD — unstaged deletion (committed dir removed from disk) |
| 199 | # --------------------------------------------------------------------------- |
| 200 | |
| 201 | class TestStateUnstagedDeleted: |
| 202 | """Committed empty dir removed from disk, not yet staged.""" |
| 203 | |
| 204 | def test_shows_in_deleted(self, tmp_path: pathlib.Path) -> None: |
| 205 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 206 | (root / "mydir").mkdir() |
| 207 | shutil.rmtree(root / "mydir") |
| 208 | d = _status_json(root) |
| 209 | assert "mydir/" in d["deleted"] |
| 210 | assert d["clean"] is False |
| 211 | |
| 212 | def test_in_unstaged_deleted(self, tmp_path: pathlib.Path) -> None: |
| 213 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 214 | (root / "mydir").mkdir() |
| 215 | shutil.rmtree(root / "mydir") |
| 216 | d = _status_json(root) |
| 217 | assert "mydir/" in d["unstaged"]["deleted"] |
| 218 | assert "mydir/" not in d["staged"]["deleted"] |
| 219 | |
| 220 | def test_status_text_shows_deleted(self, tmp_path: pathlib.Path) -> None: |
| 221 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 222 | (root / "mydir").mkdir() |
| 223 | shutil.rmtree(root / "mydir") |
| 224 | out = runner.invoke(None, ["status"], env=_env(root)).output |
| 225 | assert "deleted" in out |
| 226 | assert "mydir/" in out |
| 227 | |
| 228 | def test_code_add_dot_stages_deletion(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 229 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 230 | (root / "mydir").mkdir() |
| 231 | shutil.rmtree(root / "mydir") |
| 232 | monkeypatch.chdir(root) |
| 233 | out = runner.invoke(None, ["code", "add", "."], env=_env(root)).output |
| 234 | assert "director" in out # "1 directory" or "directories" |
| 235 | d = _status_json(root) |
| 236 | assert "mydir/" in d["staged"]["deleted"] |
| 237 | |
| 238 | def test_code_add_explicit_path_stages_deletion(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 239 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 240 | (root / "mydir").mkdir() |
| 241 | shutil.rmtree(root / "mydir") |
| 242 | monkeypatch.chdir(root) |
| 243 | runner.invoke(None, ["code", "add", "mydir/"], env=_env(root)) |
| 244 | d = _status_json(root) |
| 245 | assert "mydir/" in d["staged"]["deleted"] |
| 246 | |
| 247 | |
| 248 | # --------------------------------------------------------------------------- |
| 249 | # State SD — staged deletion |
| 250 | # --------------------------------------------------------------------------- |
| 251 | |
| 252 | class TestStateStagedDeleted: |
| 253 | """Committed empty dir staged for deletion.""" |
| 254 | |
| 255 | def test_shows_in_staged_deleted(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 256 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 257 | (root / "mydir").mkdir() |
| 258 | shutil.rmtree(root / "mydir") |
| 259 | monkeypatch.chdir(root) |
| 260 | runner.invoke(None, ["code", "add", "."], env=_env(root)) |
| 261 | d = _status_json(root) |
| 262 | assert "mydir/" in d["staged"]["deleted"] |
| 263 | assert "mydir/" not in d["unstaged"]["deleted"] |
| 264 | |
| 265 | def test_status_text_shows_deleted_directory(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 266 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 267 | (root / "mydir").mkdir() |
| 268 | shutil.rmtree(root / "mydir") |
| 269 | monkeypatch.chdir(root) |
| 270 | runner.invoke(None, ["code", "add", "."], env=_env(root)) |
| 271 | out = runner.invoke(None, ["status"], env=_env(root)).output |
| 272 | assert "deleted directory" in out |
| 273 | assert "mydir/" in out |
| 274 | |
| 275 | def test_reset_moves_back_to_unstaged(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 276 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 277 | (root / "mydir").mkdir() |
| 278 | shutil.rmtree(root / "mydir") |
| 279 | monkeypatch.chdir(root) |
| 280 | runner.invoke(None, ["code", "add", "."], env=_env(root)) |
| 281 | runner.invoke(None, ["code", "reset"], env=_env(root)) |
| 282 | d = _status_json(root) |
| 283 | assert "mydir/" in d["unstaged"]["deleted"] |
| 284 | assert d["staged"]["deleted"] == [] |
| 285 | |
| 286 | def test_no_double_entry_in_deleted(self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 287 | """mydir/ must appear exactly once in deleted[], not twice.""" |
| 288 | root = _make_repo(tmp_path, dirs=["mydir"]) |
| 289 | (root / "mydir").mkdir() |
| 290 | shutil.rmtree(root / "mydir") |
| 291 | monkeypatch.chdir(root) |
| 292 | runner.invoke(None, ["code", "add", "."], env=_env(root)) |
| 293 | d = _status_json(root) |
| 294 | assert d["deleted"].count("mydir/") == 1 |
| 295 | |
| 296 | |
| 297 | # --------------------------------------------------------------------------- |
| 298 | # State SR — staged rename (muse mv) |
| 299 | # --------------------------------------------------------------------------- |
| 300 | |
| 301 | class TestStateStagedRenamed: |
| 302 | """Committed empty dir renamed via muse mv — D(old)+A(new)+rename_map.""" |
| 303 | |
| 304 | def test_status_json_shows_renamed(self, tmp_path: pathlib.Path) -> None: |
| 305 | root = _make_repo(tmp_path, dirs=["olddir"]) |
| 306 | (root / "olddir").mkdir() |
| 307 | runner.invoke(None, ["mv", "olddir", "newdir"], env=_env(root)) |
| 308 | d = _status_json(root) |
| 309 | assert "olddir/" in d["renamed"] |
| 310 | assert d["renamed"]["olddir/"] == "newdir/" |
| 311 | |
| 312 | def test_staged_renamed_not_in_added_or_deleted(self, tmp_path: pathlib.Path) -> None: |
| 313 | root = _make_repo(tmp_path, dirs=["olddir"]) |
| 314 | (root / "olddir").mkdir() |
| 315 | runner.invoke(None, ["mv", "olddir", "newdir"], env=_env(root)) |
| 316 | d = _status_json(root) |
| 317 | assert "olddir/" not in d["added"] |
| 318 | assert "newdir/" not in d["added"] |
| 319 | assert "olddir/" not in d["deleted"] |
| 320 | assert "newdir/" not in d["deleted"] |
| 321 | |
| 322 | def test_total_changes_is_one(self, tmp_path: pathlib.Path) -> None: |
| 323 | root = _make_repo(tmp_path, dirs=["olddir"]) |
| 324 | (root / "olddir").mkdir() |
| 325 | runner.invoke(None, ["mv", "olddir", "newdir"], env=_env(root)) |
| 326 | d = _status_json(root) |
| 327 | assert d["total_changes"] == 1 |
| 328 | |
| 329 | def test_status_text_shows_renamed_directory(self, tmp_path: pathlib.Path) -> None: |
| 330 | root = _make_repo(tmp_path, dirs=["olddir"]) |
| 331 | (root / "olddir").mkdir() |
| 332 | runner.invoke(None, ["mv", "olddir", "newdir"], env=_env(root)) |
| 333 | out = runner.invoke(None, ["status"], env=_env(root)).output |
| 334 | assert "renamed directory" in out |
| 335 | assert "olddir/" in out |
| 336 | assert "newdir/" in out |
| 337 | |
| 338 | def test_diff_shows_r_not_a_plus_d(self, tmp_path: pathlib.Path) -> None: |
| 339 | root = _make_repo(tmp_path, dirs=["olddir"]) |
| 340 | (root / "olddir").mkdir() |
| 341 | runner.invoke(None, ["mv", "olddir", "newdir"], env=_env(root)) |
| 342 | d = _diff_json(root) |
| 343 | assert "olddir/" in d["renamed"] |
| 344 | assert d["renamed"]["olddir/"] == "newdir/" |
| 345 | assert "olddir/" not in d["deleted"] |
| 346 | assert "newdir/" not in d["added"] |
| 347 | |
| 348 | def test_reset_clears_rename_map(self, tmp_path: pathlib.Path) -> None: |
| 349 | root = _make_repo(tmp_path, dirs=["olddir"]) |
| 350 | (root / "olddir").mkdir() |
| 351 | runner.invoke(None, ["mv", "olddir", "newdir"], env=_env(root)) |
| 352 | runner.invoke(None, ["code", "reset"], env=_env(root)) |
| 353 | d = _status_json(root) |
| 354 | assert d["renamed"] == {} |
| 355 | # After reset: olddir deleted from disk, newdir untracked |
| 356 | assert "olddir/" in d["deleted"] |
| 357 | assert "newdir/" in d["untracked"] |
| 358 | |
| 359 | def test_trailing_slash_on_dest_is_cosmetic(self, tmp_path: pathlib.Path) -> None: |
| 360 | """muse mv olddir newdir/ where newdir doesn't exist → rename to newdir.""" |
| 361 | root = _make_repo(tmp_path, dirs=["olddir"]) |
| 362 | (root / "olddir").mkdir() |
| 363 | runner.invoke(None, ["mv", "olddir", "newdir/"], env=_env(root)) |
| 364 | d = _status_json(root) |
| 365 | assert "olddir/" in d["renamed"] |
| 366 | assert d["renamed"]["olddir/"] == "newdir/" |
| 367 | |
| 368 | def test_rename_chain_collapses(self, tmp_path: pathlib.Path) -> None: |
| 369 | """A→B then B→C collapses to A→C in the rename map.""" |
| 370 | root = _make_repo(tmp_path, dirs=["alpha"]) |
| 371 | (root / "alpha").mkdir() |
| 372 | runner.invoke(None, ["mv", "alpha", "beta"], env=_env(root)) |
| 373 | runner.invoke(None, ["mv", "beta", "gamma"], env=_env(root)) |
| 374 | d = _status_json(root) |
| 375 | assert "alpha/" in d["renamed"] |
| 376 | assert d["renamed"]["alpha/"] == "gamma/" |
| 377 | assert "beta/" not in d["renamed"] |
| 378 | |
| 379 | def test_move_inside_existing_dir(self, tmp_path: pathlib.Path) -> None: |
| 380 | """muse mv src existingdir/ where existingdir exists → moves inside.""" |
| 381 | root = _make_repo(tmp_path, dirs=["src", "lib"]) |
| 382 | (root / "src").mkdir() |
| 383 | (root / "lib").mkdir() |
| 384 | runner.invoke(None, ["mv", "src", "lib/"], env=_env(root)) |
| 385 | # lib/ exists → src moves inside to lib/src |
| 386 | assert (root / "lib" / "src").exists() |
| 387 | assert not (root / "src").exists() |
| 388 | |
| 389 | |
| 390 | # --------------------------------------------------------------------------- |
| 391 | # muse mv for non-empty directories (files inside) |
| 392 | # --------------------------------------------------------------------------- |
| 393 | |
| 394 | class TestMvNonEmptyDir: |
| 395 | def test_mv_nonempty_dir_restages_all_files(self, tmp_path: pathlib.Path) -> None: |
| 396 | root = _make_repo(tmp_path) |
| 397 | content = b"hello\n" |
| 398 | oid = blob_id(content) |
| 399 | write_object(root, oid, content) |
| 400 | # Manually add a file in a subdirectory to HEAD |
| 401 | from muse.core.refs import get_head_commit_id, read_current_branch |
| 402 | from muse.core.commits import read_commit |
| 403 | from muse.core.snapshots import read_snapshot, write_snapshot |
| 404 | branch = read_current_branch(root) |
| 405 | cid = get_head_commit_id(root, branch) |
| 406 | commit = read_commit(root, cid) |
| 407 | snap = read_snapshot(root, commit.snapshot_id) |
| 408 | new_manifest = dict(snap.manifest) |
| 409 | new_manifest["src/hello.py"] = oid |
| 410 | new_snap_id = hash_snapshot(new_manifest, []) |
| 411 | write_snapshot(root, SnapshotRecord( |
| 412 | snapshot_id=new_snap_id, manifest=new_manifest, directories=[] |
| 413 | )) |
| 414 | now = datetime.datetime.now(datetime.timezone.utc) |
| 415 | new_cid = hash_commit( |
| 416 | parent_ids=[cid], snapshot_id=new_snap_id, |
| 417 | message="add file", committed_at_iso=now.isoformat(), |
| 418 | ) |
| 419 | write_commit(root, CommitRecord( |
| 420 | commit_id=new_cid, branch=branch, snapshot_id=new_snap_id, |
| 421 | message="add file", committed_at=now, parent_commit_id=cid, |
| 422 | )) |
| 423 | ref_path(root, branch).write_text(new_cid, encoding="utf-8") |
| 424 | (root / "src").mkdir() |
| 425 | (root / "src" / "hello.py").write_bytes(content) |
| 426 | |
| 427 | runner.invoke(None, ["mv", "src", "lib"], env=_env(root)) |
| 428 | |
| 429 | d = _status_json(root) |
| 430 | assert "lib/hello.py" in d["staged"]["added"] |
| 431 | assert "src/hello.py" in d["staged"]["deleted"] |
| 432 | assert (root / "lib" / "hello.py").exists() |
| 433 | assert not (root / "src").exists() |
File History
2 commits
sha256:a154bc65916614c833d5a40a10d81ba3eae0d0495b0afddd34dc34f18d5e91b8
fix: test suite alignment and typing audit — zero violations
Sonnet 4.6
minor
⚠
23 days ago
sha256:8b3bbc331871a67d637a5dfd8fa2dcdcf6c73b682bab4cd11fb534220913e7bc
fix: untracked dirs invisible to muse diff; add 30-test dir…
Sonnet 4.6
minor
⚠
23 days ago