mcvay-mind 1.1.0 → 1.1.1

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/index.js CHANGED
@@ -18,6 +18,7 @@
18
18
  * node index.js embed-rebuild [--types preference,lesson] [--batch-size 16]
19
19
  * node index.js detect-preferences --from <file>
20
20
  * node index.js feedback-preference --label <correct|missed|wrong> --utterance "..." --expected "..."
21
+ * node index.js auto-capture status|enable|disable
21
22
  */
22
23
 
23
24
  const store = require('./lib/store');
@@ -31,6 +32,7 @@ const expandCache = require('./lib/expand-cache');
31
32
  const metrics = require('./lib/metrics');
32
33
  const { extractPreferences } = require('./lib/preference-extractor/Rule');
33
34
  const preferenceState = require('./lib/preference-state');
35
+ const autoMemoryCapture = require('./lib/auto-memory-capture');
34
36
  const { readFileSync } = require('fs');
35
37
 
36
38
  // ============================================================================
@@ -94,6 +96,9 @@ async function main() {
94
96
  case 'preference-state':
95
97
  handlePreferenceState();
96
98
  break;
99
+ case 'auto-capture':
100
+ handleAutoCapture();
101
+ break;
97
102
  case 'help':
98
103
  printUsage();
99
104
  break;
@@ -123,6 +128,7 @@ Usage:
123
128
  node index.js detect-preferences --from <file>
124
129
  node index.js feedback-preference --label <correct|missed|wrong> --utterance "..." --expected "..."
125
130
  node index.js preference-state --query "<message>"
131
+ node index.js auto-capture status|enable|disable
126
132
 
127
133
  Types: decision, preference, relationship, commitment, lesson, task, project, moc
128
134
 
@@ -183,6 +189,7 @@ Examples:
183
189
  node index.js detect-preferences --from ./conversation.txt
184
190
  node index.js feedback-preference --label missed --utterance "avoid emoji" --expected "Don't use emoji."
185
191
  node index.js preference-state --query "writing a Discord reply"
192
+ node index.js auto-capture status
186
193
  `);
187
194
  }
188
195
 
@@ -553,6 +560,41 @@ function handleRebuildIndexes() {
553
560
  console.log('✓ Indexes rebuilt.');
554
561
  }
555
562
 
563
+ function handleAutoCapture() {
564
+ const action = args[1];
565
+
566
+ if (!action || action === 'help') {
567
+ console.log('Usage:');
568
+ console.log(' node index.js auto-capture status');
569
+ console.log(' node index.js auto-capture enable');
570
+ console.log(' node index.js auto-capture disable');
571
+ process.exit(action ? 0 : 1);
572
+ }
573
+
574
+ if (action === 'status') {
575
+ const state = autoMemoryCapture.getAutoCaptureState(cwd);
576
+ console.log(`Auto-capture: ${state.enabled ? 'enabled' : 'disabled'}`);
577
+ console.log(`Last capture at: ${state.last_capture_at || 'never'}`);
578
+ console.log(`Updated at: ${state.updated_at || 'never'}`);
579
+ return;
580
+ }
581
+
582
+ if (action === 'enable') {
583
+ autoMemoryCapture.setAutoCaptureEnabled(true, cwd);
584
+ console.log('✓ Auto-capture enabled');
585
+ return;
586
+ }
587
+
588
+ if (action === 'disable') {
589
+ autoMemoryCapture.setAutoCaptureEnabled(false, cwd);
590
+ console.log('✓ Auto-capture disabled');
591
+ return;
592
+ }
593
+
594
+ console.error(`Unknown auto-capture action: ${action}`);
595
+ process.exit(1);
596
+ }
597
+
556
598
  async function handleEmbedRebuild() {
557
599
  const options = parseArgs(args.slice(1));
558
600
  const types = options.types ? options.types.split(',').map(t => t.trim()).filter(Boolean) : undefined;
@@ -0,0 +1,270 @@
1
+ const { existsSync, mkdirSync, readFileSync, writeFileSync } = require('fs');
2
+ const { createHash } = require('crypto');
3
+ const { join, dirname } = require('path');
4
+ const store = require('./store');
5
+
6
+ const DEFAULT_STATE = {
7
+ enabled: true,
8
+ updated_at: null,
9
+ last_capture_hash: null,
10
+ last_capture_at: null,
11
+ };
12
+
13
+ const DEFAULT_PATTERN_CONFIG = {
14
+ positive: [
15
+ '\\bthanks\\b',
16
+ '\\bthank\\s*you\\b',
17
+ '\\bthx\\b',
18
+ '\\bappreciate\\s+it\\b',
19
+ '\\bgood\\s+job\\b',
20
+ '\\bgreat\\s+job\\b',
21
+ '\\bnice\\s+work\\b',
22
+ '\\bwell\\s+done\\b',
23
+ '认同',
24
+ '同意',
25
+ '赞同',
26
+ '做得好',
27
+ ],
28
+ correction: [
29
+ '^no\\b',
30
+ "that's\\s+wrong",
31
+ '\\byou\\s+are\\s+wrong\\b',
32
+ '\\bincorrect\\b',
33
+ '\\bnot\\s+quite\\b',
34
+ '\\bactually\\b',
35
+ '\\bto\\s+clarify\\b',
36
+ '\\bthat\\s+is\\s+not\\s+right\\b',
37
+ '\\byou\\s+missed\\b',
38
+ '\\bnope\\b',
39
+ ],
40
+ };
41
+
42
+ function getStatePath(cwd = process.cwd()) {
43
+ return join(cwd, 'state', 'mcvay-mind-auto-capture.json');
44
+ }
45
+
46
+ function ensureDirectory(filePath) {
47
+ const directory = dirname(filePath);
48
+ if (directory && !existsSync(directory)) {
49
+ mkdirSync(directory, { recursive: true });
50
+ }
51
+ }
52
+
53
+ function readJsonSafe(filePath) {
54
+ try {
55
+ return JSON.parse(readFileSync(filePath, 'utf8'));
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ function writeJson(filePath, value) {
62
+ ensureDirectory(filePath);
63
+ writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
64
+ }
65
+
66
+ function getAutoCaptureState(cwd = process.cwd()) {
67
+ const statePath = getStatePath(cwd);
68
+ if (!existsSync(statePath)) {
69
+ return { ...DEFAULT_STATE };
70
+ }
71
+
72
+ const raw = readJsonSafe(statePath);
73
+ if (!raw || typeof raw !== 'object') {
74
+ return { ...DEFAULT_STATE };
75
+ }
76
+
77
+ return {
78
+ ...DEFAULT_STATE,
79
+ ...raw,
80
+ enabled: raw.enabled !== false,
81
+ };
82
+ }
83
+
84
+ function setAutoCaptureEnabled(enabled, cwd = process.cwd()) {
85
+ const previous = getAutoCaptureState(cwd);
86
+ const next = {
87
+ ...previous,
88
+ enabled: Boolean(enabled),
89
+ updated_at: new Date().toISOString(),
90
+ };
91
+ writeJson(getStatePath(cwd), next);
92
+ return next;
93
+ }
94
+
95
+ function normalizePatternConfig(patternConfig = {}) {
96
+ const input = patternConfig && typeof patternConfig === 'object' ? patternConfig : {};
97
+ const positive = Array.isArray(input.positive) ? input.positive : DEFAULT_PATTERN_CONFIG.positive;
98
+ const correction = Array.isArray(input.correction) ? input.correction : DEFAULT_PATTERN_CONFIG.correction;
99
+ return {
100
+ positive: positive.map((v) => `${v}`.trim()).filter(Boolean),
101
+ correction: correction.map((v) => `${v}`.trim()).filter(Boolean),
102
+ };
103
+ }
104
+
105
+ function compilePatterns(patterns = []) {
106
+ const compiled = [];
107
+ for (const pattern of patterns) {
108
+ try {
109
+ compiled.push(new RegExp(pattern, 'iu'));
110
+ } catch {
111
+ // Ignore invalid regex patterns and continue.
112
+ }
113
+ }
114
+ return compiled;
115
+ }
116
+
117
+ function detectSignal(userMessage, patternConfig = {}) {
118
+ const text = `${userMessage || ''}`.trim();
119
+ if (!text) {
120
+ return { type: null, pattern: null };
121
+ }
122
+
123
+ const normalized = normalizePatternConfig(patternConfig);
124
+ const correctionPatterns = compilePatterns(normalized.correction);
125
+ for (const regex of correctionPatterns) {
126
+ if (regex.test(text)) {
127
+ return { type: 'correction', pattern: regex.source };
128
+ }
129
+ }
130
+
131
+ const positivePatterns = compilePatterns(normalized.positive);
132
+ for (const regex of positivePatterns) {
133
+ if (regex.test(text)) {
134
+ return { type: 'positive', pattern: regex.source };
135
+ }
136
+ }
137
+
138
+ return { type: null, pattern: null };
139
+ }
140
+
141
+ function createInteractionHash(userMessage, assistantResponse, signalType) {
142
+ return createHash('sha1')
143
+ .update(`${signalType || ''}\n${userMessage || ''}\n${assistantResponse || ''}`, 'utf8')
144
+ .digest('hex');
145
+ }
146
+
147
+ function buildLessonPayload(signal, userMessage, assistantResponse) {
148
+ const assistantSnippet = `${assistantResponse || ''}`.replace(/\s+/g, ' ').trim().slice(0, 160);
149
+ const userSnippet = `${userMessage || ''}`.replace(/\s+/g, ' ').trim().slice(0, 160);
150
+
151
+ if (signal.type === 'correction') {
152
+ return {
153
+ type: 'lesson',
154
+ title: 'User correction on response quality',
155
+ tags: ['user_correction', 'auto_capture', 'feedback'],
156
+ lesson_type: 'correction',
157
+ severity: 'moderate',
158
+ context: 'Automatic post-response interaction capture.',
159
+ outcome: 'Detected corrective user feedback and logged as actionable lesson.',
160
+ confidence: 95,
161
+ source: 'auto-capture',
162
+ content: [
163
+ '## Content',
164
+ 'User indicated the previous response was incorrect or needed correction.',
165
+ '',
166
+ '## Context',
167
+ `User message: "${userSnippet || '(empty)'}"`,
168
+ `Assistant response excerpt: "${assistantSnippet || '(empty)'}"`,
169
+ '',
170
+ '## Takeaway',
171
+ 'Re-check assumptions and ground future responses in the user correction.',
172
+ ].join('\n'),
173
+ };
174
+ }
175
+
176
+ if (signal.type === 'positive') {
177
+ return {
178
+ type: 'lesson',
179
+ title: 'Positive user feedback on response',
180
+ tags: ['positive_impact', 'auto_capture', 'feedback'],
181
+ lesson_type: 'success',
182
+ severity: 'minor',
183
+ context: 'Automatic post-response interaction capture.',
184
+ outcome: 'Detected positive reinforcement from user and logged successful approach.',
185
+ confidence: 88,
186
+ source: 'auto-capture',
187
+ content: [
188
+ '## Content',
189
+ 'User gave positive feedback about the response.',
190
+ '',
191
+ '## Context',
192
+ `User message: "${userSnippet || '(empty)'}"`,
193
+ `Assistant response excerpt: "${assistantSnippet || '(empty)'}"`,
194
+ '',
195
+ '## Takeaway',
196
+ 'Keep this response style and quality pattern for similar requests.',
197
+ ].join('\n'),
198
+ };
199
+ }
200
+
201
+ return null;
202
+ }
203
+
204
+ function captureFromInteraction({
205
+ userMessage = '',
206
+ assistantResponse = '',
207
+ patternConfig = {},
208
+ dedupeWindowMs = 10 * 60 * 1000,
209
+ } = {}, cwd = process.cwd()) {
210
+ const state = getAutoCaptureState(cwd);
211
+ if (!state.enabled) {
212
+ return { captured: false, reason: 'disabled' };
213
+ }
214
+
215
+ const signal = detectSignal(userMessage, patternConfig);
216
+ if (!signal.type) {
217
+ return { captured: false, reason: 'no_match' };
218
+ }
219
+
220
+ const interactionHash = createInteractionHash(userMessage, assistantResponse, signal.type);
221
+ const nowMs = Date.now();
222
+ const previousCaptureAt = Number(new Date(state.last_capture_at || 0).getTime()) || 0;
223
+ const withinWindow = nowMs - previousCaptureAt >= 0 && nowMs - previousCaptureAt < dedupeWindowMs;
224
+ if (withinWindow && state.last_capture_hash === interactionHash) {
225
+ return {
226
+ captured: false,
227
+ reason: 'duplicate_recent',
228
+ signalType: signal.type,
229
+ };
230
+ }
231
+
232
+ const payload = buildLessonPayload(signal, userMessage, assistantResponse);
233
+ if (!payload) {
234
+ return { captured: false, reason: 'no_payload', signalType: signal.type };
235
+ }
236
+
237
+ const result = store.ingest(payload, cwd);
238
+ if (!result.success) {
239
+ return {
240
+ captured: false,
241
+ reason: 'ingest_failed',
242
+ signalType: signal.type,
243
+ errors: result.errors || [],
244
+ };
245
+ }
246
+
247
+ const nextState = {
248
+ ...state,
249
+ last_capture_hash: interactionHash,
250
+ last_capture_at: new Date(nowMs).toISOString(),
251
+ updated_at: new Date(nowMs).toISOString(),
252
+ };
253
+ writeJson(getStatePath(cwd), nextState);
254
+
255
+ return {
256
+ captured: true,
257
+ signalType: signal.type,
258
+ matchedPattern: signal.pattern,
259
+ slug: result.slug,
260
+ tags: payload.tags,
261
+ };
262
+ }
263
+
264
+ module.exports = {
265
+ DEFAULT_PATTERN_CONFIG,
266
+ getAutoCaptureState,
267
+ setAutoCaptureEnabled,
268
+ detectSignal,
269
+ captureFromInteraction,
270
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcvay-mind",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Typed memory system with search, recall, and response guidance for agent workflows.",
5
5
  "main": "index.js",
6
6
  "bin": {