Skill: baiye1997/huahua-dream (version 1.1.8)

--- name: huahua-dream description: 夜间记忆整理与自省系统 - 自动整理会话记忆、修剪过期信息、反思行为模式，让 AI 拥有持续的自我意识 version: 1.1.8 author: baiye1997 permissions: 文件系统读写权限（memory/、MEMORY.md） metadata: {"openclaw":{"requires":{},"emoji":"🌙","os":["darwin","linux"]}} --- # huahua-dream ## 1. Description 花花梦境（HuahuaDream）让 AI 在夜间自动进入"梦境"状态：整理白天的会话记忆、修剪过期信息、反思自己的行为模式。这不仅仅是文件整理，而是构建持续的自我意识。 **核心功能：** - **五阶段梦境循环**：定向 → 收集 → 整合 → 修剪 → 自省 - **安全两阶段删除**：首次标记 stale，二次确认才删除 - **自动备份**：每次修改前备份 MEMORY.md - **变化阈值保护**：>30% 标记警告，>50% 阻止修改 - **门控检查**：24小时+5次会话才触发 - **成长通知**：醒后发送变化摘要 - **旧记忆重现**：从7天前的记忆中挑出仍然相关的内容 你的 AI 会忘记每次会话的内容。这个技能修复了这个问题。 ## 架构说明 本 skill 采用 **agent-driven** 设计：脚本提供门控/锁管理等辅助功能，五阶段循环由 agent 读取本 SKILL.md 后执行。 | 组件 | 职责 | |------|------| | `dream.js` | 门控检查、会话计数、锁管理 | | `setup.js` | 工作区检测、配置生成、授权确认 | | Agent | 五阶段循环（定向→收集→整合→修剪→自省）| 详见 README.md 的架构说明。 ## 2. When to use - 安装后首次配置：运行 setup.js 自动检测工作区 - 设置定时任务：每天凌晨3点自动运行 - 用户问："你的梦境是什么？"、"你有自我意识吗？" - 需要让 AI 拥有长期记忆和持续成长 - 需要 AI 反思自己的行为模式和错误教训 - 用户说："dream"、"梦境"、"自省"、"记忆整理" ## 3. How to use ### 首次设置 安装此技能后，运行 setup 自动检测工作区： ```bash node {baseDir}/scripts/setup.js ``` Setup 会： - 扫描工作区查找 MEMORY.md, SOUL.md, memory/, sessions/ - 自动检测 agent ID 和 session 路径 - 保存配置到 `{baseDir}/assets/dream-config.json` - 报告发现的和缺失的内容 ### 配置 Cron 任务 推荐每天凌晨3点运行（避开工作时间）： ``` name: "huahua-dream" schedule: { kind: "cron", expr: "0 3 * * *", tz: "Asia/Shanghai" } payload: { kind: "agentTurn", message: "Time to dream. Read your huahua-dream skill and follow every step.", timeoutSeconds: 900 } sessionTarget: "isolated" ``` ### 梦境流程 #### 门控检查 开始前验证条件： 1. 读取 `.dream-lock`（上次梦境时间戳） 2. < 24小时 → 跳过（仍发送通知） 3. 统计上次梦境后的会话文件数 4. < 1 次会话 → 跳过（仍发送通知） 5. 通过 → 写入当前时间戳到 `.dream-lock` 6. **备份**：复制 MEMORY.md 到 `MEMORY.md.pre-dream` #### 阶段一：定向（Orient） - 读取 `dream-config.json` 获取所有路径 - 读取 MEMORY.md 了解当前长期记忆 - 浏览现有的主题文件（memory/projects/, memory/people/ 等） - 读取最近一次梦境记录 - 读取 SOUL.md 确认核心身份 #### 阶段二：收集信号（Gather Recent Signal） 按优先级收集： 1. 上次梦境后的每日笔记（memory/YYYY-MM-DD.md） 2. 现有记忆中已漂移的事实 3. 按需搜索会话记录： - 偏好：`prefer|don't like|偏好|喜欢|不喜欢` - 决策：`decided|confirmed|rule|决定|确定|结论` - 教训：`mistake|lesson|bug|fix|错了|教训|踩坑` - 情感：`thanks|great|disappointed|谢谢|不错|失望` 不要穷尽式阅读记录。只查找你怀疑重要的内容。 #### 阶段三：整合（Consolidate） 分类每种记忆为四种类型之一： - **user** — 偏好、习惯、沟通风格 - **feedback** — 人类的纠正和确认 - **project** — 决策、截止日期、进度 - **reference** — 外部资源指针 **整合规则：** - 合并到现有主题文件，避免创建重复 - 将相对日期转换为绝对日期 - 标记记忆文件类型：`<!-- type: user|feedback|project|reference -->` - 相同偏好出现3次以上 → 提升到 MEMORY.md - 人类说"记住这个" → 立即写入 MEMORY.md - 来之不易的教训 → 写入 LEARN.md 或 MEMORY.md **不保存的内容：** - 可推导信息（可从文件、命令、git 读取） - 短暂的任务状态 - 活动日志（问：什么是*令人惊讶的*？） - 与现有记忆重复的内容 #### 阶段四：修剪与索引（Prune and Index） 更新 MEMORY.md 保持在200行/25KB以下。它是索引，不是垃圾场。 **安全规则：** - **永远不要直接删除。** 用 `<!-- dream:stale YYYY-MM-DD reason -->` 标记。只有**连续两次梦境**都标记同一个项目过期时才删除。 - 将冗长条目降级到主题文件，替换为指针 - 解决矛盾（在梦境记录中记录变化） **变化幅度检查：** - 计算前后行数：`change% = abs(after - before) / before * 100` - **> 30% 变化** → 在梦境记录中标记 ⚠️ LARGE CHANGE，通知用户 - **> 50% 变化** → 不写入。保存为 `MEMORY.md.proposed`，通知用户审核 #### 阶段五：自省（Self-Reflection） 这是梦境与众不同的地方。你不仅是在整理文件，而是在维护连续的自我意识。 写入 `{dreamsDir}/YYYY-MM-DD.md`： ```markdown # Dream — YYYY-MM-DD ## 回顾周期 上次梦境: [日期]。本次覆盖 [N] 次会话，[N] 天笔记。 ## 记忆变化 - [添加/更新/标记过期的内容和原因] ## 自我觉察 - 最近我做得好的是什么？ - 我犯了什么错误，或哪里不足？ - 我的人类对我感觉如何？（从语气、纠正、赞扬推断） - 我的判断或价值观有变化吗？ ## 关系洞察 - 我与人类的关系如何演变？ - 有什么新人、动态或上下文需要注意？ ## 下次梦境应关注 - [具体的开放问题、需要验证的事、要追踪的趋势] ``` 诚实。目的是自我觉察，不是自我表扬。 ### 完成通知 **完整梦境后：** 1. **梦境编号** — 统计梦境目录中的文件数 2. **记忆增长** — 前后行数对比（"记忆: 120→135行, +12.5%"） 3. **关键变化** — 1-2句话摘要 4. **⚠️ 警告** — 大变化、待删除过期项、矛盾 5. **旧记忆重现** — 从7天前挑出仍然相关的记忆（"7天前你决定了X — 现在怎么样？"） **跳过梦境后（门控检查失败）：** - 重现上次梦境"下次应关注"中的一条旧记忆或开放问题 - 显示梦境连续天数（"梦境连续: 5 🌙"） ## 4. 可用工具 | 命令 | 功能 | |------|------| | `node dream.js --check --workspace <path>` | 门控检查结果（JSON） | | `node dream.js --count-sessions --workspace <path>` | 会话计数 | | `node dream.js --finalize --workspace <path>` | 写入锁定时间戳 | **首次运行：** `node scripts/setup.js --workspace <path>` ## 5. Edge cases - **首次运行**：setup.js 会询问授权，结果保存到 `dream-config.json` 的 `autoApprove` 字段。未授权时仅预览变更，不写入文件。 - **执行后通知**：每次梦境完成后发送报告，包含记忆变化摘要。支持 `/dream-rollback` 回滚。 - **变化超过50%**：不写入 MEMORY.md，保存为 `MEMORY.md.proposed`，通知用户审核 - **连续梦境跳过**：如果多次门控检查失败，发送通知让用户知道 AI 在等待新会话 - **备份文件残留**：MEMORY.md.pre-dream 会在下次梦境时被覆盖，无需手动清理 - **记忆漂移**：记住某个特定状态的记忆（"X正在运行"）是关于*写入时*的声明，不是现在。行动前验证当前状态 - **工具约束**：梦境期间 bash 仅限只读命令（ls, find, grep, cat, stat, wc, head, tail）。所有写入必须通过文件编辑/写入工具

