guanghulab/scripts/time-tree-manager.js
2026-05-10 13:12:44 +08:00

383 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 };