mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
d24104f7dc
包名 @ali/bailian-kb-dsh → bailian-kb-dsh(公开 npm):package.json name + cordis.patch.yml insert.name + tsdown PLUGIN_ID 三处同步(漏一处即 dsh 运行时崩)。 产物 lib/ → dist/(本仓 .gitignore 忽略 dist 不忽略 lib),连带 main/types/ exports/files/tsdown outDir 同步;.gitignore 补 *.tsbuildinfo。 依赖接 catalog(yaml/typescript/@types/node/vite-plus);测试导入 vitest → vite-plus/test(全仓统一约定,消掉唯一的 vitest 依赖漂移)。 tsconfig 拆三件套:tsconfig.json 纯类型检查覆盖 src+tests(供 oxlint 自动发现, 含 jsx/DOM),tsconfig.build.json 产出 node 半,tsconfig.web.json 隔离检查 web 半。 补齐 tests 从未被类型检查暴露的一处 partial 输入类型错误。 根 vite.config.ts 新增两条 override:web 半 no-restricted-imports 把 tsdown 构建期 的 bundle purity gate 提前到 lint 期;全包放开 _ 前缀的 no-unused-vars。 文档:新增 docs/agents/dsh-plugin.md,AGENTS.md 项目地图/版本锁步例外/分层边界/ 场景索引同步,packages.mjs 注释说明故意不进发布白名单。 格式化(单引号无分号 → 双引号加分号)由 pre-commit 的 vp check --fix 自动完成, 无法单独成 commit,一并纳入。 全仓 vp check 0 error;插件 13 文件 85 测试全绿;build + typecheck 通过。
100 lines
4.2 KiB
TypeScript
100 lines
4.2 KiB
TypeScript
import { describe, expect, it } from "vite-plus/test";
|
|
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { parseSkillFile } from "../src/skill.js";
|
|
|
|
const SKILL_PATH = fileURLToPath(new URL("../skills/bailian-kb/SKILL.md", import.meta.url));
|
|
|
|
describe("parseSkillFile", () => {
|
|
it("takes name and description from frontmatter and strips the block from the body", () => {
|
|
const parsed = parseSkillFile(
|
|
[
|
|
"---",
|
|
"name: demo-skill",
|
|
"description: >-",
|
|
" First line of the folded description,",
|
|
" continued on the next source line.",
|
|
"---",
|
|
"",
|
|
"# Heading",
|
|
"",
|
|
"Body text.",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
expect(parsed?.name).toBe("demo-skill");
|
|
// A folded scalar joins its lines with spaces; the value must arrive unfolded.
|
|
expect(parsed?.description).toBe(
|
|
"First line of the folded description, continued on the next source line.",
|
|
);
|
|
// The registry performs no parsing of its own, so the YAML must already be gone.
|
|
expect(parsed?.content).toBe("\n# Heading\n\nBody text.\n");
|
|
expect(parsed?.content).not.toContain("---");
|
|
expect(parsed?.content).not.toContain("description:");
|
|
});
|
|
|
|
it("carries optional whenToUse and metadata, omitting them when absent or blank", () => {
|
|
const withExtras = parseSkillFile(
|
|
[
|
|
"---",
|
|
"name: demo-skill",
|
|
"description: Routing text.",
|
|
"whenToUse: Extra guidance.",
|
|
"metadata:",
|
|
" owner: platform",
|
|
"---",
|
|
"Body.",
|
|
].join("\n"),
|
|
);
|
|
expect(withExtras?.whenToUse).toBe("Extra guidance.");
|
|
expect(withExtras?.metadata).toEqual({ owner: "platform" });
|
|
|
|
const bare = parseSkillFile("---\nname: demo-skill\ndescription: Routing text.\n---\nBody.");
|
|
expect(bare).not.toHaveProperty("whenToUse");
|
|
expect(bare).not.toHaveProperty("metadata");
|
|
});
|
|
|
|
it("rejects a file without usable frontmatter instead of throwing", () => {
|
|
// No fence at all: a plain markdown file.
|
|
expect(parseSkillFile("# Just markdown\n")).toBeUndefined();
|
|
// Opening fence never closes.
|
|
expect(parseSkillFile("---\nname: demo-skill\n")).toBeUndefined();
|
|
// Unparsable YAML inside the fence.
|
|
expect(parseSkillFile("---\nname: [unclosed\n---\nBody.")).toBeUndefined();
|
|
// Scalar and sequence roots are not frontmatter records.
|
|
expect(parseSkillFile("---\njust a string\n---\nBody.")).toBeUndefined();
|
|
expect(parseSkillFile("---\n- one\n- two\n---\nBody.")).toBeUndefined();
|
|
// name or description missing, blank, or the wrong type.
|
|
expect(parseSkillFile("---\ndescription: Routing text.\n---\nBody.")).toBeUndefined();
|
|
expect(parseSkillFile("---\nname: demo-skill\n---\nBody.")).toBeUndefined();
|
|
expect(parseSkillFile('---\nname: demo-skill\ndescription: " "\n---\nBody.')).toBeUndefined();
|
|
expect(parseSkillFile("---\nname: 42\ndescription: Routing text.\n---\nBody.")).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("the packaged bailian-kb SKILL.md", () => {
|
|
const parsed = parseSkillFile(readFileSync(SKILL_PATH, "utf8"));
|
|
|
|
it("parses, and is the single source of the registered name and description", () => {
|
|
expect(parsed?.name).toBe("bailian-kb");
|
|
expect(parsed?.description).toContain("bl");
|
|
// The frontmatter must state where credentials come from: that sentence used
|
|
// to live in skill.ts and would otherwise be lost with the duplicate.
|
|
expect(parsed?.description).toContain("DASHSCOPE_API_KEY");
|
|
});
|
|
|
|
it("keeps its description within the catalog truncation budget", () => {
|
|
// tool-skill publishes catalog entries through catalogDescription(), which
|
|
// whitespace-normalizes and hard-truncates at 500 characters.
|
|
const normalized = (parsed?.description ?? "").replaceAll(/\s+/g, " ").trim();
|
|
expect(normalized.length).toBeLessThanOrEqual(500);
|
|
});
|
|
|
|
it("registers a body with no frontmatter residue", () => {
|
|
expect(parsed?.content.startsWith("---")).toBe(false);
|
|
expect(parsed?.content).not.toContain("description: >-");
|
|
// The real body still begins with the document heading.
|
|
expect(parsed?.content.trimStart().startsWith("#")).toBe(true);
|
|
});
|
|
});
|