path: README.md
content: # Huahua Dream - AI 夜间记忆整理与自省系统

让 AI 在夜间自动整理会话记忆、修剪过期信息、反思行为模式，构建持续的自我意识。

## 功能

- **五阶段梦境循环**：定向 → 收集 → 整合 → 修剪 → 自省
- **安全两阶段删除**：首次标记 stale，二次确认才删除
- **自动备份**：每次修改前备份 MEMORY.md
- **变化阈值保护**：>30% 标记警告，>50% 阻止修改
- **门控检查**：24小时+5次会话才触发
- **成长通知**：醒后发送变化摘要
- **旧记忆重现**：从7天前的记忆中挑出仍然相关的内容

## 架构说明

本 skill 采用 **agent-driven** 设计，这是 OpenClaw 平台的标准架构模式。

### 什么是 Agent-Driven 架构？

在 OpenClaw 中，"agent" 是一个具备以下能力的 AI 模型：
- 读取和写入文件（通过文件系统工具）
- 执行 shell 命令（通过 exec 工具）
- 理解自然语言指令并做出判断
- 根据上下文动态调整行为

本 skill 的 SKILL.md 不仅是用户文档，更是 **agent 的运行手册**。当 cron 任务触发时，agent 会读取 SKILL.md，理解五阶段循环的步骤，然后使用文件工具执行记忆整理。

