@equilateral_ai/mindmeld 3.4.0 → 3.5.0

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.
@@ -5,16 +5,29 @@
5
5
  * Records session completion and outcomes when a Claude Code session ends.
6
6
  * Calls POST /api/sessions/end with session metadata.
7
7
  *
8
+ * When reason === "clear" (user cleared context to continue), also harvests
9
+ * patterns from the transcript before context is lost. This covers the gap
10
+ * where PreCompact doesn't fire on /clear — only on compaction.
11
+ *
8
12
  * Input (stdin JSON from Claude Code):
9
13
  * { session_id, transcript_path, cwd, reason, hook_event_name }
10
14
  *
11
- * @equilateral_ai/mindmeld v3.3.0
15
+ * @equilateral_ai/mindmeld v3.5.0
12
16
  */
13
17
 
14
18
  const path = require('path');
15
19
  const fs = require('fs').promises;
16
20
  const { execSync } = require('child_process');
17
21
 
22
+ // Import pattern harvesting from pre-compact hook
23
+ let harvestPatterns = null;
24
+ try {
25
+ const preCompact = require('./pre-compact');
26
+ harvestPatterns = preCompact.harvestPatterns;
27
+ } catch (error) {
28
+ // pre-compact module not available — pattern harvesting on clear will be skipped
29
+ }
30
+
18
31
  /**
19
32
  * Load auth token for API calls
20
33
  * Priority: env var → project credentials.json → global ~/.mindmeld/auth.json
@@ -166,9 +179,94 @@ function readStdin() {
166
179
  });
167
180
  }
168
181
 
182
+ /**
183
+ * Harvest patterns from transcript on clear events.
184
+ * Reads the JSONL transcript, extracts conversation text,
185
+ * and delegates to pre-compact's harvestPatterns.
186
+ *
187
+ * @param {string} transcriptPath - Path to the JSONL transcript file
188
+ * @param {string} sessionId - Current session ID
189
+ * @returns {Promise<Object|null>} Harvest results or null
190
+ */
191
+ async function harvestPatternsOnClear(transcriptPath, sessionId) {
192
+ try {
193
+ console.error('[MindMeld] Clear detected — harvesting patterns before context is lost');
194
+
195
+ // Read transcript JSONL (cap at 200KB to keep processing fast)
196
+ const stat = await fs.stat(transcriptPath);
197
+ let transcriptContent;
198
+
199
+ if (stat.size > 200 * 1024) {
200
+ // Read last 200KB — most recent context is most valuable
201
+ const fd = await fs.open(transcriptPath, 'r');
202
+ const buffer = Buffer.alloc(200 * 1024);
203
+ await fd.read(buffer, 0, buffer.length, stat.size - buffer.length);
204
+ await fd.close();
205
+ transcriptContent = buffer.toString('utf-8');
206
+ // Skip partial first line
207
+ const firstNewline = transcriptContent.indexOf('\n');
208
+ if (firstNewline > 0) {
209
+ transcriptContent = transcriptContent.substring(firstNewline + 1);
210
+ }
211
+ } else {
212
+ transcriptContent = await fs.readFile(transcriptPath, 'utf-8');
213
+ }
214
+
215
+ // Parse JSONL — extract assistant message text for pattern detection
216
+ const lines = transcriptContent.split('\n').filter(l => l.trim());
217
+ const textParts = [];
218
+
219
+ for (const line of lines) {
220
+ try {
221
+ const entry = JSON.parse(line);
222
+ // Claude Code transcript entries have varied formats
223
+ const content = entry.message?.content || entry.content;
224
+ if (!content) continue;
225
+
226
+ if (typeof content === 'string') {
227
+ textParts.push(content);
228
+ } else if (Array.isArray(content)) {
229
+ // Content blocks — extract text blocks
230
+ for (const block of content) {
231
+ if (block.type === 'text' && block.text) {
232
+ textParts.push(block.text);
233
+ }
234
+ }
235
+ }
236
+ } catch (e) {
237
+ // Skip unparseable lines
238
+ }
239
+ }
240
+
241
+ const transcriptText = textParts.join('\n\n');
242
+
243
+ if (transcriptText.length < 100) {
244
+ console.error('[MindMeld] Transcript too short for pattern detection, skipping');
245
+ return null;
246
+ }
247
+
248
+ // Delegate to pre-compact's full harvesting pipeline
249
+ const result = await harvestPatterns({
250
+ sessionId: sessionId,
251
+ userId: process.env.USER || 'unknown',
252
+ transcript: transcriptText
253
+ });
254
+
255
+ console.error(`[MindMeld] Clear-event harvest: ${result.patternsDetected || 0} patterns, ` +
256
+ `${result.violations || 0} violations, ${result.reinforced || 0} reinforced` +
257
+ (result.plansHarvested ? `, ${result.plansHarvested} plans` : ''));
258
+
259
+ return result;
260
+ } catch (error) {
261
+ console.error(`[MindMeld] Clear-event harvest failed (non-fatal): ${error.message}`);
262
+ return null;
263
+ }
264
+ }
265
+
169
266
  /**
170
267
  * Main hook execution
171
268
  * Records session end via API call (fire-and-forget)
269
+ * On clear events, also harvests patterns from the transcript
172
270
  */
173
271
  async function recordSessionEnd() {
174
272
  const startTime = Date.now();
@@ -217,7 +315,7 @@ async function recordSessionEnd() {
217
315
  git_branch: gitBranch,
218
316
  session_data: {
219
317
  end_reason: reason,
220
- hook_version: '3.3.0'
318
+ hook_version: '3.5.0'
221
319
  }
222
320
  };
223
321
 
@@ -238,7 +336,7 @@ async function recordSessionEnd() {
238
336
  timeout: 3000
239
337
  };
240
338
 
241
- await new Promise((resolve) => {
339
+ const metadataPromise = new Promise((resolve) => {
242
340
  const req = http.request(options, (res) => {
243
341
  let body = '';
244
342
  res.on('data', (chunk) => { body += chunk; });
@@ -268,10 +366,21 @@ async function recordSessionEnd() {
268
366
  req.end();
269
367
  });
270
368
 
369
+ // Pattern harvesting on clear — PreCompact doesn't fire on /clear,
370
+ // so we harvest here before context is lost
371
+ let harvestResult = null;
372
+ const harvestPromise = (reason === 'clear' && transcriptPath && harvestPatterns)
373
+ ? harvestPatternsOnClear(transcriptPath, sessionId)
374
+ : Promise.resolve(null);
375
+
376
+ // Run metadata recording and pattern harvesting in parallel
377
+ [, harvestResult] = await Promise.all([metadataPromise, harvestPromise]);
378
+
271
379
  return {
272
380
  sessionId,
273
381
  reason,
274
382
  duration,
383
+ harvest: harvestResult,
275
384
  elapsed: Date.now() - startTime
276
385
  };
277
386
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equilateral_ai/mindmeld",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Intelligent standards injection for AI coding sessions - context-aware, self-documenting, scales to large codebases",
5
5
  "main": "src/index.js",
6
6
  "bin": {