#!/usr/bin/env node /** * Guard 6: Dreaming Phase Guard * Prevents dreaming phase proliferation beyond 3 phases. * * → Architecture Freeze v2 (PR-1): docs/architecture-freeze-v2.md * → Current: light, deep, rem | Legacy: rem → to be replaced by promote * * Currently allowed phases: light, deep, rem * After PR-5: will be reduced to collect, promote * * FAIL: new dreaming phase appears (count > 3) * PASS: phase count <= 3 */ import { readdirSync, readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; const OPENCLAW_HOME = process.env.OPENCLAW_HOME || "/opt/homebrew/lib/node_modules/openclaw"; const EXTENSIONS_DIR = join(OPENCLAW_HOME, "dist", "extensions"); const PHASE_LIMIT = 3; // Current baseline const POST_PR5_LIMIT = 2; // After PR-5: collect + promote function guardDreamingPhases() { const issues = []; let foundPhases = []; let source = "none"; // Check memory-core plugin config schema const memoryCorePath = join(EXTENSIONS_DIR, "memory-core", "openclaw.plugin.json"); if (existsSync(memoryCorePath)) { try { const plugin = JSON.parse(readFileSync(memoryCorePath, "utf8")); const dreaming = plugin.configSchema?.properties?.dreaming; if (dreaming) { const phases = dreaming.properties?.phases?.properties; if (phases) { foundPhases = Object.keys(phases); source = "memory-core/openclaw.plugin.json → configSchema.properties.dreaming.properties.phases"; } } } catch (e) { issues.push({ level: "ERROR", msg: `Could not parse memory-core plugin.json: ${e.message}` }); } } // Also check active-memory for dreaming references const activeMemoryPath = join(EXTENSIONS_DIR, "active-memory", "openclaw.plugin.json"); if (existsSync(activeMemoryPath)) { try { const plugin = JSON.parse(readFileSync(activeMemoryPath, "utf8")); // Check if active-memory references phases const configKeys = Object.keys(plugin.configSchema?.properties || {}); if (configKeys.includes("dreaming")) { // Active memory has dreaming config too } } catch {} } console.log(""); console.log("═══════════════════════════════════════"); console.log(" Guard 6: Dreaming Phase Guard"); console.log("═══════════════════════════════════════"); console.log(""); if (foundPhases.length === 0) { console.log(" No dreaming phases found in plugin config schemas."); console.log(" (This may indicate a config schema location change.)"); issues.push({ level: "WARN", msg: "Could not locate dreaming phases in config schema" }); } else { console.log(` Source: ${source}`); console.log(` Dreaming phases: ${foundPhases.join(", ")}`); console.log(` Phase count: ${foundPhases.length}`); console.log(` Current limit: ${PHASE_LIMIT} (light, deep, rem)`); console.log(` Post-PR5 limit: ${POST_PR5_LIMIT} (collect, promote)`); if (foundPhases.length > PHASE_LIMIT) { issues.push({ level: "FAIL", msg: `Dreaming phase count ${foundPhases.length} exceeds limit ${PHASE_LIMIT}` }); } else { console.log(`\n Phase details:`); for (const phase of foundPhases) { console.log(` • ${phase}`); } } } console.log(""); for (const issue of issues) { console.log(` [${issue.level}] ${issue.msg}`); } if (issues.filter(i => i.level === "FAIL").length === 0) { console.log(" ✓ PASS: dreaming phase count within allowed limit"); console.log(` (After PR-5, limit will be reduced from ${PHASE_LIMIT} → ${POST_PR5_LIMIT})`); process.exit(0); } else { console.log(" ✗ FAIL: dreaming phase count exceeded"); process.exit(1); } } guardDreamingPhases();