metame-cli 1.4.15 → 1.4.18

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.
@@ -13,6 +13,14 @@ const path = require('path');
13
13
  const os = require('os');
14
14
 
15
15
  const BUFFER_FILE = path.join(os.homedir(), '.metame', 'raw_signals.jsonl');
16
+ const OVERFLOW_FILE = path.join(os.homedir(), '.metame', 'raw_signals.overflow.jsonl');
17
+ const LOCK_FILE = path.join(os.homedir(), '.metame', 'raw_signals.lock');
18
+ const LOCK_RETRY_MAX = 80;
19
+ const LOCK_RETRY_MS = 8;
20
+ const LOCK_STALE_MS = 30 * 1000;
21
+ const MAX_BUFFER_LINES = 300;
22
+ const MAX_CAPTURE_CHARS = 1600;
23
+ const ABSOLUTE_MAX_CAPTURE_CHARS = 6000;
16
24
 
17
25
  // === CONFIDENCE PATTERNS ===
18
26
 
@@ -33,6 +41,90 @@ const CORRECTION_EN = /(no,? I meant|that's not what I|you misunderstood|wrong.+
33
41
  const META_ZH = /我(发现|意识到|觉得|反思|总结|复盘)|想错了|换个(思路|方向|方案)|回头(想想|看看)|之前的(方案|思路|方向).*(不行|不对|有问题)|我的(问题|毛病|习惯)是|下次(应该|要|得)/;
34
42
  const META_EN = /(I realize|looking back|on reflection|my (mistake|problem|habit) is|let me rethink|wrong approach|next time I should)/i;
35
43
 
44
+ // Internal/system prompts must never enter cognition signal buffer.
45
+ const INTERNAL_PROMPT_PATTERNS = [
46
+ /You are a MetaMe cognitive profile distiller/i,
47
+ /You are a metacognition pattern detector/i,
48
+ /你是精准的知识提取引擎/,
49
+ /RECALLED LONG-TERM FACTS \(context only/i,
50
+ /\[System hints - DO NOT mention these to user:/i,
51
+ /\[Mac automation policy - do NOT expose this block:/i,
52
+ /MANDATORY FIRST ACTION: The user has not been calibrated yet/i,
53
+ /<\!--\s*FACTS:START\s*-->/i,
54
+ /<\!--\s*MEMORY:START\s*-->/i,
55
+ /\[Task notification\]/i,
56
+ /<task-notification\b/i,
57
+ ];
58
+
59
+ function sanitizeMetaMePrompt(text) {
60
+ let prompt = String(text || '');
61
+ if (!prompt) return '';
62
+
63
+ // Remove daemon-injected RAG blocks
64
+ prompt = prompt.replace(/<!--\s*FACTS:START\s*-->[\s\S]*?<!--\s*FACTS:END\s*-->/gi, ' ');
65
+ prompt = prompt.replace(/<!--\s*MEMORY:START\s*-->[\s\S]*?<!--\s*MEMORY:END\s*-->/gi, ' ');
66
+
67
+ // Remove daemon/system internal hint blocks
68
+ prompt = prompt.replace(/\[System hints - DO NOT mention these to user:[\s\S]*?\]/gi, ' ');
69
+ prompt = prompt.replace(/\[Mac automation policy - do NOT expose this block:[\s\S]*?\]/gi, ' ');
70
+ prompt = prompt.replace(/\[Task notification\][\s\S]*?(?=\n{2,}|$)/gi, ' ');
71
+ prompt = prompt.replace(/<task-notification\b[\s\S]*?<\/task-notification>/gi, ' ');
72
+ prompt = prompt.replace(/<task-notification\b[\s\S]*$/gi, ' ');
73
+
74
+ return prompt.trim();
75
+ }
76
+
77
+ function isInternalPrompt(text) {
78
+ const prompt = String(text || '');
79
+ if (!prompt) return false;
80
+ return INTERNAL_PROMPT_PATTERNS.some((re) => re.test(prompt));
81
+ }
82
+
83
+ function sleep(ms) {
84
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
85
+ }
86
+
87
+ function withBufferLock(fn) {
88
+ const dir = path.dirname(BUFFER_FILE);
89
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
90
+
91
+ let acquired = false;
92
+ for (let i = 0; i < LOCK_RETRY_MAX; i++) {
93
+ try {
94
+ const fd = fs.openSync(LOCK_FILE, 'wx');
95
+ fs.writeSync(fd, process.pid.toString());
96
+ fs.closeSync(fd);
97
+ acquired = true;
98
+ break;
99
+ } catch (e) {
100
+ if (e.code !== 'EEXIST') throw e;
101
+ try {
102
+ const age = Date.now() - fs.statSync(LOCK_FILE).mtimeMs;
103
+ if (age > LOCK_STALE_MS) {
104
+ fs.unlinkSync(LOCK_FILE);
105
+ continue;
106
+ }
107
+ } catch { /* lock released by another process */ }
108
+ sleep(LOCK_RETRY_MS);
109
+ }
110
+ }
111
+
112
+ if (!acquired) return false;
113
+ try {
114
+ fn();
115
+ return true;
116
+ } finally {
117
+ try { fs.unlinkSync(LOCK_FILE); } catch { /* non-fatal */ }
118
+ }
119
+ }
120
+
121
+ function writeBufferAtomically(lines) {
122
+ const tmp = BUFFER_FILE + `.tmp.${process.pid}`;
123
+ const content = lines.length > 0 ? (lines.join('\n') + '\n') : '';
124
+ fs.writeFileSync(tmp, content, 'utf8');
125
+ fs.renameSync(tmp, BUFFER_FILE);
126
+ }
127
+
36
128
  // Read JSON from stdin
37
129
  let input = '';
38
130
  process.stdin.setEncoding('utf8');
@@ -40,7 +132,23 @@ process.stdin.on('data', (chunk) => { input += chunk; });
40
132
  process.stdin.on('end', () => {
41
133
  try {
42
134
  const data = JSON.parse(input);
43
- const prompt = (data.prompt || '').trim();
135
+ let prompt = (data.prompt || '').trim();
136
+
137
+ // Internal Claude subprocesses (distill/memory extract/skill evolution) set this flag.
138
+ if (process.env.METAME_INTERNAL_PROMPT === '1') {
139
+ process.exit(0);
140
+ }
141
+
142
+ // Strip daemon/system injected wrappers first; keep user payload if present.
143
+ prompt = sanitizeMetaMePrompt(prompt);
144
+ if (!prompt) {
145
+ process.exit(0);
146
+ }
147
+
148
+ // Belt-and-suspenders: filter known internal prompt templates.
149
+ if (isInternalPrompt(prompt)) {
150
+ process.exit(0);
151
+ }
44
152
 
45
153
  // === LAYER 0: Metacognitive bypass — always capture self-reflection ===
46
154
  const isMeta = META_ZH.test(prompt) || META_EN.test(prompt);
@@ -55,6 +163,14 @@ process.stdin.on('end', () => {
55
163
  process.exit(0);
56
164
  }
57
165
 
166
+ // Hard cap to prevent giant prompt pastes from poisoning distill budget.
167
+ if (prompt.length > ABSOLUTE_MAX_CAPTURE_CHARS) {
168
+ process.exit(0);
169
+ }
170
+ if (prompt.length > MAX_CAPTURE_CHARS) {
171
+ prompt = prompt.slice(0, MAX_CAPTURE_CHARS);
172
+ }
173
+
58
174
  // Skip messages that are purely code or file paths
59
175
  if (!isMeta && /^(```|\/[\w/]+\.\w+$)/.test(prompt)) {
60
176
  process.exit(0);
@@ -113,18 +229,62 @@ process.stdin.on('end', () => {
113
229
  cwd: data.cwd || null
114
230
  };
115
231
 
116
- // Append to buffer, drop oldest if over cap
117
- let existingLines = [];
118
- try {
119
- existingLines = fs.readFileSync(BUFFER_FILE, 'utf8')
120
- .split('\n').filter(l => l.trim());
121
- } catch {
122
- // File doesn't exist yet, that's fine
123
- }
232
+ // Append/update with process-level lock to avoid concurrent read-modify-write loss.
233
+ const locked = withBufferLock(() => {
234
+ let existingLines = [];
235
+ try {
236
+ existingLines = fs.readFileSync(BUFFER_FILE, 'utf8').split('\n').filter(l => l.trim());
237
+ } catch {
238
+ // File doesn't exist yet, that's fine
239
+ }
240
+
241
+ // Drain overflow written during prior lock-contention periods.
242
+ // unlink AFTER writeBufferAtomically succeeds — crash-safe ordering.
243
+ let overflowDrained = false;
244
+ try {
245
+ const overflowLines = fs.readFileSync(OVERFLOW_FILE, 'utf8').split('\n').filter(Boolean);
246
+ if (overflowLines.length > 0) {
247
+ existingLines = existingLines.concat(overflowLines);
248
+ overflowDrained = true;
249
+ }
250
+ } catch { /* no overflow file — normal case */ }
124
251
 
125
- existingLines.push(JSON.stringify(entry));
252
+ // Opportunistic hygiene: remove old internal/system lines if any slipped in historically.
253
+ existingLines = existingLines.filter((line) => {
254
+ try {
255
+ const parsed = JSON.parse(line);
256
+ const p = String(parsed && parsed.prompt ? parsed.prompt : '');
257
+ if (!p) return false;
258
+ if (p.length > ABSOLUTE_MAX_CAPTURE_CHARS) return false;
259
+ if (isInternalPrompt(p)) return false;
260
+ return true;
261
+ } catch {
262
+ return false;
263
+ }
264
+ });
126
265
 
127
- fs.writeFileSync(BUFFER_FILE, existingLines.join('\n') + '\n');
266
+ existingLines.push(JSON.stringify(entry));
267
+ if (existingLines.length > MAX_BUFFER_LINES) {
268
+ existingLines = existingLines.slice(-MAX_BUFFER_LINES);
269
+ }
270
+ writeBufferAtomically(existingLines);
271
+ // Unlink overflow only after main file is safely written.
272
+ if (overflowDrained) {
273
+ try { fs.unlinkSync(OVERFLOW_FILE); } catch { /* already gone */ }
274
+ }
275
+ });
276
+
277
+ if (!locked) {
278
+ // Last-resort fallback: write to overflow side-file so the next lock-holder
279
+ // can drain, clean, and cap it — never bypassing buffer rules.
280
+ // Guard against unbounded growth: drop entry if overflow already at cap.
281
+ fs.mkdirSync(path.dirname(OVERFLOW_FILE), { recursive: true });
282
+ try {
283
+ const ofLines = fs.readFileSync(OVERFLOW_FILE, 'utf8').split('\n').filter(Boolean);
284
+ if (ofLines.length >= MAX_BUFFER_LINES) return; // shed load
285
+ } catch { /* overflow file doesn't exist yet */ }
286
+ fs.appendFileSync(OVERFLOW_FILE, JSON.stringify(entry) + '\n', 'utf8');
287
+ }
128
288
 
129
289
  } catch {
130
290
  // Silently ignore parse errors — never block the user's workflow