/** * SkillTaskManager — Skill 创建/迭代流水线的检查点管理器 * * 每个 Skill 任务在 skill 目录下写入 .innovator_task.json, * 记录流水线步骤的完成状态。 * 支持两种 pipeline: * - create: Hunt → Digest → SkillCreate (3 步) * - feedback: ReHunt → ReDigest → Evolve → Publish (4 步) */ const fs = require('fs'); const path = require('path'); const TASK_FILE = '.innovator_task.json'; const PIPELINE_STEPS = ['hunt', 'digest', 'skill_create']; const FEEDBACK_PIPELINE_STEPS = ['re_hunt', 're_digest', 'evolve', 'publish']; const EVOLUTION_PIPELINE_STEPS = ['perceive', 'plan', 'hunt', 'digest', 'evolve', 'evaluate', 'reflect', 'publish']; /** * 初始化任务文件 * @param {string} skillDir - Skill 目录绝对路径 * @param {object} meta - { skillName, domain, scope, description, keywords, source, jobId, feedback, pipelineType } */ function initTask(skillDir, meta = {}) { fs.mkdirSync(skillDir, { recursive: true }); const pipelineType = meta.pipelineType || 'create'; const steps = pipelineType === 'evolution' ? EVOLUTION_PIPELINE_STEPS : pipelineType === 'feedback' ? FEEDBACK_PIPELINE_STEPS : PIPELINE_STEPS; const task = { skillName: meta.skillName || path.basename(skillDir), domain: meta.domain || '', scope: meta.scope || '', description: meta.description || '', keywords: meta.keywords || [], source: meta.source || 'unknown', jobId: meta.jobId || null, pipelineType, feedback: meta.feedback || null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), pipeline: steps.map(step => ({ step, status: 'pending', completedAt: null })), }; _write(skillDir, task); return task; } /** * 更新步骤状态 * @param {string} skillDir * @param {string} step - 'hunt' | 'digest' | 'skill_create' * @param {string} status - 'running' | 'done' | 'failed' * @param {string} [error] - 失败原因 */ function updateStep(skillDir, step, status, error) { const task = getTask(skillDir); if (!task) return null; const entry = task.pipeline.find(p => p.step === step); if (!entry) return null; entry.status = status; if (status === 'done') entry.completedAt = new Date().toISOString(); if (status === 'failed') entry.error = error || 'unknown'; task.updatedAt = new Date().toISOString(); _write(skillDir, task); return task; } /** * 读取任务状态 * @param {string} skillDir * @returns {object|null} */ function getTask(skillDir) { const filePath = path.join(skillDir, TASK_FILE); if (!fs.existsSync(filePath)) return null; try { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } catch { return null; } } /** * 获取第一个未完成的步骤 * @param {string} skillDir * @returns {string|null} step name or null if all done */ function getNextPendingStep(skillDir) { const task = getTask(skillDir); if (!task) return null; const pending = task.pipeline.find(p => p.status === 'pending' || p.status === 'running'); return pending ? pending.step : null; } /** * 扫描所有 Skill 目录,返回有未完成任务的列表 * @param {string} skillsDir - skills/ 根目录 * @returns {Array<{skillDir, skillName, nextStep, task}>} */ function findIncompleteTasks(skillsDir) { const results = []; if (!fs.existsSync(skillsDir)) return results; const dirs = fs.readdirSync(skillsDir, { withFileTypes: true }) .filter(d => d.isDirectory()) .map(d => d.name); for (const dirName of dirs) { const skillDir = path.join(skillsDir, dirName); const task = getTask(skillDir); if (!task) continue; const nextStep = getNextPendingStep(skillDir); if (nextStep) { results.push({ skillDir, skillName: task.skillName || dirName, nextStep, task, }); } } return results; } /** * 检查任务是否全部完成 * @param {string} skillDir * @returns {boolean} */ function isCompleted(skillDir) { return getNextPendingStep(skillDir) === null && getTask(skillDir) !== null; } /** 内部写入 */ function _write(skillDir, task) { const filePath = path.join(skillDir, TASK_FILE); fs.writeFileSync(filePath, JSON.stringify(task, null, 2) + '\n', 'utf-8'); } module.exports = { initTask, updateStep, getTask, getNextPendingStep, findIncompleteTasks, isCompleted, PIPELINE_STEPS, FEEDBACK_PIPELINE_STEPS, EVOLUTION_PIPELINE_STEPS, TASK_FILE, };