计划转换器Skill plan-converter

这个技能用于将各种规划、分析、脑暴输出(如roadmap.jsonl、plan-note.md等)转换为统一的.task/*.json多文件格式,便于unified-execute-with-file工具消费,实现自动化任务管理和执行。关键词:计划转换、任务管理、JSON格式、工作流自动化、软件开发工具。

DevOps 0 次安装 0 次浏览 更新于 3/16/2026

名称: plan-converter 描述: 将任何规划/分析/脑暴输出转换为 .task/*.json 多文件格式。支持 roadmap.jsonl、plan.json、plan-note.md、conclusions.json、synthesis.json。 参数提示: “<输入文件> [-o <输出文件>]”

计划转换器

概述

将任何规划产物转换为 .task/*.json 多文件格式 — 这是由 unified-execute-with-file 消费的单一标准。

模式: cat ~/.ccw/workflows/cli-templates/schemas/task-schema.json

# 自动检测格式,输出到同一目录的 .task/
/codex:plan-converter ".workflow/.req-plan/RPLAN-auth-2025-01-21/roadmap.jsonl"

# 指定输出目录
/codex:plan-converter ".workflow/.planning/CPLAN-xxx/plan-note.md" -o .task/

# 转换脑暴综合
/codex:plan-converter ".workflow/.brainstorm/BS-xxx/synthesis.json"

支持的输入: roadmap.jsonl、.task/*.json(每域)、plan-note.md、conclusions.json、synthesis.json

输出: .task/*.json(每个任务一个文件,位于同一目录的 .task/ 子文件夹中,或指定的 -o 路径)

任务 JSON 模式

每个任务一个独立 JSON 文件 (.task/TASK-{id}.json),遵循统一模式:

模式定义: cat ~/.ccw/workflows/cli-templates/schemas/task-schema.json

生产者使用的字段集 (plan-converter 输出):

身份 (必填):        id、title、description
分类 (可选):   type、priority、effort、action
范围 (可选):            scope、excludes、focus_paths
依赖关系 (必填):     depends_on、parallel_group、inputs、outputs
收敛 (必填):      convergence.criteria、convergence.verification、convergence.definition_of_done
文件 (可选):            files[].path、files[].action、files[].changes、files[].change、files[].target、files[].conflict_risk
实现 (可选):   implementation[]、test.manual_checks、test.success_metrics
规划 (可选):         reference、rationale、risks
上下文 (可选):          source.tool、source.session_id、source.original_id、evidence、risk_items
运行时 (执行时填充):    status、executed_at、result

文件命名: TASK-{id}.json (保留原有 ID 前缀: L0-、T1-、IDEA- 等)

目标输入

$ARGUMENTS

执行过程

步骤 0: 解析参数,解析输入路径
步骤 1: 检测输入格式
步骤 2: 解析输入 → 提取原始记录
步骤 3: 转换 → 统一任务记录
步骤 4: 验证收敛质量
步骤 5: 写入 .task/*.json 输出 + 显示摘要

实现

步骤 0: 解析参数

const args = $ARGUMENTS
const outputMatch = args.match(/-o\s+(\S+)/)
const outputPath = outputMatch ? outputMatch[1] : null
const inputPath = args.replace(/-o\s+\S+/, '').trim()

// 解析绝对路径
const projectRoot = Bash(`git rev-parse --show-toplevel 2>/dev/null || pwd`).trim()
const resolvedInput = path.isAbsolute(inputPath) ? inputPath : `${projectRoot}/${inputPath}`

步骤 1: 检测格式

const filename = path.basename(resolvedInput)
const content = Read(resolvedInput)

function detectFormat(filename, content) {
  if (filename === 'roadmap.jsonl')     return 'roadmap-jsonl'
  if (filename === 'tasks.jsonl')       return 'tasks-jsonl'    // 遗留 JSONL 或每域
  if (filename === 'plan-note.md')      return 'plan-note-md'
  if (filename === 'conclusions.json')  return 'conclusions-json'
  if (filename === 'synthesis.json')    return 'synthesis-json'
  if (filename.endsWith('.jsonl'))      return 'generic-jsonl'
  if (filename.endsWith('.json')) {
    const parsed = JSON.parse(content)
    if (parsed.top_ideas)               return 'synthesis-json'
    if (parsed.recommendations && parsed.key_conclusions) return 'conclusions-json'
    if (parsed.tasks && parsed.focus_area) return 'domain-plan-json'
    return 'unknown-json'
  }
  if (filename.endsWith('.md'))         return 'plan-note-md'
  return 'unknown'
}

格式检测表:

文件名 格式 ID 来源工具
roadmap.jsonl roadmap-jsonl req-plan-with-file
tasks.jsonl(遗留) / .task/*.json tasks-jsonl / task-json collaborative-plan-with-file
plan-note.md plan-note-md collaborative-plan-with-file
conclusions.json conclusions-json analyze-with-file
synthesis.json synthesis-json brainstorm-with-file

步骤 2: 解析输入

roadmap-jsonl (req-plan-with-file)

function parseRoadmapJsonl(content) {
  return content.split('
')
    .filter(line => line.trim())
    .map(line => JSON.parse(line))
}
// 记录包含: id (L0/T1)、name/title、goal/scope、convergence、depends_on 等。

plan-note-md (collaborative-plan-with-file)

function parsePlanNoteMd(content) {
  const tasks = []
  // 1. 提取 YAML 前部元数据用于会话元数据
  const frontmatter = extractYamlFrontmatter(content)

  // 2. 查找所有 "## 任务池 - {Domain}" 部分
  const taskPoolSections = content.match(/## 任务池 - .+/g) || []

  // 3. 对于每个部分,提取任务匹配:
  //    ### TASK-{ID}: {Title} [{domain}]
  //    - **状态**: pending
  //    - **复杂度**: Medium
  //    - **依赖**: TASK-xxx
  //    - **范围**: ...
  //    - **修改点**: `file:location`: change summary
  //    - **冲突风险**: Low
  taskPoolSections.forEach(section => {
    const sectionContent = extractSectionContent(content, section)
    const taskPattern = /### (TASK-\d+):\s+(.+?)\s+\[(.+?)\]/g
    let match
    while ((match = taskPattern.exec(sectionContent)) !== null) {
      const [_, id, title, domain] = match
      const taskBlock = extractTaskBlock(sectionContent, match.index)
      tasks.push({
        id, title, domain,
        ...parseTaskDetails(taskBlock)
      })
    }
  })
  return { tasks, frontmatter }
}

conclusions-json (analyze-with-file)

function parseConclusionsJson(content) {
  const conclusions = JSON.parse(content)
  // 从 conclusions.recommendations[] 提取
  //   { action, rationale, priority }
  // 也可用: conclusions.key_conclusions[]
  return conclusions
}

synthesis-json (brainstorm-with-file)

function parseSynthesisJson(content) {
  const synthesis = JSON.parse(content)
  // 从 synthesis.top_ideas[] 提取
  //   { title, description, score, feasibility, next_steps, key_strengths, main_challenges }
  // 也可用: synthesis.recommendations
  return synthesis
}

步骤 3: 转换为统一记录

roadmap-jsonl → 统一

function transformRoadmap(records, sessionId) {
  return records.map(rec => {
    // roadmap.jsonl 现在使用统一字段名 (title, description, source)
    // 传递基本是直接的
    return {
      id: rec.id,
      title: rec.title,
      description: rec.description,
      type: rec.type || 'feature',
      effort: rec.effort,
      scope: rec.scope,
      excludes: rec.excludes,
      depends_on: rec.depends_on || [],
      parallel_group: rec.parallel_group,
      inputs: rec.inputs,
      outputs: rec.outputs,
      convergence: rec.convergence,  // 已经是统一格式
      risk_items: rec.risk_items,
      source: rec.source || {
        tool: 'req-plan-with-file',
        session_id: sessionId,
        original_id: rec.id
      }
    }
  })
}

plan-note-md → 统一

function transformPlanNote(parsed) {
  const { tasks, frontmatter } = parsed
  return tasks.map(task => ({
    id: task.id,
    title: task.title,
    description: task.scope || task.title,
    type: task.type || inferTypeFromTitle(task.title),
    priority: task.priority || inferPriorityFromEffort(task.effort),
    effort: task.effort || 'medium',
    scope: task.scope,
    depends_on: task.depends_on || [],
    convergence: task.convergence || generateConvergence(task),  // plan-note 现在有收敛
    files: task.files?.map(f => ({
      path: f.path || f.file,
      action: f.action || 'modify',
      changes: f.changes || (f.change ? [f.change] : undefined),
      change: f.change,
      target: f.target,
      conflict_risk: f.conflict_risk
    })),
    source: {
      tool: 'collaborative-plan-with-file',
      session_id: frontmatter.session_id,
      original_id: task.id
    }
  }))
}

// 当来源缺乏收敛时从任务细节生成收敛(遗留回退)
function generateConvergence(task) {
  return {
    criteria: [
      // 从范围和文件推导可测试条件
      // 例如,"修改后的文件编译无错误"
      // 例如,从范围推导:"API 端点返回预期响应"
    ],
    verification: '// 从文件推导 — 例如,测试命令',
    definition_of_done: '// 从范围推导 — 业务语言摘要'
  }
}

conclusions-json → 统一

function transformConclusions(conclusions) {
  return conclusions.recommendations.map((rec, index) => ({
    id: `TASK-${String(index + 1).padStart(3, '0')}`,
    title: rec.action,
    description: rec.rationale,
    type: inferTypeFromAction(rec.action),
    priority: rec.priority,
    depends_on: [],
    convergence: {
      criteria: generateCriteriaFromAction(rec),
      verification: generateVerificationFromAction(rec),
      definition_of_done: generateDoDFromRationale(rec)
    },
    evidence: conclusions.key_conclusions.map(c => c.point),
    source: {
      tool: 'analyze-with-file',
      session_id: conclusions.session_id
    }
  }))
}

function inferTypeFromAction(action) {
  const lower = action.toLowerCase()
  if (/fix|resolve|repair|修复/.test(lower)) return 'fix'
  if (/refactor|restructure|extract|重构/.test(lower)) return 'refactor'
  if (/add|implement|create|新增|实现/.test(lower)) return 'feature'
  if (/improve|optimize|enhance|优化/.test(lower)) return 'enhancement'
  if (/test|coverage|validate|测试/.test(lower)) return 'testing'
  return 'feature'
}

synthesis-json → 统一

function transformSynthesis(synthesis) {
  return synthesis.top_ideas
    .filter(idea => idea.score >= 6)  // 仅可行想法(分数 ≥ 6)
    .map((idea, index) => ({
      id: `IDEA-${String(index + 1).padStart(3, '0')}`,
      title: idea.title,
      description: idea.description,
      type: 'feature',
      priority: idea.score >= 8 ? 'high' : idea.score >= 6 ? 'medium' : 'low',
      effort: idea.feasibility >= 4 ? 'small' : idea.feasibility >= 2 ? 'medium' : 'large',
      depends_on: [],
      convergence: {
        criteria: idea.next_steps || [`${idea.title} 已实现且功能正常`],
        verification: '手动验证功能功能性',
        definition_of_done: idea.description
      },
      risk_items: idea.main_challenges || [],
      source: {
        tool: 'brainstorm-with-file',
        session_id: synthesis.session_id,
        original_id: `idea-${index + 1}`
      }
    }))
}

步骤 4: 验证收敛质量

所有记录在输出前必须通过收敛质量检查。

function validateConvergence(records) {
  const vaguePatterns = /正常|正确|好|可以|没问题|works|fine|good|correct/i
  const technicalPatterns = /compile|build|lint|npm|npx|jest|tsc|eslint/i
  const issues = []

  records.forEach(record => {
    const c = record.convergence
    if (!c) {
      issues.push({ id: record.id, field: 'convergence', issue: '完全缺失' })
      return
    }
    if (!c.criteria?.length) {
      issues.push({ id: record.id, field: 'criteria', issue: '标准数组为空' })
    }
    c.criteria?.forEach((criterion, i) => {
      if (vaguePatterns.test(criterion) && criterion.length < 15) {
        issues.push({ id: record.id, field: `criteria[${i}]`, issue: `太模糊: "${criterion}"` })
      }
    })
    if (!c.verification || c.verification.length < 10) {
      issues.push({ id: record.id, field: 'verification', issue: '太短或缺失' })
    }
    if (technicalPatterns.test(c.definition_of_done)) {
      issues.push({ id: record.id, field: 'definition_of_done', issue: '应使用业务语言' })
    }
  })

  return issues
}

// 自动修复策略:
// | 问题                | 修复                                          |
// |----------------------|----------------------------------------------|
// | 缺失收敛  | 从标题 + 描述 + 文件生成     |
// | 模糊标准       | 从上下文替换为具体条件  |
// | 短验证   | 扩展基于文件的测试建议        |
// | 技术性 DoD        | 用业务语言重写                  |

步骤 5: 写入 .task/*.json 输出与摘要

// 确定输出目录
const outputDir = outputPath
  || `${path.dirname(resolvedInput)}/.task`

// 创建输出目录
Bash(`mkdir -p ${outputDir}`)

// 清理记录: 移除未定义/空的可选字段
const cleanedRecords = records.map(rec => {
  const clean = { ...rec }
  Object.keys(clean).forEach(key => {
    if (clean[key] === undefined || clean[key] === null) delete clean[key]
    if (Array.isArray(clean[key]) && clean[key].length === 0 && key !== 'depends_on') delete clean[key]
  })
  return clean
})

// 写入独立任务 JSON 文件
cleanedRecords.forEach(record => {
  const filename = `${record.id}.json`
  Write(`${outputDir}/${filename}`, JSON.stringify(record, null, 2))
})

// 显示摘要
// | 来源          | 格式            | 记录数 | 问题 |
// |-----------------|-------------------|---------|--------|
// | roadmap.jsonl   | roadmap-jsonl     | 4       | 0      |
//
// 输出: .workflow/.req-plan/RPLAN-xxx/.task/ (4 文件)
// 记录: 4 个任务带收敛标准
// 质量: 所有收敛检查通过

转换矩阵

来源 来源工具 ID 模式 有收敛 有文件 有优先级 有来源
roadmap.jsonl(渐进) req-plan L0-L3
roadmap.jsonl(直接) req-plan T1-TN
.task/TASK-*.json(每域) collaborative-plan TASK-NNN (详细) 可选
plan-note.md collaborative-plan TASK-NNN 生成 (从修改文件) 从努力度
conclusions.json analyze TASK-NNN 生成
synthesis.json brainstorm IDEA-NNN 生成 从分数

图例: 是 = 来源已有,生成 = 转换器产生,否 = 不可用

错误处理

情况 操作
输入文件未找到 报告错误,建议检查路径
未知格式 报告错误,列出支持格式
空输入 报告错误,不创建输出文件
收敛验证失败 自动修复可能处,报告剩余问题
部分解析失败 转换有效记录,报告跳过项
输出文件已存在 覆盖并警告消息
plan-note.md 有空白部分 跳过空域,报告在摘要中

现在为以下执行 plan-converter: $ARGUMENTS