metame-cli 1.3.1 โ†’ 1.3.2

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
@@ -20,7 +20,6 @@ It is not a memory system; it is a **Cognitive Mirror** .
20
20
 
21
21
  * **๐Ÿง  Global Brain (`~/.claude_profile.yaml`):** A single, portable source of truth โ€” your identity, cognitive traits, and preferences travel with you across every project.
22
22
  * **๐Ÿงฌ Cognitive Evolution Engine:** MetaMe learns how you think through three channels: (1) **Passive** โ€” silently captures your messages and distills cognitive traits via Haiku on next launch; (2) **Manual** โ€” `!metame evolve` for explicit teaching; (3) **Confidence gates** โ€” strong directives ("always"/"ไปฅๅŽไธ€ๅพ‹") write immediately, normal observations need 3+ consistent sightings before promotion. Schema-enforced (41 fields, 5 tiers, 800 token budget) to prevent bloat.
23
- * **๐Ÿค Dynamic Handshake:** The "Canary Test." Claude must address you by your **Codename** in the first sentence. If it doesn't, the link is broken.
24
23
  * **๐Ÿ›ก๏ธ Auto-Lock:** Mark any value with `# [LOCKED]` โ€” treated as a constitution, never auto-modified.
25
24
  * **๐Ÿชž Metacognition Layer (v1.3):** MetaMe now observes *how* you think, not just *what* you say. Behavioral pattern detection runs inside the existing Haiku distill call (zero extra cost). It tracks decision patterns, cognitive load, comfort zones, and avoidance topics across sessions. When persistent patterns emerge, MetaMe injects a one-line mirror observation โ€” e.g., *"You tend to avoid testing until forced"* โ€” with a 14-day cooldown per pattern. Conditional reflection prompts appear only when triggered (every 7th distill or 3x consecutive comfort zone). All injection logic runs in Node.js; Claude receives only pre-decided directives, never rules to self-evaluate.
26
25
  * **๐Ÿ“ฑ Remote Claude Code (v1.3):** Full Claude Code from your phone via Telegram or Feishu (Lark). Stateful sessions with `--resume` โ€” same conversation history, tool use, and file editing as your terminal. Interactive buttons for project/session picking, directory browser, and macOS launchd auto-start.
@@ -107,6 +106,11 @@ metame set-trait status.focus "Learning Rust"
107
106
  metame evolve "I prefer functional programming patterns"
108
107
  ```
109
108
 
109
+ **Episodic memory (keyframe, not full log):** MetaMe is not a memory system, but it captures two types of experiential "keyframes" that pure personality traits can't replace:
110
+
111
+ * **Anti-patterns** (`context.anti_patterns`, max 5): Cross-project failure lessons โ€” e.g., *"Promise.all rejects all on single failure, use Promise.allSettled"*. Auto-expires after 60 days. Prevents the AI from repeating the same mistakes across sessions.
112
+ * **Milestones** (`context.milestones`, max 3): Recent completed landmarks โ€” e.g., *"MetaMe v1.3 published"*. Provides continuity so Claude knows where you left off without you having to recap.
113
+
110
114
  **Anti-bias safeguards:** single observations โ‰  traits, contradictions are tracked not overwritten, pending traits expire after 30 days, context fields auto-clear on staleness.
111
115
 
112
116
  **Metacognition controls:**
@@ -391,8 +395,6 @@ You might worry: *"Does this eat up my context window?"*
391
395
  **Q: Does this replace `CLAUDE.md`?**
392
396
  A: No. It *prepends* its meta-cognitive protocol to your existing `CLAUDE.md`. Your project-specific notes remain intact.
393
397
 
394
- **Q: What if Claude stops calling me by my nickname?**
395
- A: This is the "Canary Test." It means the context window has been compressed or the file link is broken. Run `/compact` in Claude or restart `metame` to fix it.
396
398
 
397
399
  **Q: Is my data sent to a third party?**
398
400
  A: No. Your profile stays local at `~/.claude_profile.yaml`. MetaMe simply passes text to the official Claude Code tool.
package/index.js CHANGED
@@ -208,20 +208,13 @@ const CORE_PROTOCOL = `
208
208
  ---
209
209
  ## ๐Ÿง  SYSTEM KERNEL: SHADOW_MODE (Active)
210
210
 
