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 number Diff line number Diff line change
Expand Up @@ -112,7 +112,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` (694) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (695) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (212: 43 daemon_client + 19 conn-manager + 22 app-commands + 91 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` + 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
9 changes: 7 additions & 2 deletions README.cn.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# EMRG

<p align="center">
<img src="packaging/assets/icon.svg" alt="EMRG" width="96" height="96">
</p>

<p align="center">
<strong>一个会写代码的 AI 编程助手——而且它还会<em>重写自己</em>。</strong>
</p>
Expand Down Expand Up @@ -35,9 +39,10 @@ EMRG 是一个关于*自主进化*的实验:一个能帮你写代码的 AI 智

**一句话定位**:EMRG 是唯一一个**会因你的反馈而自我改进**的编程智能体——吐槽哪里不好,下一个演化周期它就自己写代码修好并上线。

> **唯一核心卖点:自我改进。** 每一条 `/rant` 都会变成真实的 PR——分析、编码、测试、合并,全程无人值守。没有其他编程智能体做得到。

| 特性 | 差异点 |
|---|---|
| **越用越好(核心)** | 你的 `/rant`、GitHub Issues、竞品动态都会变成真实改进——分析、编码、测试、提 PR、合并,全自动无人值守 |
| **Electron GUI(主入口)** | 安装即用:首次启动引导配置 API Key;全部 `/` 指令可用;结果面板、Ask/Auto 模式、自进化可见化。GUI 配好即 TUI 可用 |
| **全功能 TUI + 守护进程** | 流式 Markdown、`/` 自动补全、会话管理、快捷键——跑在常驻的 `emrgd` 守护进程上,随时重连 |
| **100% 开源** | MIT 协议——没有围墙,没有厂商锁定。默认英文,提供中文版 |
Expand Down Expand Up @@ -161,7 +166,7 @@ EMRG 不只是追赶——它自己追上来。

贡献指南、源码安装、架构、详细 FAQ → [DEVELOPMENT.md](DEVELOPMENT.md)。

