diff --git a/Agent.md b/Agent.md index 9523fab..804f010 100644 --- a/Agent.md +++ b/Agent.md @@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design: pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg ``` -Python: `uv run pytest tests/ -v` (761) — import check: `uv run python -c "from emrg.client.app import run_client"` +Python: `uv run pytest tests/ -v` (762) — import check: `uv run python -c "from emrg.client.app import run_client"` GUI: `cd emrg/gui && npm test` (231: 44 daemon_client + 19 conn-manager + 22 app-commands + 109 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响) diff --git a/emrg/server/scheduler.py b/emrg/server/scheduler.py index 3ea2f22..1b09061 100644 --- a/emrg/server/scheduler.py +++ b/emrg/server/scheduler.py @@ -474,6 +474,14 @@ def _ensure_evolution_workspace(self) -> bool: if self._is_usable_git_repo(str(evolve_dir)): self._source_dir = str(evolve_dir) self.project_path = str(evolve_dir) + # Persist the corrected path (idempotent — writes only when the + # emrg entry differs). #716 repairs a stale emrg entry (deleted + # pytest-temp dir leaked into projects.yml) at scheduler startup + # only; this re-persists every cycle so a mid-run pollution on a + # long-running daemon self-heals within one cycle without a + # restart (list_projects/GUI pickers stay correct). + if self._project_name == "emrg": + self._ensure_project_entry() self._ensure_origin_reachable() return True logger.warning( diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index c8d93f8..41659d7 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -860,6 +860,57 @@ def test_ensure_self_evolution_task_other_entries_preserved(tmp_path): assert len(names) == 2 +def test_ensure_evolution_workspace_persists_repaired_emrg_path(tmp_path): + """Long-running daemon: a stale emrg path (deleted pytest-temp dir) is + healed in-memory to the canonical workspace AND persisted back to + projects.yml on the next cycle — not only at scheduler startup (#716 + follow-up: startup-only repair leaves a dangling entry forever when the + daemon never restarts; list_projects/GUI pickers keep showing a dead path).""" + from emrg.server import scheduler as mod + + evolve_dir = tmp_path / "evolution" / "emrg" + evolve_dir.mkdir(parents=True) + + # Stale emrg entry pointing at a path that no longer exists on disk + # (exactly the 2026-08-12 pytest-temp-leak shape). + stale = tmp_path / "gone" / "emrg" + projects_yml = tmp_path / "projects.yml" + projects_yml.write_text(yaml.safe_dump([ + {"name": "emrg", "path": str(stale), "last_active": "2026-08-12T18:44:50"}, + {"name": "other", "path": str(tmp_path / "other")}, + ])) + + fake = FakeGitRun(git_repo=True) + orig_run = mod.subprocess.run + orig_evolve = mod.EVOLUTION_CWD + orig_config = mod.config_dir + mod.subprocess.run = fake + mod.EVOLUTION_CWD = tmp_path / "evolution" + mod.config_dir = lambda: tmp_path + try: + handler = TaskHandler( + name="emrg-task", + config={"project": "emrg"}, + interval=60, + identity=InstanceIdentity(), + ) + handler._source_dir = str(stale) # stale as resolved at handler start + handler.project_path = str(stale) + ok = handler._ensure_evolution_workspace() + finally: + mod.subprocess.run = orig_run + mod.EVOLUTION_CWD = orig_evolve + mod.config_dir = orig_config + + assert ok is True + assert handler._source_dir == str(evolve_dir) # in-memory heal (pre-existing) + data = yaml.safe_load(projects_yml.read_text(encoding="utf-8")) + by_name = {e["name"]: e for e in data} + assert by_name["emrg"]["path"] == str(evolve_dir) # NEW: persisted this cycle + assert by_name["other"]["path"] == str(tmp_path / "other") # untouched + assert len(data) == 2 + + def test_ensure_evolution_workspace_dev_repo_untouched(tmp_path): """A real writable git repo (dev machine) is used as-is — no clone.""" import subprocess as real_subprocess