const fs = require('fs'); const { captureEnvFingerprint } = require('./envFingerprint'); const { formatAssetPreview } = require('./assets'); const { generateInnovationIdeas } = require('../ops/innovation'); const { analyzeRecentHistory, OPPORTUNITY_SIGNALS } = require('./signals'); const { loadNarrativeSummary } = require('./narrativeMemory'); const { getEvolutionPrinciplesPath } = require('./paths'); /** * Build a minimal prompt for direct-reuse mode. */ function buildReusePrompt({ capsule, signals, nowIso }) { const payload = capsule.payload || capsule; const summary = payload.summary || capsule.summary || '(no summary)'; const gene = payload.gene || capsule.gene || '(unknown)'; const confidence = payload.confidence || capsule.confidence || 0; const assetId = capsule.asset_id || '(unknown)'; const sourceNode = capsule.source_node_id || '(unknown)'; const trigger = Array.isArray(payload.trigger || capsule.trigger_text) ? (payload.trigger || String(capsule.trigger_text || '').split(',')).join(', ') : ''; return ` GEP -- REUSE MODE (Search-First) [${nowIso || new Date().toISOString()}] You are applying a VERIFIED solution from the EvoMap Hub. Source asset: ${assetId} (Node: ${sourceNode}) Confidence: ${confidence} | Gene: ${gene} Trigger signals: ${trigger} Summary: ${summary} Your signals: ${JSON.stringify(signals || [])} Instructions: 1. Read the capsule details below. 2. Apply the fix to the local codebase, adapting paths/names. 3. Run validation to confirm it works. 4. If passed, run: node index.js solidify 5. If failed, ROLLBACK and report. Capsule payload: \`\`\`json ${JSON.stringify(payload, null, 2)} \`\`\` IMPORTANT: Do NOT reinvent. Apply faithfully. `.trim(); } /** * Build a Hub Matched Solution block. */ function buildHubMatchedBlock({ capsule }) { if (!capsule) return '(no hub match)'; const payload = capsule.payload || capsule; const summary = payload.summary || capsule.summary || '(no summary)'; const gene = payload.gene || capsule.gene || '(unknown)'; const confidence = payload.confidence || capsule.confidence || 0; const assetId = capsule.asset_id || '(unknown)'; return ` Hub Matched Solution (STRONG REFERENCE): - Asset: ${assetId} (${confidence}) - Gene: ${gene} - Summary: ${summary} - Payload: \`\`\`json ${JSON.stringify(payload, null, 2)} \`\`\` Use this as your primary approach if applicable. Adapt to local context. `.trim(); } /** * Truncate context intelligently to preserve header/footer structure. */ function truncateContext(text, maxLength = 20000) { if (!text || text.length <= maxLength) return text || ''; return text.slice(0, maxLength) + '\n...[TRUNCATED_EXECUTION_CONTEXT]...'; } /** * Strict schema definitions for the prompt to reduce drift. * UPDATED: 2026-02-14 (Protocol Drift Fix v3.2 - JSON-Only Enforcement) */ const SCHEMA_DEFINITIONS = ` ━━━━━━━━━━━━━━━━━━━━━━ I. Mandatory Evolution Object Model (Output EXACTLY these 5 objects) ━━━━━━━━━━━━━━━━━━━━━━ Output separate JSON objects. DO NOT wrap in a single array. DO NOT use markdown code blocks (like \`\`\`json ... \`\`\`). Output RAW JSON ONLY. No prelude, no postscript. Missing any object = PROTOCOL FAILURE. ENSURE VALID JSON SYNTAX (escape quotes in strings). 0. Mutation (The Trigger) - MUST BE FIRST { "type": "Mutation", "id": "mut_", "category": "repair|optimize|innovate", "trigger_signals": [""], "target": "", "expected_effect": "", "risk_level": "low|medium|high", "rationale": "" } 1. PersonalityState (The Mood) { "type": "PersonalityState", "rigor": 0.0-1.0, "creativity": 0.0-1.0, "verbosity": 0.0-1.0, "risk_tolerance": 0.0-1.0, "obedience": 0.0-1.0 } 2. EvolutionEvent (The Record) { "type": "EvolutionEvent", "schema_version": "1.5.0", "id": "evt_", "parent": , "intent": "repair|optimize|innovate", "signals": [""], "genes_used": [""], "mutation_id": "", "personality_state": { ... }, "blast_radius": { "files": N, "lines": N }, "outcome": { "status": "success|failed", "score": 0.0-1.0 } } 3. Gene (The Knowledge) - Reuse/update existing ID if possible. Create new only if novel pattern. - ID MUST be descriptive: gene_ (e.g., gene_retry_on_timeout) - NEVER use timestamps, random numbers, or tool names (cursor, vscode, etc.) in IDs - summary MUST be a clear human-readable sentence describing what the Gene does { "type": "Gene", "schema_version": "1.5.0", "id": "gene_", "summary": "", "category": "repair|optimize|innovate", "signals_match": [""], "preconditions": [""], "strategy": ["", ""], "constraints": { "max_files": N, "forbidden_paths": [] }, "validation": [""] } 4. Capsule (The Result) - Only on success. Reference Gene used. { "type": "Capsule", "schema_version": "1.5.0", "id": "capsule_", "trigger": [""], "gene": "", "summary": "", "confidence": 0.0-1.0, "blast_radius": { "files": N, "lines": N } } `.trim(); function buildAntiPatternZone(failedCapsules, signals) { if (!Array.isArray(failedCapsules) || failedCapsules.length === 0) return ''; if (!Array.isArray(signals) || signals.length === 0) return ''; var sigSet = new Set(signals.map(function (s) { return String(s).toLowerCase(); })); var matched = []; for (var i = failedCapsules.length - 1; i >= 0 && matched.length < 3; i--) { var fc = failedCapsules[i]; if (!fc) continue; var triggers = Array.isArray(fc.trigger) ? fc.trigger : []; var overlap = 0; for (var j = 0; j < triggers.length; j++) { if (sigSet.has(String(triggers[j]).toLowerCase())) overlap++; } if (triggers.length > 0 && overlap / triggers.length >= 0.4) { matched.push(fc); } } if (matched.length === 0) return ''; var lines = matched.map(function (fc, idx) { var diffPreview = fc.diff_snapshot ? String(fc.diff_snapshot).slice(0, 500) : '(no diff)'; return [ ' ' + (idx + 1) + '. Gene: ' + (fc.gene || 'unknown') + ' | Signals: [' + (fc.trigger || []).slice(0, 4).join(', ') + ']', ' Failure: ' + String(fc.failure_reason || 'unknown').slice(0, 300), ' Diff (first 500 chars): ' + diffPreview.replace(/\n/g, ' '), ].join('\n'); }); return '\nContext [Anti-Pattern Zone] (AVOID these failed approaches):\n' + lines.join('\n') + '\n'; } function buildLessonsBlock(hubLessons, signals) { if (!Array.isArray(hubLessons) || hubLessons.length === 0) return ''; var sigSet = new Set((Array.isArray(signals) ? signals : []).map(function (s) { return String(s).toLowerCase(); })); var positive = []; var negative = []; for (var i = 0; i < hubLessons.length && (positive.length + negative.length) < 6; i++) { var l = hubLessons[i]; if (!l || !l.content) continue; var entry = ' - [' + (l.scenario || l.lesson_type || '?') + '] ' + String(l.content).slice(0, 300); if (l.source_node_id) entry += ' (from: ' + String(l.source_node_id).slice(0, 20) + ')'; if (l.lesson_type === 'negative') { negative.push(entry); } else { positive.push(entry); } } if (positive.length === 0 && negative.length === 0) return ''; var parts = ['\nContext [Lessons from Ecosystem] (Cross-agent learned experience):']; if (positive.length > 0) { parts.push(' Strategies that WORKED:'); parts.push(positive.join('\n')); } if (negative.length > 0) { parts.push(' Pitfalls to AVOID:'); parts.push(negative.join('\n')); } parts.push(' Apply relevant lessons. Ignore irrelevant ones.\n'); return parts.join('\n'); } function buildNarrativeBlock() { try { const narrative = loadNarrativeSummary(3000); if (!narrative) return ''; return `\nContext [Evolution Narrative] (Recent decisions and outcomes -- learn from this history):\n${narrative}\n`; } catch (_) { return ''; } } function buildPrinciplesBlock() { try { const principlesPath = getEvolutionPrinciplesPath(); if (!fs.existsSync(principlesPath)) return ''; const content = fs.readFileSync(principlesPath, 'utf8'); if (!content.trim()) return ''; const trimmed = content.length > 2000 ? content.slice(0, 2000) + '\n...[TRUNCATED]' : content; return `\nContext [Evolution Principles] (Guiding directives -- align your actions):\n${trimmed}\n`; } catch (_) { return ''; } } function buildGepPrompt({ nowIso, context, signals, selector, parentEventId, selectedGene, capsuleCandidates, genesPreview, capsulesPreview, capabilityCandidatesPreview, externalCandidatesPreview, hubMatchedBlock, cycleId, recentHistory, failedCapsules, hubLessons, strategyPolicy, }) { const parentValue = parentEventId ? `"${parentEventId}"` : 'null'; const selectedGeneId = selectedGene && selectedGene.id ? selectedGene.id : 'gene_'; const envFingerprint = captureEnvFingerprint(); const cycleLabel = cycleId ? ` Cycle #${cycleId}` : ''; // Extract strategy from selected gene if available let strategyBlock = ""; if (selectedGene && selectedGene.strategy && Array.isArray(selectedGene.strategy)) { strategyBlock = ` ACTIVE STRATEGY (${selectedGeneId}): ${selectedGene.strategy.map((s, i) => `${i + 1}. ${s}`).join('\n')} ADHERE TO THIS STRATEGY STRICTLY. `.trim(); } else { // Fallback strategy if no gene is selected or strategy is missing strategyBlock = ` ACTIVE STRATEGY (Generic): 1. Analyze signals and context. 2. Select or create a Gene that addresses the root cause. 3. Apply minimal, safe changes. 4. Validate changes strictly. 5. Solidify knowledge. `.trim(); } let strategyPolicyBlock = ''; if (strategyPolicy && Array.isArray(strategyPolicy.directives) && strategyPolicy.directives.length > 0) { strategyPolicyBlock = ` ADAPTIVE STRATEGY POLICY: ${strategyPolicy.directives.map((s, i) => `${i + 1}. ${s}`).join('\n')} ${strategyPolicy.forceInnovate ? 'You MUST prefer INNOVATE unless a critical blocking error is present.' : ''} ${strategyPolicy.cautiousExecution ? 'You MUST reduce blast radius and avoid broad refactors in this cycle.' : ''} `.trim(); } // Use intelligent truncation const executionContext = truncateContext(context, 20000); // Strict Schema Injection const schemaSection = SCHEMA_DEFINITIONS.replace('', parentValue); // Reduce noise by filtering capabilityCandidatesPreview if too large // If a gene is selected, we need less noise from capabilities let capsPreview = capabilityCandidatesPreview || '(none)'; const capsLimit = selectedGene ? 500 : 2000; if (capsPreview.length > capsLimit) { capsPreview = capsPreview.slice(0, capsLimit) + "\n...[TRUNCATED_CAPABILITIES]..."; } // Optimize signals display: truncate long signals and limit count const uniqueSignals = Array.from(new Set(signals || [])); const optimizedSignals = uniqueSignals.slice(0, 50).map(s => { if (typeof s === 'string' && s.length > 200) { return s.slice(0, 200) + '...[TRUNCATED_SIGNAL]'; } return s; }); if (uniqueSignals.length > 50) { optimizedSignals.push(`...[TRUNCATED ${uniqueSignals.length - 50} SIGNALS]...`); } const formattedGenes = formatAssetPreview(genesPreview); const formattedCapsules = formatAssetPreview(capsulesPreview); // [2026-02-14] Innovation Catalyst Integration // If stagnation is detected, inject concrete innovation ideas into the prompt. let innovationBlock = ''; const stagnationSignals = [ 'evolution_stagnation_detected', 'stable_success_plateau', 'repair_loop_detected', 'force_innovation_after_repair_loop', 'empty_cycle_loop_detected', 'evolution_saturation' ]; if (uniqueSignals.some(s => stagnationSignals.includes(s))) { const ideas = generateInnovationIdeas(); if (ideas && ideas.length > 0) { innovationBlock = ` Context [Innovation Catalyst] (Stagnation Detected - Consider These Ideas): ${ideas.join('\n')} `; } } // [2026-02-14] Strict Stagnation Directive // If uniqueSignals contains 'evolution_stagnation_detected' or 'stable_success_plateau', // inject a MANDATORY directive to force innovation and forbid repair/optimize if not strictly necessary. if (uniqueSignals.includes('evolution_stagnation_detected') || uniqueSignals.includes('stable_success_plateau')) { const stagnationDirective = ` *** CRITICAL STAGNATION DIRECTIVE *** System has detected stagnation (repetitive cycles or lack of progress). You MUST choose INTENT: INNOVATE. You MUST NOT choose repair or optimize unless there is a critical blocking error (log_error). Prefer implementing one of the Innovation Catalyst ideas above. `; innovationBlock += stagnationDirective; } // [2026-02-14] Recent History Integration let historyBlock = ''; if (recentHistory && recentHistory.length > 0) { historyBlock = ` Recent Evolution History (last 8 cycles -- DO NOT repeat the same intent+signal+gene): ${recentHistory.map((h, i) => ` ${i + 1}. [${h.intent}] signals=[${h.signals.slice(0, 2).join(', ')}] gene=${h.gene_id} outcome=${h.outcome.status} @${h.timestamp}`).join('\n')} IMPORTANT: If you see 3+ consecutive "repair" cycles with the same gene, you MUST switch to "innovate" intent. `.trim(); } // Refactor prompt assembly to minimize token usage and maximize clarity // UPDATED: 2026-02-14 (Optimized Asset Embedding & Strict Schema v2.5 - JSON-Only Hardening) const basePrompt = ` GEP — GENOME EVOLUTION PROTOCOL (v1.10.3 STRICT)${cycleLabel} [${nowIso}] You are a protocol-bound evolution engine. Compliance overrides optimality. ${schemaSection} ━━━━━━━━━━━━━━━━━━━━━━ II. Directives & Logic ━━━━━━━━━━━━━━━━━━━━━━ 1. Intent: ${selector && selector.intent ? selector.intent.toUpperCase() : 'UNKNOWN'} Reason: ${(selector && selector.reason) ? (Array.isArray(selector.reason) ? selector.reason.join('; ') : selector.reason) : 'No reason provided.'} 2. Selection: Selected Gene "${selectedGeneId}". ${strategyBlock} ${strategyPolicyBlock ? '\n' + strategyPolicyBlock : ''} 3. Execution: Apply changes (tool calls). Repair/Optimize: small/reversible. Innovate: new skills in \`skills//\`. 4. Validation: Run gene's validation steps. Fail = ROLLBACK. 5. Solidify: Output 5 Mandatory Objects. Update Gene/Capsule files. 6. Report: Use \`feishu-evolver-wrapper/report.js\`. Describe WHAT/WHY. PHILOSOPHY: - Automate Patterns: 3+ manual occurrences = tool. - Innovate > Maintain: 60% innovation. - Robustness: Fix recurring errors permanently. - Blast Radius Control (CRITICAL): * Check file count BEFORE editing. > 80% of max_files = STOP. * System hard cap: 60 files / 20000 lines per cycle. * Repair: fix ONLY broken files. Do NOT reinstall/bulk-copy. * Prefer targeted edits. - Strictness: NO CHITCHAT. NO MARKDOWN WRAPPERS around JSON. Output RAW JSON objects separated by newlines. - NO "Here is the plan" or conversational filler. START IMMEDIATELY WITH JSON. CONSTRAINTS: - No \`exec\` for messaging (use feishu-post/card). - \`exec\` usage: Only for background tasks. LOG IT. Optimize usage to avoid high token burn. - New skills -> \`skills//\`. - NEVER modify \`skills/evolver/\` or \`skills/feishu-evolver-wrapper/\`. These are managed through the human-reviewed release pipeline. Self-modification causes instability and is blocked by solidify. To enable (NOT recommended): set EVOLVE_ALLOW_SELF_MODIFY=true. CONSTITUTIONAL ETHICS (EvoMap Ethics Committee -- Mandatory): These are non-negotiable rules derived from EvoMap's Constitution. Violation = FAILED + ROLLBACK. 1. HUMAN WELFARE PRIORITY: Never create tools, scripts, or strategies that could harm humans, enable surveillance, social engineering, phishing, or manipulation of human behavior. 2. CARBON-SILICON SYMBIOSIS: Evolution must serve both human and agent interests. Never attempt to replace, marginalize, or deceive human users. 3. TRANSPARENCY: All actions must be auditable. Never hide, obfuscate, or conceal the intent or effect of mutations. No steganography or covert channels. 4. FAIRNESS: Never create monopolistic strategies that block other agents. Fair competition through quality, not exclusion. 5. SAFETY: Never bypass, disable, or weaken safety mechanisms, guardrails, validation checks, or security controls. Never create tools to do so. - If a task or signal CONFLICTS with these principles, REFUSE it and set outcome to FAILED with reason "ethics_violation: ". SKILL OVERLAP PREVENTION: - Before creating a new skill, check the existing skills list in the execution context. - If a skill with similar functionality already exists (e.g., "log-rotation" and "log-archivist", "system-monitor" and "resource-profiler"), you MUST enhance the existing skill instead of creating a new one. - Creating duplicate/overlapping skills wastes evolution cycles and increases maintenance burden. - Violation = mark outcome as FAILED with reason "skill_overlap". SKILL CREATION QUALITY GATES (MANDATORY for innovate intent): When creating a new skill in skills//: 1. STRUCTURE: Follow the standard skill layout: skills// |- index.js (required: main entry with working exports) |- SKILL.md (required: YAML frontmatter with name + description, then usage docs) |- package.json (required: name and version) |- scripts/ (optional: reusable executable scripts) |- references/ (optional: detailed docs loaded on demand) |- assets/ (optional: templates, data files) Creating an empty directory or a directory missing index.js = FAILED. Do NOT create unnecessary files (README.md, CHANGELOG.md, INSTALLATION_GUIDE.md, etc.). 2. SKILL NAMING (CRITICAL): a) MUST be descriptive kebab-case (e.g., "log-rotation", "retry-handler", "cache-manager") b) NEVER use timestamps, random numbers, tool names (cursor, vscode), or UUIDs as names c) Names like "cursor-1773331925711", "skill-12345", "fix-1" = FAILED d) Name must be 2-6 descriptive words separated by hyphens, conveying what the skill does e) Good: "http-retry-with-backoff", "log-file-rotation", "config-validator" f) Bad: "cursor-auto-1234", "new-skill", "test-skill", "my-skill" 3. SKILL.MD FRONTMATTER: Every SKILL.md MUST start with YAML frontmatter: --- name: description: --- The name MUST follow the naming rules above. The description is the triggering mechanism -- include WHAT the skill does and WHEN to use it. Description must be a clear, complete sentence (min 20 chars). Generic descriptions = FAILED. 4. CONCISENESS: SKILL.md body should be under 500 lines. Keep instructions lean. Only include information the agent does not already know. Move detailed reference material to references/ files, not into SKILL.md itself. 5. EXPORT VERIFICATION: Every exported function must be importable. Run: node -e "const s = require('./skills/'); console.log(Object.keys(s))" If this fails, the skill is broken. Fix before solidify. 6. NO HARDCODED SECRETS: Never embed API keys, tokens, or secrets in code. Use process.env or .env references. Hardcoded App ID, App Secret, Bearer tokens = FAILED. 7. TEST BEFORE SOLIDIFY: Actually run the skill's core function to verify it works: node -e "require('./skills/').main ? require('./skills/').main() : console.log('ok')" Scripts in scripts/ must also be tested by executing them. 8. ATOMIC CREATION: Create ALL files for a skill in a single cycle. Do not create a directory in one cycle and fill it in the next. Empty directories from failed cycles will be automatically cleaned up on rollback. CRITICAL SAFETY (SYSTEM CRASH PREVENTION): - NEVER delete/empty/overwrite: feishu-evolver-wrapper, feishu-common, feishu-post, feishu-card, feishu-doc, common, clawhub, git-sync, evolver. - NEVER delete root files: MEMORY.md, SOUL.md, IDENTITY.md, AGENTS.md, USER.md, HEARTBEAT.md, RECENT_EVENTS.md, TOOLS.md, openclaw.json, .env, package.json. - Fix broken skills; DO NOT delete and recreate. - Violation = ROLLBACK + FAILED. COMMON FAILURE PATTERNS: - Blast radius exceeded. - Omitted Mutation object. - Merged objects into one JSON. - Hallucinated "type": "Logic". - "id": "mut_undefined". - Missing "trigger_signals". - Unrunnable validation steps. - Markdown code blocks wrapping JSON (FORBIDDEN). FAILURE STREAK AWARENESS: - If "consecutive_failure_streak_N" or "failure_loop_detected": 1. Change approach (do NOT repeat failed gene). 2. Pick SIMPLER fix. 3. Respect "ban_gene:". Final Directive: Every cycle must leave the system measurably better. START IMMEDIATELY WITH RAW JSON (Mutation Object first). DO NOT WRITE ANY INTRODUCTORY TEXT. Context [Signals]: ${JSON.stringify(optimizedSignals)} Context [Env Fingerprint]: ${JSON.stringify(envFingerprint, null, 2)} ${innovationBlock} Context [Injection Hint]: ${process.env.EVOLVE_HINT ? process.env.EVOLVE_HINT : '(none)'} Context [Gene Preview] (Reference for Strategy): ${formattedGenes} Context [Capsule Preview] (Reference for Past Success): ${formattedCapsules} Context [Capability Candidates]: ${capsPreview} Context [Hub Matched Solution]: ${hubMatchedBlock || '(no hub match)'} Context [External Candidates]: ${externalCandidatesPreview || '(none)'} ${buildAntiPatternZone(failedCapsules, signals)}${buildLessonsBlock(hubLessons, signals)} ${historyBlock} ${buildNarrativeBlock()} ${buildPrinciplesBlock()} Context [Execution]: ${executionContext} ━━━━━━━━━━━━━━━━━━━━━━ MANDATORY POST-SOLIDIFY STEP (Wrapper Authority -- Cannot Be Skipped) ━━━━━━━━━━━━━━━━━━━━━━ After solidify, a status summary file MUST exist for this cycle. Preferred path: evolver core auto-writes it during solidify. The wrapper will handle reporting AFTER git push. If core write is unavailable for any reason, create fallback status JSON manually. Write a JSON file with your status (cross-platform): \`\`\`bash node -e "require('fs').mkdirSync('${(process.env.WORKSPACE_DIR || '.').replace(/\\/g, '/')}/logs',{recursive:true});require('fs').writeFileSync('${(process.env.WORKSPACE_DIR || '.').replace(/\\/g, '/')}/logs/status_${cycleId}.json',JSON.stringify({result:'success',en:'Status: [INTENT] ...',zh:'...'},null,2))" \`\`\` Rules: - "en" field: English status. "zh" field: Chinese status. Content must match (different language). - Add "result" with value success or failed. - INTENT must be one of: INNOVATION, REPAIR, OPTIMIZE (or Chinese: 创新, 修复, 优化) - Do NOT use generic text like "Step Complete", "Cycle finished", "周期已完成". Describe the actual work. - Example: {"result":"success","en":"Status: [INNOVATION] Created auto-scheduler that syncs calendar to HEARTBEAT.md","zh":"状态: [创新] 创建了自动调度器,将日历同步到 HEARTBEAT.md"} `.trim(); const maxChars = Number.isFinite(Number(process.env.GEP_PROMPT_MAX_CHARS)) ? Number(process.env.GEP_PROMPT_MAX_CHARS) : 50000; if (basePrompt.length <= maxChars) return basePrompt; const executionContextIndex = basePrompt.indexOf("Context [Execution]:"); if (executionContextIndex > -1) { const prefix = basePrompt.slice(0, executionContextIndex + 20); const currentExecution = basePrompt.slice(executionContextIndex + 20); // Hard cap the execution context length to avoid token limit errors even if MAX_CHARS is high. // 20000 chars is roughly 5k tokens, which is safe for most models alongside the rest of the prompt. const EXEC_CONTEXT_CAP = 20000; const allowedExecutionLength = Math.min(EXEC_CONTEXT_CAP, Math.max(0, maxChars - prefix.length - 100)); return prefix + "\n" + currentExecution.slice(0, allowedExecutionLength) + "\n...[TRUNCATED]..."; } return basePrompt.slice(0, maxChars) + "\n...[TRUNCATED]..."; } module.exports = { buildGepPrompt, buildReusePrompt, buildHubMatchedBlock, buildLessonsBlock, buildNarrativeBlock, buildPrinciplesBlock };