From 05a66c2283e29fd0dafab97be2e68a5c712779d9 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Tue, 14 Jul 2026 15:24:02 -0400 Subject: [PATCH] refactor(shared): move the SKILL.md serializer into @posthog/shared MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit serializeSkillMarkdown lived in workspace-server; move it to @posthog/shared and have workspace-server re-export it. SKILL.md is a serialization contract the cloud sandbox consumes, so any client that emits skills (the workspace-server bundler today, a browser bundler later) must produce byte-identical output — sharing one implementation keeps them from drifting. No behavior change: desktop's bundler resolves the same function through the re-export. Generated-By: PostHog Code Task-Id: 77d13a30-444d-4045-9d80-5e2a9f2e68ae --- packages/shared/src/index.ts | 6 ++- packages/shared/src/skills.ts | 40 +++++++++++++++++++ .../skills/write-skill-frontmatter.ts | 40 ++----------------- 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 35fbca6805..6b2a55ac62 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -224,7 +224,11 @@ export type { SkillSource, UploadableSkillSource, } from "./skills"; -export { SKILL_EXISTS_MARKER, stripFrontmatter } from "./skills"; +export { + SKILL_EXISTS_MARKER, + serializeSkillMarkdown, + stripFrontmatter, +} from "./skills"; export type { ArtifactType, PostHogAPIConfig, diff --git a/packages/shared/src/skills.ts b/packages/shared/src/skills.ts index 540929b7be..540b92eba5 100644 --- a/packages/shared/src/skills.ts +++ b/packages/shared/src/skills.ts @@ -33,6 +33,46 @@ export interface ExportedSkill { files: ExportedSkillFile[]; } +/** + * Serializes a SKILL.md file from frontmatter metadata plus a markdown body. + * + * The output must round-trip through `parseSkillFrontmatter` and also be valid + * YAML for the agents that consume these files, so scalars fall back from plain + * → double-quoted → literal block as they get more hostile. Lives here (shared) + * so both the workspace-server bundler and the web-host bundler produce the + * exact same SKILL.md — this is a serialization contract consumed by the cloud + * sandbox, so it must not drift between hosts. + */ +export function serializeSkillMarkdown( + meta: { name: string; description: string }, + body: string, +): string { + const frontmatter = [ + "---", + `name: ${serializeSkillScalar(meta.name)}`, + `description: ${serializeSkillScalar(meta.description)}`, + "---", + ].join("\n"); + + const trimmedBody = body.replace(/^\n+/, ""); + return `${frontmatter}\n\n${trimmedBody.trimEnd()}\n`; +} + +const SKILL_PLAIN_SAFE = /^[A-Za-z0-9][A-Za-z0-9 _.,;()/-]*$/; + +function serializeSkillScalar(value: string): string { + if (value === "") return '""'; + if (!value.includes("\n")) { + if (SKILL_PLAIN_SAFE.test(value) && !value.endsWith(" ")) return value; + if (!value.includes('"') && !value.includes("\\")) return `"${value}"`; + } + // Literal block: survives quotes, backslashes, and newlines. + const lines = value + .split("\n") + .map((line) => (line.trim() ? ` ${line}` : "")); + return `|-\n${lines.join("\n")}`; +} + /** * Server "skill already exists" messages must include this marker verbatim; * the UI keys its overwrite-confirmation flow on it. diff --git a/packages/workspace-server/src/services/skills/write-skill-frontmatter.ts b/packages/workspace-server/src/services/skills/write-skill-frontmatter.ts index 0bc24979b1..b597453ada 100644 --- a/packages/workspace-server/src/services/skills/write-skill-frontmatter.ts +++ b/packages/workspace-server/src/services/skills/write-skill-frontmatter.ts @@ -1,36 +1,4 @@ -/** - * Serializes a SKILL.md file from frontmatter metadata plus a markdown body. - * - * The output must round-trip through `parseSkillFrontmatter` and also be - * valid YAML for the agents that consume these files, so scalars fall back - * from plain → double-quoted → literal block as they get more hostile. - */ -export function serializeSkillMarkdown( - meta: { name: string; description: string }, - body: string, -): string { - const frontmatter = [ - "---", - `name: ${serializeScalar(meta.name)}`, - `description: ${serializeScalar(meta.description)}`, - "---", - ].join("\n"); - - const trimmedBody = body.replace(/^\n+/, ""); - return `${frontmatter}\n\n${trimmedBody.trimEnd()}\n`; -} - -const PLAIN_SAFE = /^[A-Za-z0-9][A-Za-z0-9 _.,;()/-]*$/; - -function serializeScalar(value: string): string { - if (value === "") return '""'; - if (!value.includes("\n")) { - if (PLAIN_SAFE.test(value) && !value.endsWith(" ")) return value; - if (!value.includes('"') && !value.includes("\\")) return `"${value}"`; - } - // Literal block: survives quotes, backslashes, and newlines. - const lines = value - .split("\n") - .map((line) => (line.trim() ? ` ${line}` : "")); - return `|-\n${lines.join("\n")}`; -} +// The SKILL.md serializer lives in @posthog/shared so the workspace-server +// bundler and the web-host bundler emit byte-identical SKILL.md files — this is +// a serialization contract the cloud sandbox consumes and must not drift. +export { serializeSkillMarkdown } from "@posthog/shared";