快速检查:`uv run pytest tests/ -v`(当前 694 项)· `cd emrg/gui && npm test`(212 项:43 daemon_client + 19 conn-manager + 22 app-commands + 91 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state
快速检查:`uv run pytest tests/ -v` · `cd emrg/gui && npm test`(测试:见上方徽章

---

Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# EMRG

<p align="center">
<img src="packaging/assets/icon.svg" alt="EMRG" width="96" height="96">
</p>

<p align="center">
<strong>The AI coding agent that writes code — and rewrites <em>itself</em>.</strong>
</p>
Expand Down Expand Up @@ -35,9 +39,10 @@ EMRG is an experiment in *autonomous self-improvement*: an AI agent that helps y

**The one-line pitch**: EMRG is the only coding agent that **improves itself from your feedback** — rant about what bothers you, and the next evolution cycle writes the fix and ships it.

> **The one feature: self-improvement.** Every `/rant` becomes a real PR — coded, tested, merged, unattended. No other coding agent does this.

| What | Why it's different |
|---|---|
| **Gets better on its own** | **The core feature.** Your `/rant`s, GitHub issues, and competitor updates become real improvements — analyzed, coded, tested, PR'd, merged, unattended |
| **Electron GUI (main entry)** | Install & go: first-run wizard, all slash commands, results panel, Ask/Auto modes, visible self-evolution. GUI configured = TUI ready |
| **Full-featured TUI + daemon** | Streaming markdown, `/` autocomplete, sessions, shortcuts — on a persistent `emrgd` daemon you can reconnect to anytime |
| **100% open source** | MIT — no walled garden, no vendor lock-in. English default, Chinese version available |
Expand Down Expand Up @@ -161,7 +166,7 @@ They're products. EMRG is an experiment in *closing the loop* — the AI improve

Contributing, source installs, architecture, and the full FAQ → [DEVELOPMENT.md](DEVELOPMENT.md).

Quick checks: `uv run pytest tests/ -v` (currently 694 items) · `cd emrg/gui && npm test` (212: 43 daemon_client + 19 conn-manager + 22 app-commands + 91 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state)
Quick checks: `uv run pytest tests/ -v` · `cd emrg/gui && npm test` (tests: see badge above)

---

Expand Down
64 changes: 26 additions & 38 deletions bin/stop-emrg.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,21 @@ 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: 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 4. bundled git: GUILT-BY-ASSOCIATION force-kill via stop-git.ps1
REM (host 2026-08-11T19:47:44 FINAL decision OVERRIDES #689: install success
REM has priority -- if sh/vim hold msys-2.0.dll and block the git-tree kill,
REM they are killed too; only the install\git\ prefix is touched, system Git
REM in Program Files is never affected). stop-git.ps1 is a standalone script
REM (no cmd inline-PowerShell quoting escapes). The installer passes the
REM {tmp}-extracted copy as %~1 when the OLD install lacks it.
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).
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
set "EXIT_CODE=0"

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
Expand Down Expand Up @@ -80,20 +69,21 @@ 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: 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
REM --- 4. bundled git: guilt-by-association force-kill via stop-git.ps1 ---
REM (host 2026-08-11T19:47:44 FINAL decision OVERRIDES #689's snapshot-only
REM approach -- "如果杀死git时,发现sh/vim导致杀不掉,则一起杀": install success
REM has priority; sh/vim holding msys-2.0.dll under install\git\ are killed too.
REM Only the install\git\ prefix is touched; system Git (Program Files) is never
REM matched. 0.2.26's cmd inline-PowerShell quoting escape is avoided by calling
REM the standalone stop-git.ps1 via -File.)
set "GITSTOP=%INSTALL%\bin\stop-git.ps1"
if not exist "%GITSTOP%" set "GITSTOP=%~1"
if exist "%GITSTOP%" (
powershell -NoProfile -ExecutionPolicy Bypass -File "%GITSTOP%" >nul 2>&1
if errorlevel 1 set "EXIT_CODE=1"
)

:verify
set "EXIT_CODE=0"
tasklist /FI "IMAGENAME eq EMRG.exe" 2>nul | findstr /i "EMRG.exe" >nul && set "EXIT_CODE=1"
REM %-variables inside a parenthesized block expand at parse time (after set "DPID=" they hold the old/empty value) -> must use
REM enabledelayedexpansion !DPID! (runtime expansion); if defined itself is a runtime check.
Expand All @@ -104,12 +94,10 @@ if exist "%EMRG_DIR%\emrgd.pid" (
tasklist /FI "PID eq !DPID!" 2>nul | findstr /i "!DPID!" >nul && 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
REM Bundled-git survival check (plain prefix; guilt-by-association semantics):
REM any process still under install\git\ -> exit 1 (installer aborts). This is the
REM same condition stop-git.ps1 reports; kept as belt-and-braces for the case the
REM script file could not be located (no old install, no {tmp} arg).
powershell -NoProfile -Command "$p = Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" }; if ($p) { exit 1 }" >nul 2>&1
if errorlevel 1 set "EXIT_CODE=1"
endlocal & exit /b %EXIT_CODE%
35 changes: 35 additions & 0 deletions bin/stop-git.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# stop-git.ps1 -- force-kill every process under the bundled git install prefix
# (host 2026-08-11T19:47:44 FINAL decision, OVERRIDES #689: install success has
# priority -- "要杀死相关进程,如果杀死git时,发现sh/vim导致杀不掉,则一起杀。"
# If sh/vim hold msys-2.0.dll and block the git-tree kill, they are killed too.)
#
# 1. collect all processes whose ExecutablePath is under
# %USERPROFILE%\.emrg\install\git\ (the portable Git the daemon spawns for
# evolution-cycle ops; system Git under Program Files is NEVER matched)
# 2. pass 1: force-kill the git tree (git.exe/ssh.exe/plink.exe/bash.exe)
# 3. pass 2: guilt-by-association -- force-kill EVERY survivor under the prefix
# (sh/vim included, per host decision; 0.2.25 msys-2.0.dll popup must not regress)
# 4. exit 0 when nothing survives (or nothing was running -- clean install safe);
# exit 1 when a process still holds the prefix (installer aborts with a hint)
#
# Run via `powershell -NoProfile -ExecutionPolicy Bypass -File stop-git.ps1`
# (a standalone script -- no cmd inline-PowerShell quoting escapes, the root
# cause of the 0.2.26 step-4 escape bug).

$ErrorActionPreference = 'SilentlyContinue'
$prefix = "$env:USERPROFILE\.emrg\install\git\*"

# pass 1: git tree (portable Git executables under the prefix)
Get-CimInstance Win32_Process |
Where-Object { $_.ExecutablePath -like $prefix -and $_.Name -in @('git.exe', 'ssh.exe', 'plink.exe', 'bash.exe') } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
Start-Sleep -Milliseconds 300

# pass 2: guilt-by-association -- anything still under the prefix (sh/vim/...)
Get-CimInstance Win32_Process |
Where-Object { $_.ExecutablePath -like $prefix } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
Start-Sleep -Milliseconds 300

$left = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like $prefix })
if ($left.Count -gt 0) { exit 1 } else { exit 0 }
1 change: 1 addition & 0 deletions packaging/build-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ cp -R "$PY_ROOT/." "$DIST/bin/python-dist/"
cp "$ROOT/bin/emrg.cmd" emrg.cmd 2>/dev/null || true
cp "$ROOT/bin/emrgd.cmd" emrgd.cmd 2>/dev/null || true
cp "$ROOT/bin/stop-emrg.cmd" stop-emrg.cmd 2>/dev/null || true
cp "$ROOT/bin/stop-git.ps1" stop-git.ps1 2>/dev/null || true
cp "$ROOT/bin/emrg-uninstall" emrg-uninstall
chmod +x emrg emrgd emrg-uninstall
)
Expand Down
20 changes: 15 additions & 5 deletions packaging/make-installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ Source: "$STAGE_WIN/payload\\*"; DestDir: "{app}"; Flags: recursesubdirs createa
; pythonw daemon 锁文件导致卡在"停止已有进程")。正常安装时该文件仍由上方通配符
; 装入 {app}\bin\stop-emrg.cmd。
Source: "$STAGE_WIN/payload\\bin\\stop-emrg.cmd"; DestDir: "{tmp}"; Flags: dontcopy
; R128: dontcopy — 供 [Code] PrepareToInstall 在覆盖文件前 ExtractTemporaryFile 取出并
; 作为 %~1 传给 stop-emrg.cmd step 4(连坐强杀 bundled git,宿主 2026-08-11T19:47:44
; 拍板覆盖 #689:旧安装 %INSTALL%\bin\stop-git.ps1 不存在时用 {tmp} 提取版)。
Source: "$STAGE_WIN/payload\\bin\\stop-git.ps1"; DestDir: "{tmp}"; Flags: dontcopy
[Icons]
Name: "{userprograms}\\EMRG"; Filename: "{app}\\emrg-gui\\EMRG\\EMRG.exe"; IconFilename: "{app}\\emrg-gui\\EMRG\\EMRG.exe"
[UninstallRun]
Expand Down Expand Up @@ -372,20 +376,26 @@ end;
// -m emrg.server 常驻锁文件),覆盖 ~/.emrg\install 时卡在"停止已有进程"。
// PrepareToInstall 在安装开始前运行 bin\stop-emrg.cmd:taskkill EMRG.exe
// 优雅→/F 兜底、wmic/PowerShell 命令行过滤 TUI、emrg server stop 协议关闭
// daemon + emrgd.pid 轮询兜底、按可执行文件路径杀 bundled git 孤儿
// (rant 2026-08-11T17:56:25: daemon 演化周期 git 操作中途被杀 → git/ssh/bash
// 孤儿锁 install\git\usr\bin\msys-2.0.dll → Inno DeleteFile code 5)。干净安装
// (无旧 install)脚本自行跳过。返回非空字符串 = 中止安装并显示该消息(宁可中止也不卡死)。
// daemon + emrgd.pid 轮询兜底、step 4 调 stop-git.ps1 连坐强杀 bundled git
// (宿主 2026-08-11T19:47:44 拍板覆盖 #689:sh/vim 锁 install\git\usr\bin\
// msys-2.0.dll 导致 Inno DeleteFile code 5 时一并强杀,安装成功优先;只碰
// install\git\ 前缀,系统 Git 不受影响)。旧安装可能没有 stop-git.ps1 →
// {tmp} dontcopy 提取版作为 %~1 传入(cmd 内先查 %INSTALL%\bin,缺失才用
// 参数)。干净安装(无旧 install)脚本自行跳过。返回非空字符串 = 中止安装
// 并显示该消息(宁可中止也不卡死)。
// {cmd} = cmd.exe(Inno 预定义常量,批处理文件须经 cmd 启动)。
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ResultCode: Integer;
StopScript: string;
GitStopScript: string;
begin
Result := '';
ExtractTemporaryFile('stop-emrg.cmd');
ExtractTemporaryFile('stop-git.ps1');
StopScript := ExpandConstant('{tmp}\\stop-emrg.cmd');
if Exec(ExpandConstant('{cmd}'), '/c "' + StopScript + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
GitStopScript := ExpandConstant('{tmp}\\stop-git.ps1');
if Exec(ExpandConstant('{cmd}'), '/c "' + StopScript + '" "' + GitStopScript + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
begin
if ResultCode <> 0 then
Result := 'EMRG could not stop all running processes (stop-emrg.cmd exit code ' + IntToStr(ResultCode) + '). Please close EMRG (GUI/TUI) and retry the install, or restart the computer and retry (a helper process such as the bundled Git may still hold a file lock).';
Expand Down
32 changes: 17 additions & 15 deletions tests/test_doc_counts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
after #580 added 3 GUI tests. Both checks now cover all three docs
(README.md, README.cn.md, Agent.md); CJK full-width parens and the
"项:" separator are normalized before matching.
#692: rant 2026-08-11T19:50:37 — README.md/README.cn.md switched to the
Tests badge (no hardcoded counts); the python-count check now guards
Agent.md only, while the GUI-breakdown check still picks up any
"(N: ...)" line it finds in any doc (Agent.md keeps the breakdown).
"""

import re
Expand Down Expand Up @@ -60,21 +64,19 @@ def _gui_breakdowns() -> list[tuple[str, int, list[int]]]:

def test_python_count_matches_docs() -> None:
collected = _collected_pytest_count()
for doc in ("README.md", "README.cn.md", "Agent.md"):
text = (REPO_ROOT / doc).read_text(encoding="utf-8")
# README: "run tests (currently N items)" | README.cn: "(当前 N 项)"
# Agent.md: "pytest tests/ -v` (N)"
m = re.search(r"currently (\d+) items", text) or re.search(
r"当前 (\d+) 项", text
) or re.search(
r"uv run pytest tests/ -v` \((\d+)\)", text
)
assert m, f"no documented Python count found in {doc}"
documented = int(m.group(1))
assert documented == collected, (
f"{doc} documents {documented} Python tests but {collected} are collected "
f"(--collect-only). Sync the doc (and this guard) when adding/removing tests."
)
# rant 2026-08-11T19:50:37: README.md/README.cn.md dropped hardcoded counts in
# favor of the Tests badge (dynamic — no more doc drift). Agent.md keeps the
# number (project-context file, checked by the same guard).
doc = "Agent.md"
text = (REPO_ROOT / doc).read_text(encoding="utf-8")
# Agent.md: "pytest tests/ -v` (N)"
m = re.search(r"uv run pytest tests/ -v` \((\d+)\)", text)
assert m, f"no documented Python count found in {doc}"
documented = int(m.group(1))
assert documented == collected, (
f"{doc} documents {documented} Python tests but {collected} are collected "
f"(--collect-only). Sync the doc (and this guard) when adding/removing tests."
)


def test_gui_breakdown_sums_to_headline() -> None:
Expand Down
Loading
Loading