feat: build local skills registry

This commit is contained in:
2026-06-05 09:30:00 +02:00
parent f0a948aec9
commit 370aeec2a0
18 changed files with 1334 additions and 16 deletions

View File

@@ -0,0 +1,250 @@
import assert from "node:assert/strict";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, sep } from "node:path";
import test from "node:test";
import {
type AuditUsedSkill,
loadSkillsRegistry,
parseSkillsRegistry,
toAuditUsedSkill,
SKILL_CATEGORIES,
} from "../lib/skills-registry";
function assertIncludes(values: readonly string[], value: string) {
assert.ok(values.includes(value), `Expected ${value} in [${values.join(", ")}]`);
}
function withTempProjectRegistry(
source: string,
run: () => Promise<void> | void,
) {
return mkdtemp(`${tmpdir()}${sep}`).then(async (projectRoot) => {
const registryPath = join(projectRoot, "skills.md");
const originalCwd = process.cwd();
await writeFile(registryPath, source, "utf8");
process.chdir(projectRoot);
try {
await run();
} finally {
process.chdir(originalCwd);
await rm(projectRoot, { recursive: true, force: true });
}
});
}
test("parseSkillsRegistry parses valid entries, trims whitespace, and normalizes category", async () => {
const registrySource = `
## Design Audit
Purpose: Evaluate layout, visual hierarchy, and CTA clarity.
When to use: Use when a homepage is available and should be assessed for conversion quality.
When not to use: Don't run during technical outages or non-web touchpoints.
Required input: Homepage URL, top-level page sections, style language, brand context.
Expected output: Prioritized improvement list with concrete design changes.
Category: design
Version: 2026.06
Source: skills/design-audit.md
`;
const parsed = parseSkillsRegistry(registrySource);
assert.equal(parsed.length, 1);
const entry = parsed.at(0);
assert.ok(entry);
assert.equal(entry!.name, "Design Audit");
assert.equal(entry!.purpose, "Evaluate layout, visual hierarchy, and CTA clarity.");
assert.equal(entry!.category, "design");
assert.equal(entry!.version, "2026.06");
assert.equal(entry!.source, "skills/design-audit.md");
});
test("parseSkillsRegistry accepts indented field labels", () => {
const registrySource = `
## Local SEO Boost
Purpose: Evaluate visibility for local search with nearby intent.
When to use: Use for local business pages and service locations.
When not to use: Avoid for non-local marketing pages.
Required input: City, address, NAP consistency.
Expected output: Prioritized local SEO recommendations.
Category: seo
`;
const parsed = parseSkillsRegistry(registrySource);
assert.equal(parsed.length, 1);
const entry = parsed.at(0);
assert.ok(entry);
assert.equal(entry!.name, "Local SEO Boost");
assert.equal(entry!.purpose, "Evaluate visibility for local search with nearby intent.");
assert.equal(entry!.category, "seo");
});
test("parseSkillsRegistry throws for missing required fields", () => {
const registrySource = `
## UX Friction Review
Purpose: Review interaction patterns for friction points.
When to use: Use for lead capture and booking flows.
When not to use: Use only when there is a user journey.
Required input: Session flow and target action.
Category: ux
`;
assert.throws(
() => parseSkillsRegistry(registrySource),
/missing required field "Expected output"/i,
);
});
test("parseSkillsRegistry throws for unknown category", () => {
const registrySource = `
## Bad Category Example
Purpose: Example.
When to use: Example scenario.
When not to use: Never for this case.
Required input: Example data.
Expected output: Example output.
Category: analytics
`;
assert.throws(
() => parseSkillsRegistry(registrySource),
/unknown category "analytics"/i,
);
});
test("parseSkillsRegistry throws for duplicate skill names", () => {
const registrySource = `
## Local SEO Boost
Purpose: Strengthen local SERPs.
When to use: Use for local service businesses.
When not to use: Not for international-only landing pages.
Required input: Name, address, service area.
Expected output: Local SEO gaps and quick wins.
Category: seo
## Local SEO Boost
Purpose: Another local SEO pass.
When to use: Use for new regions.
When not to use: Skip for pure lead-gen pages.
Required input: Name, address, service area.
Expected output: Competitor baseline.
Category: seo
`;
assert.throws(
() => parseSkillsRegistry(registrySource),
/duplicate skill name "Local SEO Boost"/i,
);
});
test("parseSkillsRegistry accepts all configured categories", () => {
assertIncludes(SKILL_CATEGORIES, "design");
assertIncludes(SKILL_CATEGORIES, "ux");
assertIncludes(SKILL_CATEGORIES, "marketing");
assertIncludes(SKILL_CATEGORIES, "copy");
assertIncludes(SKILL_CATEGORIES, "seo");
assertIncludes(SKILL_CATEGORIES, "offer");
const registrySource = SKILL_CATEGORIES.map(
(category) => `
## ${category}-skill
Purpose: Valid for ${category}.
When to use: Use for ${category} tasks.
When not to use: Skip when ${category} is not in scope.
Required input: Category inputs.
Expected output: Category-specific recommendations.
Category: ${category}
`,
).join("\n\n");
const parsed = parseSkillsRegistry(registrySource);
assert.equal(parsed.length, SKILL_CATEGORIES.length);
for (const category of SKILL_CATEGORIES) {
const match = parsed.find((entry) => entry.name === `${category}-skill`);
assert.ok(match, `Expected parsed entry for ${category}`);
assert.equal(match.category, category);
}
});
test("loadSkillsRegistry reads skills.md from process.cwd() by default", async () => {
await withTempProjectRegistry(
`
## Offer Writing
Purpose: Build offer-focused copy for outreach.
When to use: Use before drafting proposals.
When not to use: Avoid when no offer exists.
Required input: Offer structure and pricing envelope.
Expected output: Offer draft and pricing emphasis.
Category: offer
`,
async () => {
const parsed = await loadSkillsRegistry();
const parsedEntry = parsed.find((entry) => entry.name === "Offer Writing");
assert.ok(parsedEntry);
assert.equal(parsedEntry.category, "offer");
},
);
});
test("loadSkillsRegistry accepts an explicit registry path", async () => {
const projectRoot = await mkdtemp(`${tmpdir()}${sep}`);
const registryPath = join(projectRoot, "seed-skills.md");
await writeFile(
registryPath,
`
## Design Audit
Purpose: Validate design quality for local business pages.
When to use: Use for a quick visual prioritization pass.
When not to use: Skip when no public page exists.
Required input: Homepage URL and target conversion goal.
Expected output: Ranked design actions with confidence.
Category: design
`,
"utf8",
);
try {
const parsed = await loadSkillsRegistry(registryPath);
assert.equal(parsed.at(0)?.name, "Design Audit");
} finally {
await rm(projectRoot, { recursive: true, force: true });
}
});
test("toAuditUsedSkill returns only required audit-facing fields", () => {
const skill = {
name: "Copy Clarity",
purpose: "Reduce complexity and improve readability.",
whenToUse: "When existing copy is verbose.",
whenNotToUse: "Skip if website is plain text-only.",
requiredInput: "Page sections and CTAs.",
expectedOutput: "A concise writing pass.",
category: "copy",
version: "1.0",
source: "skills/copy-clarity.md",
} satisfies {
name: string;
purpose: string;
whenToUse: string;
whenNotToUse: string;
requiredInput: string;
expectedOutput: string;
category: "copy";
version: string;
source: string;
};
const auditUsed = toAuditUsedSkill(skill);
const expected: AuditUsedSkill = {
name: "Copy Clarity",
category: "copy",
version: "1.0",
source: "skills/copy-clarity.md",
};
assert.deepEqual(auditUsed, expected);
});