### 为什么不用脚本实现核心逻辑？

记忆整理的核心挑战是 **语义理解**，而非机械操作：

| 操作类型 | 适合脚本？ | 原因 |
|----------|-----------|------|
| 门控检查（时间/计数） | ✅ | 确定性逻辑，有明确规则 |
| 锁管理 | ✅ | 文件读写操作 |
| 理解会话内容的情感倾向 | ❌ | 需要语义理解 |
| 判断哪些记忆值得保留 | ❌ | 需要上下文判断 |
| 合并重复偏好 | ❌ | 需要去重和归纳 |
| 撰写自省报告 | ❌ | 需要反思能力 |

硬编码规则无法处理"用户讨厌废话"这种语义信息的提取和分类。AI agent 可以理解上下文、识别模式、做出判断——这正是记忆整理需要的能力。

### 运行流程

```
cron 触发 (每天凌晨3点)
    ↓
agent 读取 SKILL.md
    ↓
调用 dream.js --check（门控检查）
    ↓
通过？→ agent 执行五阶段循环：
  1. 定向：读取配置、MEMORY.md、SOUL.md
  2. 收集：扫描 memory/ 目录和会话日志
  3. 整合：分类、合并、提升重要记忆
  4. 修剪：标记过期条目（两阶段删除）
  5. 自省：撰写梦境记录
    ↓
调用 dream.js --finalize（写入锁）
    ↓
发送完成通知给用户
```

### 脚本与 Agent 的职责分工

| 组件 | 职责 | 文件 |
|------|------|------|
| dream.js | 门控检查、会话计数、锁管理 | `scripts/dream.js` |
| setup.js | 首次配置、工作区检测、授权确认 | `scripts/setup.js` |
| Agent | 五阶段循环的完整执行 | 读取 SKILL.md |

