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
3 changes: 1 addition & 2 deletions Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -120,9 +120,8 @@ pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg
```

Python: `uv run pytest tests/ -v` (1123) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (89: 45 daemon_client + 20 conn-manager + 8 integration + 6 build-config + 7 gui-state + 3 preload-api) — syntax: `node --check main.js preload.js daemon_client.js`
GUI: `cd emrg/gui && npm test` (92: 45 daemon_client + 20 conn-manager + 8 integration + 6 build-config + 7 gui-state + 3 preload-api + 3 boot-contract) — syntax: `node --check main.js preload.js daemon_client.js`
Renderer: `cd emrg/gui/renderer && npm run typecheck && npm test` (445: 5 snapshot-store + 9 utils + 3 ErrorBoundary + 2 App smoke + 11 commands + 4 copywriting + 11 i18n + 11 markdown + 15 transcript + 7 TranscriptView + 15 history + 22 composer + 14 Composer + 12 sidebar + 17 Sidebar + 9 fileTree + 9 FileTree + 16 resultPanel + 8 ResultPanel + 29 workspaceView + 8 WorkspaceView + 10 dialog + 6 Dialog + 9 ConfirmDialog + 9 RenameDialog + 10 dialogLists + 3 HelpDialog + 9 MemoryDialog + 6 SkillsDialog + 9 openSession + 6 WelcomeDialog + 8 OpenSessionDialog + 7 NewSessionDialog + 7 rewind + 8 RewindDialog + 7 GithubDeviceDialog + 14 daemonBridge + 7 DaemonBridgeProvider + 24 Shell + 15 DialogHost + 19 SettingsPanel + 6 TaskFormDialog + 5 RantDialog + 4 vendorMarkdown) + `npm run build` → `renderer/dist/`
Renderer: `cd emrg/gui/renderer && npm run typecheck && npm test` (442: 5 snapshot-store + 9 utils + 3 ErrorBoundary + 2 App smoke + 11 commands + 4 copywriting + 11 i18n + 11 markdown + 15 transcript + 7 TranscriptView + 15 history + 22 composer + 14 Composer + 12 sidebar + 17 Sidebar + 9 fileTree + 9 FileTree + 16 resultPanel + 8 ResultPanel + 29 workspaceView + 8 WorkspaceView + 10 dialog + 6 Dialog + 9 ConfirmDialog + 9 RenameDialog + 10 dialogLists + 3 HelpDialog + 9 MemoryDialog + 6 SkillsDialog + 9 openSession + 6 WelcomeDialog + 8 OpenSessionDialog + 7 NewSessionDialog + 7 rewind + 8 RewindDialog + 7 GithubDeviceDialog + 12 daemonBridge + 6 DaemonBridgeProvider + 24 Shell + 15 DialogHost + 19 SettingsPanel + 6 TaskFormDialog + 5 RantDialog + 4 vendorMarkdown) + `npm run build` → `renderer/dist/`
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 路径不受影响)
Git-over-https 兜底: `python scripts/sync-master-from-api.py [--repo owner/name] [--ref master]` — 受限网络下 github.com:443 不可达而 api.github.com 可达时,用 Git Data API 的 verification payload + signature 字节级重建上游 commit(含 web-flow GPG 签名 squash merge,reconstruct_commit 经 hermetic 测试验证 sha 一致)并推进本地 refs;内容对象缺失时 fail-loud 提示改用 git fetch(10+ 周期实证的恢复路径)
Expand Down
88 changes: 88 additions & 0 deletions emrg/gui/test/boot-contract.test.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
"use strict";
/**
* boot-contract.test.js — GUI 启动链路契约守卫(v0.2.81 断连回归,rant 2026-08-27T10:53:38)。
*
* v0.2.81 事故:React 迁移(Batch 5)丢掉了 `window.emrg.init()` 调用——renderer 只订阅
* onEvent + sendMessage,从不调 init;而 main.js 的 ensureConnected()(唯一通向 daemon
* websocket 的路径)只能由 emrg:init / saveSettings / scheduleReconnect 触发。init 不调 →
* ensureConnected 永不执行 → GUI 永久断连(常显断连横幅),v0.2.81 发布后才被宿主发现。
*
* 修复(#1036,renderer 侧:DaemonBridgeProvider 挂载即调 window.emrg.init(),含运行时
* 单测)恢复了链路。本守卫钉死 main.js + preload 两侧的静态契约——这两层没有任何运行时
* 测试覆盖(renderer 测试全部 mock window.emrg;integration 测试直接打 daemon_client,
* 不走 main.js boot 路径),防止未来重构静默破坏:
*
* renderer 挂载(#1036 运行时测试覆盖)
* → window.emrg.init()
* → preload init → ipcRenderer.invoke("emrg:init") ← 本守卫
* → main.js emrg:init handler → ensureConnected() ← 本守卫
* → daemon websocket
*
* 断言均为静态正则(build-config.test.js 同款模式):廉价、无运行时依赖、全 CI 步骤可跑。
*/

const { test } = require("node:test");
const assert = require("node:assert");
const fs = require("node:fs");
const path = require("node:path");

const GUI_ROOT = path.join(__dirname, "..");

function read(rel) {
return fs.readFileSync(path.join(GUI_ROOT, rel), "utf8");
}

test("boot chain: main.js emrg:init handler must call ensureConnected()", () => {
// 唯一通向 daemon websocket 的引导路径。若未来重构把 ensureConnected 移出 init、
// 改名或删除,静态守卫立即红——没有任何运行时测试覆盖 main 侧这条链。
const main = read("main.js");
const handler = main.match(/ipcMain\.handle\("emrg:init"[\s\S]*?\n\s*\}\);/);
assert.ok(handler, "main.js must contain an emrg:init ipcMain.handle block");
assert.match(
handler[0],
/ensureConnected\(\)/,
"emrg:init handler must call ensureConnected() — this is the only renderer-driven " +
"path to the daemon websocket (v0.2.81 regression: dropping renderer init dropped " +
"the connection)."
);
// G34/G71/G112:config 缺失 / key 未配置的快速返回必须先于 ensureConnected 分支
assert.match(
handler[0],
/configExists = fs\.existsSync\(configPath\(\)\)/,
"emrg:init must check config existence first (config missing → no daemon spawn)."
);
assert.match(
handler[0],
/if \(!keyConfigured\)/,
"emrg:init must gate ensureConnected on api_key_configured (unconfigured → return early)."
);
});

test("boot chain: preload exposes init -> emrg:init IPC channel", () => {
// preload 桥契约:init 方法必须映射到 emrg:init 频道(preload-api.test.js 已守卫存在性;
// 这里钉死频道映射,防止改名错位)。
const preload = read("preload.js");
assert.match(
preload,
/init:\s*\(\)\s*=>\s*ipcRenderer\.invoke\("emrg:init"\)/,
"preload.js must map init() → ipcRenderer.invoke(\"emrg:init\") — the channel name " +
"is part of the boot contract."
);
});

test("boot chain: ensureConnected must exist and be the daemon-connect entry", () => {
// ensureConnected 本身必须存在(conn-manager 职责)且不是空实现——防止"调用保留、
// 实现被掏空"的隐性回归。
const main = read("main.js");
assert.match(
main,
/function ensureConnected|ensureConnected\s*=|const ensureConnected/,
"main.js must define ensureConnected (the daemon websocket entry point)."
);
const connManager = read("conn-manager.js");
assert.match(
connManager,
/ensureConnected/,
"conn-manager.js must expose ensureConnected — main.js delegates to it."
);
});
Loading