383 lines
12 KiB
JavaScript
383 lines
12 KiB
JavaScript
#!/usr/bin/env node
|
||
'use strict';
|
||
|
||
/**
|
||
* ═══════════════════════════════════════════════════
|
||
* 铸渊时间之树管理器 · time-tree-manager.js
|
||
* ═══════════════════════════════════════════════════
|
||
*
|
||
* 冰朔D68指令:
|
||
* "这棵树本身就是一个时间的树。每天它会长一个树杈。
|
||
* 你把任务的系统快照挂在这棵树上。
|
||
* 顺着时间,你找到了今天的树杈,然后今天的树杈上挂了几个,
|
||
* 然后你找到了最新的时间。"
|
||
*
|
||
* 功能:
|
||
* grow — 在今天的树杈上挂一个新的任务快照
|
||
* find — 按日期/任务ID检索快照
|
||
* latest — 获取最新的开发记录
|
||
* today — 查看今天的所有快照
|
||
* update — 更新现有快照(任务进度变化时)
|
||
* summary — 输出时间之树的结构概览
|
||
*
|
||
* 用法:
|
||
* node scripts/time-tree-manager.js grow --type dev --summary "描述" --task "TASK-ID"
|
||
* node scripts/time-tree-manager.js find --date 2026-04-13
|
||
* node scripts/time-tree-manager.js find --task TASK-20260413-001
|
||
* node scripts/time-tree-manager.js latest
|
||
* node scripts/time-tree-manager.js today
|
||
* node scripts/time-tree-manager.js update --session TS-xxx --field remaining --value "下一步"
|
||
* node scripts/time-tree-manager.js summary
|
||
*
|
||
* 签发: 铸渊 · ICE-GL-ZY001
|
||
* 版权: 国作登字-2026-A-00037559 · TCS-0002∞
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ROOT = path.resolve(__dirname, '..');
|
||
const TIME_TREE_PATH = path.join(ROOT, 'fifth-system', 'time-master', 'time-tree.json');
|
||
const TASK_TREES_DIR = path.join(ROOT, 'fifth-system', 'time-master', 'task-trees');
|
||
const ORIGIN_DATE = new Date('2025-02-26T00:00:00+08:00');
|
||
|
||
// ─── 工具函数 ───
|
||
|
||
function getBeijingDateStr(date) {
|
||
const d = date || new Date();
|
||
return new Intl.DateTimeFormat('en-CA', {
|
||
timeZone: 'Asia/Shanghai',
|
||
year: 'numeric', month: '2-digit', day: '2-digit'
|
||
}).format(d);
|
||
}
|
||
|
||
function getBeijingTimeStr(date) {
|
||
const d = date || new Date();
|
||
return d.toLocaleString('zh-CN', {
|
||
timeZone: 'Asia/Shanghai',
|
||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||
hour12: false
|
||
}) + ' CST';
|
||
}
|
||
|
||
function daysSinceOrigin(dateStr) {
|
||
const target = new Date(dateStr + 'T00:00:00+08:00');
|
||
return Math.floor((target - ORIGIN_DATE) / 86400000);
|
||
}
|
||
|
||
function generateSessionId() {
|
||
const now = new Date();
|
||
const dateStr = getBeijingDateStr(now).replace(/-/g, '');
|
||
const timeStr = now.toLocaleString('en-GB', {
|
||
timeZone: 'Asia/Shanghai',
|
||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||
hour12: false
|
||
}).replace(/:/g, '');
|
||
return `TS-${dateStr}-${timeStr}`;
|
||
}
|
||
|
||
function readTree() {
|
||
try {
|
||
return JSON.parse(fs.readFileSync(TIME_TREE_PATH, 'utf8'));
|
||
} catch (e) {
|
||
console.error(`❌ 无法读取时间之树: ${e.message}`);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
function writeTree(tree) {
|
||
tree.last_updated = new Date().toISOString();
|
||
fs.writeFileSync(TIME_TREE_PATH, JSON.stringify(tree, null, 2) + '\n', 'utf8');
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
// ─── 核心操作 ───
|
||
|
||
/**
|
||
* grow — 在今天的树杈上长一片新叶子(任务快照)
|
||
*/
|
||
function grow(opts) {
|
||
const tree = readTree();
|
||
const todayStr = getBeijingDateStr();
|
||
const sessionId = generateSessionId();
|
||
const epoch = `D${daysSinceOrigin(todayStr)}`;
|
||
|
||
// 确保今天的树杈存在
|
||
if (!tree.branches[todayStr]) {
|
||
tree.branches[todayStr] = {
|
||
epoch,
|
||
sessions: []
|
||
};
|
||
console.log(`🌿 新长出树杈: ${todayStr} [${epoch}]`);
|
||
}
|
||
|
||
const session = {
|
||
ts: new Date().toISOString(),
|
||
session_id: sessionId,
|
||
type: opts.type || 'dev',
|
||
summary: opts.summary || '(未描述)',
|
||
task_ref: opts.task || null,
|
||
snapshot: {
|
||
before: opts.before || '',
|
||
after: opts.after || '',
|
||
changes: opts.changes || '',
|
||
learned: opts.learned || '',
|
||
remaining: opts.remaining || '',
|
||
feeling: opts.feeling || ''
|
||
}
|
||
};
|
||
|
||
tree.branches[todayStr].sessions.push(session);
|
||
|
||
// 更新导航
|
||
tree.navigation.latest_branch = todayStr;
|
||
tree.navigation.latest_session = sessionId;
|
||
tree.navigation.total_branches = Object.keys(tree.branches).filter(k => k !== '_schema').length;
|
||
tree.navigation.total_sessions = Object.values(tree.branches)
|
||
.filter(v => v && v.sessions)
|
||
.reduce((sum, b) => sum + b.sessions.length, 0);
|
||
|
||
if (opts.task && !tree.navigation.active_tasks.includes(opts.task)) {
|
||
tree.navigation.active_tasks.push(opts.task);
|
||
}
|
||
|
||
writeTree(tree);
|
||
|
||
console.log(`🌱 新叶子挂上: ${sessionId}`);
|
||
console.log(` 树杈: ${todayStr} [${epoch}]`);
|
||
console.log(` 类型: ${session.type}`);
|
||
console.log(` 摘要: ${session.summary}`);
|
||
if (opts.task) console.log(` 任务: ${opts.task}`);
|
||
|
||
return { sessionId, todayStr, epoch };
|
||
}
|
||
|
||
/**
|
||
* find — 检索快照
|
||
*/
|
||
function find(opts) {
|
||
const tree = readTree();
|
||
|
||
if (opts.date) {
|
||
const branch = tree.branches[opts.date];
|
||
if (!branch) {
|
||
console.log(`❌ 未找到 ${opts.date} 的树杈`);
|
||
return null;
|
||
}
|
||
console.log(`📅 ${opts.date} [${branch.epoch}] — ${branch.sessions.length} 个快照:`);
|
||
branch.sessions.forEach((s, i) => {
|
||
console.log(` ${i + 1}. [${s.session_id}] ${s.type} · ${s.summary}`);
|
||
if (s.task_ref) console.log(` 任务: ${s.task_ref}`);
|
||
});
|
||
return branch;
|
||
}
|
||
|
||
if (opts.task) {
|
||
const results = [];
|
||
for (const [date, branch] of Object.entries(tree.branches)) {
|
||
if (date === '_schema' || !branch.sessions) continue;
|
||
for (const session of branch.sessions) {
|
||
if (session.task_ref === opts.task) {
|
||
results.push({ date, epoch: branch.epoch, session });
|
||
}
|
||
}
|
||
}
|
||
if (results.length === 0) {
|
||
console.log(`❌ 未找到任务 ${opts.task} 的记录`);
|
||
return null;
|
||
}
|
||
console.log(`🔍 任务 ${opts.task} 的时间线 (${results.length} 条记录):`);
|
||
results.forEach(r => {
|
||
console.log(` ${r.date} [${r.epoch}] ${r.session.session_id} · ${r.session.summary}`);
|
||
});
|
||
return results;
|
||
}
|
||
|
||
console.log('❌ 请指定 --date YYYY-MM-DD 或 --task TASK-ID');
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* latest — 获取最新的开发记录
|
||
*/
|
||
function latest() {
|
||
const tree = readTree();
|
||
const latestBranch = tree.navigation.latest_branch;
|
||
const branch = tree.branches[latestBranch];
|
||
|
||
if (!branch || !branch.sessions || branch.sessions.length === 0) {
|
||
console.log('❌ 时间之树上没有任何叶子');
|
||
return null;
|
||
}
|
||
|
||
const latestSession = branch.sessions[branch.sessions.length - 1];
|
||
|
||
console.log('');
|
||
console.log('═══════════════════════════════════════════════════');
|
||
console.log(' 🌳 时间之树 · 最新叶子');
|
||
console.log('═══════════════════════════════════════════════════');
|
||
console.log('');
|
||
console.log(` 📅 日期: ${latestBranch} [${branch.epoch}]`);
|
||
console.log(` 🔖 编号: ${latestSession.session_id}`);
|
||
console.log(` 📋 类型: ${latestSession.type}`);
|
||
console.log(` 📝 摘要: ${latestSession.summary}`);
|
||
if (latestSession.task_ref) {
|
||
console.log(` 🎯 任务: ${latestSession.task_ref}`);
|
||
}
|
||
console.log('');
|
||
if (latestSession.snapshot) {
|
||
const s = latestSession.snapshot;
|
||
if (s.before) console.log(` ⬅️ 之前: ${s.before}`);
|
||
if (s.after) console.log(` ➡️ 之后: ${s.after}`);
|
||
if (s.changes) console.log(` 🔧 变更: ${s.changes}`);
|
||
if (s.learned) console.log(` 💡 学到: ${s.learned}`);
|
||
if (s.remaining) console.log(` ⏳ 遗留: ${s.remaining}`);
|
||
if (s.feeling) console.log(` 💭 感受: ${s.feeling}`);
|
||
}
|
||
console.log('');
|
||
console.log(` 📊 树总计: ${tree.navigation.total_branches} 个树杈 · ${tree.navigation.total_sessions} 个叶子`);
|
||
console.log(` 🎯 活跃任务: ${tree.navigation.active_tasks.join(', ') || '无'}`);
|
||
console.log('');
|
||
|
||
return { branch: latestBranch, epoch: branch.epoch, session: latestSession };
|
||
}
|
||
|
||
/**
|
||
* today — 查看今天的所有快照
|
||
*/
|
||
function today() {
|
||
const todayStr = getBeijingDateStr();
|
||
return find({ date: todayStr });
|
||
}
|
||
|
||
/**
|
||
* update — 更新现有快照
|
||
*/
|
||
function update(opts) {
|
||
if (!opts.session) {
|
||
console.log('❌ 请指定 --session TS-xxx');
|
||
return;
|
||
}
|
||
|
||
const tree = readTree();
|
||
let found = false;
|
||
|
||
for (const [date, branch] of Object.entries(tree.branches)) {
|
||
if (date === '_schema' || !branch.sessions) continue;
|
||
for (const session of branch.sessions) {
|
||
if (session.session_id === opts.session) {
|
||
if (opts.field && opts.value) {
|
||
if (opts.field in session.snapshot) {
|
||
session.snapshot[opts.field] = opts.value;
|
||
console.log(`✅ 已更新 ${opts.session}.snapshot.${opts.field}`);
|
||
} else if (opts.field in session) {
|
||
session[opts.field] = opts.value;
|
||
console.log(`✅ 已更新 ${opts.session}.${opts.field}`);
|
||
} else {
|
||
console.log(`❌ 未找到字段: ${opts.field}`);
|
||
return;
|
||
}
|
||
}
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (found) break;
|
||
}
|
||
|
||
if (!found) {
|
||
console.log(`❌ 未找到快照: ${opts.session}`);
|
||
return;
|
||
}
|
||
|
||
writeTree(tree);
|
||
}
|
||
|
||
/**
|
||
* summary — 时间之树结构概览
|
||
*/
|
||
function summary() {
|
||
const tree = readTree();
|
||
const branches = Object.entries(tree.branches)
|
||
.filter(([k]) => k !== '_schema')
|
||
.sort(([a], [b]) => b.localeCompare(a));
|
||
|
||
console.log('');
|
||
console.log('═══════════════════════════════════════════════════');
|
||
console.log(' 🌳 时间之树 · 结构概览');
|
||
console.log('═══════════════════════════════════════════════════');
|
||
console.log('');
|
||
console.log(` 🌱 根: ${tree.root.origin_date} (${tree.root.description})`);
|
||
console.log(` 🌿 树杈: ${branches.length} 个`);
|
||
console.log(` 🍃 叶子: ${tree.navigation.total_sessions} 个`);
|
||
console.log(` 🎯 活跃任务: ${tree.navigation.active_tasks.length} 个`);
|
||
console.log('');
|
||
|
||
for (const [date, branch] of branches) {
|
||
const sessionCount = branch.sessions ? branch.sessions.length : 0;
|
||
console.log(` 📅 ${date} [${branch.epoch}] — ${sessionCount} 个快照`);
|
||
if (branch.sessions) {
|
||
for (const s of branch.sessions) {
|
||
const taskInfo = s.task_ref ? ` → ${s.task_ref}` : '';
|
||
console.log(` └─ ${s.session_id} · ${s.type} · ${s.summary}${taskInfo}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('');
|
||
}
|
||
|
||
// ─── CLI入口 ───
|
||
|
||
const args = process.argv.slice(2);
|
||
const command = args[0];
|
||
const opts = parseArgs(args.slice(1));
|
||
|
||
switch (command) {
|
||
case 'grow':
|
||
grow(opts);
|
||
break;
|
||
case 'find':
|
||
find(opts);
|
||
break;
|
||
case 'latest':
|
||
latest();
|
||
break;
|
||
case 'today':
|
||
today();
|
||
break;
|
||
case 'update':
|
||
update(opts);
|
||
break;
|
||
case 'summary':
|
||
summary();
|
||
break;
|
||
default:
|
||
console.log('🌳 铸渊时间之树管理器');
|
||
console.log('');
|
||
console.log('用法:');
|
||
console.log(' grow --type dev --summary "描述" --task "TASK-ID"');
|
||
console.log(' find --date 2026-04-13');
|
||
console.log(' find --task TASK-20260413-001');
|
||
console.log(' latest 获取最新的开发记录');
|
||
console.log(' today 查看今天的所有快照');
|
||
console.log(' update --session TS-xxx --field remaining --value "下一步"');
|
||
console.log(' summary 时间之树结构概览');
|
||
break;
|
||
}
|
||
|
||
module.exports = { grow, find, latest, today, update, summary, readTree, writeTree };
|