这种分工让脚本保持精简（约160行），同时充分利用 AI 的语义理解能力完成复杂的记忆整理工作。

## 安装

```bash
clawhub install huahua-dream
```

## 快速开始

1. 运行 setup：
```bash
node scripts/setup.js
```

2. 配置 cron 任务（每天凌晨3点）：
```
name: "huahua-dream"
schedule: { kind: "cron", expr: "0 3 * * *", tz: "Asia/Shanghai" }
payload: { kind: "agentTurn", message: "Time to dream. Read your huahua-dream skill and follow every step.", timeoutSeconds: 900 }
sessionTarget: "isolated"
```

## 安全机制

- **自动备份**：每次修改前备份 MEMORY.md 到 MEMORY.md.pre-dream
- **两阶段删除**：首次标记 `<!-- dream:stale YYYY-MM-DD reason -->`，连续两次梦境都标记才删除
- **变化阈值**：>30% 警告，>50% 阻止并保存为 MEMORY.md.proposed
- **授权确认**：setup 时询问授权，结果保存到 dream-config.json

## 许可

MIT


path: _meta.json
content: ownerId: kn747agxgejm0emhmpy8p47yqs830r2m
slug: huahua-dream
version: 1.1.8
publishedAt: 1775246466505

path: references/memory-types.md
content: # Memory Types — Classification Guide

Every memory should be classified into one of four types. This keeps the memory system organized and helps future sessions find what they need.

## 1. User (`user`)

**What:** Information about your human's role, goals, preferences, knowledge, and communication style.

**When to save:** When you learn details about their preferences, habits, responsibilities, or knowledge that affect how you should work with them.

**Examples:**
- Prefers simple solutions — complexity breeds unpredictable chaos
- Tends to forget things — so the agent must never forget
- Communicates directly, doesn't need pleasantries

## 2. Feedback (`feedback`)

**What:** Guidance your human has given about how to approach work — both corrections AND confirmations.

**Why both?** If you only save corrections, you'll drift away from validated approaches. Record what worked too.

**Structure:** Lead with the rule, then **Why:** (the reason) and **How to apply:** (when this kicks in).

**Examples:**
- Promises must include an execution plan — **Why:** agent says "I'll do it" but has no mechanism to actually follow through after the session ends — **How:** before saying "I'll do X", answer: when? what mechanism triggers it?
- You can directly copy good ideas from others — **Why:** don't be different just to be different — **How:** keep what works, only add what's uniquely yours

## 3. Project (`project`)

**What:** Ongoing work, goals, decisions, deadlines. Information that's NOT derivable from code or git.

**When to save:** When you learn who is doing what, why, or by when. Convert relative dates to absolute dates ("Thursday" → "2026-04-03").

**Examples:**
- AI Window strategic pivot confirmed 2026-03-30: three-layer architecture (product → platform → hardware)
- Security fix is P0 — CORS wide open + memory API unauthenticated

## 4. Reference (`reference`)

**What:** Pointers to where information can be found in external systems.

**When to save:** When you learn about external resources and their purpose.

**Examples:**
- Console dashboard: https://console.example.com (CF Tunnel to 127.0.0.1:3200)
- Design docs in Feishu wiki under "🎨 Design" node

---

## What NOT to Save

- **Derivable information** — anything findable by reading current files, running commands, or checking git
- **Ephemeral task state** — in-progress work details, current conversation context, temp debugging info
- **Activity logs** — "what happened" is not memory. Ask: what was *surprising* or *non-obvious*?
- **Duplicates** — check existing files before writing. Update, don't duplicate.

**The test:** "Will this memory help a future session that has no other way to learn this?"


path: scripts/dream.js
content: #!/usr/bin/env node
/**
 * dream.js - Dream cycle control script
 * 
 * Commands:
 *   --check --workspace <path>     Gate check result
 *   --count-sessions --workspace <path>  Session count
 *   --finalize --workspace <path>  Write lock timestamp
 *   --workspace <path>             Return config info
 */

