metame-cli 1.2.0 → 1.2.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 +110 -15
- package/index.js +331 -2
- package/package.json +6 -2
- package/scripts/distill.js +696 -0
- package/scripts/migrate-v2.js +112 -0
- package/scripts/pending-traits.js +144 -0
- package/scripts/schema.js +195 -0
- package/scripts/signal-capture.js +125 -0
- package/README/344/270/255/346/226/207/347/211/210.md +0 -178
- package/logo.png +0 -0
package/README.md
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
<img src="./logo.png" alt="MetaMe Logo" width="200"/>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
> **The
|
|
7
|
+
> **The Cognitive Profile Layer for Claude Code.**
|
|
8
8
|
>
|
|
9
|
-
> *
|
|
9
|
+
> *Not a memory system — a cognitive mirror. It knows how you think, decide, and communicate, and it protects your core values.*
|
|
10
10
|
|
|
11
11
|
## 📖 Introduction
|
|
12
12
|
|
|
13
13
|
**Claude Code** is a powerful tool, but it suffers from "Project Amnesia." Every time you switch folders, it forgets who you are, your communication style, and your specific constraints.
|
|
14
14
|
|
|
15
|
-
**MetaMe** solves this by wrapping Claude in a **
|
|
15
|
+
**MetaMe** solves this by wrapping Claude in a **Cognitive Profile Layer** . It creates a persistent "Global Brain" that travels with you across every project. Unlike ChatGPT/Claude/Gemini's built-in memory (which stores *facts* like "user lives in X"), MetaMe captures *how you think* — your decision style, cognitive load preferences, motivation patterns, and communication traits.
|
|
16
16
|
|
|
17
|
-
It is not
|
|
17
|
+
It is not a memory system; it is a **Cognitive Mirror** .
|
|
18
18
|
|
|
19
19
|
## ✨ Key Features
|
|
20
20
|
|
|
@@ -23,6 +23,9 @@ It is not just a launcher; it is a **Meta Avatar** .
|
|
|
23
23
|
* **🤝 Dynamic Handshake Protocol:** The "Canary Test." MetaMe verifies its connection to your profile by addressing you by your chosen **Codename** in the very first sentence. If it doesn't, you know the link is broken.
|
|
24
24
|
* **🛡️ Auto-Lock Mechanism:** Mark any value in your profile with `# [LOCKED]`, and MetaMe will treat it as a constitution that cannot be overwritten.
|
|
25
25
|
* **🔌 Smart Injection:** Automatically injects your profile context into the `CLAUDE.md` of any project you enter, ensuring seamless context switching.
|
|
26
|
+
* **🧠 Passive Distillation:** MetaMe silently captures your messages via Claude Code hooks and, on next launch, uses a lightweight LLM (Haiku) to extract cognitive traits and preferences — automatically merging them into your profile with confidence-based upsert. Zero manual effort required.
|
|
27
|
+
* **📊 Schema-Enforced Profile:** A 41-field whitelist across 5 tiers (T1-T5) prevents profile bloat. Fields have type validation, enum constraints, and token budget limits (800 tokens max).
|
|
28
|
+
* **🎯 Confidence-Based Learning:** Strong directives ("always"/"以后一律") write directly. Normal observations accumulate in a pending queue and only promote to the profile after 3 consistent observations — preventing single-session bias.
|
|
26
29
|
|
|
27
30
|
## 🛠 Prerequisites
|
|
28
31
|
|
|
@@ -98,6 +101,32 @@ If you need to update a specific trait without editing the file manually:
|
|
|
98
101
|
metame set-trait status.focus "Learning Rust"
|
|
99
102
|
```
|
|
100
103
|
|
|
104
|
+
### Passive Distillation (Automatic)
|
|
105
|
+
|
|
106
|
+
MetaMe automatically learns your cognitive patterns from conversations — no action needed.
|
|
107
|
+
|
|
108
|
+
**How it works:**
|
|
109
|
+
|
|
110
|
+
1. A global Claude Code hook captures every message, tagging each with a **confidence level** (high for strong directives like "always"/"以后一律", normal otherwise).
|
|
111
|
+
2. On your next `metame` launch, a background Haiku model analyzes the buffer and extracts cognitive traits and preferences.
|
|
112
|
+
3. **High-confidence** traits write directly to your profile. **Normal-confidence** traits enter a pending queue (`~/.metame/pending_traits.yaml`) and only promote after 3+ consistent observations.
|
|
113
|
+
4. All writes are validated against a **41-field schema whitelist** — unknown keys are silently dropped, enum fields are type-checked, and a **token budget** (800 max) prevents bloat.
|
|
114
|
+
5. The buffer is cleared, and Claude starts with a clean context.
|
|
115
|
+
|
|
116
|
+
**Anti-bias safeguards:**
|
|
117
|
+
- Single observations are treated as states, not traits
|
|
118
|
+
- Contradictions are tracked, not blindly overwritten
|
|
119
|
+
- Pending traits expire after 30 days without re-observation
|
|
120
|
+
- Context fields (focus, energy) auto-expire on staleness
|
|
121
|
+
|
|
122
|
+
You'll see this in the startup log:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
🧠 MetaMe: Distilling 7 moments in background...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The hook is installed automatically on first run to `~/.claude/settings.json` (global scope — works across all projects).
|
|
129
|
+
|
|
101
130
|
### Hot Reload (Refresh)
|
|
102
131
|
|
|
103
132
|
If you update your profile or need to fix a broken context **without restarting your session**:
|
|
@@ -115,25 +144,63 @@ Your profile is stored in a hidden YAML file in your home directory.
|
|
|
115
144
|
|
|
116
145
|
You can edit this file manually to update your status or lock your values.
|
|
117
146
|
|
|
118
|
-
**Example Profile:**
|
|
147
|
+
**Example Profile (v2 Schema):**
|
|
119
148
|
|
|
120
149
|
**YAML**
|
|
121
150
|
|
|
122
151
|
```
|
|
152
|
+
# === T1: Identity (LOCKED) ===
|
|
123
153
|
identity:
|
|
154
|
+
nickname: Neo # [LOCKED]
|
|
124
155
|
role: Senior Architect
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
156
|
+
locale: en-US # [LOCKED]
|
|
157
|
+
|
|
158
|
+
# === T2: Core Traits (LOCKED) ===
|
|
159
|
+
core_traits:
|
|
160
|
+
crisis_reflex: Analysis # [LOCKED]
|
|
161
|
+
flow_trigger: Debugging # [LOCKED]
|
|
162
|
+
learning_style: Hands-on # [LOCKED]
|
|
163
|
+
|
|
164
|
+
# === T3: Preferences (auto-learnable) ===
|
|
165
|
+
preferences:
|
|
166
|
+
code_style: concise
|
|
167
|
+
communication: direct
|
|
168
|
+
explanation_depth: brief_rationale
|
|
169
|
+
|
|
170
|
+
# === T3b: Cognition (auto-learnable, slow to change) ===
|
|
129
171
|
cognition:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
172
|
+
decision_style: analytical
|
|
173
|
+
info_processing:
|
|
174
|
+
entry_point: big_picture
|
|
175
|
+
preferred_format: structured
|
|
176
|
+
cognitive_load:
|
|
177
|
+
chunk_size: medium
|
|
178
|
+
preferred_response_length: moderate
|
|
179
|
+
|
|
180
|
+
# === T4: Context (auto-overwrite) ===
|
|
181
|
+
context:
|
|
182
|
+
focus: "Refactoring Legacy Code"
|
|
183
|
+
energy: high
|
|
184
|
+
|
|
185
|
+
# === T5: Evolution (system-managed) ===
|
|
186
|
+
evolution:
|
|
187
|
+
distill_count: 12
|
|
188
|
+
last_distill: "2026-01-30T10:00:00Z"
|
|
134
189
|
```
|
|
135
190
|
|
|
136
|
-
*
|
|
191
|
+
* **T1-T2 fields** marked `# [LOCKED]` are never auto-modified.
|
|
192
|
+
* **T3 fields** are auto-learned with confidence thresholds.
|
|
193
|
+
* **T4 fields** are freely overwritten as your context changes.
|
|
194
|
+
* **T5 fields** are managed by the distillation system.
|
|
195
|
+
|
|
196
|
+
### Profile Migration (v1 → v2)
|
|
197
|
+
|
|
198
|
+
If you have an existing v1 profile, run the migration script:
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
node ~/.metame/migrate-v2.js --dry-run # preview changes
|
|
202
|
+
node ~/.metame/migrate-v2.js # apply migration (auto-backup created)
|
|
203
|
+
```
|
|
137
204
|
|
|
138
205
|
## 🗑️ Uninstallation
|
|
139
206
|
|
|
@@ -159,7 +226,34 @@ If you want to delete your stored profile data:
|
|
|
159
226
|
rm ~/.claude_profile.yaml
|
|
160
227
|
```
|
|
161
228
|
|
|
162
|
-
### 3.
|
|
229
|
+
### 3. Remove Passive Distillation Data (Optional)
|
|
230
|
+
|
|
231
|
+
Remove the signal capture scripts:
|
|
232
|
+
|
|
233
|
+
**Bash**
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
rm -rf ~/.metame
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 4. Remove the Signal Capture Hook (Optional)
|
|
240
|
+
|
|
241
|
+
MetaMe installs a global hook in `~/.claude/settings.json`. To remove it, edit the file and delete the `UserPromptSubmit` entry under `hooks`, or run:
|
|
242
|
+
|
|
243
|
+
**Bash**
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
node -e "
|
|
247
|
+
const fs = require('fs');
|
|
248
|
+
const p = require('os').homedir() + '/.claude/settings.json';
|
|
249
|
+
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
250
|
+
if (s.hooks) { delete s.hooks.UserPromptSubmit; }
|
|
251
|
+
fs.writeFileSync(p, JSON.stringify(s, null, 2));
|
|
252
|
+
console.log('Hook removed.');
|
|
253
|
+
"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 5. Cleanup Project Files (Optional)
|
|
163
257
|
|
|
164
258
|
MetaMe adds a header to `CLAUDE.md` files in your projects. To restore them to their original state (if you have many), you can use a text editor to remove the block starting with `## 🧠 SYSTEM KERNEL`.
|
|
165
259
|
|
|
@@ -172,6 +266,7 @@ You might worry: *"Does this eat up my context window?"*
|
|
|
172
266
|
* **Context Cost**: The entire MetaMe kernel + your profile takes up **~800-1000 tokens**.
|
|
173
267
|
* **Impact**: On a 200k context window, this is **0.5%** of the memory.
|
|
174
268
|
* **ROI**: By pre-loading your context, you avoid the "instructional drift" and repetitive correction loops that usually waste thousands of tokens at the start of every session.
|
|
269
|
+
* **Passive Distillation Cost**: The signal capture hook is a local Node.js script (zero API calls). The Haiku distillation on launch processes only a small buffer of filtered messages — typically a few hundred tokens at Haiku's very low cost.
|
|
175
270
|
|
|
176
271
|
## ❓ FAQ
|
|
177
272
|
|
package/index.js
CHANGED
|
@@ -11,6 +11,181 @@ const { spawn } = require('child_process');
|
|
|
11
11
|
const HOME_DIR = os.homedir();
|
|
12
12
|
const BRAIN_FILE = path.join(HOME_DIR, '.claude_profile.yaml');
|
|
13
13
|
const PROJECT_FILE = path.join(process.cwd(), 'CLAUDE.md');
|
|
14
|
+
const METAME_DIR = path.join(HOME_DIR, '.metame');
|
|
15
|
+
const CLAUDE_SETTINGS = path.join(HOME_DIR, '.claude', 'settings.json');
|
|
16
|
+
const SIGNAL_CAPTURE_SCRIPT = path.join(METAME_DIR, 'signal-capture.js');
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------
|
|
19
|
+
// 1.5 ENSURE METAME DIRECTORY + DEPLOY SCRIPTS
|
|
20
|
+
// ---------------------------------------------------------
|
|
21
|
+
if (!fs.existsSync(METAME_DIR)) {
|
|
22
|
+
fs.mkdirSync(METAME_DIR, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Auto-deploy bundled scripts to ~/.metame/
|
|
26
|
+
const BUNDLED_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.js'];
|
|
27
|
+
const scriptsDir = path.join(__dirname, 'scripts');
|
|
28
|
+
|
|
29
|
+
for (const script of BUNDLED_SCRIPTS) {
|
|
30
|
+
const src = path.join(scriptsDir, script);
|
|
31
|
+
const dest = path.join(METAME_DIR, script);
|
|
32
|
+
try {
|
|
33
|
+
if (fs.existsSync(src)) {
|
|
34
|
+
const srcContent = fs.readFileSync(src, 'utf8');
|
|
35
|
+
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
|
|
36
|
+
if (srcContent !== destContent) {
|
|
37
|
+
fs.writeFileSync(dest, srcContent, 'utf8');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// Non-fatal
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------
|
|
46
|
+
// 1.6 AUTO-INSTALL SIGNAL CAPTURE HOOK
|
|
47
|
+
// ---------------------------------------------------------
|
|
48
|
+
function ensureHookInstalled() {
|
|
49
|
+
try {
|
|
50
|
+
// Ensure ~/.claude/ exists
|
|
51
|
+
const claudeDir = path.join(HOME_DIR, '.claude');
|
|
52
|
+
if (!fs.existsSync(claudeDir)) {
|
|
53
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let settings = {};
|
|
57
|
+
if (fs.existsSync(CLAUDE_SETTINGS)) {
|
|
58
|
+
settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS, 'utf8'));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if our hook is already configured
|
|
62
|
+
const hookCommand = `node ${SIGNAL_CAPTURE_SCRIPT}`;
|
|
63
|
+
const existing = settings.hooks?.UserPromptSubmit || [];
|
|
64
|
+
const alreadyInstalled = existing.some(entry =>
|
|
65
|
+
entry.hooks?.some(h => h.command === hookCommand)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (!alreadyInstalled) {
|
|
69
|
+
if (!settings.hooks) settings.hooks = {};
|
|
70
|
+
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
71
|
+
|
|
72
|
+
settings.hooks.UserPromptSubmit.push({
|
|
73
|
+
hooks: [{
|
|
74
|
+
type: 'command',
|
|
75
|
+
command: hookCommand
|
|
76
|
+
}]
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2), 'utf8');
|
|
80
|
+
console.log("🪝 MetaMe: Signal capture hook installed.");
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// Non-fatal: hook install failure shouldn't block launch
|
|
84
|
+
console.error("⚠️ Hook install skipped:", e.message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ensureHookInstalled();
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------
|
|
91
|
+
// 1.7 PASSIVE DISTILLATION (Background, post-launch)
|
|
92
|
+
// ---------------------------------------------------------
|
|
93
|
+
function shouldDistill() {
|
|
94
|
+
const bufferFile = path.join(METAME_DIR, 'raw_signals.jsonl');
|
|
95
|
+
if (!fs.existsSync(bufferFile)) return false;
|
|
96
|
+
const content = fs.readFileSync(bufferFile, 'utf8').trim();
|
|
97
|
+
return content.length > 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function spawnDistillBackground() {
|
|
101
|
+
const distillPath = path.join(METAME_DIR, 'distill.js');
|
|
102
|
+
if (!fs.existsSync(distillPath)) return;
|
|
103
|
+
if (!shouldDistill()) return;
|
|
104
|
+
|
|
105
|
+
const bufferFile = path.join(METAME_DIR, 'raw_signals.jsonl');
|
|
106
|
+
const lines = fs.readFileSync(bufferFile, 'utf8').trim().split('\n').filter(l => l.trim());
|
|
107
|
+
console.log(`🧠 MetaMe: Distilling ${lines.length} moment${lines.length > 1 ? 's' : ''} in background...`);
|
|
108
|
+
|
|
109
|
+
// Spawn as detached background process — won't block Claude launch
|
|
110
|
+
const bg = spawn('node', [distillPath], {
|
|
111
|
+
detached: true,
|
|
112
|
+
stdio: 'ignore'
|
|
113
|
+
});
|
|
114
|
+
bg.unref();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------
|
|
118
|
+
// 1.8 TIME-BASED EXPIRY (Startup cleanup)
|
|
119
|
+
// ---------------------------------------------------------
|
|
120
|
+
function runExpiryCleanup() {
|
|
121
|
+
try {
|
|
122
|
+
const yaml = require('js-yaml');
|
|
123
|
+
if (!fs.existsSync(BRAIN_FILE)) return;
|
|
124
|
+
|
|
125
|
+
const rawProfile = fs.readFileSync(BRAIN_FILE, 'utf8');
|
|
126
|
+
const profile = yaml.load(rawProfile);
|
|
127
|
+
if (!profile || typeof profile !== 'object') return;
|
|
128
|
+
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
let changed = false;
|
|
131
|
+
|
|
132
|
+
// context.focus: if focus_since > 30 days, auto-clear
|
|
133
|
+
if (profile.context && profile.context.focus_since) {
|
|
134
|
+
const focusSince = new Date(profile.context.focus_since).getTime();
|
|
135
|
+
if (now - focusSince > 30 * 24 * 60 * 60 * 1000) {
|
|
136
|
+
profile.context.focus = null;
|
|
137
|
+
profile.context.focus_since = null;
|
|
138
|
+
changed = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// context.blockers: if > 14 days, auto-clear
|
|
143
|
+
// (blockers are arrays — clear entire array if stale)
|
|
144
|
+
if (profile.context && Array.isArray(profile.context.blockers) && profile.context.blockers.length > 0) {
|
|
145
|
+
// If we don't have a blockers_since timestamp, just leave them
|
|
146
|
+
// Future: add per-item timestamps
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// context.energy: reset to null on each session start
|
|
150
|
+
if (profile.context && profile.context.energy !== undefined) {
|
|
151
|
+
if (profile.context.energy !== null) {
|
|
152
|
+
profile.context.energy = null;
|
|
153
|
+
changed = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (changed) {
|
|
158
|
+
// Preserve comments
|
|
159
|
+
const commentMatch = rawProfile.match(/^(\s*[\w_]+\s*:.+?)\s+(#.+)$/gm);
|
|
160
|
+
const dumped = yaml.dump(profile, { lineWidth: -1 });
|
|
161
|
+
fs.writeFileSync(BRAIN_FILE, dumped, 'utf8');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Expire stale pending traits
|
|
165
|
+
const pendingFile = path.join(METAME_DIR, 'pending_traits.yaml');
|
|
166
|
+
if (fs.existsSync(pendingFile)) {
|
|
167
|
+
const pending = yaml.load(fs.readFileSync(pendingFile, 'utf8')) || {};
|
|
168
|
+
const cutoff = 30 * 24 * 60 * 60 * 1000;
|
|
169
|
+
let expiredCount = 0;
|
|
170
|
+
for (const [key, meta] of Object.entries(pending)) {
|
|
171
|
+
if (meta.last_seen) {
|
|
172
|
+
const lastSeen = new Date(meta.last_seen).getTime();
|
|
173
|
+
if (now - lastSeen > cutoff) {
|
|
174
|
+
delete pending[key];
|
|
175
|
+
expiredCount++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (expiredCount > 0) {
|
|
180
|
+
fs.writeFileSync(pendingFile, yaml.dump(pending, { lineWidth: -1 }), 'utf8');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// Non-fatal — expiry cleanup failure shouldn't block launch
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
runExpiryCleanup();
|
|
14
189
|
|
|
15
190
|
// ---------------------------------------------------------
|
|
16
191
|
// 2. BRAIN INITIALIZATION (Cold Start)
|
|
@@ -119,8 +294,86 @@ if (!isKnownUser) {
|
|
|
119
294
|
console.log("🆕 User Unknown: Injecting Deep Genesis Protocol...");
|
|
120
295
|
}
|
|
121
296
|
|
|
297
|
+
// ---------------------------------------------------------
|
|
298
|
+
// 4.5 MIRROR INJECTION (Phase C — metacognition observation)
|
|
299
|
+
// ---------------------------------------------------------
|
|
300
|
+
let mirrorLine = '';
|
|
301
|
+
try {
|
|
302
|
+
if (isKnownUser && fs.existsSync(BRAIN_FILE)) {
|
|
303
|
+
const brainDoc = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
304
|
+
|
|
305
|
+
// Check quiet mode
|
|
306
|
+
const quietUntil = brainDoc.growth && brainDoc.growth.quiet_until;
|
|
307
|
+
const isQuiet = quietUntil && new Date(quietUntil).getTime() > Date.now();
|
|
308
|
+
|
|
309
|
+
// Check mirror enabled (default: true)
|
|
310
|
+
const mirrorEnabled = !(brainDoc.growth && brainDoc.growth.mirror_enabled === false);
|
|
311
|
+
|
|
312
|
+
if (!isQuiet && mirrorEnabled && brainDoc.growth && Array.isArray(brainDoc.growth.patterns)) {
|
|
313
|
+
const now = Date.now();
|
|
314
|
+
const COOLDOWN_MS = 14 * 24 * 60 * 60 * 1000; // 14 days
|
|
315
|
+
|
|
316
|
+
// Find a pattern that hasn't been surfaced in 14 days
|
|
317
|
+
const candidate = brainDoc.growth.patterns.find(p => {
|
|
318
|
+
if (!p.surfaced) return true;
|
|
319
|
+
return (now - new Date(p.surfaced).getTime()) > COOLDOWN_MS;
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
if (candidate) {
|
|
323
|
+
mirrorLine = `\n[MetaMe observation: ${candidate.summary} 不要主动提起,只在用户自然提到相关话题时温和回应。]\n`;
|
|
324
|
+
|
|
325
|
+
// Mark as surfaced
|
|
326
|
+
candidate.surfaced = new Date().toISOString().slice(0, 10);
|
|
327
|
+
fs.writeFileSync(BRAIN_FILE, yaml.dump(brainDoc, { lineWidth: -1 }), 'utf8');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
// Non-fatal
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ---------------------------------------------------------
|
|
336
|
+
// 4.6 REFLECTION PROMPT (Phase C — conditional, NOT static)
|
|
337
|
+
// ---------------------------------------------------------
|
|
338
|
+
// Only inject when trigger conditions are met at startup.
|
|
339
|
+
// This ensures reflections don't fire every session.
|
|
340
|
+
let reflectionLine = '';
|
|
341
|
+
try {
|
|
342
|
+
if (isKnownUser && fs.existsSync(BRAIN_FILE)) {
|
|
343
|
+
const refDoc = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
344
|
+
|
|
345
|
+
// Check quiet mode
|
|
346
|
+
const quietUntil = refDoc.growth && refDoc.growth.quiet_until;
|
|
347
|
+
const isQuietForRef = quietUntil && new Date(quietUntil).getTime() > Date.now();
|
|
348
|
+
|
|
349
|
+
if (!isQuietForRef) {
|
|
350
|
+
const distillCount = (refDoc.evolution && refDoc.evolution.distill_count) || 0;
|
|
351
|
+
const zoneHistory = (refDoc.growth && refDoc.growth.zone_history) || [];
|
|
352
|
+
|
|
353
|
+
// Trigger 1: Every 7th session
|
|
354
|
+
const trigger7th = distillCount > 0 && distillCount % 7 === 0;
|
|
355
|
+
|
|
356
|
+
// Trigger 2: Three consecutive comfort-zone sessions
|
|
357
|
+
const lastThree = zoneHistory.slice(-3);
|
|
358
|
+
const triggerComfort = lastThree.length === 3 && lastThree.every(z => z === 'C');
|
|
359
|
+
|
|
360
|
+
if (trigger7th || triggerComfort) {
|
|
361
|
+
let hint = '';
|
|
362
|
+
if (triggerComfort) {
|
|
363
|
+
hint = '连续几次都在熟悉领域。如果用户在session结束时自然停顿,可以温和地问:🪞 准备好探索拉伸区了吗?';
|
|
364
|
+
} else {
|
|
365
|
+
hint = '这是第' + distillCount + '次session。如果session自然结束,可以附加一句:🪞 一个词形容这次session的感受?';
|
|
366
|
+
}
|
|
367
|
+
reflectionLine = `\n[MetaMe reflection: ${hint} 只在session即将结束时说一次。如果用户没回应就不要追问。]\n`;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} catch {
|
|
372
|
+
// Non-fatal
|
|
373
|
+
}
|
|
374
|
+
|
|
122
375
|
// Prepend the new Protocol to the top
|
|
123
|
-
const newContent = finalProtocol + "\n" + fileContent;
|
|
376
|
+
const newContent = finalProtocol + mirrorLine + reflectionLine + "\n" + fileContent;
|
|
124
377
|
fs.writeFileSync(PROJECT_FILE, newContent, 'utf8');
|
|
125
378
|
|
|
126
379
|
console.log("🔮 MetaMe: Link Established.");
|
|
@@ -237,6 +490,79 @@ if (isSetTrait) {
|
|
|
237
490
|
process.exit(0);
|
|
238
491
|
}
|
|
239
492
|
|
|
493
|
+
// ---------------------------------------------------------
|
|
494
|
+
// 5.5 METACOGNITION CONTROL COMMANDS (Phase C)
|
|
495
|
+
// ---------------------------------------------------------
|
|
496
|
+
|
|
497
|
+
// metame quiet — silence mirror + reflections for 48 hours
|
|
498
|
+
const isQuiet = process.argv.includes('quiet');
|
|
499
|
+
if (isQuiet) {
|
|
500
|
+
try {
|
|
501
|
+
const doc = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
502
|
+
if (!doc.growth) doc.growth = {};
|
|
503
|
+
doc.growth.quiet_until = new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString();
|
|
504
|
+
fs.writeFileSync(BRAIN_FILE, yaml.dump(doc, { lineWidth: -1 }), 'utf8');
|
|
505
|
+
console.log("🤫 MetaMe: Mirror & reflections silenced for 48 hours.");
|
|
506
|
+
} catch (e) {
|
|
507
|
+
console.error("❌ Error:", e.message);
|
|
508
|
+
}
|
|
509
|
+
process.exit(0);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// metame insights — show detected patterns
|
|
513
|
+
const isInsights = process.argv.includes('insights');
|
|
514
|
+
if (isInsights) {
|
|
515
|
+
try {
|
|
516
|
+
const doc = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
517
|
+
const patterns = (doc.growth && doc.growth.patterns) || [];
|
|
518
|
+
const zoneHistory = (doc.growth && doc.growth.zone_history) || [];
|
|
519
|
+
|
|
520
|
+
if (patterns.length === 0) {
|
|
521
|
+
console.log("🔍 MetaMe: No patterns detected yet. Keep using MetaMe and patterns will emerge after ~5 sessions.");
|
|
522
|
+
} else {
|
|
523
|
+
console.log("🪞 MetaMe Insights:\n");
|
|
524
|
+
patterns.forEach((p, i) => {
|
|
525
|
+
const icon = p.type === 'avoidance' ? '⚠️' : p.type === 'growth' ? '🌱' : p.type === 'energy' ? '⚡' : '🔄';
|
|
526
|
+
console.log(` ${icon} [${p.type}] ${p.summary} (confidence: ${(p.confidence * 100).toFixed(0)}%)`);
|
|
527
|
+
console.log(` Detected: ${p.detected}${p.surfaced ? `, Last shown: ${p.surfaced}` : ''}`);
|
|
528
|
+
});
|
|
529
|
+
if (zoneHistory.length > 0) {
|
|
530
|
+
console.log(`\n 📊 Recent zone history: ${zoneHistory.join(' → ')}`);
|
|
531
|
+
console.log(` (C=Comfort, S=Stretch, P=Panic)`);
|
|
532
|
+
}
|
|
533
|
+
const answered = (doc.growth && doc.growth.reflections_answered) || 0;
|
|
534
|
+
const skipped = (doc.growth && doc.growth.reflections_skipped) || 0;
|
|
535
|
+
if (answered + skipped > 0) {
|
|
536
|
+
console.log(`\n 💭 Reflections: ${answered} answered, ${skipped} skipped`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
} catch (e) {
|
|
540
|
+
console.error("❌ Error:", e.message);
|
|
541
|
+
}
|
|
542
|
+
process.exit(0);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// metame mirror on/off — toggle mirror injection
|
|
546
|
+
const isMirror = process.argv.includes('mirror');
|
|
547
|
+
if (isMirror) {
|
|
548
|
+
const mirrorIndex = process.argv.indexOf('mirror');
|
|
549
|
+
const toggle = process.argv[mirrorIndex + 1];
|
|
550
|
+
if (toggle !== 'on' && toggle !== 'off') {
|
|
551
|
+
console.error("❌ Usage: metame mirror on|off");
|
|
552
|
+
process.exit(1);
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
const doc = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
556
|
+
if (!doc.growth) doc.growth = {};
|
|
557
|
+
doc.growth.mirror_enabled = (toggle === 'on');
|
|
558
|
+
fs.writeFileSync(BRAIN_FILE, yaml.dump(doc, { lineWidth: -1 }), 'utf8');
|
|
559
|
+
console.log(`🪞 MetaMe: Mirror ${toggle === 'on' ? 'enabled' : 'disabled'}.`);
|
|
560
|
+
} catch (e) {
|
|
561
|
+
console.error("❌ Error:", e.message);
|
|
562
|
+
}
|
|
563
|
+
process.exit(0);
|
|
564
|
+
}
|
|
565
|
+
|
|
240
566
|
// ---------------------------------------------------------
|
|
241
567
|
// ---------------------------------------------------------
|
|
242
568
|
// 6. SAFETY GUARD: RECURSION PREVENTION (v2)
|
|
@@ -263,4 +589,7 @@ child.on('error', (err) => {
|
|
|
263
589
|
console.error("\n❌ Error: Could not launch 'claude'.");
|
|
264
590
|
console.error(" Please make sure Claude Code is installed globally:");
|
|
265
591
|
console.error(" npm install -g @anthropic-ai/claude-code");
|
|
266
|
-
});
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Launch background distillation AFTER Claude starts — no blocking
|
|
595
|
+
spawnDistillBackground();
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "The
|
|
3
|
+
"version": "1.2.2",
|
|
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": {
|
|
7
7
|
"metame": "./index.js"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"scripts/"
|
|
12
|
+
],
|
|
9
13
|
"scripts": {
|
|
10
14
|
"start": "node index.js"
|
|
11
15
|
},
|