Make English the repository working language (#5)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-20 17:45:02 +08:00
committed by GitHub
parent e80cde0ea6
commit 0eaaa54e25
136 changed files with 1515 additions and 1352 deletions
+8 -8
View File
@@ -2,40 +2,40 @@ import { describe, expect, it } from "vitest";
import { decideSigint } from "../src/commands/chat.js";
import { parseApprovalAnswer } from "../src/approval.js";
describe("decideSigint (Ctrl-C 行为状态机)", () => {
it("approving → deny(无论缓冲区是否有内容)", () => {
describe("decideSigint (Ctrl-C behavior state machine)", () => {
it("approving -> deny (whether or not the buffer has content)", () => {
expect(decideSigint("approving", false)).toBe("deny");
expect(decideSigint("approving", true)).toBe("deny");
});
it("running → abort(中断当前 Task,不退出)", () => {
it("running -> abort (interrupts the current Task, does not exit)", () => {
expect(decideSigint("running", false)).toBe("abort");
expect(decideSigint("running", true)).toBe("abort");
});
it("idle + 有输入 → clear(清空缓冲区)", () => {
it("idle + input -> clear (empties the buffer)", () => {
expect(decideSigint("idle", true)).toBe("clear");
});
it("idle + 无输入 → confirm-exit(弹出 y/N 退出确认)", () => {
it("idle + no input -> confirm-exit (shows the y/N exit confirmation)", () => {
expect(decideSigint("idle", false)).toBe("confirm-exit");
});
it("confirming-exit → exit(确认中再次 Ctrl-C 直接退出)", () => {
it("confirming-exit -> exit (a second Ctrl-C during confirmation exits immediately)", () => {
expect(decideSigint("confirming-exit", false)).toBe("exit");
expect(decideSigint("confirming-exit", true)).toBe("exit");
});
});
describe("parseApprovalAnswer", () => {
it("y / yes(trim、不区分大小写)→ allow;n / no → deny", () => {
it("y / yes (trimmed, case-insensitive) -> allow; n / no -> deny", () => {
expect(parseApprovalAnswer("y")).toBe("allow");
expect(parseApprovalAnswer(" YES \n")).toBe("allow");
expect(parseApprovalAnswer("Y")).toBe("allow");
expect(parseApprovalAnswer("n")).toBe("deny");
expect(parseApprovalAnswer("NO")).toBe("deny");
});
it("空/无关输入用 fallback(缺省 deny;工具审批传 allow)", () => {
it("empty/unrelated input uses the fallback (default deny; tool approval passes allow)", () => {
expect(parseApprovalAnswer("")).toBe("deny"); // default fallback
expect(parseApprovalAnswer("nope")).toBe("deny");
expect(parseApprovalAnswer("", "allow")).toBe("allow"); // tool approval defaults to allow
+4 -4
View File
@@ -29,7 +29,7 @@ describe("formatModelRows", () => {
],
};
it("provider 与 model_id 双列展示;预置模型 vision 经目录成对匹配,默认模型以 * 标记", () => {
it("provider and model_id shown as two columns; preset model vision resolved by catalog pair match, default model marked with *", () => {
const lines = formatModelRows(cfg);
expect(lines).toHaveLength(2);
expect(lines[0]).toMatch(/^\* anthropic\s+claude-sonnet-4-6\s+vision=Y/);
@@ -39,7 +39,7 @@ describe("formatModelRows", () => {
expect(lines[0]).not.toContain("anthropic/claude-sonnet-4-6");
});
it("自定义模型 vision 按标注(显式 false 记 -);内联 api_key 掩码显示", () => {
it("custom model vision follows its annotation (explicit false shown as -); inline api_key displayed masked", () => {
const lines = formatModelRows(cfg);
expect(lines[1]).toMatch(/^ {2}custom\s+my-proxy-model\s+vision=-/);
expect(lines[1]).toContain("client_type=openai");
@@ -47,7 +47,7 @@ describe("formatModelRows", () => {
expect(lines[1]).not.toContain("sk-test-abcd-1234");
});
it("同名 model_id 双 provider 并存时各占一行,默认标记只落在成对命中的那行", () => {
it("two providers sharing a model_id each get their own row; the default marker lands only on the exact pair match", () => {
const lines = formatModelRows({
default_model: { provider: "deepseek", model_id: "m1" },
models: [
@@ -59,7 +59,7 @@ describe("formatModelRows", () => {
expect(lines[1]).toMatch(/^ {2}siliconflow\s+m1\s+vision=Y/);
});
it("无标注按「缺省=支持」记 Y;全空列省略", () => {
it('unannotated vision defaults to "supported" and shows Y; fully empty columns are omitted', () => {
const lines = formatModelRows({
models: [{ provider: "custom", model_id: "m1" }],
});
+10 -10
View File
@@ -78,8 +78,8 @@ async function runModel(args: string[]): Promise<{ out: string; err: string; cod
}
}
describe("penguin config model add/list(--root 与 provider / model_id 分列存储)", () => {
it("--root 优先于 PENGUIN_HOME:落盘到指定根目录的隐藏 .project_config.toml(0600)", async () => {
describe("penguin config model add/list (--root plus provider / model_id stored as separate fields)", () => {
it("--root beats PENGUIN_HOME: written to the hidden .project_config.toml under the given root (0600)", async () => {
const add = await runModel([
"add",
"--model-id",
@@ -119,7 +119,7 @@ describe("penguin config model add/list(--root 与 provider / model_id 分列
expect(list.out).not.toContain("sk-root-secret-1");
});
it("内置目录推断分组:上游 id 命中目录时条目落该 provider;--set-default 写成对引用", async () => {
it("built-in catalog infers the grouping: an upstream id matching the catalog lands under that provider; --set-default writes a pair reference", async () => {
const add = await runModel([
"add",
"--model-id",
@@ -144,7 +144,7 @@ describe("penguin config model add/list(--root 与 provider / model_id 分列
).toBeDefined();
});
it("--provider 显式指定分组:同名上游 id 与预置条目互不冲突(各自独立条目)", async () => {
it("--provider picks the grouping explicitly: a same-name upstream id does not clash with the preset entry (independent entries)", async () => {
const add = await runModel([
"add",
"--model-id",
@@ -167,7 +167,7 @@ describe("penguin config model add/list(--root 与 provider / model_id 分列
expect(list.out.split("\n").some((l) => /anthropic\s+claude-sonnet-4-6/.test(l))).toBe(true);
});
it("client_type 缺省按分组语义(PRN-021):custom / 自建 / 网关落 openai,一方厂商不落", async () => {
it("client_type defaults by grouping semantics (PRN-021): custom / self-hosted / gateway get openai, first-party providers get none", async () => {
// custom (catalog inference fails) and self-hosted groups (--provider not a catalog value): default to client_type=openai.
await runModel(["add", "--model-id", "my-openai-proxy", "--root", tmpRoot]);
await runModel(["add", "--model-id", "in-house-1", "--provider", "mylab", "--root", tmpRoot]);
@@ -217,7 +217,7 @@ describe("penguin config model add/list(--root 与 provider / model_id 分列
expect(by("mylab", "special-1").client_type).toBe("verbatim-type");
});
it("model default 经 --root 指定根目录设置默认模型(--model-id 上游 id + --provider 成对)", async () => {
it("model default sets the default model under the --root data root (--model-id upstream id + --provider as a pair)", async () => {
const set = await runModel([
"default",
"--model-id",
@@ -241,14 +241,14 @@ describe("penguin config model add/list(--root 与 provider / model_id 分列
});
});
describe("model default/vision:--provider 必填,(provider, model_id) 成对引用", () => {
it("缺 --provider:commander 用法报错,非零退出码", async () => {
describe("model default/vision: --provider is required, (provider, model_id) pair reference", () => {
it("missing --provider: commander usage error, nonzero exit code", async () => {
const bad = await runModel(["default", "--model-id", "deepseek-v4-flash", "--root", tmpRoot]);
expect(bad.code).not.toBe(0);
expect(bad.err).toContain("--provider");
});
it("引用落空:成对引用不在 models 中,报错带成对引用与 model list 提示", async () => {
it("dangling reference: the pair is not in models; the error carries the pair reference and a model list hint", async () => {
const bad = await runModel([
"default",
"--model-id",
@@ -276,7 +276,7 @@ describe("model default/vision:--provider 必填,(provider, model_id) 成对
expect(wrongGroup.err).toContain("penguin config model list");
});
it("model vision 成对引用命中:设置视觉模型(落盘内联表)", async () => {
it("model vision pair reference hit: sets the vision model (written as an inline table)", async () => {
const ok = await runModel([
"vision",
"--model-id",
+4 -4
View File
@@ -57,7 +57,7 @@ async function runVault(args: string[]): Promise<{ out: string; err: string; cod
}
describe("penguin config vault", () => {
it("set → list(掩码)→ remove 全链路;落盘为隐藏 .vault.toml 且 0600", async () => {
it("set -> list (masked) -> remove full round trip; stored as hidden .vault.toml with 0600", async () => {
const set = await runVault(["set", "--key", "MY_KEY", "--value", "vault-secret-9876"]);
expect(set.code).toBe(0);
expect(set.out).toContain("Saved vault entry MY_KEY.");
@@ -81,7 +81,7 @@ describe("penguin config vault", () => {
expect(empty.out).toContain("The vault is empty.");
});
it("--agent-id 定向到目标 Agent 的 vault,不影响 default_agent", async () => {
it("--agent-id targets that Agent's vault without touching default_agent", async () => {
const set = await runVault([
"set",
"--key",
@@ -99,7 +99,7 @@ describe("penguin config vault", () => {
expect(defaultList.out).toContain("The vault is empty.");
});
it("--root 指定数据根目录(优先于 PENGUIN_HOME),set/list 均定向到该根目录", async () => {
it("--root picks the data root (beats PENGUIN_HOME); set/list both target that root", async () => {
const otherRoot = await fs.mkdtemp(path.join(os.tmpdir(), "penguin-cli-vault-root-"));
try {
const set = await runVault([
@@ -125,7 +125,7 @@ describe("penguin config vault", () => {
}
});
it("非法键名 / 超长值以非零码退出并打印原因;remove 不存在的键报错", async () => {
it("invalid key name / oversized value exit nonzero and print the reason; removing a missing key errors", async () => {
const badKey = await runVault(["set", "--key", "1BAD", "--value", "v"]);
expect(badKey.code).toBe(1);
expect(badKey.err).toContain("Invalid vault key");
+3 -3
View File
@@ -292,7 +292,7 @@ describe("StreamRenderer", () => {
);
});
it("轮结束后的压缩:压缩完成行展示本次消耗,但不计入本轮统计增量;不更新上下文", () => {
it("compaction after the turn ends: the completion line shows its own cost, excluded from the turn stats delta; context not updated", () => {
const { stream, text } = collector();
const r = new StreamRenderer(stream, t);
// Ordinary request: context 5000.
@@ -325,7 +325,7 @@ describe("StreamRenderer", () => {
expect(s).toContain("tokens 14k (+5k)");
});
it("轮途中的压缩(其后还有普通 request_end):用时含压缩跨度,Token 增量计入压缩", () => {
it("mid-turn compaction (a normal request_end follows): elapsed time includes the compaction span, Token delta includes compaction", () => {
const { stream, text } = collector();
const r = new StreamRenderer(stream, t);
// own1: ordinary request, request 5000, 00:00 -> 00:02.
@@ -362,7 +362,7 @@ describe("StreamRenderer", () => {
expect(s).toContain("16s (+16s)");
});
it("轮结束后的压缩(带 request 事件):用时止于压缩前的最后一个 request_end", () => {
it("compaction after the turn ends (with request events): elapsed time stops at the last request_end before compaction", () => {
const { stream, text } = collector();
const r = new StreamRenderer(stream, t);
// own1: 00:00 -> 00:03.
+13 -13
View File
@@ -10,19 +10,19 @@ import {
} from "../src/commands/serve.js";
import { getMessages } from "../src/i18n.js";
describe("resolvePort(选项 > 环境变量 > 缺省 7364)", () => {
it("都未给时用缺省 7364", () => {
describe("resolvePort (option > env var > default 7364)", () => {
it("uses the default 7364 when neither is given", () => {
expect(DEFAULT_PORT).toBe(7364);
expect(resolvePort(undefined, undefined)).toBe(7364);
expect(resolvePort(undefined, "")).toBe(7364); // an empty string counts as unset
});
it("只有环境变量时取环境变量", () => {
it("takes the env var when only the env var is set", () => {
expect(resolvePort(undefined, "8080")).toBe(8080);
});
it("选项优先于环境变量", () => {
it("the option beats the env var", () => {
expect(resolvePort("9000", "8080")).toBe(9000);
});
it("非法值(非整数 / 越界)抛错", () => {
it("throws on invalid values (non-integer / out of range)", () => {
expect(() => resolvePort("abc", undefined)).toThrow(/abc/);
expect(() => resolvePort("3.14", undefined)).toThrow();
expect(() => resolvePort("-1", undefined)).toThrow();
@@ -31,36 +31,36 @@ describe("resolvePort(选项 > 环境变量 > 缺省 7364)", () => {
});
});
describe("browserCommand(按平台选择打开命令)", () => {
describe("browserCommand (picks the open command per platform)", () => {
const url = "http://127.0.0.1:7364/";
it("darwin → open", () => {
expect(browserCommand("darwin", url)).toEqual({ command: "open", args: [url] });
});
it("win32 → cmd /c start(空标题占位在 URL 前)", () => {
it("win32 -> cmd /c start (empty title placeholder before the URL)", () => {
expect(browserCommand("win32", url)).toEqual({
command: "cmd",
args: ["/c", "start", "", url],
});
});
it("其他平台(linux 等)→ xdg-open", () => {
it("other platforms (linux etc.) -> xdg-open", () => {
expect(browserCommand("linux", url)).toEqual({ command: "xdg-open", args: [url] });
expect(browserCommand("freebsd", url)).toEqual({ command: "xdg-open", args: [url] });
});
});
describe("browserUrl(通配监听地址转 127.0.0.1)", () => {
it("常规 host 原样拼接", () => {
describe("browserUrl (wildcard listen addresses map to 127.0.0.1)", () => {
it("a regular host is joined as-is", () => {
expect(browserUrl(DEFAULT_HOST, 7364)).toBe("http://127.0.0.1:7364/");
expect(browserUrl("192.168.1.2", 8080)).toBe("http://192.168.1.2:8080/");
});
it("0.0.0.0 / :: 时浏览器 URL 用 127.0.0.1", () => {
it("the browser URL uses 127.0.0.1 for 0.0.0.0 / ::", () => {
expect(browserUrl("0.0.0.0", 7364)).toBe("http://127.0.0.1:7364/");
expect(browserUrl("::", 7364)).toBe("http://127.0.0.1:7364/");
});
});
describe("registerServeCommands(命令注册)", () => {
it("注册 server 与 web 两个顶层命令,web 缺省 open=true(--no-open 可关)", () => {
describe("registerServeCommands (command registration)", () => {
it("registers the server and web top-level commands; web defaults to open=true (--no-open turns it off)", () => {
const program = new Command();
registerServeCommands(program, getMessages("en"));
const names = program.commands.map((c) => c.name());