metame-cli 1.4.12 → 1.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -105,11 +105,11 @@ Built into the daemon. Runs every 60 seconds regardless of what's in your config
105
105
  - Detects when you go idle → generates session continuity summaries
106
106
 
107
107
  **Layer 1 — System Evolution (built-in defaults)**
108
- Three tasks shipped out of the box. Only fire when you're idle they never interrupt active work:
108
+ Three tasks shipped out of the box. They are precondition-gated and run only when useful:
109
109
 
110
110
  ```yaml
111
111
  - cognitive-distill # 4h · has signals? → distill preferences into profile
112
- - memory-extract # 2h · scan sessions → extract long-term facts + topic tags
112
+ - memory-extract # 4h · scan sessions → extract long-term facts + topic tags
113
113
  - skill-evolve # 6h · has signals? → evolve skills from task outcomes
114
114
  ```
115
115
 
@@ -150,7 +150,7 @@ Chain skills into multi-step workflows — research → write → publish — fu
150
150
  prompt: "Publish it"
151
151
  ```
152
152
 
153
- Task options: `require_idle` (defer when you're active), `precondition` (shell guard — skip if false, zero tokens), `notify` (push result to phone), `model`, `cwd`, `allowedTools`, `timeout`.
153
+ Task options: `require_idle` (defer when you're active, retry on next heartbeat tick), `precondition` (shell guard — skip if false, zero tokens), `notify` (push result to phone), `model`, `cwd`, `allowedTools`, `timeout`.
154
154
 
155
155
  ### 5. Skills That Evolve Themselves
156
156
 
@@ -319,14 +319,14 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
319
319
  │ (memory layer) ← NEW │
320
320
  └──────────────────────────────┘
321
321
 
322
- sleep mode → memory consolidation
323
- (background, automatic)
322
+ idle mode → summaries + background memory tasks
323
+ (automatic, precondition-gated)
324
324
  ```
325
325
 
326
326
  - **Profile** (`~/.claude_profile.yaml`): Your cognitive fingerprint. Injected into every Claude session via `CLAUDE.md`.
327
- - **Daemon** (`scripts/daemon.js`): Background process handling Telegram/Feishu messages, heartbeat tasks, Unix socket dispatch, and sleep-mode memory triggers.
328
- - **Distillation** (`scripts/distill.js`): On each launch, silently analyzes your recent messages and updates your profile.
329
- - **Memory Extract** (`scripts/memory-extract.js`): Triggered on sleep mode. Extracts long-term facts and session topic tags from completed conversations.
327
+ - **Daemon** (`scripts/daemon.js`): Background process handling Telegram/Feishu messages, heartbeat tasks, Unix socket dispatch, and idle/sleep transitions.
328
+ - **Distillation** (`scripts/distill.js`): Heartbeat task (default 4h, signal-gated) that updates your profile.
329
+ - **Memory Extract** (`scripts/memory-extract.js`): Heartbeat task (default 4h, idle-gated) that extracts long-term facts and session topic tags.
330
330
  - **Session Summarize** (`scripts/session-summarize.js`): Generates a 2-4 sentence summary for idle sessions. Injected as context when resuming after a 2h+ gap.
331
331
 
332
332
  ## Security
@@ -349,7 +349,7 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
349
349
  | Session summary (per session) | ~400–900 tokens input + ≤250 tokens output (Haiku) |
350
350
  | Mobile commands (`/stop`, `/list`, `/undo`) | 0 tokens |
351
351
 
352
- > Both memory consolidation and session summarization run in the background via Haiku (`--model haiku`). Input is capped by code: skeleton text ≤ 3,000 chars, summary output ≤ 500 chars. Neither runs per-message — memory consolidation triggers on sleep mode (30-min idle), summaries trigger once per idle session.
352
+ > Both memory consolidation and session summarization run in the background via Haiku (`--model haiku`). Input is capped by code: skeleton text ≤ 3,000 chars, summary output ≤ 500 chars. Neither runs per-message — memory consolidation follows heartbeat schedule with idle/precondition guards, and summaries trigger once per idle session on sleep-mode transitions.
353
353
 
