落档自动把产物挂进 context_order;创作期用 declare_variable/declare_map 钉目录与分档;真值变更持续重投影。顺带强制 tools 请求 reasoning_effort=none,并支持消息 restart。 Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
4.7 KiB
TypeScript
153 lines
4.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { Blackboard } from "../src/blackboard/blackboard.js";
|
|
import {
|
|
lookupValueMapContent,
|
|
parseValueMapDoc,
|
|
reprojectValueMaps,
|
|
serializeValueMapDoc,
|
|
upsertValueMapEntry,
|
|
VALUE_MAP_TAG,
|
|
} from "../src/skills/value-map.js";
|
|
import {
|
|
parseVariableCatalog,
|
|
seedVariablesFromCatalog,
|
|
serializeVariableCatalog,
|
|
upsertVariableField,
|
|
VARIABLE_CATALOG_TAG,
|
|
} from "../src/skills/variable-catalog.js";
|
|
import { stringifyTableDoc, createTableFromValues, parseTableDoc } from "../src/blackboard/table-cells.js";
|
|
|
|
describe("variable-catalog", () => {
|
|
it("upserts fields and seeds empty 变量.当前", () => {
|
|
let doc = null as ReturnType<typeof parseVariableCatalog>;
|
|
const a = upsertVariableField(doc, {
|
|
key: "好感",
|
|
type: "number",
|
|
initial: 10,
|
|
user_visible: true,
|
|
});
|
|
expect(a.error).toBeUndefined();
|
|
const b = upsertVariableField(a.doc, {
|
|
key: "地点",
|
|
type: "string",
|
|
initial: "旧港",
|
|
user_visible: false,
|
|
});
|
|
const raw = serializeVariableCatalog(b.doc);
|
|
const parsed = parseVariableCatalog(raw);
|
|
expect(parsed?.fields).toHaveLength(2);
|
|
expect(parsed?.fields.find((f) => f.key === "地点")?.user_visible).toBe(false);
|
|
|
|
const seeded = seedVariablesFromCatalog({
|
|
catalogRaw: raw,
|
|
currentRaw: null,
|
|
initialRaw: null,
|
|
});
|
|
expect(seeded.seededKeys).toEqual(expect.arrayContaining(["好感", "地点"]));
|
|
expect(seeded.current).toContain("好感");
|
|
expect(seeded.initial).toContain("旧港");
|
|
});
|
|
});
|
|
|
|
describe("value-map", () => {
|
|
it("looks up range bands (min inclusive, max exclusive)", () => {
|
|
const { doc } = upsertValueMapEntry(null, {
|
|
id: "aff",
|
|
field: "好感",
|
|
target_tag: "上下文.角色态度",
|
|
bands: [
|
|
{ min: 0, max: 30, content: "冷淡" },
|
|
{ min: 30, max: 60, content: "熟悉" },
|
|
{ min: 60, content: "恋爱" },
|
|
],
|
|
});
|
|
const entry = doc.maps[0]!;
|
|
expect(lookupValueMapContent(entry, 0)).toBe("冷淡");
|
|
expect(lookupValueMapContent(entry, 29)).toBe("冷淡");
|
|
expect(lookupValueMapContent(entry, 30)).toBe("熟悉");
|
|
expect(lookupValueMapContent(entry, 60)).toBe("恋爱");
|
|
});
|
|
|
|
it("reprojects target tag when 变量.当前 changes", () => {
|
|
const board = new Blackboard();
|
|
const { doc } = upsertValueMapEntry(null, {
|
|
id: "aff",
|
|
field: "好感",
|
|
target_tag: "上下文.角色态度",
|
|
bands: [
|
|
{ min: 0, max: 50, content: "档A" },
|
|
{ min: 50, content: "档B" },
|
|
],
|
|
});
|
|
board.write({
|
|
tag: VALUE_MAP_TAG,
|
|
content: serializeValueMapDoc(doc),
|
|
source: "test",
|
|
});
|
|
const tableRaw = stringifyTableDoc(
|
|
createTableFromValues({ ["好感"]: 20 }, "test"),
|
|
);
|
|
board.write({
|
|
tag: "变量.当前",
|
|
content: tableRaw,
|
|
source: "test",
|
|
});
|
|
expect(parseTableDoc(board.getContentByTag("变量.当前"))?.rows.map((r) => r.key)).toEqual([
|
|
"好感",
|
|
]);
|
|
const r1 = reprojectValueMaps({ blackboard: board, currentRaw: tableRaw });
|
|
expect(r1.skipped, JSON.stringify(r1)).toEqual([]);
|
|
expect(r1.written[0]?.tag).toBe("上下文.角色态度");
|
|
expect(board.getContentByTag("上下文.角色态度")).toBe("档A");
|
|
|
|
const tableRaw2 = stringifyTableDoc(
|
|
createTableFromValues({ ["好感"]: 80 }, "test"),
|
|
);
|
|
board.write({
|
|
tag: "变量.当前",
|
|
content: tableRaw2,
|
|
source: "test",
|
|
});
|
|
reprojectValueMaps({ blackboard: board, currentRaw: tableRaw2 });
|
|
expect(board.getContentByTag("上下文.角色态度")).toBe("档B");
|
|
});
|
|
|
|
it("rejects bad target_tag", () => {
|
|
const { error } = upsertValueMapEntry(null, {
|
|
id: "x",
|
|
field: "好感",
|
|
target_tag: "设计.美学纲领",
|
|
bands: [{ content: "no" }],
|
|
});
|
|
expect(error).toMatch(/上下文/);
|
|
});
|
|
|
|
it("round-trips parse", () => {
|
|
const raw = serializeValueMapDoc({
|
|
schema: "value-map.v1",
|
|
maps: [
|
|
{
|
|
id: "ch",
|
|
field: "章节",
|
|
target_tag: "大纲.当前章",
|
|
bands: [
|
|
{ when: "eq", value: 1, content: "第一章" },
|
|
{ when: "default", content: "未定" },
|
|
],
|
|
},
|
|
],
|
|
});
|
|
const parsed = parseValueMapDoc(raw);
|
|
expect(parsed?.maps[0]?.bands).toHaveLength(2);
|
|
expect(lookupValueMapContent(parsed!.maps[0]!, 1)).toBe("第一章");
|
|
expect(lookupValueMapContent(parsed!.maps[0]!, 9)).toBe("未定");
|
|
});
|
|
});
|
|
|
|
describe("catalog tag constants", () => {
|
|
it("uses stable design tags", () => {
|
|
expect(VARIABLE_CATALOG_TAG).toBe("设计.变量目录");
|
|
expect(VALUE_MAP_TAG).toBe("设计.变量映射");
|
|
});
|
|
});
|