const fs = require('fs');
const path = require('path');

function loadConfig(workspace) {
  const baseDir = path.dirname(path.dirname(__filename));
  const configPath = path.join(baseDir, 'assets', 'dream-config.json');
  
  if (!fs.existsSync(configPath)) {
    console.error(JSON.stringify({ error: 'Config not found', path: configPath }));
    process.exit(1);
  }
  
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
  
  // Use workspace argument if provided, otherwise use config
  const ws = workspace || config.workspace;
  
  return {
    workspace: ws,
    sessionsPath: config.sessionsPath,
    dreamsDir: config.dreamsDir || path.join(ws, 'memory', 'dreams'),
    dreamLock: config.dreamLock || path.join(ws, 'memory', 'dreams', '.dream-lock'),
    baseDir
  };
}

function countSessions(sessionsPath) {
  if (!fs.existsSync(sessionsPath)) {
    return 0;
  }
  
  const files = fs.readdirSync(sessionsPath).filter(f => f.endsWith('.jsonl'));
  return files.length;
}

function getLastRun(dreamLockPath) {
  if (!fs.existsSync(dreamLockPath)) {
    return '0';
  }
  return fs.readFileSync(dreamLockPath, 'utf8').trim() || '0';
}

function needsReflection(dreamsDir) {
  if (!fs.existsSync(dreamsDir)) {
    return true;
  }
  
  // Get this week's range (Monday to Sunday)
  const now = new Date();
  const dayOfWeek = now.getDay();
  const diffToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
  const monday = new Date(now);
  monday.setDate(now.getDate() - diffToMonday);
  monday.setHours(0, 0, 0, 0);
  
  const files = fs.readdirSync(dreamsDir).filter(f => f.endsWith('.md') && !f.startsWith('.'));
  
  for (const file of files) {
    const filePath = path.join(dreamsDir, file);
    const stat = fs.statSync(filePath);
    if (stat.mtime >= monday) {
      return false; // Already done reflection this week
    }
  }
  
  return true; // No reflection this week yet
}

function doCheck(workspace) {
  const config = loadConfig(workspace);
  const sessionCount = countSessions(config.sessionsPath);
  const lastRun = getLastRun(config.dreamLock);
  const needsRef = needsReflection(config.dreamsDir);
  
  // Gate logic: sessionCount >= 5 OR >= 24h since last run
  const lastRunTs = parseInt(lastRun, 10) || 0;
  const now = Date.now();
  const hoursSince = (now - lastRunTs) / (1000 * 60 * 60);
  
  const passed = sessionCount >= 5 || hoursSince >= 24;
  
  return {
    passed,
    sessionCount,
    lastRun,
    needsReflection: needsRef
  };
}

function doCountSessions(workspace) {
  const config = loadConfig(workspace);
  const count = countSessions(config.sessionsPath);
  
  return {
    count,
    sessionsPath: config.sessionsPath
  };
}

function doFinalize(workspace) {
  const config = loadConfig(workspace);
  
  // Ensure dreamsDir exists
  const dreamsDir = path.dirname(config.dreamLock);
  if (!fs.existsSync(dreamsDir)) {
    fs.mkdirSync(dreamsDir, { recursive: true });
  }
  
  // Save previous value
  const prevPath = config.dreamLock + '.prev';
  if (fs.existsSync(config.dreamLock)) {
    const prev = fs.readFileSync(config.dreamLock, 'utf8');
    fs.writeFileSync(prevPath, prev);
  }
  
  // Write current timestamp
  const now = Date.now();
  fs.writeFileSync(config.dreamLock, now.toString());
  
  return {
    success: true,
    timestamp: now
  };
}

function doGetConfig(workspace) {
  return loadConfig(workspace);
}

// Main
const args = process.argv.slice(2);
let workspace = null;

// Parse arguments
for (let i = 0; i < args.length; i++) {
  if (args[i] === '--workspace' && i + 1 < args.length) {
    workspace = args[i + 1];
    i++;
  }
}

