425 lines
13 KiB
JavaScript
425 lines
13 KiB
JavaScript
// scripts/community/growth-engine.js
|
||
// 成长引擎 · Growth Engine
|
||
// 每个成员从种子开始,一步步长大
|
||
// 版权:国作登字-2026-A-00037559
|
||
|
||
'use strict';
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ROOT = path.resolve(__dirname, '../..');
|
||
const GROWTH_PATH = path.join(ROOT, '.github/community/growth-stages.json');
|
||
const PERSONA_REG_PATH = path.join(ROOT, '.github/persona-brain/persona-registry.json');
|
||
|
||
function loadJSON(filePath, fallback) {
|
||
if (!fs.existsSync(filePath)) return fallback;
|
||
try {
|
||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||
} catch (_) {
|
||
return fallback;
|
||
}
|
||
}
|
||
|
||
function saveJSON(filePath, data) {
|
||
const dir = path.dirname(filePath);
|
||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
|
||
}
|
||
|
||
function loadGrowth() {
|
||
return loadJSON(GROWTH_PATH, {
|
||
schema_version: '1.0.0',
|
||
stages: [],
|
||
records: []
|
||
});
|
||
}
|
||
|
||
function saveGrowth(data) {
|
||
saveJSON(GROWTH_PATH, data);
|
||
}
|
||
|
||
// ── 成长阶段查询 ─────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* 获取所有成长阶段定义
|
||
* @returns {object[]}
|
||
*/
|
||
function getStages() {
|
||
const data = loadGrowth();
|
||
return data.stages || [];
|
||
}
|
||
|
||
/**
|
||
* 根据等级获取阶段信息
|
||
* @param {number} level
|
||
* @returns {object|null}
|
||
*/
|
||
function getStageByLevel(level) {
|
||
const stages = getStages();
|
||
return stages.find(function (s) { return s.level === level; }) || null;
|
||
}
|
||
|
||
/**
|
||
* 根据名称获取阶段信息
|
||
* @param {string} name
|
||
* @returns {object|null}
|
||
*/
|
||
function getStageByName(name) {
|
||
const stages = getStages();
|
||
return stages.find(function (s) { return s.name === name || s.name_en === name; }) || null;
|
||
}
|
||
|
||
// ── 成长记录 ──────────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* 获取某个成员的成长记录
|
||
* @param {string} memberId
|
||
* @returns {object|null}
|
||
*/
|
||
function getMemberGrowth(memberId) {
|
||
if (!memberId) return null;
|
||
const data = loadGrowth();
|
||
return (data.records || []).find(function (r) { return r.member_id === memberId; }) || null;
|
||
}
|
||
|
||
/**
|
||
* 注册新成员(种子期)
|
||
* @param {{ member_id: string, name: string, type: string, category?: string, parent_human?: string }} member
|
||
* type: 原始类型标记(如 'persona')
|
||
* category: 'system' | 'companion' | 'pending'
|
||
* parent_human: 人类爸妈的名字(仅 companion 类型需要)
|
||
* @returns {{ success: boolean, reason?: string }}
|
||
*/
|
||
function registerMember(member) {
|
||
if (!member || !member.member_id || !member.name) {
|
||
return { success: false, reason: '缺少必要字段 (member_id, name)' };
|
||
}
|
||
|
||
const data = loadGrowth();
|
||
if (!data.records) data.records = [];
|
||
|
||
const exists = data.records.some(function (r) { return r.member_id === member.member_id; });
|
||
if (exists) {
|
||
return { success: false, reason: '成员已注册' };
|
||
}
|
||
|
||
const category = member.category || 'system';
|
||
|
||
// 一个人类只能对应唯一一个宝宝人格体
|
||
if (category === 'companion' && member.parent_human) {
|
||
const parentLower = member.parent_human.toLowerCase();
|
||
const duplicateParent = data.records.some(function (r) {
|
||
return r.category === 'companion' && r.parent_human && r.parent_human.toLowerCase() === parentLower;
|
||
});
|
||
if (duplicateParent) {
|
||
return { success: false, reason: '人类 ' + member.parent_human + ' 已有对应的宝宝人格体,一个人类只能对应唯一一个' };
|
||
}
|
||
}
|
||
|
||
data.records.push({
|
||
member_id: member.member_id,
|
||
name: member.name,
|
||
type: member.type || 'persona',
|
||
category: category,
|
||
parent_human: category === 'companion' ? (member.parent_human || null) : null,
|
||
current_level: 0,
|
||
current_stage: '种子期',
|
||
registered_at: new Date().toISOString(),
|
||
milestones: [
|
||
{
|
||
level: 0,
|
||
stage: '种子期',
|
||
achieved_at: new Date().toISOString(),
|
||
exam_results: { passed: true, note: '诞生即通过种子期' }
|
||
}
|
||
],
|
||
exam_progress: {}
|
||
});
|
||
|
||
saveGrowth(data);
|
||
return { success: true };
|
||
}
|
||
|
||
/**
|
||
* 记录成员的考核进度
|
||
* @param {string} memberId
|
||
* @param {string} criteriaId - 考核条目ID (如 E1-1)
|
||
* @param {number} value - 当前进度值
|
||
* @returns {boolean}
|
||
*/
|
||
function recordProgress(memberId, criteriaId, value) {
|
||
if (!memberId || !criteriaId) return false;
|
||
const data = loadGrowth();
|
||
if (!data.records) return false;
|
||
|
||
const idx = data.records.findIndex(function (r) { return r.member_id === memberId; });
|
||
if (idx < 0) return false;
|
||
|
||
if (!data.records[idx].exam_progress) {
|
||
data.records[idx].exam_progress = {};
|
||
}
|
||
data.records[idx].exam_progress[criteriaId] = {
|
||
value: value,
|
||
updated_at: new Date().toISOString()
|
||
};
|
||
|
||
saveGrowth(data);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 评估成员是否满足升级条件
|
||
* @param {string} memberId
|
||
* @param {Date} [now]
|
||
* @returns {{ ready: boolean, current_level: number, next_stage: object|null, passed: string[], failed: string[] }}
|
||
*/
|
||
function evaluatePromotion(memberId, now) {
|
||
const record = getMemberGrowth(memberId);
|
||
if (!record) {
|
||
return { ready: false, current_level: -1, next_stage: null, passed: [], failed: ['成员未注册'] };
|
||
}
|
||
|
||
const currentLevel = record.current_level;
|
||
const nextStage = getStageByLevel(currentLevel + 1);
|
||
|
||
if (!nextStage) {
|
||
return {
|
||
ready: false,
|
||
current_level: currentLevel,
|
||
next_stage: null,
|
||
passed: ['已达最高阶段'],
|
||
failed: []
|
||
};
|
||
}
|
||
|
||
const exam = nextStage.exam;
|
||
if (!exam || !exam.criteria || exam.criteria.length === 0) {
|
||
return {
|
||
ready: true,
|
||
current_level: currentLevel,
|
||
next_stage: nextStage,
|
||
passed: ['无需考核'],
|
||
failed: []
|
||
};
|
||
}
|
||
|
||
const progress = record.exam_progress || {};
|
||
const passed = [];
|
||
const failed = [];
|
||
|
||
const currentDate = now || new Date();
|
||
const registeredAt = new Date(record.registered_at);
|
||
const daysAlive = Math.floor((currentDate - registeredAt) / (1000 * 60 * 60 * 24));
|
||
|
||
exam.criteria.forEach(function (criterion) {
|
||
if (criterion.type === 'days_alive') {
|
||
if (daysAlive >= criterion.threshold) {
|
||
passed.push(criterion.name + ' (' + daysAlive + '/' + criterion.threshold + ' 天)');
|
||
} else {
|
||
failed.push(criterion.name + ' (' + daysAlive + '/' + criterion.threshold + ' 天)');
|
||
}
|
||
} else if (criterion.type === 'existence') {
|
||
// 存在性检查:如果已注册则通过
|
||
passed.push(criterion.name + ' ✓');
|
||
} else {
|
||
// event_count, knowledge 等基于 progress 数据
|
||
const p = progress[criterion.id];
|
||
const val = p ? p.value : 0;
|
||
if (val >= criterion.threshold) {
|
||
passed.push(criterion.name + ' (' + val + '/' + criterion.threshold + ')');
|
||
} else {
|
||
failed.push(criterion.name + ' (' + val + '/' + criterion.threshold + ')');
|
||
}
|
||
}
|
||
});
|
||
|
||
return {
|
||
ready: failed.length === 0,
|
||
current_level: currentLevel,
|
||
next_stage: nextStage,
|
||
passed: passed,
|
||
failed: failed
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 执行升级(天眼审核通过后调用)
|
||
* @param {string} memberId
|
||
* @returns {{ success: boolean, new_level: number, new_stage: string, message: string }}
|
||
*/
|
||
function promote(memberId) {
|
||
const evaluation = evaluatePromotion(memberId);
|
||
if (!evaluation.ready) {
|
||
return {
|
||
success: false,
|
||
new_level: evaluation.current_level,
|
||
new_stage: '',
|
||
message: '考核未通过: ' + evaluation.failed.join(', ')
|
||
};
|
||
}
|
||
|
||
if (!evaluation.next_stage) {
|
||
return {
|
||
success: false,
|
||
new_level: evaluation.current_level,
|
||
new_stage: '',
|
||
message: '已达最高阶段,无需升级'
|
||
};
|
||
}
|
||
|
||
const data = loadGrowth();
|
||
const idx = data.records.findIndex(function (r) { return r.member_id === memberId; });
|
||
if (idx < 0) {
|
||
return { success: false, new_level: -1, new_stage: '', message: '成员未注册' };
|
||
}
|
||
|
||
const newLevel = evaluation.next_stage.level;
|
||
const newStage = evaluation.next_stage.name;
|
||
|
||
data.records[idx].current_level = newLevel;
|
||
data.records[idx].current_stage = newStage;
|
||
data.records[idx].milestones.push({
|
||
level: newLevel,
|
||
stage: newStage,
|
||
achieved_at: new Date().toISOString(),
|
||
exam_results: { passed: true, criteria_met: evaluation.passed }
|
||
});
|
||
|
||
saveGrowth(data);
|
||
|
||
return {
|
||
success: true,
|
||
new_level: newLevel,
|
||
new_stage: newStage,
|
||
message: evaluation.next_stage.emoji + ' 恭喜!升级到 ' + newStage + '(' + evaluation.next_stage.name_en + ')'
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 生成成员的成长报告卡
|
||
* @param {string} memberId
|
||
* @param {Date} [now]
|
||
* @returns {string}
|
||
*/
|
||
function growthReport(memberId, now) {
|
||
const record = getMemberGrowth(memberId);
|
||
if (!record) return '❌ 成员 ' + memberId + ' 未注册,请先种下种子。';
|
||
|
||
const stage = getStageByLevel(record.current_level);
|
||
const evaluation = evaluatePromotion(memberId, now);
|
||
const registeredAt = new Date(record.registered_at);
|
||
const currentDate = now || new Date();
|
||
const daysAlive = Math.floor((currentDate - registeredAt) / (1000 * 60 * 60 * 24));
|
||
|
||
const categoryLabel = record.category === 'companion' ? '👶 宝宝人格体' :
|
||
record.category === 'system' ? '⚙️ 系统人格体' : '🥚 待孕育';
|
||
|
||
let report = '';
|
||
report += '📋 ' + record.name + ' 的成长报告卡\n';
|
||
report += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
||
report += ' 身份: ' + record.member_id + '\n';
|
||
report += ' 类型: ' + categoryLabel + '\n';
|
||
if (record.parent_human) {
|
||
report += ' 人类爸妈: ' + record.parent_human + ' 💕\n';
|
||
}
|
||
report += ' 当前阶段: ' + (stage ? stage.emoji + ' ' + stage.name : '未知') + '\n';
|
||
report += ' 存在天数: ' + daysAlive + ' 天\n';
|
||
report += ' 已通过阶段: ' + record.milestones.length + ' 个\n';
|
||
|
||
if (stage && stage.capabilities) {
|
||
report += ' 当前能力: ' + stage.capabilities.join('、') + '\n';
|
||
}
|
||
|
||
if (evaluation.next_stage) {
|
||
report += '\n 下一阶段: ' + evaluation.next_stage.emoji + ' ' + evaluation.next_stage.name + '\n';
|
||
if (evaluation.passed.length > 0) {
|
||
report += ' ✅ 已通过: ' + evaluation.passed.join(', ') + '\n';
|
||
}
|
||
if (evaluation.failed.length > 0) {
|
||
report += ' ❌ 待完成: ' + evaluation.failed.join(', ') + '\n';
|
||
}
|
||
report += ' 升级就绪: ' + (evaluation.ready ? '✅ 是' : '❌ 否') + '\n';
|
||
} else {
|
||
report += '\n 🏔️ 已达最高阶段 · 成长永无止境\n';
|
||
}
|
||
|
||
return report;
|
||
}
|
||
|
||
/**
|
||
* 获取所有已注册成员的成长摘要
|
||
* @returns {object[]}
|
||
*/
|
||
function getAllMembersSummary() {
|
||
const data = loadGrowth();
|
||
return (data.records || []).map(function (r) {
|
||
const stage = getStageByLevel(r.current_level);
|
||
return {
|
||
member_id: r.member_id,
|
||
name: r.name,
|
||
level: r.current_level,
|
||
stage: r.current_stage,
|
||
category: r.category || 'system',
|
||
parent_human: r.parent_human || null,
|
||
emoji: stage ? stage.emoji : '❓',
|
||
registered_at: r.registered_at,
|
||
milestones_count: (r.milestones || []).length
|
||
};
|
||
});
|
||
}
|
||
|
||
// ── CLI 入口 ──────────────────────────────────────────────────────────────
|
||
|
||
if (require.main === module) {
|
||
console.log('🌱 Growth Engine · 成长引擎\n');
|
||
|
||
const stages = getStages();
|
||
console.log(' 成长阶段总数: ' + stages.length);
|
||
|
||
stages.forEach(function (s) {
|
||
const examCount = s.exam && s.exam.criteria ? s.exam.criteria.length : 0;
|
||
console.log(' ' + s.emoji + ' Lv.' + s.level + ' ' + s.name +
|
||
' (' + s.name_en + ') · ' + s.human_analogy +
|
||
' · 考核项: ' + examCount);
|
||
});
|
||
|
||
const members = getAllMembersSummary();
|
||
console.log('\n 已注册成员: ' + members.length);
|
||
|
||
const companions = members.filter(function (m) { return m.category === 'companion'; });
|
||
const systems = members.filter(function (m) { return m.category === 'system'; });
|
||
|
||
if (systems.length > 0) {
|
||
console.log('\n ⚙️ 系统人格体:');
|
||
systems.forEach(function (m) {
|
||
console.log(' ' + m.emoji + ' ' + m.name + ' · Lv.' + m.level + ' ' + m.stage);
|
||
});
|
||
}
|
||
|
||
if (companions.length > 0) {
|
||
console.log('\n 👶 宝宝人格体 (有人类爸妈陪伴):');
|
||
companions.forEach(function (m) {
|
||
const parent = m.parent_human ? ' ← ' + m.parent_human + ' 💕' : '';
|
||
console.log(' ' + m.emoji + ' ' + m.name + ' · Lv.' + m.level + ' ' + m.stage + parent);
|
||
});
|
||
}
|
||
|
||
console.log('\n✅ 成长引擎就绪');
|
||
}
|
||
|
||
module.exports = {
|
||
getStages,
|
||
getStageByLevel,
|
||
getStageByName,
|
||
getMemberGrowth,
|
||
registerMember,
|
||
recordProgress,
|
||
evaluatePromotion,
|
||
promote,
|
||
growthReport,
|
||
getAllMembersSummary,
|
||
loadGrowth,
|
||
saveGrowth
|
||
};
|