Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (1078) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (1079) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (259: 45 daemon_client + 20 conn-manager + 22 app-commands + 129 renderer smoke + 15 i18n + 8 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — 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 路径不受影响)
Expand Down
7 changes: 6 additions & 1 deletion emrg/server/scheduler.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -351,7 +351,12 @@ def _is_dirty_tree_sync(source_dir: str) -> bool:
"""
import subprocess as _sp # noqa: PLC0415 — local import keeps the module invariant

if not os.path.isdir(os.path.join(source_dir, ".git")):
# .git may be a directory (regular repo) or a file (linked worktree —
# "gitdir: ..." pointer). Treat both as git repos; only a bare/non-git
# dir fails open (cycle 20260825-194513: isdir-only check silently
# bypassed the guard in linked-worktree setups).
git_marker = os.path.join(source_dir, ".git")
if not os.path.isdir(git_marker) and not os.path.isfile(git_marker):
return False
try:
out = _sp.run(
Expand Down
24 changes: 24 additions & 0 deletions tests/test_scheduler.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2496,6 +2496,30 @@ def test_is_dirty_tree_detects_uncommitted_changes():
assert TaskHandler._is_dirty_tree_sync(d) is False


def test_is_dirty_tree_linked_worktree():
"""Cycle 20260825-194513: a linked git worktree has `.git` as a FILE, not
a dir — the probe must detect dirt there too (isdir-only check silently
failed open, bypassing the read-only guard in worktree setups)."""
with tempfile.TemporaryDirectory() as d:
subprocess.run(["git", "init", d], capture_output=True, timeout=10)
subprocess.run(["git", "-C", d, "config", "user.email", "t@t.t"],
capture_output=True, timeout=10)
subprocess.run(["git", "-C", d, "config", "user.name", "t"],
capture_output=True, timeout=10)
(Path(d) / "base.txt").write_text("base", encoding="utf-8")
subprocess.run(["git", "-C", d, "add", "base.txt"], capture_output=True, timeout=10)
subprocess.run(["git", "-C", d, "commit", "-m", "base"],
capture_output=True, timeout=10)
wt = Path(d) / "wt"
subprocess.run(["git", "-C", d, "worktree", "add", str(wt), "HEAD"],
capture_output=True, timeout=10)
# .git is a file in the linked worktree
assert (wt / ".git").is_file(), "linked worktree .git should be a file"
assert TaskHandler._is_dirty_tree_sync(str(wt)) is False # clean worktree
(wt / "dirty.txt").write_text("dirty", encoding="utf-8")
assert TaskHandler._is_dirty_tree_sync(str(wt)) is True # untracked → dirty


def test_dirty_tree_forces_read_only_structural_guard():
"""Community issue #979: a dirty source tree forces the cycle's effective
sandbox to read-only regardless of configuration — topology over rules."""
Expand Down
Loading