@jiggai/recipes 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/handlers/team.ts +25 -3
- package/src/lib/recipe-lint.ts +44 -0
package/package.json
CHANGED
package/src/handlers/team.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { pickRecipeId } from "../lib/recipe-id";
|
|
|
13
13
|
import { recipeIdTakenForTeam, validateRecipeAndSkills, writeWorkspaceRecipeFile } from "../lib/scaffold-utils";
|
|
14
14
|
import { scaffoldAgentFromRecipe } from "./scaffold";
|
|
15
15
|
import { reconcileRecipeCronJobs } from "./cron";
|
|
16
|
+
import { lintRecipe } from "../lib/recipe-lint";
|
|
16
17
|
|
|
17
18
|
async function ensureTeamDirectoryStructure(
|
|
18
19
|
teamDir: string,
|
|
@@ -119,6 +120,19 @@ async function scaffoldTeamAgents(
|
|
|
119
120
|
): Promise<AgentScaffoldResult[]> {
|
|
120
121
|
const agents = recipe.agents ?? [];
|
|
121
122
|
if (!agents.length) throw new Error("Team recipe must include agents[]");
|
|
123
|
+
|
|
124
|
+
// Resilience: some custom recipes may define templates but forget files[].
|
|
125
|
+
// In that case, scaffold a minimal per-role file set.
|
|
126
|
+
const baseFiles = (recipe.files ?? []).length
|
|
127
|
+
? (recipe.files ?? [])
|
|
128
|
+
: [
|
|
129
|
+
{ path: "SOUL.md", template: "soul", mode: "createOnly" },
|
|
130
|
+
{ path: "AGENTS.md", template: "agents", mode: "createOnly" },
|
|
131
|
+
{ path: "TOOLS.md", template: "tools", mode: "createOnly" },
|
|
132
|
+
{ path: "STATUS.md", template: "status", mode: "createOnly" },
|
|
133
|
+
{ path: "NOTES.md", template: "notes", mode: "createOnly" },
|
|
134
|
+
];
|
|
135
|
+
|
|
122
136
|
const results: AgentScaffoldResult[] = [];
|
|
123
137
|
for (const a of agents) {
|
|
124
138
|
const role = a.role;
|
|
@@ -131,9 +145,9 @@ async function scaffoldTeamAgents(
|
|
|
131
145
|
requiredSkills: recipe.requiredSkills,
|
|
132
146
|
optionalSkills: recipe.optionalSkills,
|
|
133
147
|
templates: recipe.templates,
|
|
134
|
-
files:
|
|
148
|
+
files: baseFiles.map((f) => ({
|
|
135
149
|
...f,
|
|
136
|
-
template: f.template.includes(".") ? f.template : `${role}.${f.template}`,
|
|
150
|
+
template: String(f.template).includes(".") ? f.template : `${role}.${f.template}`,
|
|
137
151
|
})),
|
|
138
152
|
tools: a.tools ?? recipe.tools,
|
|
139
153
|
};
|
|
@@ -143,7 +157,8 @@ async function scaffoldTeamAgents(
|
|
|
143
157
|
agentName,
|
|
144
158
|
update: overwrite,
|
|
145
159
|
filesRootDir: roleDir,
|
|
146
|
-
|
|
160
|
+
// IMPORTANT: per-role workspace so the Kitchen UI / agent files panel reads the role files.
|
|
161
|
+
workspaceRootDir: roleDir,
|
|
147
162
|
vars: { teamId, teamDir, role, agentId, agentName, roleDir },
|
|
148
163
|
});
|
|
149
164
|
results.push({ role, agentId, ...r });
|
|
@@ -178,6 +193,13 @@ export async function handleScaffoldTeam(
|
|
|
178
193
|
};
|
|
179
194
|
}
|
|
180
195
|
const { loaded, recipe, cfg, workspaceRoot: baseWorkspace } = validation;
|
|
196
|
+
|
|
197
|
+
// Lint (warn-only) for common team scaffolding pitfalls.
|
|
198
|
+
for (const issue of lintRecipe(recipe)) {
|
|
199
|
+
if (issue.level === "warn") console.warn(`[recipes] WARN ${issue.code}: ${issue.message}`);
|
|
200
|
+
else console.warn(`[recipes] ${issue.code}: ${issue.message}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
181
203
|
const teamId = String(options.teamId);
|
|
182
204
|
const teamDir = path.resolve(baseWorkspace, "..", `workspace-${teamId}`);
|
|
183
205
|
await ensureDir(teamDir);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { RecipeFrontmatter } from "./recipe-frontmatter";
|
|
2
|
+
|
|
3
|
+
export type RecipeLintIssue = { level: "warn" | "error"; code: string; message: string };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lightweight recipe lint for common scaffolding pitfalls.
|
|
7
|
+
* Intentionally conservative: warnings should be actionable and low-noise.
|
|
8
|
+
*/
|
|
9
|
+
export function lintRecipe(recipe: RecipeFrontmatter): RecipeLintIssue[] {
|
|
10
|
+
const issues: RecipeLintIssue[] = [];
|
|
11
|
+
|
|
12
|
+
if ((recipe.kind ?? "") === "team") {
|
|
13
|
+
const agents = recipe.agents ?? [];
|
|
14
|
+
const files = recipe.files ?? [];
|
|
15
|
+
const templates = recipe.templates ?? {};
|
|
16
|
+
|
|
17
|
+
if (agents.length && files.length === 0) {
|
|
18
|
+
const hasRoleTemplates = Object.keys(templates).some((k) => k.includes(".") && /(\.soul|\.agents|\.tools|\.status|\.notes)$/.test(k));
|
|
19
|
+
issues.push({
|
|
20
|
+
level: "warn",
|
|
21
|
+
code: "team.missing_files",
|
|
22
|
+
message:
|
|
23
|
+
`Team recipe has agents[] but no files[]. Per-role workspaces will be empty. ` +
|
|
24
|
+
`Add a files: section (e.g. SOUL.md/AGENTS.md/TOOLS.md/STATUS.md/NOTES.md) ` +
|
|
25
|
+
`or rely on scaffold defaults. ${hasRoleTemplates ? "(Detected role templates; likely meant to scaffold role files.)" : ""}`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Templates exist but won't be used if files[] doesn't reference them.
|
|
30
|
+
if (agents.length && Object.keys(templates).length && files.length) {
|
|
31
|
+
const baseTemplates = new Set(files.map((f) => String(f.template ?? "").trim()).filter(Boolean));
|
|
32
|
+
const missing = ["soul", "agents", "tools"].filter((t) => !baseTemplates.has(t) && ![...baseTemplates].some((x) => x.endsWith(`.${t}`)));
|
|
33
|
+
if (missing.length) {
|
|
34
|
+
issues.push({
|
|
35
|
+
level: "warn",
|
|
36
|
+
code: "team.files.missing_core_templates",
|
|
37
|
+
message: `Team recipe files[] is missing some common templates (${missing.join(", ")}). This may be intentional, but usually each role should have SOUL.md/AGENTS.md/TOOLS.md at minimum.`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return issues;
|
|
44
|
+
}
|