354
354
  ## Plugin
355
355
 
package/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
- const { spawn } = require('child_process');
6
+ const { spawn, execSync } = require('child_process');
7
7
 
8
8
  // ---------------------------------------------------------
9
9
  // 1. CONFIGURATION
@@ -29,8 +29,16 @@ if (!fs.existsSync(METAME_DIR)) {
29
29
 
30
30
  // Auto-deploy bundled scripts to ~/.metame/
31
31
  // IMPORTANT: daemon.yaml is USER CONFIG — never overwrite it. Only daemon-default.yaml (template) is synced.
32
- const BUNDLED_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js', 'daemon.js', 'telegram-adapter.js', 'feishu-adapter.js', 'daemon-default.yaml', 'providers.js', 'session-analytics.js', 'resolve-yaml.js', 'utils.js', 'skill-evolution.js', 'memory.js', 'memory-extract.js', 'qmd-client.js', 'session-summarize.js'];
33
32
  const scriptsDir = path.join(__dirname, 'scripts');
33
+ const BUNDLED_BASE_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js', 'daemon.js', 'telegram-adapter.js', 'feishu-adapter.js', 'daemon-default.yaml', 'providers.js', 'session-analytics.js', 'resolve-yaml.js', 'utils.js', 'skill-evolution.js', 'memory.js', 'memory-extract.js', 'qmd-client.js', 'session-summarize.js'];
34
+ const DAEMON_MODULE_SCRIPTS = (() => {
35
+ try {
36
+ return fs.readdirSync(scriptsDir).filter((f) => /^daemon-[\w-]+\.js$/.test(f));
37
+ } catch {
38
+ return [];
39
+ }
40
+ })();
41
+ const BUNDLED_SCRIPTS = [...new Set([...BUNDLED_BASE_SCRIPTS, ...DAEMON_MODULE_SCRIPTS])];
34
42
 
35
43
  // Protect daemon.yaml: create backup before any sync operation
36
44
  const DAEMON_YAML_BACKUP = path.join(METAME_DIR, 'daemon.yaml.bak');
@@ -213,15 +221,12 @@ function spawnDistillBackground() {
213
221
 
214
222
  if (!hasSignals && !bootstrap) return;
215
223
 
216
- if (hasSignals) {
217
- const bufferFile = path.join(METAME_DIR, 'raw_signals.jsonl');
218
- const lines = fs.readFileSync(bufferFile, 'utf8').trim().split('\n').filter(l => l.trim());
219
- console.log(`🧠 MetaMe: Distilling ${lines.length} moment${lines.length > 1 ? 's' : ''} in background...`);
220
- }
224
+ // Note: status display is handled separately in startup output — no log here
221
225
  if (bootstrap) {
222
- console.log('📊 MetaMe: Bootstrapping session history...');
226
+ // Background bootstrap silent, no need to inform user
223
227
  }
224
228
 
229
+
225
230
  // Spawn as detached background process — won't block Claude launch
226
231
  // Remove CLAUDECODE env var so distill.js can call `claude -p` without nested-session rejection
227
232
  const distillEnvClean = { ...process.env };
@@ -609,41 +614,72 @@ try {
609
614
  // Non-fatal
610
615
  }
611
616
 
612
- // Prepend the new Protocol to the top (mirror + reflection inside markers)
613
- const newContent = finalProtocol + mirrorLine + reflectionLine + METAME_END + "\n" + fileContent;
617
+ // Project-level CLAUDE.md: KERNEL has moved to global ~/.claude/CLAUDE.md.
618
+ // Only inject dynamic per-session observations (mirror / reflection).
619
+ // If nothing dynamic, write the cleaned file with no METAME block at all.
620
+ const dynamicContent = mirrorLine + reflectionLine;
621
+ const newContent = dynamicContent.trim()
622
+ ? METAME_START + '\n' + dynamicContent + METAME_END + '\n' + fileContent
623
+ : fileContent;
614
624
  fs.writeFileSync(PROJECT_FILE, newContent, 'utf8');
615
625
 
616
626
  // ---------------------------------------------------------
617
- // 4.7 GLOBAL CLAUDE.MD INJECTION (Agent capabilities)
627
+ // 4.7 GLOBAL CLAUDE.MD INJECTION (Full Kernel + Capabilities)
618
628
  // ---------------------------------------------------------
619
- // Inject MetaMe capabilities into ~/.claude/CLAUDE.md so ALL projects' agents
620
- // automatically know about dispatch, memory, and skill systems.
629
+ // Inject the full MetaMe KERNEL into ~/.claude/CLAUDE.md.
630
+ // This file is read by ALL Claude Code sessions regardless of working directory,
631
+ // so every project (小美, 3D, MetaMe, etc.) gets the system automatically.
632
+ // Project-level CLAUDE.md only needs role definitions — no kernel duplication.
621
633
  const GLOBAL_CLAUDE_MD = path.join(os.homedir(), '.claude', 'CLAUDE.md');
622
634
  const GLOBAL_MARKER_START = '<!-- METAME-GLOBAL:START -->';
623
635
  const GLOBAL_MARKER_END = '<!-- METAME-GLOBAL:END -->';
624
636
 
625
- // Sections to inject (each only injected if not already present in user's manual content)
626
- const GLOBAL_SECTIONS = [
627
- { detect: /dispatch_to|Agent Dispatch/i, text: [
628
- '## Agent Dispatch',
629
- '"告诉X/让X"→ `~/.metame/bin/dispatch_to <project_key> "内容"`,手机端 `/dispatch <key> <消息>`。',
630
- '新增 Agent:`/agent bind <名称> <工作目录>`',
631
- ]},
632
- { detect: /memory-search\.js|跨会话记忆/i, text: [
633
- '## 跨会话记忆',
634
- '用户提"上次/之前"时搜索:`node ~/.metame/memory-search.js "关键词1" "keyword2"`',
635
- '一次传 3-4 个关键词(中文+英文+函数名),`--facts` 只搜事实,`--sessions` 只搜会话。',
636
- ]},
637
- { detect: /skill-manager|Skills.*技能/i, text: [
638
- '## Skills',
639
- '能力不足/工具缺失/任务失败 → 先查 `cat ~/.claude/skills/skill-manager/SKILL.md`,不要自己猜。',
640
- ]},
641
- { detect: /\[\[FILE:|手机端文件/i, text: [
642
- '## 手机端文件交互',
643
- '**收**:用户发图片/文件自动存到 `upload/`,用 Read 查看。',
644
- '**发**:回复末尾加 `[[FILE:/absolute/path]]`,daemon 自动发手机。不要读内容再复述。',
645
- ]},
646
- ];
637
+ // Build dynamic Agent dispatch table from daemon.yaml projects.
638
+ // Only include agents whose cwd actually exists on disk — test/stale agents
639
+ // with deleted paths are automatically excluded, no manual cleanup needed.
640
+ let dispatchTable = '';
641
+ try {
642
+ const daemonYamlPath = path.join(os.homedir(), '.metame', 'daemon.yaml');
643
+ if (fs.existsSync(daemonYamlPath)) {
644
+ const daemonCfg = yaml.load(fs.readFileSync(daemonYamlPath, 'utf8')) || {};
645
+ const projects = daemonCfg.projects || {};
646
+ const rows = Object.entries(projects)
647
+ .filter(([, p]) => {
648
+ if (!p || !p.name || !p.cwd) return false;
649
+ // Expand ~ to home directory
650
+ const expandedCwd = String(p.cwd).replace(/^~/, os.homedir());
651
+ return fs.existsSync(expandedCwd);
652
+ })
653
+ .map(([key, p]) => `| \`${key}\` | ${p.name} |`);
654
+ if (rows.length > 0) {
655
+ dispatchTable = '\n\n| project_key | 昵称 |\n|-------------|------|\n' + rows.join('\n') + '\n\n`--new` 强制新建会话(用户说"新开会话"时加此参数)。';
656
+ }
657
+ }
658
+ } catch { /* daemon.yaml missing or invalid — skip dispatch table */ }
659
+
660
+
661
+ // Full kernel body: reuse PROTOCOL_NORMAL, strip project-level marker
662
+ const KERNEL_BODY = PROTOCOL_NORMAL
663
+ .replace(/^<!-- METAME:START -->\n/, '') // remove project-level marker
664
+ .trimEnd();
665
+
666
+ const CAPABILITY_SECTIONS = [
667
+ '## Agent Dispatch',
668
+ `"告诉X/让X" → \`~/.metame/bin/dispatch_to <project_key> "内容"\`,手机端 \`/dispatch to <key> <消息>\`。` + dispatchTable,
669
+ '新增 Agent:`/agent bind <名称> <工作目录>`',
670
+ '',
671
+ '## 跨会话记忆',
672
+ '用户提"上次/之前"时搜索:`node ~/.metame/memory-search.js "关键词1" "keyword2"`',
673
+ '一次传 3-4 个关键词(中文+英文+函数名),`--facts` 只搜事实,`--sessions` 只搜会话。',
674
+ '',
675
+ '## Skills',
676
+ '能力不足/工具缺失/任务失败 → 先查 `cat ~/.claude/skills/skill-manager/SKILL.md`,不要自己猜。',
677
+ '',
678
+ '## 手机端文件交互',
679
+ '**收**:用户发图片/文件自动存到 `upload/`,用 Read 查看。',
680
+ '**发**:回复末尾加 `[[FILE:/absolute/path]]`,daemon 自动发手机。不要读内容再复述。',
681
+
682
+ ].join('\n');
647
683
 
648
684
  try {
649
685
  const globalDir = path.join(os.homedir(), '.claude');
@@ -652,34 +688,32 @@ try {
652
688
  let globalContent = '';
653
689
  if (fs.existsSync(GLOBAL_CLAUDE_MD)) {
654
690
  globalContent = fs.readFileSync(GLOBAL_CLAUDE_MD, 'utf8');
655
- // Remove previous injection
691
+ // Remove previous global injection (always replace with latest)
656
692
  globalContent = globalContent.replace(new RegExp(
657
- GLOBAL_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
693
+ GLOBAL_MARKER_START.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&') +
658
694
  '[\\s\\S]*?' +
659
- GLOBAL_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\n?'
695
+ GLOBAL_MARKER_END.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&') + '\\n?'
660
696
  ), '');
661
697
  }
662
698
 
663
- // Only inject sections not already present in user's manual content
664
- const contentOutsideMarkers = globalContent;
665
- const needed = GLOBAL_SECTIONS.filter(s => !s.detect.test(contentOutsideMarkers));
699
+ const injection =
700
+ GLOBAL_MARKER_START + '\n' +
701
+ KERNEL_BODY + '\n\n' +
702
+ '# MetaMe 能力注入(自动生成,勿手动编辑)\n\n' +
703
+ CAPABILITY_SECTIONS + '\n\n' +
704
+ GLOBAL_MARKER_END;
666
705
 
667
- if (needed.length > 0) {
668
- const injection = GLOBAL_MARKER_START + '\n\n# MetaMe 能力注入(自动生成,勿手动编辑)\n\n' +
669
- needed.map(s => s.text.join('\n')).join('\n\n') + '\n\n' + GLOBAL_MARKER_END;
670
- const finalGlobal = globalContent.trimEnd() + '\n\n' + injection + '\n';
671
- fs.writeFileSync(GLOBAL_CLAUDE_MD, finalGlobal, 'utf8');
672
- } else {
673
- // All sections already present, just clean up stale marker block
674
- fs.writeFileSync(GLOBAL_CLAUDE_MD, globalContent.trimEnd() + '\n', 'utf8');
675
- }
706
+ const finalGlobal = globalContent.trimEnd() + (globalContent.trim() ? '\n\n' : '') + injection + '\n';
707
+ fs.writeFileSync(GLOBAL_CLAUDE_MD, finalGlobal, 'utf8');
676
708
  } catch (e) {
677
709
  // Non-fatal: global CLAUDE.md injection is best-effort
678
710
  console.error(`⚠️ Failed to inject global CLAUDE.md: ${e.message}`);
679
711
  }
680
712
 
713
+
714
+
715
+
681
716
  console.log("🔮 MetaMe: Link Established.");
682
- console.log("🧬 Protocol: Dynamic Handshake Active");
683
717
 
684
718
  // Memory system status — show live stats without blocking launch
685
719
  try {
@@ -699,6 +733,39 @@ try {
699
733
  }
700
734
  } catch { /* non-fatal */ }
701
735
 
736
+ // Cognitive distillation status — always show so user knows the system's state
737
+ try {
738
+ const bufferFile = path.join(METAME_DIR, 'raw_signals.jsonl');
739
+ const pendingCount = fs.existsSync(bufferFile)
740
+ ? fs.readFileSync(bufferFile, 'utf8').trim().split('\n').filter(l => l.trim()).length
741
+ : 0;
742
+
743
+ if (pendingCount > 0) {
744
+ console.log(`🧬 Cognition: ${pendingCount} moment${pendingCount > 1 ? 's' : ''} pending distillation`);
745
+ } else {
746
+ // Show last distill time
747
+ let lastDistillStr = '从未';
748
+ try {
749
+ const profilePath = path.join(process.env.HOME || '', '.claude_profile.yaml');
750
+ if (fs.existsSync(profilePath)) {
751
+ const _yaml = require('js-yaml');
752
+ const profile = _yaml.load(fs.readFileSync(profilePath, 'utf8'));
753
+ const distillLog = profile && profile.evolution && profile.evolution.auto_distill;
754
+ if (Array.isArray(distillLog) && distillLog.length > 0) {
755
+ const lastTs = new Date(distillLog[distillLog.length - 1].ts).getTime();
756
+ const diffMs = Date.now() - lastTs;
757
+ const diffH = Math.floor(diffMs / 3600000);
758
+ const diffM = Math.floor((diffMs % 3600000) / 60000);
759
+ lastDistillStr = diffH > 0 ? `${diffH}h${diffM}m 前` : `${diffM}m 前`;
760
+ }
761
+ }
762
+ } catch { /* non-fatal */ }
763
+ console.log(`🧬 Cognition: 无新信号 · 上次蒸馏 ${lastDistillStr}`);
764
+ }
765
+ } catch { /* non-fatal */ }
766
+
767
+
768
+
702
769
  // ---------------------------------------------------------
703
770
  // 4.9 AUTO-UPDATE CHECK (non-blocking)
704
771
  // ---------------------------------------------------------
@@ -715,7 +782,7 @@ const CURRENT_VERSION = require('./package.json').version;
715
782
  res.on('end', () => {
716
783
  try { resolve(JSON.parse(data).version); } catch { reject(); }
717
784
  });
718
- }).on('error', reject).on('timeout', function() { this.destroy(); reject(); });
785
+ }).on('error', reject).on('timeout', function () { this.destroy(); reject(); });
719
786
  });
