feat: 审查修复一轮——sync 解引用软链技能/防 CLAUDE.md 崩溃/init 建 agent-tasks;upgrade SHA256 校验+收尾容错;新增 xiaohe switch 回滚;uninstall --all 扩到 ~/.metabot ~/.mytoolkit;下载路径改 xiaohe-agent
This commit is contained in:
@@ -86,6 +86,41 @@ class TestSyncWorkspace:
|
||||
report = sync_mod.sync_workspace(workspace, force=True)
|
||||
assert report["added"]
|
||||
|
||||
def test_symlinked_skill_dir_is_dereferenced(
|
||||
self, runtime: Path, workspace: Path
|
||||
) -> None:
|
||||
# Skills living in contrib/ are linked into .agents/skills (relative
|
||||
# symlink); the workspace must receive real self-contained files.
|
||||
real = runtime / "contrib" / "mytoolkit" / ".claude" / "skills" / "mytoolkit"
|
||||
real.mkdir(parents=True)
|
||||
(real / "SKILL.md").write_text("toolkit skill v1")
|
||||
import os
|
||||
|
||||
rel_target = os.path.relpath(real, runtime / ".agents" / "skills")
|
||||
(runtime / ".agents" / "skills" / "mytoolkit").symlink_to(
|
||||
rel_target, target_is_directory=True
|
||||
)
|
||||
report = sync_mod.sync_workspace(workspace)
|
||||
copied = workspace / ".agents" / "skills" / "mytoolkit" / "SKILL.md"
|
||||
assert copied.is_file() and not copied.is_symlink()
|
||||
assert copied.read_text() == "toolkit skill v1"
|
||||
assert ".agents/skills/mytoolkit/SKILL.md" in report["added"]
|
||||
# Second run: content identical -> no spurious updates.
|
||||
assert sync_mod.sync_workspace(workspace) == {
|
||||
"added": [], "updated": [], "skipped": [], "removed": []
|
||||
}
|
||||
|
||||
def test_existing_real_managed_symlink_target_is_kept(
|
||||
self, runtime: Path, workspace: Path
|
||||
) -> None:
|
||||
# A pre-existing real CLAUDE.md must not be replaced by a symlink.
|
||||
(workspace / "CLAUDE.md").write_text("my own claude rules")
|
||||
sync_mod.sync_workspace(workspace)
|
||||
claude_md = workspace / "CLAUDE.md"
|
||||
assert not claude_md.is_symlink()
|
||||
assert claude_md.read_text() == "my own claude rules"
|
||||
assert (workspace / ".claude").is_symlink() # other links still made
|
||||
|
||||
|
||||
class TestCreateLauncher:
|
||||
@pytest.fixture()
|
||||
@@ -133,3 +168,43 @@ class TestCreateLauncher:
|
||||
text = entry.read_text()
|
||||
assert "Name=Xiaohe Agent" in text
|
||||
assert "Icon=xiaohe-agent" in text
|
||||
|
||||
|
||||
class TestInitCommand:
|
||||
@pytest.fixture()
|
||||
def fake_home(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
monkeypatch.setattr(Path, "home", staticmethod(lambda: home))
|
||||
monkeypatch.setattr(sync_mod, "get_setting", lambda *a, **kw: "")
|
||||
monkeypatch.setattr(sync_mod, "set_setting", lambda *a, **kw: None)
|
||||
return home
|
||||
|
||||
def test_agent_tasks_created_from_template(
|
||||
self, runtime: Path, workspace: Path, fake_home: Path
|
||||
) -> None:
|
||||
from click.testing import CliRunner
|
||||
|
||||
template = runtime / "assistant" / "agent-tasks.template.md"
|
||||
template.parent.mkdir(parents=True)
|
||||
template.write_text("# task board template")
|
||||
result = CliRunner().invoke(sync_mod.init_cmd, [str(workspace)])
|
||||
assert result.exit_code == 0, result.output
|
||||
live = workspace / "assistant" / "agent-tasks.md"
|
||||
assert live.read_text() == "# task board template"
|
||||
assert (workspace / "tmp").is_dir() # skeleton dirs created
|
||||
|
||||
def test_existing_agent_tasks_untouched(
|
||||
self, runtime: Path, workspace: Path, fake_home: Path
|
||||
) -> None:
|
||||
from click.testing import CliRunner
|
||||
|
||||
template = runtime / "assistant" / "agent-tasks.template.md"
|
||||
template.parent.mkdir(parents=True)
|
||||
template.write_text("# task board template")
|
||||
live = workspace / "assistant" / "agent-tasks.md"
|
||||
live.parent.mkdir(parents=True)
|
||||
live.write_text("# my live board")
|
||||
result = CliRunner().invoke(sync_mod.init_cmd, [str(workspace)])
|
||||
assert result.exit_code == 0, result.output
|
||||
assert live.read_text() == "# my live board"
|
||||
|
||||
Reference in New Issue
Block a user