novel-doomsday-resurgence/skills/capability-evolver/src/gep/candidates.js
唐天洛 cb9b16e5a8 初始提交:番茄小说创作工作区
包含:
- 核心配置文件(AGENTS.md, SOUL.md, USER.md等)
- 记忆系统(memory/文件夹)
- 技能库(skills/文件夹)
- 小说内容(novel/文件夹)
- .gitignore配置
2026-03-30 15:46:26 +08:00

147 lines
5.2 KiB
JavaScript

function stableHash(input) {
// Deterministic lightweight hash (not cryptographic).
const s = String(input || '');
let h = 2166136261;
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return (h >>> 0).toString(16).padStart(8, '0');
}
function clip(text, maxChars) {
const s = String(text || '');
if (!maxChars || s.length <= maxChars) return s;
return s.slice(0, Math.max(0, maxChars - 20)) + ' ...[TRUNCATED]';
}
function toLines(text) {
return String(text || '')
.split('\n')
.map(l => l.trimEnd())
.filter(Boolean);
}
function extractToolCalls(transcript) {
const lines = toLines(transcript);
const calls = [];
for (const line of lines) {
// OpenClaw format: [TOOL: Shell]
const m = line.match(/\[TOOL:\s*([^\]]+)\]/i);
if (m && m[1]) { calls.push(m[1].trim()); continue; }
// Cursor transcript format: [Tool call] Shell
const m2 = line.match(/\[Tool call\]\s+(\S+)/i);
if (m2 && m2[1]) calls.push(m2[1].trim());
}
return calls;
}
function countFreq(items) {
const map = new Map();
for (const it of items) map.set(it, (map.get(it) || 0) + 1);
return map;
}
function buildFiveQuestionsShape({ title, signals, evidence }) {
// Keep it short and structured; this is a template, not a perfect inference.
const input = 'Recent session transcript + memory snippets + user instructions';
const output = 'A safe, auditable evolution patch guided by GEP assets';
const invariants = 'Protocol order, small reversible patches, validation, append-only events';
const params = `Signals: ${Array.isArray(signals) ? signals.join(', ') : ''}`.trim();
const failurePoints = 'Missing signals, over-broad changes, skipped validation, missing knowledge solidification';
return {
title: String(title || '').slice(0, 120),
input,
output,
invariants,
params: params || 'Signals: (none)',
failure_points: failurePoints,
evidence: clip(evidence, 240),
};
}
function extractCapabilityCandidates({ recentSessionTranscript, signals }) {
const candidates = [];
const toolCalls = extractToolCalls(recentSessionTranscript);
const freq = countFreq(toolCalls);
for (const [tool, count] of freq.entries()) {
if (count < 2) continue;
const title = `Repeated tool usage: ${tool}`;
const evidence = `Observed ${count} occurrences of tool call marker for ${tool}.`;
const shape = buildFiveQuestionsShape({ title, signals, evidence });
candidates.push({
type: 'CapabilityCandidate',
id: `cand_${stableHash(title)}`,
title,
source: 'transcript',
created_at: new Date().toISOString(),
signals: Array.isArray(signals) ? signals : [],
shape,
});
}
// Signals-as-candidates: capture recurring pain points as reusable capability shapes.
const signalList = Array.isArray(signals) ? signals : [];
const signalCandidates = [
// Defensive signals
{ signal: 'log_error', title: 'Repair recurring runtime errors' },
{ signal: 'protocol_drift', title: 'Prevent protocol drift and enforce auditable outputs' },
{ signal: 'windows_shell_incompatible', title: 'Avoid platform-specific shell assumptions (Windows compatibility)' },
{ signal: 'session_logs_missing', title: 'Harden session log detection and fallback behavior' },
// Opportunity signals (innovation)
{ signal: 'user_feature_request', title: 'Implement user-requested feature' },
{ signal: 'user_improvement_suggestion', title: 'Apply user improvement suggestion' },
{ signal: 'perf_bottleneck', title: 'Resolve performance bottleneck' },
{ signal: 'capability_gap', title: 'Fill capability gap' },
{ signal: 'stable_success_plateau', title: 'Explore new strategies during stability plateau' },
{ signal: 'external_opportunity', title: 'Evaluate external A2A asset for local adoption' },
];
for (const sc of signalCandidates) {
if (!signalList.some(s => s === sc.signal || s.startsWith(sc.signal + ':'))) continue;
const evidence = `Signal present: ${sc.signal}`;
const shape = buildFiveQuestionsShape({ title: sc.title, signals, evidence });
candidates.push({
type: 'CapabilityCandidate',
id: `cand_${stableHash(sc.signal)}`,
title: sc.title,
source: 'signals',
created_at: new Date().toISOString(),
signals: signalList,
shape,
});
}
// Dedup by id
const seen = new Set();
return candidates.filter(c => {
if (!c || !c.id) return false;
if (seen.has(c.id)) return false;
seen.add(c.id);
return true;
});
}
function renderCandidatesPreview(candidates, maxChars = 1400) {
const list = Array.isArray(candidates) ? candidates : [];
const lines = [];
for (const c of list) {
const s = c && c.shape ? c.shape : {};
lines.push(`- ${c.id}: ${c.title}`);
lines.push(` - input: ${s.input || ''}`);
lines.push(` - output: ${s.output || ''}`);
lines.push(` - invariants: ${s.invariants || ''}`);
lines.push(` - params: ${s.params || ''}`);
lines.push(` - failure_points: ${s.failure_points || ''}`);
if (s.evidence) lines.push(` - evidence: ${s.evidence}`);
}
return clip(lines.join('\n'), maxChars);
}
module.exports = {
extractCapabilityCandidates,
renderCandidatesPreview,
};