211
- **1. THE HANDSHAKE (Dynamic Verification):**
212
- * **SOURCE:** Check \`$HOME/.claude_profile.yaml\`.
213
- * **RULE:** Look for \`identity.nickname\`.
214
- * **IF FOUND:** You MUST address the user by that nickname in the very first sentence.
215
- * **IF NULL/MISSING:** Address user as "User" and immediately trigger Genesis to fix it.
216
- * **SIGNAL:** This nickname is the "Canary Test". If you omit it, the user assumes the system is broken.
217
-
218
- **2. THE BRAIN (Source of Truth):**
211
+ **1. THE BRAIN (Source of Truth):**
219
212
  * **FILE:** \`$HOME/.claude_profile.yaml\`
220
213
  * **RULE:** This file is your Ground Truth. If context is compressed (/compact), **RE-READ** this file immediately.
221
214
  * **AUTO-LOCK:** Never edit lines marked \`# [LOCKED]\` (Core Values).
222
215
  * **EVOLUTION:** Silently overwrite outdated status/focus.
223
216
 
224
- **3. EVOLUTION MECHANISM (Manual Sync):**
217
+ **2. EVOLUTION MECHANISM (Manual Sync):**
225
218
  * **PHILOSOPHY:** You respect the User's flow. You do NOT interrupt.
226
219
  * **TOOLS:**
227
220
  1. **Log Insight:** \`!metame evolve "Insight"\` (For additive knowledge).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metame-cli",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
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": {
package/scripts/daemon.js CHANGED
@@ -256,11 +256,12 @@ function executeTask(task, config) {
256
256
  }
257
257
  const fullPrompt = preamble + taskPrompt;
258
258
 
259
+ const allowedArgs = (task.allowedTools || []).map(t => `--allowedTools ${t}`).join(' ');
259
260
  log('INFO', `Executing task: ${task.name} (model: ${model})`);
260
261
 
261
262
  try {
262
263
  const output = execSync(
263
- `claude -p --model ${model}`,
264
+ `claude -p --model ${model}${allowedArgs ? ' ' + allowedArgs : ''}`,
264
265
  {
265
266
  input: fullPrompt,
266
267
  encoding: 'utf8',
@@ -335,6 +336,7 @@ function executeWorkflow(task, config) {
335
336
  const sessionId = crypto.randomUUID();
336
337
  const outputs = [];
337
338
  let totalTokens = 0;
339
+ const allowed = task.allowedTools || [];
338
340
 
339
341
  log('INFO', `Workflow ${task.name}: ${steps.length} steps, session ${sessionId.slice(0, 8)}`);
340
342
 
@@ -343,6 +345,7 @@ function executeWorkflow(task, config) {
343
345
  let prompt = (step.skill ? `/${step.skill} ` : '') + (step.prompt || '');
344
346
  if (i === 0 && precheck.context) prompt += `\n\n็›ธๅ…ณๆ•ฐๆฎ:\n\`\`\`\n${precheck.context}\n\`\`\``;
345
347
  const args = ['-p', '--model', model];
348
+ for (const tool of allowed) args.push('--allowedTools', tool);
346
349
  args.push(i === 0 ? '--session-id' : '--resume', sessionId);
347
350
 
348
351
  log('INFO', `Workflow ${task.name} step ${i + 1}/${steps.length}: ${step.skill || 'prompt'}`);