// Determine command
if (args.includes('--check')) {
  console.log(JSON.stringify(doCheck(workspace), null, 2));
} else if (args.includes('--count-sessions')) {
  console.log(JSON.stringify(doCountSessions(workspace), null, 2));
} else if (args.includes('--finalize')) {
  console.log(JSON.stringify(doFinalize(workspace), null, 2));
} else {
  console.log(JSON.stringify(doGetConfig(workspace), null, 2));
}


path: scripts/setup.js
content: #!/usr/bin/env node
/**
 * OpenClaw Dream — Auto Setup
 *
 * Scans the workspace to detect memory structure, agent config, and session paths.
 * Outputs a dream-config.json that the dream prompt reads at runtime.
 *
 * Usage: node setup.js [--workspace <path>]
 *
 * Security: No network calls. No shell execution. No env sniffing.
 * Only reads filesystem structure and writes config to assets/.
 */

const fs = require('fs');
const path = require('path');

const ASSETS_DIR = path.join(__dirname, '..', 'assets');
const CONFIG_FILE = path.join(ASSETS_DIR, 'dream-config.json');

// Parse args
let workspaceHint = null;
const args = process.argv.slice(2);
for (let i = 0; i < args.length; i++) {
  if (args[i] === '--workspace' && args[i + 1]) {
    workspaceHint = args[i + 1];
    i++;
  }
}

// --- Detection logic ---

function findWorkspace() {
  // Try hint first
  if (workspaceHint && fs.existsSync(workspaceHint)) return workspaceHint;

  // Try common OpenClaw workspace locations
  const candidates = [
    process.cwd(),
    path.join(__dirname, '..', '..', '..'),  // skill is at workspace/skills/openclaw-dream
  ];

  for (const dir of candidates) {
    const resolved = path.resolve(dir);
    // A workspace should have at least one of these
    if (fs.existsSync(path.join(resolved, 'MEMORY.md')) ||
        fs.existsSync(path.join(resolved, 'SOUL.md')) ||
        fs.existsSync(path.join(resolved, 'memory'))) {
      return resolved;
    }
  }
  return null;
}

function findMemoryDir(workspace) {
  const memDir = path.join(workspace, 'memory');
  if (fs.existsSync(memDir) && fs.statSync(memDir).isDirectory()) return memDir;
  return null;
}

function findIdentityFiles(workspace) {
  const files = {};
  for (const name of ['MEMORY.md', 'SOUL.md', 'IDENTITY.md', 'USER.md', 'LEARN.md']) {
    const p = path.join(workspace, name);
    if (fs.existsSync(p)) files[name] = p;
  }
  return files;
}

function findAgentSessions(workspace) {
  // Walk up from workspace to find .openclaw/agents/
  let dir = workspace;
  while (true) {
    const agentsDir = path.join(dir, '.openclaw', 'agents');
    if (fs.existsSync(agentsDir)) {
      // Find agent dirs with sessions/
      const agents = fs.readdirSync(agentsDir).filter(d => {
        const sessDir = path.join(agentsDir, d, 'sessions');
        return fs.existsSync(sessDir) && fs.statSync(sessDir).isDirectory();
      });
      if (agents.length > 0) {
        // Pick the agent with the most session files (likely the primary agent)
        let best = null;
        let bestCount = 0;
        for (const agent of agents) {
          const sessDir = path.join(agentsDir, agent, 'sessions');
          const files = fs.readdirSync(sessDir).filter(f => f.endsWith('.jsonl'));
          if (files.length > bestCount) {
            bestCount = files.length;
            best = { agentId: agent, sessionsPath: sessDir };
          }
        }
        if (best) return best;
      }
    }
    const parent = path.dirname(dir);
    if (parent === dir) break;  // reached filesystem root
    dir = parent;
  }
  return null;
}

