← 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 };