← back to The Ai Factory
src/stages/intake.js
43 lines
// Stage 1 — intake. Free-text prompt → structured artifact spec via local Ollama.
const { ollama } = require('../llm');
const SYSTEM = `You convert a Steve-style request into a structured plan for "The AI Factory".
The Factory builds Claude Code subagents and /skills (v1 scope).
Return ONLY a JSON object with these keys:
artifact_type one of: "subagent" | "skill"
artifact_name short kebab-case identifier (e.g. "freezer-watcher")
surface where it runs: "claude-code" (subagents/skills) | "cli" | "service"
scope one short sentence describing what the artifact does
triggers array of short phrases that should activate it (max 5)
out_of_scope array of things it explicitly should NOT do (max 3)
No prose, no markdown, no code fences. Just the JSON.`;
async function runIntake({ prompt }) {
const spec = await ollama({
model: 'qwen3:14b',
system: SYSTEM,
prompt,
format: 'json',
temperature: 0.1
});
// Normalize and validate the shape so downstream stages can trust it.
const errors = [];
if (!spec.artifact_type || !['subagent', 'skill'].includes(spec.artifact_type)) {
errors.push(`artifact_type must be "subagent" or "skill", got ${JSON.stringify(spec.artifact_type)}`);
}
if (!spec.artifact_name || !/^[a-z0-9-]{2,60}$/.test(spec.artifact_name)) {
errors.push(`artifact_name must be kebab-case 2-60 chars, got ${JSON.stringify(spec.artifact_name)}`);
}
if (!spec.scope || typeof spec.scope !== 'string') {
errors.push('scope is required (string)');
}
spec.triggers = Array.isArray(spec.triggers) ? spec.triggers.slice(0, 5) : [];
spec.out_of_scope = Array.isArray(spec.out_of_scope) ? spec.out_of_scope.slice(0, 3) : [];
return { spec, errors };
}
module.exports = { runIntake };