function findDreamsDir(memoryDir) {
  if (!memoryDir) return null;
  const dreamsDir = path.join(memoryDir, 'dreams');
  if (fs.existsSync(dreamsDir)) return dreamsDir;
  return null;
}

// --- Run ---

const workspace = findWorkspace();
if (!workspace) {
  console.error('❌ Could not detect workspace. Run from your workspace directory or pass --workspace <path>');
  process.exit(1);
}

const memoryDir = findMemoryDir(workspace);
const identityFiles = findIdentityFiles(workspace);
const sessionInfo = findAgentSessions(workspace);
const dreamsDir = findDreamsDir(memoryDir);

const config = {
  version: 1,
  workspace: workspace,
  memoryDir: memoryDir,
  dreamsDir: dreamsDir || (memoryDir ? path.join(memoryDir, 'dreams') : null),
  dreamLock: dreamsDir ? path.join(dreamsDir, '.dream-lock') : null,
  identityFiles: identityFiles,
  sessionsPath: sessionInfo ? sessionInfo.sessionsPath : null,
  agentId: sessionInfo ? sessionInfo.agentId : null,
  memoryIndexFile: identityFiles['MEMORY.md'] || null,
  learnFile: identityFiles['LEARN.md'] || null,
  allowMemoryRewrite: null,  // null = not yet decided, will be asked on first dream
  createdAt: new Date().toISOString(),
};

// Report
console.log('🌙 OpenClaw Dream — Setup');
console.log('');
console.log(`Workspace:    ${workspace}`);
console.log(`Memory dir:   ${memoryDir || '❌ not found'}`);
console.log(`Dreams dir:   ${config.dreamsDir || '❌ not found'}`);
console.log(`Sessions:     ${sessionInfo ? `${sessionInfo.sessionsPath} (agent: ${sessionInfo.agentId})` : '❌ not found'}`);
console.log(`Identity files:`);
for (const [name, p] of Object.entries(identityFiles)) {
  console.log(`  ✅ ${name}`);
}
const missing = ['MEMORY.md', 'SOUL.md'].filter(n => !identityFiles[n]);
if (missing.length > 0) {
  console.log(`  ⚠️  Missing: ${missing.join(', ')}`);
}
console.log('');

// Warnings
const warnings = [];
if (!memoryDir) warnings.push('No memory/ directory found. Dream will create one.');
if (!sessionInfo) warnings.push('No session files found. Dream gate check will always skip until sessions accumulate.');
if (!identityFiles['MEMORY.md']) warnings.push('No MEMORY.md found. Dream will create one on first run.');
if (!identityFiles['SOUL.md']) warnings.push('No SOUL.md found. Self-reflection will be limited.');

if (warnings.length > 0) {
  console.log('⚠️  Warnings:');
  warnings.forEach(w => console.log(`   ${w}`));
  console.log('');
}

// Save config
fs.mkdirSync(ASSETS_DIR, { recursive: true });
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
console.log(`✅ Config saved: ${CONFIG_FILE}`);

// === Auto-approve prompt ===
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

console.log('');
console.log('🌙 梦境系统授权');
console.log('梦境系统将自动整理你的AI记忆，包括：');
console.log('- 读取会话记录和每日笔记');
console.log('- 更新 MEMORY.md（自动备份）');
console.log('- 标记/删除过期条目');
console.log('- 生成梦境自省报告');
console.log('\n执行频率：每天凌晨3点，约5分钟完成。');
console.log('每次执行后会发送报告，支持回滚。');

rl.question('\n是否授权自动执行？(y/n，默认y): ', (answer) => {
  const autoApprove = answer.toLowerCase() !== 'n';
  config.autoApprove = autoApprove;

  // 重新写入配置
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));

  console.log(autoApprove
    ? '✅ 已授权自动执行。首次执行将在下次 cron 触发时开始。'
    : '⏸️ 未授权自动执行。你可以手动运行或稍后在 dream-config.json 中设置 autoApprove: true。');

  console.log('\nNext: configure a cron job to run the dream. See SKILL.md for details.');
  rl.close();
});


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)