Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .release-notes/refactor-bundled-skill-source.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 内置 Skill 内容保持一致
<!-- release-target: v2 -->

## Bug 修复

- Agent、Workflow 和 MCP 模板现在与托管 Skill 库读取同一份内置 Skill 内容,避免开发版与安装包因副本不同步而出现行为差异。
2 changes: 1 addition & 1 deletion apps/main-2.0/assets/bundled-skills/brainstorming/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,4 @@ A browser-based companion for showing mockups, diagrams, and visual options duri
A question about a UI topic is not automatically a visual question. "What does personality mean in this context?" is a conceptual question — use the terminal. "Which wizard layout works better?" is a visual question — use the browser.

If they agree to the companion, read the detailed guide before proceeding:
`skills/brainstorming/visual-companion.md`
`references/visual-companion.md`
1 change: 0 additions & 1 deletion apps/main-2.0/scripts/check-source-entrypoints.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ function collectSourceFiles(directory) {
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
const entryPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
if (entryPath === path.join(sourceRoot, "automation", "engine", "shared", "bundled-skills")) continue;
files.push(...collectSourceFiles(entryPath));
continue;
}
Expand Down
21 changes: 19 additions & 2 deletions apps/main-2.0/scripts/package-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,26 @@ try {
await access(path.join(installedRoot, "bin", "openviking-opencode-plugin.mjs"));
await access(path.join(installedRoot, "bin", "setup-openviking-memory-hooks.cjs"));
await access(path.join(installedRoot, "THIRD_PARTY_NOTICES.md"));
await access(path.join(installedRoot, "assets", "bundled-skills", "rewrite-technical-tutorial", "SKILL.md"));
const automationSkillIds = [
"brainstorming",
"frontend-design",
"feishu-tech-diagram",
"handoff",
"skill-creator",
"systematic-debugging",
"personal-finance-planning",
"resume-optimization",
"paper-writing",
"rewrite-technical-tutorial",
"refactor-review-knowledge",
"code-review-and-quality",
];
await Promise.all(
automationSkillIds.map((id) =>
access(path.join(installedRoot, "assets", "bundled-skills", id, "SKILL.md")),
),
);
const diagramSkillRoot = path.join(installedRoot, "assets", "bundled-skills", "feishu-tech-diagram");
await access(path.join(diagramSkillRoot, "SKILL.md"));
await access(path.join(diagramSkillRoot, "tests", "validate_assets.py"));
const diagramSpecs = JSON.parse(await readFile(path.join(diagramSkillRoot, "references", "template-specs.json"), "utf8"));
if (!Array.isArray(diagramSpecs) || diagramSpecs.length !== 66) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ describe("MCP bridge", () => {
const skillTemplates = (await (await bridgeRequest("/mcp/skill-templates/list", bridge.token, {})).json()) as any;
expect(skillTemplates).toMatchObject({
ok: true,
templates: expect.arrayContaining([expect.objectContaining({ id: "brainstorming", sourcePath: "src/shared/bundled-skills/brainstorming/SKILL.md" })]),
templates: expect.arrayContaining([expect.objectContaining({ id: "brainstorming", sourcePath: "assets/bundled-skills/brainstorming/SKILL.md" })]),
});

const skillSearch = (await (await bridgeRequest("/mcp/skills/search-online", bridge.token, { query: "frontend design anthropic" })).json()) as any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ interface ImportedSkillMetadata {
}

function bundledSkillSourceDir(template: SkillTemplate): string | undefined {
if (!template.sourcePath?.startsWith("src/shared/bundled-skills/")) return undefined;
if (!template.sourcePath?.startsWith("assets/bundled-skills/")) return undefined;
const relativeDir = path.dirname(template.sourcePath);
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
const candidates = [
path.resolve(process.cwd(), relativeDir),
path.resolve(moduleDir, "..", "..", relativeDir),
path.resolve(moduleDir, "..", "shared", "bundled-skills", template.id),
path.resolve(moduleDir, "..", "..", "..", "..", "..", relativeDir),
];
return candidates.find((candidate) => pathExistsSync(candidate));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,34 @@ describe("bundled Skill assets", () => {
it("lists the technical writing and diagram Skills as official writing templates", () => {
const templates = loadBundledSkillTemplates();

expect(templates.map((template) => template.id)).toEqual([
"brainstorming",
"frontend-design",
"feishu-tech-diagram",
"handoff",
"skill-creator",
"systematic-debugging",
"personal-finance-planning",
"resume-optimization",
"paper-writing",
"rewrite-technical-tutorial",
"refactor-review-knowledge",
"code-review-and-quality",
]);

expect(templates.find((template) => template.id === "rewrite-technical-tutorial"))
.toMatchObject({
name: "rewrite-technical-tutorial",
categoryId: "writing",
sourceType: "official",
sourcePath: "assets/bundled-skills/rewrite-technical-tutorial/SKILL.md",
});
expect(templates.find((template) => template.id === "feishu-tech-diagram"))
.toMatchObject({
name: "feishu-tech-diagram",
categoryId: "writing",
sourceType: "official",
sourcePath: "assets/bundled-skills/feishu-tech-diagram/SKILL.md",
});
});

Expand Down
33 changes: 22 additions & 11 deletions apps/main-2.0/src/automation/engine/shared/bundled-skill-library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,29 @@ const BUNDLED_SKILL_ORDER = [
"code-review-and-quality",
];

const skillMarkdownFiles = import.meta.glob<string>("./bundled-skills/*/SKILL.md", {
const BUNDLED_SKILL_ASSET_ROOT = "../../../../assets/bundled-skills";

const skillMarkdownFiles = import.meta.glob<string>("../../../../assets/bundled-skills/*/SKILL.md", {
eager: true,
import: "default",
query: "?raw",
});
const skillTranslationFiles = import.meta.glob<string>("./bundled-skills/*/SKILL.zh.md", {
const skillTranslationFiles = import.meta.glob<string>("../../../../assets/bundled-skills/*/SKILL.zh.md", {
eager: true,
import: "default",
query: "?raw",
});
const skillMetadataFiles = import.meta.glob<string>("./bundled-skills/*/metadata.json", {
const skillMetadataFiles = import.meta.glob<string>("../../../../assets/bundled-skills/*/metadata.json", {
eager: true,
import: "default",
query: "?raw",
});
const directSkillAssetFiles = import.meta.glob<string>("./bundled-skills/*/*", {
const directSkillAssetFiles = import.meta.glob<string>("../../../../assets/bundled-skills/*/*", {
eager: true,
import: "default",
query: "?raw",
});
const nestedSkillAssetFiles = import.meta.glob<string>("./bundled-skills/*/**/*", {
const nestedSkillAssetFiles = import.meta.glob<string>("../../../../assets/bundled-skills/*/**/*", {
eager: true,
import: "default",
query: "?raw",
Expand All @@ -54,17 +56,20 @@ export interface BundledSkillAsset {
}

function skillIdFromPath(filePath: string): string {
const match = filePath.match(/\.\/bundled-skills\/([^/]+)\/[^/]+$/);
const match = filePath.match(/\/bundled-skills\/([^/]+)\/[^/]+$/);
if (!match?.[1]) throw new Error(`Invalid bundled skill path: ${filePath}`);
return match[1];
}

function sourcePathFor(filePath: string): string {
return `src/shared/${filePath.replace(/^\.\//, "")}`;
const marker = "assets/bundled-skills/";
const markerIndex = filePath.indexOf(marker);
if (markerIndex === -1) throw new Error(`Invalid bundled skill asset path: ${filePath}`);
return filePath.slice(markerIndex);
}

export function bundledSkillAssetsFor(skillId: string): BundledSkillAsset[] {
const prefix = `./bundled-skills/${skillId}/`;
const prefix = `${BUNDLED_SKILL_ASSET_ROOT}/${skillId}/`;
const assets = new Map<string, string>();
for (const [filePath, contents] of Object.entries({ ...directSkillAssetFiles, ...nestedSkillAssetFiles })) {
if (!filePath.startsWith(prefix)) continue;
Expand Down Expand Up @@ -103,7 +108,7 @@ function stripYamlScalar(value: string): string {
}

function metadataFor(skillId: string): BundledSkillMetadata {
const raw = skillMetadataFiles[`./bundled-skills/${skillId}/metadata.json`];
const raw = skillMetadataFiles[`${BUNDLED_SKILL_ASSET_ROOT}/${skillId}/metadata.json`];
if (!raw) return {};
const parsed = JSON.parse(raw) as Partial<BundledSkillMetadata>;
const metadata: BundledSkillMetadata = {};
Expand All @@ -116,7 +121,13 @@ function metadataFor(skillId: string): BundledSkillMetadata {

function orderedSkillEntries(): Array<[string, string]> {
const order = new Map(BUNDLED_SKILL_ORDER.map((id, index) => [id, index]));
return Object.entries(skillMarkdownFiles).sort(([leftPath], [rightPath]) => {
const entries = Object.entries(skillMarkdownFiles).filter(([filePath]) =>
order.has(skillIdFromPath(filePath)),
);
const found = new Set(entries.map(([filePath]) => skillIdFromPath(filePath)));
const missing = BUNDLED_SKILL_ORDER.filter((id) => !found.has(id));
if (missing.length > 0) throw new Error(`Missing bundled Skill assets: ${missing.join(", ")}`);
return entries.sort(([leftPath], [rightPath]) => {
const leftId = skillIdFromPath(leftPath);
const rightId = skillIdFromPath(rightPath);
const leftOrder = order.get(leftId) ?? Number.MAX_SAFE_INTEGER;
Expand Down Expand Up @@ -145,7 +156,7 @@ export function loadBundledSkillTemplates(): SkillTemplate[] {
};
const sourceUrl = metadata.sourceUrl;
if (sourceUrl) template.sourceUrl = sourceUrl;
const translationZh = skillTranslationFiles[`./bundled-skills/${id}/SKILL.zh.md`];
const translationZh = skillTranslationFiles[`${BUNDLED_SKILL_ASSET_ROOT}/${id}/SKILL.zh.md`];
if (translationZh) template.translationZh = normalizeNewlines(translationZh);
return template;
});
Expand Down

This file was deleted.

Loading
Loading