Files
Jyotisha/frontend/tests/skill-binding.test.ts
T
Jesse_Chen e635c40224
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled
fix(consult): fold the technique audit out of the spoken answer
Keep comparative tables in chat, but hide the long audit behind a collapsed control so the reply stays readable.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 15:29:04 +08:00

158 lines
7.0 KiB
TypeScript

import assert from "node:assert/strict";
import { existsSync, readFileSync, realpathSync } from "node:fs";
import { join, resolve } from "node:path";
import test from "node:test";
import { Agent } from "@mastra/core/agent";
import {
jyotishSkillBinding,
jyotishSkillBoundProcessor,
jyotishSkillMethodBlock,
jyotishSkillPackage,
jyotishSkillRuntimePath,
} from "../src/mastra/skill-binding.ts";
const model = { specificationVersion: "v2", provider: "probe", modelId: "probe" } as never;
function probeAgent(options: Record<string, unknown>) {
return new Agent({ id: "skill-binding-probe", name: "Probe", model, instructions: "x", ...options } as never);
}
function liveSkillBody() {
const live = readFileSync(resolve(jyotishSkillPackage.resolvedPath, "SKILL.md"), "utf8");
return live.slice(live.indexOf("---", 3) + 4).trim();
}
function boundMethodText() {
const opened = jyotishSkillMethodBlock.indexOf(`<jyotish-skill name="${jyotishSkillPackage.name}"`);
const closed = jyotishSkillMethodBlock.lastIndexOf("</jyotish-skill>");
assert.ok(opened >= 0 && closed > opened);
return jyotishSkillMethodBlock.slice(jyotishSkillMethodBlock.indexOf("\n", opened) + 1, closed).trim();
}
test("the method the model follows is the live skill, not a hashed snapshot", () => {
const body = liveSkillBody();
const bound = boundMethodText();
assert.match(jyotishSkillMethodBlock, new RegExp(`name="${jyotishSkillPackage.name}"`));
assert.doesNotMatch(jyotishSkillMethodBlock, /<jyotish-skill name="[^"]+" version="/);
for (const line of bound.split("\n")) {
if (line.length === 0) continue;
assert.ok(body.includes(line), line.slice(0, 80));
}
assert.ok(jyotishSkillRuntimePath.endsWith(jyotishSkillPackage.name));
assert.equal(jyotishSkillRuntimePath.includes("/versions/"), false);
assert.equal(existsSync(join(jyotishSkillRuntimePath, "versions")), false);
assert.equal(
realpathSync(join(jyotishSkillRuntimePath, "SKILL.md")),
realpathSync(join(jyotishSkillPackage.resolvedPath, "SKILL.md")),
);
});
test("the bound method is the runtime excerpt, not the maintainer manual", () => {
// Independent source: the commercial SKILL.md still contains both the
// answering contract and the repo-construction notes. Binding the whole
// file is what pushed Mastra past its 500-line warning; the excerpt keeps
// the contract and drops the notes.
const body = liveSkillBody();
const bound = boundMethodText();
for (const heading of ["商业运行时路由", "关联技法完整调取", "强制工作流", "五层硬约束", "核心方法论", "注意事项", "commercial_skill_truth_overlay"]) {
assert.ok(body.includes(heading), heading);
assert.ok(bound.includes(heading), heading);
}
for (const phrase of ["全谱系真实调用", "P0/P1 观察层", "网页对话的技法审计表由界面折叠展示", "静态分析10步"]) {
assert.ok(bound.includes(phrase), phrase);
}
for (const heading of ["施工判断原则", "冲顶路线", "37大子命令", "验证与错题体系", "开源复用边界冻结"]) {
assert.ok(body.includes(heading), heading);
assert.equal(bound.includes(heading), false, heading);
}
assert.ok(body.includes("~/.workbuddy/skills"));
assert.equal(bound.includes("~/.workbuddy/skills"), false);
assert.ok(bound.split("\n").length < 500, `${bound.split("\n").length} lines`);
assert.ok(bound.length < body.length * 0.7, `${bound.length} of ${body.length}`);
});
test("binding the method costs a fraction of activating the skill", async () => {
// What activation would have sent, measured rather than assumed: Mastra answers
// it with the entrypoint plus a flat listing of every file in the package, and
// that listing is the majority of the bytes. Nothing carries over between
// requests, so it was resent on every turn of every conversation.
const activation = probeAgent({ skills: [jyotishSkillRuntimePath] });
const skillTool = (await activation.getToolsForExecution({ runId: "probe" })).skill as {
execute: (input: unknown, context: unknown) => Promise<unknown>;
};
const activated = await skillTool.execute(
{ name: jyotishSkillPackage.name },
{ runId: "probe" } as never,
);
const activationBytes = Buffer.byteLength(
typeof activated === "string" ? activated : JSON.stringify(activated),
"utf8",
);
const boundBytes = Buffer.byteLength(jyotishSkillMethodBlock, "utf8");
assert.ok(activationBytes > boundBytes * 2, `${activationBytes} vs ${boundBytes}`);
assert.ok(boundBytes < activationBytes * 0.45, `${boundBytes} of ${activationBytes}`);
});
test("agents that bind the method are not given a tool to load it", async () => {
const bound = probeAgent(jyotishSkillBinding());
const toolNames = Object.keys(await bound.getToolsForExecution({ runId: "probe" }));
assert.deepEqual(toolNames, ["skill_read"]);
// The skill is still declared, so the references the method names by path stay
// reachable for the one question the delivered sections do not cover.
assert.equal(
(await bound.listSkills()).some((skill) => skill.name === jyotishSkillPackage.name),
true,
);
});
test("a prompt assembled without the method stops the run instead of answering", () => {
const abortReasons: string[] = [];
const abort = ((reason?: string) => {
abortReasons.push(reason ?? "");
throw new Error("aborted");
}) as never;
const step = (system: unknown[]) => jyotishSkillBoundProcessor.processInputStep({
messageList: { getAllSystemMessages: () => system },
abort,
} as never);
assert.throws(() => step([{ role: "system", content: "you are a guide" }]), /aborted/);
assert.match(abortReasons[0], /not bound into the system prompt/);
// A prompt that does carry the method, and a prompt that cannot be read at all,
// both pass: this guard exists to catch a missing method, not to become a new
// way for a run to fail.
assert.doesNotThrow(() => step([{ role: "system", content: jyotishSkillMethodBlock }]));
assert.doesNotThrow(() => step([]));
});
test("only chart-answering agents bind the method", () => {
const mastra = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8");
const chart = mastra.slice(
mastra.indexOf("const jyotishInstructions"),
mastra.indexOf("const generalJyotishInstructions"),
);
const general = mastra.slice(
mastra.indexOf("const generalJyotishInstructions"),
mastra.indexOf("const onboardingInstructions"),
);
const onboarding = mastra.slice(
mastra.indexOf("const onboardingInstructions"),
mastra.indexOf("const dailyStarlanguageInstructions"),
);
const daily = mastra.slice(
mastra.indexOf("const dailyStarlanguageInstructions"),
mastra.indexOf("const birthTimeGuideInstructions"),
);
assert.match(chart, /\.\.\.jyotishSkillBinding\(\)/);
assert.match(chart, /\$\{jyotishSkillMethodBlock\}/);
for (const [name, source] of [["general", general], ["onboarding", onboarding], ["daily", daily]] as const) {
assert.doesNotMatch(source, /jyotishSkillBinding|jyotishSkillMethodBlock/, name);
}
});