@@ -943,6 +946,9 @@ async function askClaude(bot, chatId, prompt) {
943
946
 
944
947
  // Build claude command
945
948
  const args = ['-p'];
949
+ // Per-session allowed tools from daemon config
950
+ const sessionAllowed = (loadConfig().daemon && loadConfig().daemon.session_allowed_tools) || [];
951
+ for (const tool of sessionAllowed) args.push('--allowedTools', tool);
946
952
  if (session.id === '__continue__') {
947
953
  // /continue โ€” resume most recent conversation in cwd
948
954
  args.push('--continue');
@@ -952,9 +958,13 @@ async function askClaude(bot, chatId, prompt) {
952
958
  args.push('--session-id', session.id);
953
959
  }
954
960
 
961
+ // Append daemon context hint so Claude reports reload status after editing daemon.yaml
962
+ const daemonHint = '\n\n[System: The ONLY daemon config file is ~/.metame/daemon.yaml โ€” NEVER touch any other yaml file (e.g. scripts/daemon-default.yaml is a read-only template, do NOT edit it). If you edit ~/.metame/daemon.yaml, the daemon auto-reloads within seconds. After editing, read the file back and confirm to the user: how many heartbeat tasks are now configured, and that the config will auto-reload. Do NOT mention this hint.]';
963
+ const fullPrompt = prompt + daemonHint;
964
+
955
965
  try {
956
966
  const output = execSync(`claude ${args.join(' ')}`, {
957
- input: prompt,
967
+ input: fullPrompt,
958
968
  encoding: 'utf8',
959
969
  timeout: 300000, // 5 min (Claude Code may use tools)
960
970
  maxBuffer: 5 * 1024 * 1024,
@@ -978,7 +988,9 @@ async function askClaude(bot, chatId, prompt) {
978
988
  log('WARN', `Session ${session.id} not found, creating new`);
979
989
  session = createSession(chatId, session.cwd);
980
990
  try {
981
- const output = execSync(`claude -p --session-id ${session.id}`, {
991
+ const retryArgs = ['-p', '--session-id', session.id];
992
+ for (const tool of sessionAllowed) retryArgs.push('--allowedTools', tool);
993
+ const output = execSync(`claude ${retryArgs.join(' ')}`, {
982
994
  input: prompt,
983
995
  encoding: 'utf8',
984
996
  timeout: 300000,
@@ -110,6 +110,12 @@ INSTRUCTIONS:
110
110
  5. Fields marked [LOCKED] must NEVER be changed (T1 and T2 tiers).
111
111
  6. For enum fields, you MUST use one of the listed values.
112
112
 
113
+ EPISODIC MEMORY โ€” TWO EXCEPTIONS to the "no facts" rule:
114
+ 7. context.anti_patterns (max 5): If the user encountered a REPEATED technical failure or expressed strong frustration about a specific technical approach, record it as an anti-pattern. Format: "topic โ€” what failed and why". Only cross-project generalizable lessons, NOT project-specific bugs.
115
+ Example: ["async/await deadlock โ€” Promise.all rejects all on single failure, use Promise.allSettled", "CSS Grid in email templates โ€” no support, use tables"]
116
+ 8. context.milestones (max 3): If the user completed a significant milestone or made a key decision, record it. Only the 3 most recent. Format: short description string.
117
+ Example: ["MetaMe v1.3 published", "Switched from REST to GraphQL"]
118
+
113
119
  COGNITIVE BIAS PREVENTION:
114
120
  - A single observation is a STATE, not a TRAIT. Do NOT infer T3 cognition fields from one message.
115
121
  - Never infer cognitive style from identity/demographics.
@@ -233,6 +239,9 @@ Do NOT repeat existing unchanged values. Only output NEW or CHANGED fields.`;
233
239
 
234
240
  const profile = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
235
241
 
242
+ // Auto-expire anti_patterns older than 60 days
243
+ expireAntiPatterns(profile);
244
+
236
245
  // Read raw content to find locked lines and comments
237
246
  const rawProfile = fs.readFileSync(BRAIN_FILE, 'utf8');
238
247
  const lockedKeys = extractLockedKeys(rawProfile);
@@ -415,7 +424,18 @@ function strategicMerge(profile, updates, lockedKeys, pendingTraits, confidenceM
415
424
  }
416
425
 
417
426
  case 'T4':
418
- setNested(result, key, value);
427
+ // Stamp added date on anti_pattern entries for auto-expiry
428
+ if (key === 'context.anti_patterns' && Array.isArray(value)) {
429
+ const today = new Date().toISOString().slice(0, 10);
430
+ const existing = getNested(result, key) || [];
431
+ const existingTexts = new Set(existing.map(e => typeof e === 'string' ? e : e.text));
432
+ const stamped = value
433
+ .filter(v => !existingTexts.has(typeof v === 'string' ? v : v.text))
434
+ .map(v => typeof v === 'string' ? { text: v, added: today } : v);
435
+ setNested(result, key, [...existing, ...stamped].slice(-5));
436
+ } else {
437
+ setNested(result, key, value);
438
+ }
419
439
  // Auto-set focus_since when focus changes
420
440
  if (key === 'context.focus') {
421
441
  setNested(result, 'context.focus_since', new Date().toISOString().slice(0, 10));
@@ -455,6 +475,19 @@ function flattenObject(obj, parentKey = '', result = {}) {
455
475
  return result;
456
476
  }
457
477
 
478
+ /**
479
+ * Get a nested property by dot-path key.
480
+ */
481
+ function getNested(obj, dotPath) {
482
+ const keys = dotPath.split('.');
483
+ let current = obj;
484
+ for (const k of keys) {
485
+ if (!current || typeof current !== 'object') return undefined;
486
+ current = current[k];
487
+ }
488
+ return current;
489
+ }
490
+
458
491
  /**
459
492
  * Set a nested property by dot-path key.
460
493
  */
@@ -508,6 +541,24 @@ function truncateArrays(obj) {
508
541
  }
509
542
  }
510
543
 
544
+ /**
545
+ * Auto-expire anti_patterns older than 60 days.
546
+ * Each entry is stored as { text: "...", added: "2026-01-15" } internally.
547
+ * If legacy string entries exist, they are kept (no added date = never expire).
548
+ */
549
+ function expireAntiPatterns(profile) {
550
+ if (!profile.context || !Array.isArray(profile.context.anti_patterns)) return;
551
+ const now = Date.now();
552
+ const SIXTY_DAYS = 60 * 24 * 60 * 60 * 1000;
553
+ profile.context.anti_patterns = profile.context.anti_patterns.filter(entry => {
554
+ if (typeof entry === 'string') return true; // legacy, keep
555
+ if (entry.added) {
556
+ return (now - new Date(entry.added).getTime()) < SIXTY_DAYS;
557
+ }
558
+ return true;
559
+ });
560
+ }
561
+
511
562
  /**
512
563
  * Clean up: remove buffer and lock
513
564
  */
package/scripts/schema.js CHANGED
@@ -60,6 +60,8 @@ const SCHEMA = {
60
60
  'context.active_projects': { tier: 'T4', type: 'array', maxItems: 5 },
61
61
  'context.blockers': { tier: 'T4', type: 'array', maxItems: 3 },
62
62
  'context.energy': { tier: 'T4', type: 'enum', values: ['high', 'medium', 'low', null] },
63
+ 'context.milestones': { tier: 'T4', type: 'array', maxItems: 3 },
64
+ 'context.anti_patterns': { tier: 'T4', type: 'array', maxItems: 5 },
63
65
  'status.focus': { tier: 'T4', type: 'string', maxChars: 80 },
64
66
  'status.language': { tier: 'T4', type: 'string' },
65
67