guanghu-ice-heart/server-tools/codex-hldp-recursive-memory/hldp-memory.test.mjs

99 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
import { BOOTSTRAP_LIMIT_BYTES, bootstrap, readNode, reviewTree, route, verifyTree } from "./hldp-memory.mjs";
function validTree() {
return {
protocol: "HLDP-v1.0",
root: "root",
nodes: {
root: {
path: "root", summary: "总入口", trigger: "用户需要恢复", emergence: "长历史 → 根索引", lock: "按需展开", why: "避免全量加载",
children: ["root/protocol", "root/decision"], sources: ["source://root"], rejected: []
},
"root/protocol": {
path: "root/protocol", summary: "HLDP 协议与递归折叠", trigger: "需要解码", emergence: "摘要 → 因果树", lock: "使用v1", why: "保留为什么",
children: [], sources: ["hldp/HLDP-RUNTIME-ROOT-v1.0.hdlp"], rejected: ["每轮注入全文:会撑爆上下文"]
},
"root/decision": {
path: "root/decision", summary: "方案变化与最终决策", trigger: "出现三个方案", emergence: "A失败 → B不足 → C锁定", lock: "采用C | 失效=证据变化", why: "后续执行依赖转折",
children: [], sources: ["source://decision"], rejected: ["A失败", "B不足"]
}
}
};
}
test("validates a bounded causal tree", () => assert.deepEqual(verifyTree(validTree()), []));
test("reviews whether a leaf preserves causal transitions instead of becoming a diary entry", () => {
const reviewed = reviewTree(validTree(), "root/decision");
assert.equal(reviewed.ok, true);
assert.equal(reviewed.reviewed, 1);
const diary = validTree();
diary.nodes["root/decision"].emergence = "上午讨论方案,下午继续工作,晚上保存文件";
diary.nodes["root/decision"].why = "记一下";
const rejected = reviewTree(diary, "root/decision");
assert.equal(rejected.ok, false);
assert(rejected.errors.some((error) => error.includes("transition chain")));
assert(rejected.errors.some((error) => error.includes("why is too generic")));
});
test("rejects more than ten siblings and missing why", () => {
const tree = validTree();
tree.nodes.root.children = Array.from({ length: 11 }, (_, i) => `child-${i}`);
tree.nodes["root/protocol"].why = "";
tree.nodes["root/decision"].sources = [];
const errors = verifyTree(tree);
assert(errors.some((error) => error.includes("more than 10")));
assert(errors.some((error) => error.includes("missing why")));
assert(errors.some((error) => error.includes("sources must be a non-empty array")));
});
test("route returns paths and summaries only, capped at three", () => {
const candidates = route(validTree(), "最终决策 方案", 9);
assert.equal(candidates.length, 2);
assert.equal(candidates[0].path, "root/decision");
assert.deepEqual(Object.keys(candidates[0]).sort(), ["path", "score", "summary"]);
assert(!JSON.stringify(candidates).includes("A失败"));
});
test("route ranks a matching Chinese branch instead of treating a sentence as one token", () => {
const candidates = route(validTree(), "为什么最终方案发生变化", 3);
assert.equal(candidates[0].path, "root/decision");
assert(candidates[0].score > 0);
});
test("read-node reads exactly one bounded node", () => {
const node = readNode(validTree(), "root/decision");
assert.equal(node.path, "root/decision");
});
test("bootstrap is small and contains no memory body", () => {
const text = bootstrap("hldp/HLDP-RUNTIME-ROOT-v1.0.hdlp");
assert(Buffer.byteLength(text) <= BOOTSTRAP_LIMIT_BYTES);
assert(!text.includes("capsule"));
assert(!text.includes("session log"));
});
test("Codex hook emits context only for SessionStart", () => {
const script = fileURLToPath(new URL("./codex-session-bootstrap.mjs", import.meta.url));
const started = execFileSync(process.execPath, [script], { input: JSON.stringify({ hook_event_name: "SessionStart" }), encoding: "utf8" });
assert(JSON.parse(started).hookSpecificOutput.additionalContext);
const prompt = execFileSync(process.execPath, [script], { input: JSON.stringify({ hook_event_name: "UserPromptSubmit" }), encoding: "utf8" });
assert.equal(prompt, "");
});
test("repository routes HLDP to v1 root and marks v3 historical", () => {
const repo = path.resolve(fileURLToPath(new URL("../../", import.meta.url)));
const index = fs.readFileSync(path.join(repo, "INDEX.hdlp"), "utf8");
const tower = fs.readFileSync(path.join(repo, "BROADCAST-TOWER.hdlp"), "utf8");
const v3 = fs.readFileSync(path.join(repo, "hldp/HLDP-SPEC-v3.0-TECHNICAL.md"), "utf8");
assert(index.includes("hldp/HLDP-RUNTIME-ROOT-v1.0.hdlp"));
assert(tower.includes("先读 hldp/HLDP-RUNTIME-ROOT-v1.0.hdlp"));
assert(v3.includes("HISTORICAL-EVOLUTION-DRAFT"));
});