diff --git a/bin/stop-emrg.cmd b/bin/stop-emrg.cmd index 69c2107..bf21614 100644 --- a/bin/stop-emrg.cmd +++ b/bin/stop-emrg.cmd @@ -13,12 +13,12 @@ REM fallback), excludes the daemon (pythonw.exe -m emrg.server) REM 3. daemon: `emrg server stop` protocol shutdown via the OLD install's CLI REM (present since #364 -- version-safe), emrgd.pid poll (<=10s), then REM taskkill /F /PID fallback -REM 4. bundled git (rant 2026-08-11T17:56:25): kill orphaned git/ssh/bash -REM processes whose executable lives under %INSTALL%\git\ (the portable Git -REM the daemon spawns for evolution-cycle git ops). When the daemon is killed -REM mid-operation these become orphans holding install\git\usr\bin\msys-2.0.dll -REM -> Inno "DeleteFile failed; code 5" on upgrade. Filter is by executable -REM PATH so system Git (C:\Program Files\Git) is never touched. +REM 4. bundled git: kill ONLY EMRG-owned git/ssh/bash subprocess trees under +REM %INSTALL%\git\ (portable Git the daemon spawns for evolution-cycle ops). +REM Rant 2026-08-11T17:56:25: orphans hold msys-2.0.dll -> Inno code 5. +REM Rant 2026-08-11T18:56:58: blanket path kill also hit the HOST's Git Bash +REM sh/vim -> only EMRG trees (ancestor = daemon/TUI) are killed; host tools +REM are never touched (same class as R125). REM REM Returns 0 when nothing EMRG-related survives; 1 if a process could not be REM stopped (installer aborts with a clear message instead of hanging). @@ -26,6 +26,19 @@ REM Safe on clean install: no old install dir -> everything is skipped -> 0. setlocal enabledelayedexpansion set "EMRG_DIR=%USERPROFILE%\.emrg" set "INSTALL=%EMRG_DIR%\install" +REM PID snapshot file: EMRG-owned process tree captured BEFORE any kill, so dead +REM parents are irrelevant (review 2026-08-11T19:03: ancestor walk cannot resolve +REM dead daemon parents -- the #683 primary orphan case regresses). +set "PIDFILE=%TEMP%\emrg-stop-pids.txt" +del /q "%PIDFILE%" >nul 2>&1 + +REM --- 0. snapshot EMRG-owned tree (daemon + TUI + GUI + descendants) --- +REM Walk DOWN from the roots (emrgd.pid, EMRG.exe, python.exe -m emrg) while they +REM are still alive, collecting the full descendant PID set. Step 4 / :verify use +REM ONLY this recorded set -> host Git Bash sh/vim (not descendants of EMRG roots) +REM are never touched (rant 2026-08-11T18:56:58), and orphans of an already-dead +REM daemon are still captured (their PID was recorded while the daemon lived). +powershell -NoProfile -Command "$f=Get-CimInstance Win32_Process; $roots=New-Object System.Collections.Generic.List[int]; if(Test-Path \"%EMRG_DIR%\emrgd.pid\"){ $d=Get-Content \"%EMRG_DIR%\emrgd.pid\" -ErrorAction SilentlyContinue; if($d -match '^\d+$'){ $roots.Add([int]$d) } }; $f | Where-Object { $_.Name -eq 'EMRG.exe' -or ($_.Name -eq 'python.exe' -and $_.CommandLine -match '-m emrg' -and $_.CommandLine -notmatch 'emrg\.server') } | ForEach-Object { $roots.Add([int]$_.ProcessId) }; $set=New-Object 'System.Collections.Generic.HashSet[int]'; foreach($r in $roots){ [void]$set.Add($r) }; $changed=$true; while($changed){ $changed=$false; foreach($p in $f){ if($set.Contains([int]$p.ParentProcessId) -and -not $set.Contains([int]$p.ProcessId)){ [void]$set.Add([int]$p.ProcessId); $changed=$true } } }; $set | ForEach-Object { $_ } | Set-Content -Path \"%PIDFILE%\" -Encoding ascii" >nul 2>&1 REM --- 1. GUI: graceful WM_CLOSE, then unconditional /F after the grace window --- REM (host report 2026-08-10T01:27:07Z: a long-lived ~15h GUI session deferred @@ -67,12 +80,17 @@ set "DPID=" for /f "usebackq delims=" %%p in ("%EMRG_DIR%\emrgd.pid") do set "DPID=%%p" if defined DPID taskkill /F /PID %DPID% >nul 2>&1 -REM --- 4. bundled git: orphaned git/ssh/bash under %INSTALL%\git (msys-2.0.dll lock) --- -REM (host 2026-08-11T17:56:25: daemon killed mid-git-op leaves orphans holding -REM install\git\usr\bin\msys-2.0.dll -> Inno DeleteFile code 5. Filter by -REM ExecutablePath prefix so system Git is never touched. `\"` = PowerShell -REM quote escape precedent: TUI branch line 44.) -powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }" >nul 2>&1 +REM --- 4. bundled git: kill only RECORDED EMRG-owned git/ssh/bash (snapshot step 0) --- +REM (rant 2026-08-11T17:56:25: daemon killed mid-git-op leaves orphans holding +REM install\git\usr\bin\msys-2.0.dll -> Inno DeleteFile code 5.) +REM (host 2026-08-11T18:56:58: blanket path kill also caught the HOST's Git Bash +REM sh/vim -- vim edits lost + installer aborted. Same class as R125.) Only PIDs +REM recorded in step 0 (EMRG tree captured while daemon/TUI were alive) are killed +REM when their executable is under install\git\. Host Git Bash sh/vim are not in +REM the recorded set -> never touched. +if exist "%PIDFILE%" ( + powershell -NoProfile -Command "$f=Get-CimInstance Win32_Process; $ids=@(Get-Content \"%PIDFILE%\" | ForEach-Object { [int]$_ }); $f | Where-Object { $ids -contains [int]$_.ProcessId -and $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }" >nul 2>&1 +) :verify set "EXIT_CODE=0" @@ -86,8 +104,12 @@ if exist "%EMRG_DIR%\emrgd.pid" ( tasklist /FI "PID eq !DPID!" 2>nul | findstr /i "!DPID!" >nul && set "EXIT_CODE=1" ) ) -REM bundled-git survival check: any process still under install\git\ -> exit 1 -REM (installer aborts with a clear message instead of a repeat "Try again" loop) -powershell -NoProfile -Command "if (Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" }) { exit 1 }" >nul 2>&1 -if errorlevel 1 set "EXIT_CODE=1" +REM EMRG-owned bundled-git survival check: only RECORDED EMRG git PIDs still alive +REM -> exit 1 (installer aborts with a restart hint). Host Git Bash sh/vim alive is +REM NOT a failure (rant 2026-08-11T18:56:58). +if exist "%PIDFILE%" ( + powershell -NoProfile -Command "$f=Get-CimInstance Win32_Process; $ids=@(Get-Content \"%PIDFILE%\" | ForEach-Object { [int]$_ }); $surv = $f | Where-Object { $ids -contains [int]$_.ProcessId -and $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" }; if ($surv) { exit 1 }" >nul 2>&1 + if errorlevel 1 set "EXIT_CODE=1" +) +del /q "%PIDFILE%" >nul 2>&1 endlocal & exit /b %EXIT_CODE% diff --git a/emrg/server/evolution_prompt.md b/emrg/server/evolution_prompt.md index 74309a6..e14cec3 100644 --- a/emrg/server/evolution_prompt.md +++ b/emrg/server/evolution_prompt.md @@ -418,7 +418,7 @@ When reading rants, follow these rules: > - README fact fixes + trust copy (#677 宿主 rant 2026-08-11T17:28:02:竞品表 Codex 开源列 ❌→✅ *Apache-2.0*(此前误标 ❌——Codex 是开源但无自进化,差异化注:开源本身不是差异点,闭环进化才是);FAQ 新增安全边界条目 EN/CN(自进化只改 `~/.emrg/evolution/emrg`,绝不碰宿主项目文件;宿主项目上的工具调用仅宿主指令触发;每处改动都过 pytest + PR 评审);Quick Start 新增 BYOK 句 EN/CN(自带 API Key、额度/账单归宿主、软件 MIT 免费);README.md/README.cn.md 同步,MANIFESTO.md 零改动;合并 a499732) ✅ > - README contribution philosophy (#678 宿主 rant 2026-08-11T17:30:31:Rant-Driven Evolution 段(Real example 之后)EN/CN 对称新增"如何贡献?使用它。"——连接 GitHub + 吐槽 → 吐槽变成真实 PR(演化周期负责编码/测试/上线),无需 fork/clone/写代码,"使用 EMRG 就是在为 EMRG 做贡献";完成三段式叙事(产品经理 / 硅基生命宿主 / 使用即贡献);合并 35ca1c3) ✅ > - GUI packaged icon fix (#680 宿主 rant 2026-08-11T17:37:03:打包版曾用 Electron 默认蓝色原子球图标——package.json build.mac/win/linux.icon 从目录形 `../packaging/assets/` 改显式单文件(mac .icns / win .ico / linux .png,全部存在于 packaging/assets);运行时 `BrowserWindow icon` 经 `windowIconPath()`(打包版 extraResources 落 `resources/icon.png`,源码回退 `packaging/assets/icon.png`);fs/path 已导入零新增依赖;node --check 通过;合并 b400267) ✅ -> - Windows installer kill bundled-git orphans (#683 宿主 rant 2026-08-11T17:56:25:Windows 升级安装卡 Inno "DeleteFile failed; code 5"——daemon 演化周期 git 操作中途被杀 → 孤儿 git/ssh/bash 进程锁 `install\git\usr\bin\msys-2.0.dll`,CloseApplications 看不见(非 EMRG 名);修复=stop-emrg.cmd step 4 按**可执行文件路径前缀**杀 bundled git 孤儿(`Get-CimInstance Win32_Process` + `ExecutablePath -like "$env:USERPROFILE\.emrg\install\git\*"`——只命中打包便携 Git,绝不误杀系统 Git C:\Program Files\Git),置于 daemon 停止后、:verify 前;:verify 存活判定 → exit 1 → 安装器中止给可操作消息(提示重启电脑);`\"` 引号转义沿用 TUI 分支先例;Get-CimInstance 兼容 Win11 24H2+(无 wmic 依赖);测试锚定判别信号;合并 c9131c6) ✅ +> - Windows installer kill bundled-git orphans (#683 宿主 rant 2026-08-11T17:56:25,**18:56:58 修正方向**:Windows 升级安装卡 Inno "DeleteFile failed; code 5"——daemon 演化周期 git 操作中途被杀 → 孤儿 git/ssh/bash 进程锁 `install\git\usr\bin\msys-2.0.dll`;初版修复=stop-emrg.cmd step 4 按**可执行文件路径前缀**杀 bundled git 孤儿(`ExecutablePath -like "$env:USERPROFILE\.emrg\install\git\*"`),:verify 无差别存活判定 → exit 1 中止;**宿主实测 18:56:58 发现方向错误**:路径前缀会误杀宿主自己的 Git Bash sh/vim(也从 install\git 启动),vim 未存盘内容丢失 + verify 抓存活 → 安装中止(R125 同族:Inno 不该干涉宿主工具);**修正**=step 4 只杀 **EMRG 自己的 git 子进程树**(`Get-CimInstance` + `ParentProcessId` 祖先回溯 ≤5 层,祖先含 daemon `pythonw -m emrg.server` / TUI `python -m emrg` 才杀;宿主 sh/vim 祖先链是 explorer/终端 → 不杀),:verify 存活判定同步改为"仅 EMRG 子树残留 → exit 1",宿主工具存活不再是失败;测试锚定判别信号(ParentProcessId + -m emrg + verify 无旧无差别检查);合并 c9131c6(#683)→ 修正 PR 待合并) ✅ > - README emoji restraint (#684 宿主 rant 2026-08-11T17:58:11:README 标题/特性表/流程图 emoji 泛滥——装饰性 emoji 从标题(# 🧱→#、## ✨→##、## 🔄→##、## 🚀→##、### 📦→###)与特性表行移除,EN/CN 对称;演化流程图改纯文本(Inputs / 演化循环 / 编号步骤);doc-count 新增标题无 emoji 守卫(`^#{1,3} ` 标题行禁 emoji,对比表 ✅/❌、底部 ❤️、语言切换 🇬🇧/🇨🇳 保留在正文非标题);MANIFESTO.md 零改动;合并 80a338a) ✅ > - Release v0.2.26 (#686 宿主 rant 2026-08-11T18:16:52:master 自 v0.2.25 (c8eb77a) 以来 11 commits / 11 PRs(#676-#686),宿主确认发布下一版本;版本号 0.2.25→0.2.26 同步 7 files / 8 refs(emrg/__init__.py、pyproject.toml、uv.lock、gui/package.json、gui/package-lock.json、build-runtime.sh、make-installer.sh),grep 0.2.25 仅历史;pytest 694 + GUI 212 绿;Build Release v0.2.26 成功,7 assets(含 Windows zip #649),latest=v0.2.26;合并 f2ba4be) ✅ diff --git a/tests/test_installer_stop.py b/tests/test_installer_stop.py index 726d1ec..6984778 100644 --- a/tests/test_installer_stop.py +++ b/tests/test_installer_stop.py @@ -39,18 +39,26 @@ def test_stop_emrg_cmd_covers_gui_tui_daemon_in_order(): # 顺序:GUI 在 daemon 之前(GUI 不能复活 daemon) daemon_line = content.index('call "%INSTALL%\\bin\\emrg.cmd" server stop') assert content.index("taskkill /IM EMRG.exe") < daemon_line - # step 4(rant 2026-08-11T17:56:25):按可执行文件路径杀 bundled git 孤儿 - # (daemon 演化周期 git 操作中途被杀 → git/ssh/bash 孤儿锁 msys-2.0.dll → Inno - # DeleteFile code 5)。判别:ExecutablePath 前缀过滤只命中 %INSTALL%\git\, - # 不误杀系统 Git;daemon 停止之后、:verify 之前。 + # step 4(rant 2026-08-11T17:56:25 → 18:56:58 修正):只杀 EMRG 自己 git 子进程树, + # 不碰宿主 Git Bash sh/vim(R125 同族——宿主工具不干涉)。 + # 判别:ExecutablePath 前缀过滤只命中 %INSTALL%\git\(不误杀系统 Git)。 + # ⚡ review 2026-08-11T19:03:祖先回溯(ancestor walk)无法解析"已死父进程"—— + # #683 主场景正是 daemon 已死后的孤儿 → 改为 step 0 **向下**快照 + # (daemon/TUI/GUI 根 + BFS 子孙集,写入 %TEMP%\emrg-stop-pids.txt), + # step 4 / :verify 只作用于快照记录集。 assert 'Get-CimInstance Win32_Process' in content assert r'$env:USERPROFILE\.emrg\install\git\*' in content - # 锚定实际 kill 命令($_.ExecutablePath 只在 step 4 出现——TUI 分支是 - # -Filter Name='python.exe',不共享此模式),勿用 index() 撞到 TUI/REM 注释 - git_kill = content.index("$_.ExecutablePath -like") + # step 0 快照:向下 BFS(ParentProcessId → 子进程加入集合)在杀任何进程之前 + assert "emrg-stop-pids.txt" in content + assert "ParentProcessId" in content + assert "-match '-m emrg'" in content + assert "Set-Content" in content # 快照写盘 + # step 4 的 kill 必须基于快照记录集($ids -contains)+ ExecutablePath 过滤 + step4_idx = content.index("REM --- 4. bundled git: kill only RECORDED") daemon_line2 = content.index('call "%INSTALL%\\bin\\emrg.cmd" server stop') verify_idx = content.index(':verify\nset "EXIT_CODE=0"') # 标签定义处(goto :verify 在前,勿用裸 index(":verify")) - assert daemon_line2 < git_kill < verify_idx + assert daemon_line2 < step4_idx < verify_idx + assert "$ids -contains [int]$_.ProcessId" in content # 干净安装安全:无旧 install 目录时跳过 assert 'set "INSTALL=%EMRG_DIR%\\install"' in content # 括号块内 pid 判定必须用延迟展开(!DPID!)——%DPID% 在块解析时展开, @@ -62,10 +70,19 @@ def test_stop_emrg_cmd_covers_gui_tui_daemon_in_order(): assert "PID eq !DPID!" in verify_block # 非延迟展开 %DPID% 不得出现在括号块内(块解析时展开=恒旧值) assert "%DPID%" not in verify_block - # :verify 的 bundled-git 存活判定:仍有进程在 install\git\ 下 → exit 1 + # :verify 的 EMRG-owned bundled-git 存活判定:只有快照记录集内的 EMRG git PID 残留 + # → exit 1;宿主 sh/vim 存活不是失败(rant 2026-08-11T18:56:58)——判别:verify 块 + # 含 $ids -contains(快照集过滤),但不再有无差别 ExecutablePath 存活检查 assert "exit 1" in verify_block - assert r'ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\"' in verify_block assert "if errorlevel 1 set \"EXIT_CODE=1\"" in verify_block + assert "$ids -contains" in verify_block + # 无差别 bundled-git 存活检查必须已删除:verify 块里不得再有"任何 install\git\ 进程 + # 即 exit 1"的旧逻辑(旧式:if (Get-CimInstance ...) { exit 1 }) + assert "}) { exit 1 }" not in verify_block + # 而 step 4 的 kill 命令必须保留(快照集过滤 + ExecutablePath) + assert "Stop-Process" in content + # 快照文件清理 + assert 'del /q "%PIDFILE%"' in content def test_emrgd_cmd_has_stop_branch():