feat(記憶與目標): 專案目標三條 + 固化候選不再重算(0.2.1) #17
@@ -3270,6 +3270,50 @@ console.log("\n㉜ 語氣層(voice/):學語氣,但不准變成照抄");
|
||||
const entries = ctx.split("\n").filter((l) => l.startsWith(" - 「") || l.startsWith("遇到「"));
|
||||
return ctx.includes("學語氣,不要照抄這幾句") && entries.length <= pl.VOICE_INJECT_MAX;
|
||||
})());
|
||||
// 語域:語氣層的**加法面**。跟原句同一個目錄、同一支 voice add,
|
||||
// 所以 persona-story 與 persona-anime 寫的是同一格,不會各存一份
|
||||
check("語域是第三種 voice kind,檔名與標籤都對",
|
||||
pl.VOICE_KINDS.idiolect === "idiolect.md" && pl.VOICE_LABELS.idiolect === "語域");
|
||||
check("四格是寫死的,自由欄位加不進去(會長出什麼都能塞的格子)",
|
||||
pl.addVoiceIdiolect("voicey", { facet: "風格", value: "文雅" }) === null);
|
||||
check("CLI 用同一支 voice add 寫語域", (() => {
|
||||
const res = cli(["voice", "add", "--session", S_V, "--kind", "idiolect", "--facet", "自稱", "--value", "我"]);
|
||||
return res.status === 0 && fs.existsSync(pl.voicePath("voicey", "idiolect")) &&
|
||||
pl.loadVoice("voicey").idiolect.some((r) => r.facet === "自稱" && r.value === "我");
|
||||
})(), JSON.stringify(pl.loadVoice("voicey").idiolect));
|
||||
check("--facet 給錯會被擋,而且把四格列出來", (() => {
|
||||
const res = cli(["voice", "add", "--session", S_V, "--kind", "idiolect",
|
||||
"--facet", "語氣", "--value", "軟"], { expectOk: false });
|
||||
return res.status !== 0 && pl.IDIOLECT_FACETS.every((f) => String(res.stderr).includes(f));
|
||||
})());
|
||||
for (const [facet, value] of [["句尾", "⋯吧"], ["口癖", "算了"], ["不說", "絕對"]]) {
|
||||
cli(["voice", "add", "--session", S_V, "--kind", "idiolect", "--facet", facet, "--value", value]);
|
||||
}
|
||||
check("一輪最多注入 3 格(四格輪替,不是一次全塞)", (() => {
|
||||
const brief = pl.idiolectBrief("voicey", { turn: 0 });
|
||||
const parts = brief.split(":")[1].split("。")[0].split(";");
|
||||
return parts.length === pl.IDIOLECT_INJECT_MAX && brief.includes("下一輪輪到");
|
||||
})(), pl.idiolectBrief("voicey", { turn: 0 }));
|
||||
check("輪替起點由第幾輪算出來,不用隨機數(同一輪重跑結果一樣)", (() => {
|
||||
const a = pl.idiolectBrief("voicey", { turn: 0 });
|
||||
const b = pl.idiolectBrief("voicey", { turn: 0 });
|
||||
const c = pl.idiolectBrief("voicey", { turn: 1 });
|
||||
return a === b && a !== c;
|
||||
})());
|
||||
check("四格都輪得到(跑滿一圈每格至少出現一次)", (() => {
|
||||
const seen = new Set();
|
||||
for (let t = 0; t < pl.IDIOLECT_FACETS.length; t += 1) {
|
||||
const brief = pl.idiolectBrief("voicey", { turn: t });
|
||||
for (const f of pl.IDIOLECT_FACETS) if (brief.includes(`${f}→`)) seen.add(f);
|
||||
}
|
||||
return seen.size === pl.IDIOLECT_FACETS.length;
|
||||
})(), JSON.stringify(pl.IDIOLECT_FACETS.map((f, t) => pl.idiolectBrief("voicey", { turn: t }).slice(0, 60))));
|
||||
check("語域講明它是加法(黑名單刪 AI 腔,這幾格補他自己的講法)", (() => {
|
||||
const brief = pl.idiolectBrief("voicey");
|
||||
return brief.includes("加法") && brief.includes("認得出是誰");
|
||||
})(), pl.idiolectBrief("voicey"));
|
||||
check("turnContext 帶得到語域", pl.turnContext("voicey", S_V, "你自己怎麼說").includes("語域(這一輪"));
|
||||
check("沒有語域檔的人格不注入這段", pl.idiolectBrief("calmsplit") === "");
|
||||
// voice/ 會被 `sync pull`/`import` 覆蓋 → 它是外部輸入,讀取端要自己中和
|
||||
check("語氣檔裡的注入標記讀進來就被中和(它是會被 sync pull 覆蓋的檔)", (() => {
|
||||
fs.appendFileSync(pl.voicePath("voicey", "sample"),
|
||||
|
||||
Reference in New Issue
Block a user