ef1bd6dfa9
The report writer had no interpretation methodology at all: a local agent calling the jyotish skill can read the reference library, the report model could read nothing. It could only restate the bundle. - frontend/src/lib/report-interpretation-packs/ holds one general pack and one pack per report theme, distilled from the in-repo reference guides. They constrain wording and reasoning discipline (term modernisation, how to talk about relative strength and SAV scores, the reasoning errors to avoid, the banned phrasings) and never assert a chart fact. - The general pack rides INSIDE the cached system message so the cached prefix stays byte-identical across sections; the chapter pack follows it and summary calls get the general pack only. - Skill jyotish-personal-report goes to 1.1.0 (1.0.0 deprecated): the contract now names interpretiveFacts and themeNarrativeSeeds as a bounded fact layer and states that the knowledge pack is not a fact source and cannot raise certainty. - Telemetry records interpretiveFactCount and knowledgePackCharacters as numbers only; the counter never throws so telemetry cannot break a run. Tests lock every theme resolving a pack, the 3,000 character budget, a forbidden-substring scan (paths, module names, vendor names, artefact names), the byte-stable cache prefix, and that no evidence id or date appears in the static content. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016P5RoqzmUQEbeC2qjAkeGr
92 lines
3.8 KiB
TypeScript
92 lines
3.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import {
|
|
PACK_CHARACTER_LIMIT,
|
|
allInterpretationPacks,
|
|
cachedInterpretationPreamble,
|
|
interpretationPackForTheme,
|
|
} from "../src/lib/report-interpretation-packs/index.ts";
|
|
import { REPORT_THEME_IDS } from "../src/lib/report-theme-evidence-plan.ts";
|
|
import { buildWriterSystemContents } from "../src/mastra/personal-report.ts";
|
|
|
|
test("every report theme resolves an interpretation pack", () => {
|
|
for (const theme of REPORT_THEME_IDS) {
|
|
const pack = interpretationPackForTheme(theme);
|
|
assert.ok(pack && pack.length > 200, `theme ${theme} has no usable pack`);
|
|
}
|
|
assert.equal(interpretationPackForTheme("not_a_theme"), null);
|
|
assert.equal(interpretationPackForTheme(null), null);
|
|
assert.ok(cachedInterpretationPreamble().length > 500);
|
|
});
|
|
|
|
test("packs stay inside the character budget", () => {
|
|
for (const pack of allInterpretationPacks()) {
|
|
assert.ok(
|
|
pack.text.length <= PACK_CHARACTER_LIMIT,
|
|
`${pack.id} is ${pack.text.length} characters, over the ${PACK_CHARACTER_LIMIT} budget`,
|
|
);
|
|
}
|
|
});
|
|
|
|
// The packs travel into a model prompt, so they must not leak repository
|
|
// structure, internal module names, vendor names or artefact names.
|
|
const FORBIDDEN_PACK_SUBSTRINGS = [
|
|
"references/", "scripts/", "frontend/", "src/", "node_modules",
|
|
"SKILL.md", ".md", ".ts", ".py", ".json",
|
|
"vedastro", "swisseph", "swiss ephemeris", "jyotish_engine", "mastra",
|
|
"anthropic", "openai", "claude", "gpt-",
|
|
"prompt", "system message", "schema", "bundle", "claimcard", "evidenceref",
|
|
"mevg", "workflow", "endpoint", "http://", "https://", "sha256",
|
|
];
|
|
|
|
test("packs contain no paths, module names, vendor names or artefact names", () => {
|
|
for (const pack of allInterpretationPacks()) {
|
|
const lowered = pack.text.toLowerCase();
|
|
for (const token of FORBIDDEN_PACK_SUBSTRINGS) {
|
|
assert.ok(!lowered.includes(token), `${pack.id} leaks "${token}"`);
|
|
}
|
|
}
|
|
});
|
|
|
|
test("packs restate the hard truth boundaries instead of relaxing them", () => {
|
|
const general = cachedInterpretationPreamble();
|
|
for (const banned of ["必定", "一定", "保证", "百分之百"]) {
|
|
assert.ok(general.includes(banned), `general pack must name "${banned}" as forbidden phrasing`);
|
|
}
|
|
assert.ok(general.includes("不做医疗诊断"));
|
|
const health = interpretationPackForTheme("health_pressure")!;
|
|
assert.ok(health.includes("不做任何诊断"));
|
|
const wealth = interpretationPackForTheme("wealth")!;
|
|
assert.ok(wealth.includes("不推荐任何具体投资标的"));
|
|
const timing = interpretationPackForTheme("timing")!;
|
|
assert.ok(timing.includes("不写年份、月份与日期"));
|
|
});
|
|
|
|
test("writer system content caches the general pack and appends only the chapter pack", () => {
|
|
const summaryContents = buildWriterSystemContents(null);
|
|
assert.equal(summaryContents.length, 1, "summary calls carry the general pack only");
|
|
assert.ok(summaryContents[0].includes(cachedInterpretationPreamble()));
|
|
assert.ok(summaryContents[0].includes("【上下文缓存边界】"));
|
|
|
|
const previousCached = summaryContents[0];
|
|
for (const theme of REPORT_THEME_IDS) {
|
|
const contents = buildWriterSystemContents(theme);
|
|
assert.equal(contents.length, 2, `${theme} must add its own pack`);
|
|
assert.equal(
|
|
contents[0],
|
|
previousCached,
|
|
"the cached prefix must stay byte-identical across sections or the cache never hits",
|
|
);
|
|
assert.equal(contents[1], interpretationPackForTheme(theme));
|
|
}
|
|
});
|
|
|
|
test("writer system content never carries chart, subject or evidence data", () => {
|
|
for (const theme of [null, ...REPORT_THEME_IDS]) {
|
|
for (const content of buildWriterSystemContents(theme)) {
|
|
assert.ok(!/ev-[a-z0-9_-]+/.test(content), "no evidence ids in the static packs");
|
|
assert.ok(!/\d{4}-\d{2}-\d{2}/.test(content), "no dates in the static packs");
|
|
}
|
|
}
|
|
});
|