720
787
 
721
788
  if (latest && latest !== CURRENT_VERSION) {
@@ -731,6 +798,71 @@ const CURRENT_VERSION = require('./package.json').version;
731
798
  } catch { /* network unavailable, skip silently */ }
732
799
  })();
733
800
 
801
+ // ---------------------------------------------------------
802
+ // 4.95 QMD OPTIONAL INSTALL PROMPT (one-time)
803
+ // ---------------------------------------------------------
804
+ // Only prompt when: TTY environment + QMD not installed + never asked before.
805
+ // Uses synchronous fs.readSync on stdin — no async/readline complexity.
806
+ // Writes a flag file after asking so this prompt never appears again.
807
+ (function maybeOfferQmd() {
808
+ const QMD_OFFERED_FILE = path.join(METAME_DIR, '.qmd_offered');
809
+ const isTTY = Boolean(process.stdout.isTTY && process.stdin.isTTY);
810
+ if (!isTTY) return; // non-interactive env: CI, pipe, etc.
811
+ if (fs.existsSync(QMD_OFFERED_FILE)) return; // already offered before
812
+
813
+ // Check if QMD already installed
814
+ try { execSync('which qmd', { stdio: 'pipe', timeout: 2000 }); return; } catch { }
815
+
816
+ // Mark as offered NOW — so crash/ctrl-c won't re-ask
817
+ try { fs.writeFileSync(QMD_OFFERED_FILE, new Date().toISOString(), 'utf8'); } catch { }
818
+
819
+ // Check bun availability
820
+ let bunAvailable = false;
821
+ try { execSync('which bun', { stdio: 'pipe', timeout: 2000 }); bunAvailable = true; } catch { }
822
+
823
+ console.log('');
824
+ console.log('┌─ 🔍 记忆搜索增强(可选,免费)');
825
+ console.log('│');
826
+ console.log('│ 当前模式:基础全文搜索(FTS5)');
827
+ console.log('│ 安装 QMD 后:BM25 + 向量语义 + 重排序 混合搜索');
828
+ console.log('│ 效果:召回质量约 5x,模糊描述也能精准命中历史记忆');
829
+ if (!bunAvailable) {
830
+ console.log('│');
831
+ console.log('│ ⚠️ 未检测到 bun,无法自动安装。');
832
+ console.log('│ 手动安装:curl -fsSL https://bun.sh/install | bash');
833
+ console.log('│ bun install -g github:tobi/qmd');
834
+ console.log('└────────────────────────────────────────────────');
835
+ console.log('');
836
+ return;
837
+ }
838
+ console.log('│ 耗时:约 30 秒');
839
+ console.log('│');
840
+
841
+ // Synchronous prompt — read one character from stdin
842
+ try {
843
+ process.stdout.write('└─ 立即安装?(y/N) › ');
844
+ const buf = Buffer.alloc(8);
845
+ const n = fs.readSync(0, buf, 0, 8);
846
+ const answer = buf.slice(0, n).toString().trim().toLowerCase();
847
+ process.stdout.write('\n');
848
+
849
+ if (answer === 'y' || answer === 'yes') {
850
+ console.log(' ⬇️ 正在安装 QMD...');
851
+ try {
852
+ execSync('bun install -g github:tobi/qmd', { stdio: 'inherit', timeout: 120000 });
853
+ console.log(' ✅ QMD 已安装,下次记忆搜索自动启用向量模式。');
854
+ } catch {
855
+ console.log(' ⚠️ 安装失败,可手动执行:bun install -g github:tobi/qmd');
856
+ }
857
+ } else {
858
+ console.log(' 跳过。如需日后安装:bun install -g github:tobi/qmd');
859
+ }
860
+ console.log('');
861
+ } catch {
862
+ // stdin not readable (edge case) — silent skip
863
+ }
864
+ })();
865
+
734
866
  // ---------------------------------------------------------
