feat(FN-1968): add skill manifest previews to agent import

- Add SkillManifest support in core with parseSkillManifest and skills directory parsing/export wiring
- Extend /api/agents/import responses with dry-run skill previews and skillsCount metadata
- Show parsed package skills in AgentImportModal with dedicated preview UI and styles
- Add parser, API route, and modal tests covering skill manifest parsing and preview rendering
- Keep mission interview assertion generation explicit for milestone/slice fallbacks and update memory guidance for dashboard build resolution
This commit is contained in:
Fusion
2026-04-16 16:05:12 -07:00
committed by gsxdsm
parent 8de7feeb13
commit ca2e085d76
11 changed files with 259 additions and 10 deletions

View File

@@ -17,6 +17,7 @@ import {
parseCompanyManifest,
parseProjectManifest,
parseSingleAgentManifest,
parseSkillManifest,
parseTaskManifest,
parseTeamManifest,
parseYamlFrontmatter,
@@ -185,6 +186,24 @@ schedule:
expect(manifest.assignee).toBe("./agents/ceo/AGENTS.md");
expect(manifest.schedule?.timezone).toBe("America/New_York");
});
it("parses SKILL.md with instruction body", () => {
const manifest = parseSkillManifest(`---
name: review
schema: agentcompanies/v1
kind: skill
---
# review
Add skill instructions here.`);
expect(manifest).toEqual({
name: "review",
schema: "agentcompanies/v1",
kind: "skill",
instructionBody: "# review\n\nAdd skill instructions here.",
});
});
});
describe("directory parsing", () => {
@@ -252,6 +271,43 @@ name: Solo Agent
expect(pkg.teams).toEqual([]);
});
it("parses skills from skills subdirectories", () => {
const root = createTempDir();
writeTextFile(
join(root, "skills", "review", "SKILL.md"),
`---
name: review
kind: skill
---
# review`,
);
writeTextFile(
join(root, "skills", "strategy", "SKILL.md"),
`---
name: strategy
kind: skill
---
# strategy`,
);
const pkg = parseCompanyDirectory(root);
expect(pkg.skills).toHaveLength(2);
expect(pkg.skills?.map((skill) => skill.name)).toEqual(["review", "strategy"]);
});
it("returns empty skills when skills directory is absent", () => {
const root = createTempDir();
writeTextFile(
join(root, "agents", "solo", "AGENTS.md"),
`---
name: Solo Agent
---`,
);
const pkg = parseCompanyDirectory(root);
expect(pkg.skills).toEqual([]);
});
it("parses empty directory", () => {
const root = createTempDir();
const pkg = parseCompanyDirectory(root);
@@ -261,6 +317,7 @@ name: Solo Agent
teams: [],
projects: [],
tasks: [],
skills: [],
});
});

View File

@@ -18,6 +18,7 @@ import type {
AgentManifest,
CompanyManifest,
ProjectManifest,
SkillManifest,
TaskManifest,
TeamManifest,
} from "./agent-companies-types.js";
@@ -344,6 +345,15 @@ export function parseTaskManifest(content: string): TaskManifest {
return parseTypedManifest<TaskManifest>(content, "task");
}
export function parseSkillManifest(content: string): SkillManifest {
const { frontmatter, body } = parseYamlFrontmatter(content);
requireName(frontmatter, "skill");
return {
...(frontmatter as unknown as SkillManifest),
instructionBody: body,
};
}
function parseManifestFile<T>(filePath: string, parser: (content: string) => T): T {
try {
return parser(readFileSync(filePath, "utf-8"));
@@ -443,6 +453,7 @@ export function parseCompanyDirectory(dirPath: string): AgentCompaniesPackage {
parseProjectManifest,
),
tasks: parseManifestSubdirectories(resolvedPath, "tasks", "TASK.md", parseTaskManifest),
skills: parseManifestSubdirectories(resolvedPath, "skills", "SKILL.md", parseSkillManifest),
};
}

View File

@@ -70,12 +70,17 @@ export interface TaskManifest extends AgentCompaniesFrontmatter {
};
}
export interface SkillManifest extends AgentCompaniesFrontmatter {
instructionBody?: string;
}
export interface AgentCompaniesPackage {
company?: CompanyManifest;
agents: AgentManifest[];
teams: TeamManifest[];
projects: ProjectManifest[];
tasks: TaskManifest[];
skills?: SkillManifest[];
}
export interface AgentCompaniesImportResult {

View File

@@ -482,6 +482,7 @@ export type {
AgentManifest,
ProjectManifest,
TaskManifest,
SkillManifest,
SourceReference,
} from "./agent-companies-types.js";
@@ -495,6 +496,7 @@ export {
parseSingleAgentManifest,
parseProjectManifest,
parseTaskManifest,
parseSkillManifest,
parseCompanyDirectory,
parseCompanyArchive,
mapRoleToCapability,