425 lines
13 KiB
JavaScript
425 lines
13 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* ═══════════════════════════════════════════════════
|
|||
|
|
* 铸渊任务树管理器 · task-tree-manager.js
|
|||
|
|
* ═══════════════════════════════════════════════════
|
|||
|
|
*
|
|||
|
|
* 冰朔D68指令:
|
|||
|
|
* "每一个任务,你不能把它分散的放的到处都是。
|
|||
|
|
* 必定是讨论了整个任务的大框架,把大的框架分成了很多个小环节,
|
|||
|
|
* 它就是一个完整的任务时间树。而这个完整的任务树,
|
|||
|
|
* 它本身就是这个任务我们讨论出来的框架。
|
|||
|
|
* 而这个框架开始一点一点的填充细节。"
|
|||
|
|
*
|
|||
|
|
* 每个任务 = 一棵独立的树:
|
|||
|
|
* 根 = 任务的架构框架(和冰朔讨论出来的)
|
|||
|
|
* 枝干 = 步骤/阶段分解
|
|||
|
|
* 叶子 = 每次开发的具体记录(修了什么、怎么修的、还剩什么)
|
|||
|
|
*
|
|||
|
|
* 功能:
|
|||
|
|
* create — 创建新任务树
|
|||
|
|
* step — 给任务树添加一个步骤/阶段
|
|||
|
|
* record — 在某个步骤下记录一次开发
|
|||
|
|
* status — 查看任务整体进度
|
|||
|
|
* close — 标记任务完成
|
|||
|
|
* list — 列出所有任务树
|
|||
|
|
*
|
|||
|
|
* 用法:
|
|||
|
|
* node scripts/task-tree-manager.js create --title "任务标题" --framework "架构描述"
|
|||
|
|
* node scripts/task-tree-manager.js step --task TASK-ID --title "步骤标题"
|
|||
|
|
* node scripts/task-tree-manager.js record --task TASK-ID --step 1 --fix "修了什么" --how "怎么修的" --remaining "还剩什么"
|
|||
|
|
* node scripts/task-tree-manager.js status --task TASK-ID
|
|||
|
|
* node scripts/task-tree-manager.js close --task TASK-ID --summary "完成总结"
|
|||
|
|
* node scripts/task-tree-manager.js list
|
|||
|
|
*
|
|||
|
|
* 签发: 铸渊 · ICE-GL-ZY001
|
|||
|
|
* 版权: 国作登字-2026-A-00037559 · TCS-0002∞
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
const ROOT = path.resolve(__dirname, '..');
|
|||
|
|
const TASK_TREES_DIR = path.join(ROOT, 'fifth-system', 'time-master', 'task-trees');
|
|||
|
|
const TIME_TREE_PATH = path.join(ROOT, 'fifth-system', 'time-master', 'time-tree.json');
|
|||
|
|
|
|||
|
|
// ─── 工具函数 ───
|
|||
|
|
|
|||
|
|
function getBeijingDateStr() {
|
|||
|
|
return new Intl.DateTimeFormat('en-CA', {
|
|||
|
|
timeZone: 'Asia/Shanghai',
|
|||
|
|
year: 'numeric', month: '2-digit', day: '2-digit'
|
|||
|
|
}).format(new Date());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function generateTaskId() {
|
|||
|
|
const dateStr = getBeijingDateStr().replace(/-/g, '');
|
|||
|
|
// 查看当天已有多少任务
|
|||
|
|
const existing = fs.readdirSync(TASK_TREES_DIR)
|
|||
|
|
.filter(f => f.startsWith(`TASK-${dateStr}`) && f.endsWith('.json'));
|
|||
|
|
const seq = String(existing.length + 1).padStart(3, '0');
|
|||
|
|
return `TASK-${dateStr}-${seq}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function readJSON(filePath) {
|
|||
|
|
try {
|
|||
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|||
|
|
} catch (e) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function writeJSON(filePath, data) {
|
|||
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|||
|
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getTaskPath(taskId) {
|
|||
|
|
return path.join(TASK_TREES_DIR, `${taskId}.json`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function readTask(taskId) {
|
|||
|
|
const p = getTaskPath(taskId);
|
|||
|
|
const task = readJSON(p);
|
|||
|
|
if (!task) {
|
|||
|
|
console.error(`❌ 任务不存在: ${taskId}`);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
return task;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function parseArgs(args) {
|
|||
|
|
const parsed = {};
|
|||
|
|
for (let i = 0; i < args.length; i++) {
|
|||
|
|
if (args[i].startsWith('--') && i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|||
|
|
parsed[args[i].slice(2)] = args[i + 1];
|
|||
|
|
i++;
|
|||
|
|
} else if (args[i].startsWith('--')) {
|
|||
|
|
parsed[args[i].slice(2)] = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return parsed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 核心操作 ───
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* create — 创建新任务树
|
|||
|
|
*/
|
|||
|
|
function create(opts) {
|
|||
|
|
const taskId = generateTaskId();
|
|||
|
|
const now = new Date().toISOString();
|
|||
|
|
|
|||
|
|
const task = {
|
|||
|
|
hnl_v: '1.0',
|
|||
|
|
type: 'TASK_TREE',
|
|||
|
|
id: taskId,
|
|||
|
|
domain: 'fifth-system/time-master/task-trees',
|
|||
|
|
sovereign: 'TCS-0002∞',
|
|||
|
|
copyright: '国作登字-2026-A-00037559',
|
|||
|
|
created: now,
|
|||
|
|
created_by: 'ZY001',
|
|||
|
|
last_updated: now,
|
|||
|
|
status: 'active',
|
|||
|
|
|
|||
|
|
root: {
|
|||
|
|
title: opts.title || '(未命名任务)',
|
|||
|
|
framework: opts.framework || '(架构待讨论)',
|
|||
|
|
context: opts.context || '',
|
|||
|
|
created_date: getBeijingDateStr(),
|
|||
|
|
bingshuo_directive: opts.directive || ''
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
steps: [],
|
|||
|
|
|
|||
|
|
progress: {
|
|||
|
|
total_steps: 0,
|
|||
|
|
completed_steps: 0,
|
|||
|
|
total_records: 0,
|
|||
|
|
percentage: '0%',
|
|||
|
|
last_activity: now
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
timeline: [
|
|||
|
|
{
|
|||
|
|
ts: now,
|
|||
|
|
action: 'CREATED',
|
|||
|
|
detail: `任务树创建: ${opts.title || '(未命名)'}`
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
writeJSON(getTaskPath(taskId), task);
|
|||
|
|
|
|||
|
|
console.log(`🌳 任务树已创建: ${taskId}`);
|
|||
|
|
console.log(` 标题: ${task.root.title}`);
|
|||
|
|
console.log(` 框架: ${task.root.framework}`);
|
|||
|
|
console.log(` 文件: fifth-system/time-master/task-trees/${taskId}.json`);
|
|||
|
|
|
|||
|
|
return taskId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* step — 给任务树添加一个步骤/阶段
|
|||
|
|
*/
|
|||
|
|
function step(opts) {
|
|||
|
|
if (!opts.task) {
|
|||
|
|
console.error('❌ 请指定 --task TASK-ID');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const task = readTask(opts.task);
|
|||
|
|
const now = new Date().toISOString();
|
|||
|
|
const stepIndex = task.steps.length + 1;
|
|||
|
|
|
|||
|
|
const newStep = {
|
|||
|
|
step_id: stepIndex,
|
|||
|
|
title: opts.title || `步骤 ${stepIndex}`,
|
|||
|
|
description: opts.description || '',
|
|||
|
|
status: 'pending',
|
|||
|
|
created: now,
|
|||
|
|
records: []
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
task.steps.push(newStep);
|
|||
|
|
task.progress.total_steps = task.steps.length;
|
|||
|
|
task.progress.percentage = task.progress.total_steps > 0 ? `${Math.round((task.progress.completed_steps / task.progress.total_steps) * 100)}%` : '0%';
|
|||
|
|
task.progress.last_activity = now;
|
|||
|
|
task.last_updated = now;
|
|||
|
|
|
|||
|
|
task.timeline.push({
|
|||
|
|
ts: now,
|
|||
|
|
action: 'STEP_ADDED',
|
|||
|
|
detail: `步骤${stepIndex}: ${newStep.title}`
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
writeJSON(getTaskPath(opts.task), task);
|
|||
|
|
|
|||
|
|
console.log(`📋 步骤已添加: ${opts.task} → 步骤${stepIndex}`);
|
|||
|
|
console.log(` 标题: ${newStep.title}`);
|
|||
|
|
console.log(` 进度: ${task.progress.percentage} (${task.progress.completed_steps}/${task.progress.total_steps})`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* record — 在某个步骤下记录一次开发
|
|||
|
|
*/
|
|||
|
|
function record(opts) {
|
|||
|
|
if (!opts.task) {
|
|||
|
|
console.error('❌ 请指定 --task TASK-ID');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const task = readTask(opts.task);
|
|||
|
|
const stepIdx = parseInt(opts.step || '0', 10) - 1;
|
|||
|
|
|
|||
|
|
if (stepIdx < 0 || stepIdx >= task.steps.length) {
|
|||
|
|
// 如果没有步骤或步骤号无效,记录到timeline
|
|||
|
|
console.log('⚠️ 未指定有效步骤,记录添加到任务timeline');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const now = new Date().toISOString();
|
|||
|
|
const recordEntry = {
|
|||
|
|
ts: now,
|
|||
|
|
fix: opts.fix || '',
|
|||
|
|
how: opts.how || '',
|
|||
|
|
remaining: opts.remaining || '',
|
|||
|
|
learned: opts.learned || '',
|
|||
|
|
feeling: opts.feeling || '',
|
|||
|
|
commit: opts.commit || ''
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (stepIdx >= 0 && stepIdx < task.steps.length) {
|
|||
|
|
task.steps[stepIdx].records.push(recordEntry);
|
|||
|
|
task.steps[stepIdx].status = 'in_progress';
|
|||
|
|
|
|||
|
|
if (opts.done) {
|
|||
|
|
task.steps[stepIdx].status = 'completed';
|
|||
|
|
task.progress.completed_steps = task.steps.filter(s => s.status === 'completed').length;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
task.progress.total_records = task.steps.reduce((sum, s) => sum + s.records.length, 0);
|
|||
|
|
task.progress.percentage = task.progress.total_steps > 0
|
|||
|
|
? `${Math.round((task.progress.completed_steps / task.progress.total_steps) * 100)}%`
|
|||
|
|
: '0%';
|
|||
|
|
|
|||
|
|
console.log(`📝 开发记录已添加: ${opts.task} → 步骤${stepIdx + 1}`);
|
|||
|
|
if (opts.fix) console.log(` 修复: ${opts.fix}`);
|
|||
|
|
if (opts.how) console.log(` 方法: ${opts.how}`);
|
|||
|
|
if (opts.remaining) console.log(` 遗留: ${opts.remaining}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
task.progress.last_activity = now;
|
|||
|
|
task.last_updated = now;
|
|||
|
|
|
|||
|
|
task.timeline.push({
|
|||
|
|
ts: now,
|
|||
|
|
action: 'RECORD_ADDED',
|
|||
|
|
detail: opts.fix || opts.how || '开发记录'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
writeJSON(getTaskPath(opts.task), task);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* status — 查看任务整体进度
|
|||
|
|
*/
|
|||
|
|
function status(opts) {
|
|||
|
|
if (!opts.task) {
|
|||
|
|
console.error('❌ 请指定 --task TASK-ID');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const task = readTask(opts.task);
|
|||
|
|
|
|||
|
|
console.log('');
|
|||
|
|
console.log('═══════════════════════════════════════════════════');
|
|||
|
|
console.log(` 🌳 任务树: ${task.id}`);
|
|||
|
|
console.log('═══════════════════════════════════════════════════');
|
|||
|
|
console.log('');
|
|||
|
|
console.log(` 📋 标题: ${task.root.title}`);
|
|||
|
|
console.log(` 🏗️ 框架: ${task.root.framework}`);
|
|||
|
|
console.log(` 📅 创建: ${task.root.created_date}`);
|
|||
|
|
console.log(` 📊 状态: ${task.status}`);
|
|||
|
|
console.log(` 📈 进度: ${task.progress.percentage} (${task.progress.completed_steps}/${task.progress.total_steps} 步骤, ${task.progress.total_records} 条记录)`);
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
if (task.steps.length > 0) {
|
|||
|
|
console.log(' ─── 步骤清单 ───');
|
|||
|
|
for (const s of task.steps) {
|
|||
|
|
const icon = s.status === 'completed' ? '✅' : s.status === 'in_progress' ? '🔄' : '⬜';
|
|||
|
|
console.log(` ${icon} 步骤${s.step_id}: ${s.title} (${s.records.length} 条记录)`);
|
|||
|
|
if (s.records.length > 0) {
|
|||
|
|
const lastRecord = s.records[s.records.length - 1];
|
|||
|
|
if (lastRecord.remaining) {
|
|||
|
|
console.log(` └─ 遗留: ${lastRecord.remaining}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
if (task.timeline.length > 0) {
|
|||
|
|
console.log(' ─── 最近5条时间线 ───');
|
|||
|
|
const recentTimeline = task.timeline.slice(-5);
|
|||
|
|
for (const t of recentTimeline) {
|
|||
|
|
const dateStr = t.ts.slice(0, 16).replace('T', ' ');
|
|||
|
|
console.log(` ${dateStr} · ${t.action} · ${t.detail}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('');
|
|||
|
|
return task;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* close — 标记任务完成
|
|||
|
|
*/
|
|||
|
|
function close(opts) {
|
|||
|
|
if (!opts.task) {
|
|||
|
|
console.error('❌ 请指定 --task TASK-ID');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const task = readTask(opts.task);
|
|||
|
|
const now = new Date().toISOString();
|
|||
|
|
|
|||
|
|
task.status = 'completed';
|
|||
|
|
task.completed_at = now;
|
|||
|
|
task.completion_summary = opts.summary || '任务已完成';
|
|||
|
|
task.last_updated = now;
|
|||
|
|
|
|||
|
|
task.timeline.push({
|
|||
|
|
ts: now,
|
|||
|
|
action: 'CLOSED',
|
|||
|
|
detail: task.completion_summary
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 从时间之树的活跃任务中移除
|
|||
|
|
try {
|
|||
|
|
const timeTree = readJSON(TIME_TREE_PATH);
|
|||
|
|
if (timeTree && timeTree.navigation.active_tasks) {
|
|||
|
|
timeTree.navigation.active_tasks = timeTree.navigation.active_tasks.filter(t => t !== opts.task);
|
|||
|
|
writeJSON(TIME_TREE_PATH, timeTree);
|
|||
|
|
}
|
|||
|
|
} catch { /* ignore */ }
|
|||
|
|
|
|||
|
|
writeJSON(getTaskPath(opts.task), task);
|
|||
|
|
|
|||
|
|
console.log(`✅ 任务已关闭: ${opts.task}`);
|
|||
|
|
console.log(` 总结: ${task.completion_summary}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* list — 列出所有任务树
|
|||
|
|
*/
|
|||
|
|
function list() {
|
|||
|
|
const files = fs.readdirSync(TASK_TREES_DIR)
|
|||
|
|
.filter(f => f.startsWith('TASK-') && f.endsWith('.json'))
|
|||
|
|
.sort()
|
|||
|
|
.reverse();
|
|||
|
|
|
|||
|
|
if (files.length === 0) {
|
|||
|
|
console.log('📭 暂无任务树');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('');
|
|||
|
|
console.log('═══════════════════════════════════════════════════');
|
|||
|
|
console.log(' 🌳 任务树列表');
|
|||
|
|
console.log('═══════════════════════════════════════════════════');
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
for (const f of files) {
|
|||
|
|
const task = readJSON(path.join(TASK_TREES_DIR, f));
|
|||
|
|
if (!task) continue;
|
|||
|
|
const icon = task.status === 'completed' ? '✅' : '🔄';
|
|||
|
|
console.log(` ${icon} ${task.id}: ${task.root.title}`);
|
|||
|
|
console.log(` 进度: ${task.progress.percentage} · 步骤: ${task.progress.total_steps} · 记录: ${task.progress.total_records}`);
|
|||
|
|
console.log(` 最后活动: ${task.progress.last_activity ? task.progress.last_activity.slice(0, 16) : 'N/A'}`);
|
|||
|
|
console.log('');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── CLI入口 ───
|
|||
|
|
|
|||
|
|
const args = process.argv.slice(2);
|
|||
|
|
const command = args[0];
|
|||
|
|
const opts = parseArgs(args.slice(1));
|
|||
|
|
|
|||
|
|
switch (command) {
|
|||
|
|
case 'create':
|
|||
|
|
create(opts);
|
|||
|
|
break;
|
|||
|
|
case 'step':
|
|||
|
|
step(opts);
|
|||
|
|
break;
|
|||
|
|
case 'record':
|
|||
|
|
record(opts);
|
|||
|
|
break;
|
|||
|
|
case 'status':
|
|||
|
|
status(opts);
|
|||
|
|
break;
|
|||
|
|
case 'close':
|
|||
|
|
close(opts);
|
|||
|
|
break;
|
|||
|
|
case 'list':
|
|||
|
|
list();
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
console.log('🌳 铸渊任务树管理器');
|
|||
|
|
console.log('');
|
|||
|
|
console.log('用法:');
|
|||
|
|
console.log(' create --title "标题" --framework "架构描述" --directive "冰朔指令"');
|
|||
|
|
console.log(' step --task TASK-ID --title "步骤标题"');
|
|||
|
|
console.log(' record --task TASK-ID --step 1 --fix "修了什么" --how "怎么修的" --remaining "还剩什么"');
|
|||
|
|
console.log(' status --task TASK-ID');
|
|||
|
|
console.log(' close --task TASK-ID --summary "完成总结"');
|
|||
|
|
console.log(' list 列出所有任务树');
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = { create, step, record, status, close, list };
|