735
867
  // 5. LAUNCH CLAUDE (OR HOT RELOAD)
736
868
  // ---------------------------------------------------------
@@ -1231,8 +1363,8 @@ if (isDaemon) {
1231
1363
  cfg.feishu.app_secret = feishuSecret;
1232
1364
  if (!cfg.feishu.allowed_chat_ids) cfg.feishu.allowed_chat_ids = [];
1233
1365
  console.log(" ✅ Feishu configured!");
1234
- console.log(" Note: allowed_chat_ids is empty = allow all users.");
1235
- console.log(" To restrict, add chat IDs to daemon.yaml later.\n");
1366
+ console.log(" Note: allowed_chat_ids is empty = deny all users.");
1367
+ console.log(" Add chat IDs to daemon.yaml or use /agent bind from target chat.\n");
1236
1368
  }
1237
1369
  } else {
1238
1370
  console.log(" Skipped.\n");
@@ -1463,13 +1595,29 @@ WantedBy=default.target
1463
1595
 
1464
1596
  // Tasks
1465
1597
  const tasks = state.tasks || {};
1466
- if (Object.keys(tasks).length > 0) {
1598
+ const configuredTaskNames = new Set();
1599
+ for (const t of ((config.heartbeat && config.heartbeat.tasks) || [])) {
1600
+ if (t && t.name) configuredTaskNames.add(t.name);
1601
+ }
1602
+ for (const proj of Object.values(config.projects || {})) {
1603
+ for (const t of ((proj && proj.heartbeat_tasks) || [])) {
1604
+ if (t && t.name) configuredTaskNames.add(t.name);
1605
+ }
1606
+ }
1607
+ const taskEntries = Object.entries(tasks).filter(([name]) =>
1608
+ configuredTaskNames.size === 0 || configuredTaskNames.has(name)
1609
+ );
1610
+ if (taskEntries.length > 0) {
1467
1611
  console.log(" Recent tasks:");
1468
- for (const [name, info] of Object.entries(tasks)) {
1612
+ for (const [name, info] of taskEntries) {
1469
1613
  const icon = info.status === 'success' ? '✅' : '❌';
1470
1614
  console.log(` ${icon} ${name}: ${info.last_run || 'unknown'}`);
1471
1615
  if (info.output_preview) console.log(` ${info.output_preview.slice(0, 80)}...`);
1472
1616
  }
1617
+ const hiddenStale = Object.keys(tasks).length - taskEntries.length;
1618
+ if (hiddenStale > 0) {
1619
+ console.log(` … ${hiddenStale} stale task record(s) hidden`);
1620
+ }
1473
1621
  }
1474
1622
  process.exit(0);
1475
1623
  }
@@ -1641,4 +1789,4 @@ child.on('error', () => {
1641
1789
  child.on('close', (code) => process.exit(code || 0));
1642
1790
 
1643
1791
  // Launch background distillation AFTER Claude starts — no blocking
1644
- spawnDistillBackground();
1792
+ spawnDistillBackground();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metame-cli",
3
- "version": "1.4.12",
3
+ "version": "1.4.15",
4
4
  "description": "The Cognitive Profile Layer for Claude Code. Knows how you think, not just what you said.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "scripts": {
14
14
  "test": "node --test scripts/*.test.js",
15
15
  "start": "node index.js",
16
- "sync:plugin": "cp scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-extract.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/skill-evolution.js plugin/scripts/ && echo '✅ Plugin scripts synced'",
16
+ "sync:plugin": "cp scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/daemon-agent-commands.js scripts/daemon-session-commands.js scripts/daemon-admin-commands.js scripts/daemon-exec-commands.js scripts/daemon-ops-commands.js scripts/daemon-session-store.js scripts/daemon-checkpoints.js scripts/daemon-bridges.js scripts/daemon-file-browser.js scripts/daemon-runtime-lifecycle.js scripts/daemon-notify.js scripts/daemon-claude-engine.js scripts/daemon-command-router.js scripts/daemon-agent-tools.js scripts/daemon-task-scheduler.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-extract.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/skill-evolution.js plugin/scripts/ && echo '✅ Plugin scripts synced'",
17
17
  "restart:daemon": "node index.js stop 2>/dev/null; sleep 1; node index.js start 2>/dev/null || echo '⚠️ Daemon not running or restart failed'",
18
18
  "precommit": "npm run sync:plugin && npm run restart:daemon"
19
19
  },