gabriel / muse public
feat patch dev-safety task/185-phase1-which-muse-audit #1 / 1
AI Agent gabriel · 2 days ago · Sep 10, 2026 · Diff

feat(dev-safety): Phase 1 of #185 — audit tool for ambiguous muse binaries

Adds scripts/dev/which_muse.py + which-muse.sh: classifies any muse-like executable as editable (live source) vs installed, via muse.__file__ inspection through the shim's own interpreter (handles both literal and `env`-indirect shebangs; fails clearly on non-Python dispatcher shims like pyenv's).

Audit found 5 distinct muse resolutions on this machine across 3 versions, confirming and exceeding the ambiguity #185 was filed to fix — including the interactive shell's `which muse` disagreeing with this very tool's own `shutil.which` result. Documented in docs/local-dev-safety.md along with a real cwd-shadowing bug the test suite caught in the classifier itself (subprocess inherited caller's cwd, whose own muse/ subpackage shadowed PYTHONPATH — fixed by pinning cwd to a neutral tempdir).

Observation-only: no PATH changes, no new binaries, no guard rails yet.

sha256:133f9bcf57a62ec7ebf0cd71138b54a200b15989aea2fb2a6912497e2926aa8a sha
+40 symbols
1160 changed · 1160 in snapshot files
sha256:af3124ce15be75a4470466572d0886ca1fa56db79e47365323a2290a5be81165 snapshot
+40
symbols added
1160
files changed
1160
files in snapshot
0
dead code introduced
Semantic Changes 40 symbols
+ docs/
+ muse/
+ tests/
+ tools/
~ docs/local-dev-safety.md .md 7 symbols added
+ Local Dev Safety: Isolating muse's Dev Build From Its Own Object Store section Local Dev Safety: Isolating muse's Dev Build From Its Own Object Store L1–95
+ A real bug this audit tooling itself hit section A real bug this audit tooling itself hit L46–61
+ Phase 1 finding: the resolved muse binary is ambiguous, and the default is the risky one section Phase 1 finding: the resolved muse binary is ambiguous, and the default is the risky one L16–46
+ table section table L22–29
+ The problem section The problem L5–16
+ What Phase 1 delivers section What Phase 1 delivers L61–78
+ What's next section What's next L78–95
~ scripts/dev/which_muse.py .py 14 symbols added
+ _INTROSPECT_SNIPPET variable variable _INTROSPECT_SNIPPET L28–32
+ _PYTHON_NAME_RE variable variable _PYTHON_NAME_RE L35–35
+ _resolve_shebang_argv function function _resolve_shebang_argv L38–70
+ classify_muse_binary class function classify_muse_binary L73–124
+ Path import import Path L26–26
+ annotations import import annotations L17–17
+ argparse import import argparse L19–19
+ json import import json L20–20
+ re import import re L21–21
+ shutil import import shutil L22–22
+ subprocess import import subprocess L23–23
+ sys import import sys L24–24
+ tempfile import import tempfile L25–25
+ main function function main L127–152
~ tests/test_which_muse_script.py .py 19 symbols added
+ TestClassifyMuseBinary class class TestClassifyMuseBinary L41–109
+ test_editable_source_checkout_is_classified_editable class method test_editable_source_checkout_is_classified_editable L42–52
+ test_env_indirect_shebang_is_resolved method method test_env_indirect_shebang_is_resolved L84–97
+ test_installed_venv_copy_is_classified_not_editable class method test_installed_venv_copy_is_classified_not_editable L54–63
+ test_missing_muse_package_raises_clear_error method method test_missing_muse_package_raises_clear_error L78–82
+ test_non_python_dispatcher_shebang_fails_clearly method method test_non_python_dispatcher_shebang_fails_clearly L99–109
+ test_resolved_path_follows_symlinks method method test_resolved_path_follows_symlinks L65–76
+ TestWhichMuseCli class class TestWhichMuseCli L112–137
+ test_json_output_is_valid_json_with_expected_keys method method test_json_output_is_valid_json_with_expected_keys L113–130
+ test_nonexistent_target_exits_nonzero method method test_nonexistent_target_exits_nonzero L132–137
+ _CLEAN_INTERPRETER variable variable _CLEAN_INTERPRETER L21–21
+ _write_fake_package function function _write_fake_package L24–27
+ _write_shim function function _write_shim L30–38
+ Path import import Path L10–10
+ classify_muse_binary class import classify_muse_binary L15–15
+ json import import json L7–7
+ pytest import import pytest L12–12
+ sys import import sys L8–8
+ textwrap import import textwrap L9–9
Files Changed
+1160
1160 in snapshot
+ .coverage .coverage
+ .museattributes .museattributes
+ .museignore .museignore
+ CLAUDE.md .md
+ README.md .md
+ dev-setup.sh .sh
+ pyproject.toml .toml
← Older Oldest on task/185-phase1-which-muse-audit
All commits
Newer → Latest on task/185-phase1-which-muse-audit

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:133f9bcf57a62ec7ebf0cd71138b54a200b15989aea2fb2a6912497e2926aa8a --body "your comment"