skill-task-manager.js 4.39 KB
/**
 * 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,
};