78 lines
3.1 KiB
TypeScript
78 lines
3.1 KiB
TypeScript
import { allAgents, discoverAgents } from "../agents/discover-agents.ts";
|
|
import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
|
|
import { allWorkflows, discoverWorkflows } from "../workflows/discover-workflows.ts";
|
|
import { validateWorkflowForTeam } from "../workflows/validate-workflow.ts";
|
|
|
|
export interface ValidationIssue {
|
|
level: "error" | "warning";
|
|
resource: string;
|
|
message: string;
|
|
}
|
|
|
|
export interface ValidationReport {
|
|
issues: ValidationIssue[];
|
|
agents: number;
|
|
teams: number;
|
|
workflows: number;
|
|
}
|
|
|
|
export function validateResources(cwd: string): ValidationReport {
|
|
const agents = allAgents(discoverAgents(cwd));
|
|
const teams = allTeams(discoverTeams(cwd));
|
|
const workflows = allWorkflows(discoverWorkflows(cwd));
|
|
const agentNames = new Set(agents.map((agent) => agent.name));
|
|
const workflowNames = new Set(workflows.map((workflow) => workflow.name));
|
|
const issues: ValidationIssue[] = [];
|
|
|
|
for (const agent of agents) {
|
|
const modelValues = [agent.model, ...(agent.fallbackModels ?? [])].filter((value): value is string => typeof value === "string" && value.length > 0);
|
|
for (const model of modelValues) {
|
|
if (/\s/.test(model)) {
|
|
issues.push({ level: "warning", resource: `agent:${agent.name}`, message: `Model reference '${model}' contains whitespace.` });
|
|
}
|
|
if (model.includes("/") && model.split("/").some((part) => part.trim() === "")) {
|
|
issues.push({ level: "warning", resource: `agent:${agent.name}`, message: `Model reference '${model}' has an empty provider/model segment.` });
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const team of teams) {
|
|
for (const role of team.roles) {
|
|
if (!agentNames.has(role.agent)) {
|
|
issues.push({ level: "error", resource: `team:${team.name}`, message: `Role '${role.name}' references unknown agent '${role.agent}'.` });
|
|
}
|
|
}
|
|
if (team.defaultWorkflow && !workflowNames.has(team.defaultWorkflow)) {
|
|
issues.push({ level: "error", resource: `team:${team.name}`, message: `defaultWorkflow references unknown workflow '${team.defaultWorkflow}'.` });
|
|
}
|
|
const workflow = workflows.find((candidate) => candidate.name === team.defaultWorkflow);
|
|
if (workflow) {
|
|
for (const error of validateWorkflowForTeam(workflow, team)) {
|
|
issues.push({ level: "error", resource: `workflow:${workflow.name}`, message: `Team '${team.name}': ${error}` });
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const workflow of workflows) {
|
|
if (workflow.steps.length === 0) {
|
|
issues.push({ level: "warning", resource: `workflow:${workflow.name}`, message: "Workflow has no steps." });
|
|
}
|
|
}
|
|
|
|
return { issues, agents: agents.length, teams: teams.length, workflows: workflows.length };
|
|
}
|
|
|
|
export function formatValidationReport(report: ValidationReport): string {
|
|
const lines = [
|
|
"pi-crew resource validation:",
|
|
`Agents: ${report.agents}`,
|
|
`Teams: ${report.teams}`,
|
|
`Workflows: ${report.workflows}`,
|
|
`Issues: ${report.issues.length}`,
|
|
];
|
|
if (report.issues.length > 0) {
|
|
lines.push("", ...report.issues.map((issue) => `- ${issue.level.toUpperCase()} ${issue.resource}: ${issue.message}`));
|
|
}
|
|
return lines.join("\n");
|
|
}
|