mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 17:00:59 +00:00
refactor(coding-agent): unify SkillWarning and ResourceDiagnostic
- Create shared diagnostics.ts with ResourceCollision and ResourceDiagnostic types - Update loadSkills() to return diagnostics instead of warnings - Remove SkillWarning type and skillWarningsToDiagnostics() conversion - Update skills.test.ts to use diagnostics
This commit is contained in:
parent
6d0d434d8c
commit
4719929f6a
5 changed files with 95 additions and 111 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { homedir } from "os";
|
||||
import { join, resolve } from "path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { ResourceDiagnostic } from "../src/core/diagnostics.js";
|
||||
import { formatSkillsForPrompt, loadSkills, loadSkillsFromDir, type Skill } from "../src/core/skills.js";
|
||||
|
||||
const fixturesDir = resolve(__dirname, "fixtures/skills");
|
||||
|
|
@ -9,7 +10,7 @@ const collisionFixturesDir = resolve(__dirname, "fixtures/skills-collision");
|
|||
describe("skills", () => {
|
||||
describe("loadSkillsFromDir", () => {
|
||||
it("should load a valid skill", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "valid-skill"),
|
||||
source: "test",
|
||||
});
|
||||
|
|
@ -18,95 +19,101 @@ describe("skills", () => {
|
|||
expect(skills[0].name).toBe("valid-skill");
|
||||
expect(skills[0].description).toBe("A valid skill for testing purposes.");
|
||||
expect(skills[0].source).toBe("test");
|
||||
expect(warnings).toHaveLength(0);
|
||||
expect(diagnostics).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should warn when name doesn't match parent directory", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "name-mismatch"),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
expect(skills).toHaveLength(1);
|
||||
expect(skills[0].name).toBe("different-name");
|
||||
expect(warnings.some((w) => w.message.includes("does not match parent directory"))).toBe(true);
|
||||
expect(
|
||||
diagnostics.some((d: ResourceDiagnostic) => d.message.includes("does not match parent directory")),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should warn when name contains invalid characters", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "invalid-name-chars"),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
expect(skills).toHaveLength(1);
|
||||
expect(warnings.some((w) => w.message.includes("invalid characters"))).toBe(true);
|
||||
expect(diagnostics.some((d: ResourceDiagnostic) => d.message.includes("invalid characters"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should warn when name exceeds 64 characters", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "long-name"),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
expect(skills).toHaveLength(1);
|
||||
expect(warnings.some((w) => w.message.includes("exceeds 64 characters"))).toBe(true);
|
||||
expect(diagnostics.some((d: ResourceDiagnostic) => d.message.includes("exceeds 64 characters"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should warn and skip skill when description is missing", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "missing-description"),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
expect(skills).toHaveLength(0);
|
||||
expect(warnings.some((w) => w.message.includes("description is required"))).toBe(true);
|
||||
expect(diagnostics.some((d: ResourceDiagnostic) => d.message.includes("description is required"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should warn when unknown frontmatter fields are present", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "unknown-field"),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
expect(skills).toHaveLength(1);
|
||||
expect(warnings.some((w) => w.message.includes('unknown frontmatter field "author"'))).toBe(true);
|
||||
expect(warnings.some((w) => w.message.includes('unknown frontmatter field "version"'))).toBe(true);
|
||||
expect(
|
||||
diagnostics.some((d: ResourceDiagnostic) => d.message.includes('unknown frontmatter field "author"')),
|
||||
).toBe(true);
|
||||
expect(
|
||||
diagnostics.some((d: ResourceDiagnostic) => d.message.includes('unknown frontmatter field "version"')),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should load nested skills recursively", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "nested"),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
expect(skills).toHaveLength(1);
|
||||
expect(skills[0].name).toBe("child-skill");
|
||||
expect(warnings).toHaveLength(0);
|
||||
expect(diagnostics).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should skip files without frontmatter", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "no-frontmatter"),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
// no-frontmatter has no description, so it should be skipped
|
||||
expect(skills).toHaveLength(0);
|
||||
expect(warnings.some((w) => w.message.includes("description is required"))).toBe(true);
|
||||
expect(diagnostics.some((d: ResourceDiagnostic) => d.message.includes("description is required"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should warn and skip skill when YAML frontmatter is invalid", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "invalid-yaml"),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
expect(skills).toHaveLength(0);
|
||||
expect(warnings.some((w) => w.message.includes("at line"))).toBe(true);
|
||||
expect(diagnostics.some((d: ResourceDiagnostic) => d.message.includes("at line"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should preserve multiline descriptions from YAML", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "multiline-description"),
|
||||
source: "test",
|
||||
});
|
||||
|
|
@ -114,17 +121,17 @@ describe("skills", () => {
|
|||
expect(skills).toHaveLength(1);
|
||||
expect(skills[0].description).toContain("\n");
|
||||
expect(skills[0].description).toContain("This is a multiline description.");
|
||||
expect(warnings).toHaveLength(0);
|
||||
expect(diagnostics).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should warn when name contains consecutive hyphens", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: join(fixturesDir, "consecutive-hyphens"),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
expect(skills).toHaveLength(1);
|
||||
expect(warnings.some((w) => w.message.includes("consecutive hyphens"))).toBe(true);
|
||||
expect(diagnostics.some((d: ResourceDiagnostic) => d.message.includes("consecutive hyphens"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should load all skills from fixture directory", () => {
|
||||
|
|
@ -140,13 +147,13 @@ describe("skills", () => {
|
|||
});
|
||||
|
||||
it("should return empty for non-existent directory", () => {
|
||||
const { skills, warnings } = loadSkillsFromDir({
|
||||
const { skills, diagnostics } = loadSkillsFromDir({
|
||||
dir: "/non/existent/path",
|
||||
source: "test",
|
||||
});
|
||||
|
||||
expect(skills).toHaveLength(0);
|
||||
expect(warnings).toHaveLength(0);
|
||||
expect(diagnostics).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should use parent directory name when name not in frontmatter", () => {
|
||||
|
|
@ -258,24 +265,24 @@ describe("skills", () => {
|
|||
const emptyCwd = resolve(__dirname, "fixtures/empty-cwd");
|
||||
|
||||
it("should load from explicit skillPaths", () => {
|
||||
const { skills, warnings } = loadSkills({
|
||||
const { skills, diagnostics } = loadSkills({
|
||||
agentDir: emptyAgentDir,
|
||||
cwd: emptyCwd,
|
||||
skillPaths: [join(fixturesDir, "valid-skill")],
|
||||
});
|
||||
expect(skills).toHaveLength(1);
|
||||
expect(skills[0].source).toBe("custom");
|
||||
expect(warnings).toHaveLength(0);
|
||||
expect(diagnostics).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should warn when skill path does not exist", () => {
|
||||
const { skills, warnings } = loadSkills({
|
||||
const { skills, diagnostics } = loadSkills({
|
||||
agentDir: emptyAgentDir,
|
||||
cwd: emptyCwd,
|
||||
skillPaths: ["/non/existent/path"],
|
||||
});
|
||||
expect(skills).toHaveLength(0);
|
||||
expect(warnings.some((w) => w.message.includes("does not exist"))).toBe(true);
|
||||
expect(diagnostics.some((d: ResourceDiagnostic) => d.message.includes("does not exist"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should expand ~ in skillPaths", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue