@equilateral_ai/mindmeld 3.5.2 → 4.0.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.
Files changed (140) hide show
  1. package/hooks/session-end.js +25 -0
  2. package/hooks/session-start.js +363 -83
  3. package/hooks/session-watcher.js +585 -0
  4. package/package.json +19 -13
  5. package/scripts/init-project.js +9 -23
  6. package/src/client/dbShim.js +16 -0
  7. package/src/core/AuthManager.js +3 -2
  8. package/src/handlers/helpers/dbOperations.js +9 -46
  9. package/src/index.js +2 -217
  10. package/src/utils/piiMask.js +16 -0
  11. package/scripts/harvest.js +0 -601
  12. package/scripts/inject.js +0 -409
  13. package/scripts/mcp-bridge.js +0 -220
  14. package/scripts/repo-analyzer.js +0 -870
  15. package/src/collaboration/CollaborationPrompt.js +0 -460
  16. package/src/core/AlertEngine.js +0 -813
  17. package/src/core/AlertNotifier.js +0 -363
  18. package/src/core/CorrelationAnalyzer.js +0 -931
  19. package/src/core/CrossReferenceEngine.js +0 -624
  20. package/src/core/CurationEngine.js +0 -688
  21. package/src/core/DeprecationScheduler.js +0 -183
  22. package/src/core/LoadBearingDetector.js +0 -242
  23. package/src/core/NotificationService.js +0 -1032
  24. package/src/core/RapportOrchestrator.js +0 -632
  25. package/src/core/RelevanceDetector.js +0 -694
  26. package/src/core/StandardLifecycle.js +0 -244
  27. package/src/core/StandardsIngestion.js +0 -991
  28. package/src/core/TeamLoadBearingDetector.js +0 -431
  29. package/src/core/parsers/adrParser.js +0 -479
  30. package/src/core/parsers/cursorRulesParser.js +0 -564
  31. package/src/core/parsers/eslintParser.js +0 -439
  32. package/src/database/dbOperations.js +0 -105
  33. package/src/handlers/activity/activityGetMe.js +0 -98
  34. package/src/handlers/activity/activityGetTeam.js +0 -175
  35. package/src/handlers/admin/adminSetup.js +0 -216
  36. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  37. package/src/handlers/alerts/alertsGet.js +0 -250
  38. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  39. package/src/handlers/analytics/coachingGet.js +0 -361
  40. package/src/handlers/analytics/convergenceGet.js +0 -236
  41. package/src/handlers/analytics/developerScoreGet.js +0 -137
  42. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  43. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  44. package/src/handlers/collaborators/collaboratorList.js +0 -82
  45. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  46. package/src/handlers/collaborators/inviteAccept.js +0 -122
  47. package/src/handlers/company/companyUsersDelete.js +0 -141
  48. package/src/handlers/company/companyUsersGet.js +0 -90
  49. package/src/handlers/company/companyUsersPost.js +0 -267
  50. package/src/handlers/company/companyUsersPut.js +0 -76
  51. package/src/handlers/context/contextGet.js +0 -57
  52. package/src/handlers/context/invariantsGet.js +0 -74
  53. package/src/handlers/context/loopsGet.js +0 -82
  54. package/src/handlers/context/notesCreate.js +0 -74
  55. package/src/handlers/context/purposeGet.js +0 -78
  56. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  57. package/src/handlers/correlations/correlationsGet.js +0 -93
  58. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  59. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  60. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  61. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  62. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  63. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  64. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  65. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  66. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  67. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  68. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  69. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  70. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  71. package/src/handlers/github/githubConnectionStatus.js +0 -49
  72. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  73. package/src/handlers/github/githubOAuthCallback.js +0 -178
  74. package/src/handlers/github/githubOAuthStart.js +0 -59
  75. package/src/handlers/github/githubPatternsReview.js +0 -76
  76. package/src/handlers/github/githubReposList.js +0 -105
  77. package/src/handlers/health/healthGet.js +0 -55
  78. package/src/handlers/helpers/auditLogger.js +0 -201
  79. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  80. package/src/handlers/helpers/decisionFrames.js +0 -29
  81. package/src/handlers/helpers/errorHandler.js +0 -49
  82. package/src/handlers/helpers/index.js +0 -138
  83. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  84. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  85. package/src/handlers/helpers/predictiveCache.js +0 -51
  86. package/src/handlers/helpers/projectAccess.js +0 -88
  87. package/src/handlers/helpers/responseUtil.js +0 -55
  88. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  89. package/src/handlers/mcp/mcpHandler.js +0 -569
  90. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  91. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  92. package/src/handlers/notifications/getPreferences.js +0 -84
  93. package/src/handlers/notifications/sendNotification.js +0 -170
  94. package/src/handlers/notifications/updatePreferences.js +0 -316
  95. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  96. package/src/handlers/patterns/patternUsagePost.js +0 -182
  97. package/src/handlers/patterns/patternViolationPost.js +0 -185
  98. package/src/handlers/projects/projectCreate.js +0 -248
  99. package/src/handlers/projects/projectDelete.js +0 -82
  100. package/src/handlers/projects/projectGet.js +0 -95
  101. package/src/handlers/projects/projectUpdate.js +0 -117
  102. package/src/handlers/reports/aiLeverage.js +0 -210
  103. package/src/handlers/reports/engineeringInvestment.js +0 -132
  104. package/src/handlers/reports/riskForecast.js +0 -206
  105. package/src/handlers/reports/standardsRoi.js +0 -254
  106. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  107. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  108. package/src/handlers/scheduled/generateAlerts.js +0 -135
  109. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  110. package/src/handlers/scheduled/refreshActivity.js +0 -21
  111. package/src/handlers/scheduled/scanCompliance.js +0 -334
  112. package/src/handlers/sessions/sessionEndPost.js +0 -180
  113. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  114. package/src/handlers/standards/catalogGet.js +0 -185
  115. package/src/handlers/standards/catalogSync.js +0 -120
  116. package/src/handlers/standards/discoveriesGet.js +0 -89
  117. package/src/handlers/standards/projectStandardsGet.js +0 -129
  118. package/src/handlers/standards/projectStandardsPut.js +0 -151
  119. package/src/handlers/standards/standardsAuditGet.js +0 -65
  120. package/src/handlers/standards/standardsParseUpload.js +0 -149
  121. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  122. package/src/handlers/standards/standardsTransition.js +0 -161
  123. package/src/handlers/stripe/addonManagePost.js +0 -240
  124. package/src/handlers/stripe/billingPortalPost.js +0 -93
  125. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  126. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  127. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  128. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  129. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  130. package/src/handlers/stripe/webhookPost.js +0 -482
  131. package/src/handlers/user/apiTokenCreate.js +0 -71
  132. package/src/handlers/user/apiTokenList.js +0 -64
  133. package/src/handlers/user/userSplashAck.js +0 -91
  134. package/src/handlers/user/userSplashGet.js +0 -211
  135. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  136. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  137. package/src/handlers/users/userEntitlementsGet.js +0 -89
  138. package/src/handlers/users/userGet.js +0 -118
  139. package/src/handlers/users/userProfilePut.js +0 -77
  140. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -0,0 +1,585 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MindMeld - Background Pattern Watcher
4
+ *
5
+ * Long-lived background process that monitors the JSONL session transcript
6
+ * and harvests patterns on resolution detection rather than a dumb timer.
7
+ *
8
+ * With 1M context windows, pre-compact may never fire. This watcher ensures
9
+ * patterns are harvested continuously throughout long sessions.
10
+ *
11
+ * Lifecycle:
12
+ * 1. SessionStart spawns watcher (or watcher detects breadcrumb update)
13
+ * 2. Polls transcript every 10s, buffers new content
14
+ * 3. Detects resolution signals (test runs, completions, task done)
15
+ * 4. Extracts patterns after 60s cooldown post-resolution
16
+ * 5. Survives /clear via breadcrumb transcript switch
17
+ * 6. Stops on: parent death, stop file, or 2h idle
18
+ *
19
+ * Args:
20
+ * argv[2] - project cwd
21
+ * argv[3] - parent PID (terminal process to monitor)
22
+ *
23
+ * @equilateral_ai/mindmeld v3.5.3
24
+ */
25
+
26
+ const path = require('path');
27
+ const fs = require('fs').promises;
28
+ const fsSync = require('fs');
29
+ const os = require('os');
30
+
31
+ process.title = 'mindmeld-watcher';
32
+
33
+ // Import harvest pipeline from pre-compact
34
+ let harvestPatterns = null;
35
+ try {
36
+ const preCompact = require('./pre-compact');
37
+ harvestPatterns = preCompact.harvestPatterns;
38
+ } catch (error) {
39
+ console.error(`[MindMeld:Watcher] Cannot load pre-compact module: ${error.message}`);
40
+ process.exit(0);
41
+ }
42
+
43
+ const PROJECT_CWD = process.argv[2];
44
+ const PARENT_PID = parseInt(process.argv[3], 10);
45
+
46
+ if (!PROJECT_CWD) {
47
+ console.error('[MindMeld:Watcher] No project cwd provided');
48
+ process.exit(0);
49
+ }
50
+
51
+ // ── Timing constants ──────────────────────────────────────────────────────────
52
+ const POLL_MS = 10 * 1000; // 10s transcript poll
53
+ const COOLDOWN_MS = 60 * 1000; // 60s after resolution before extracting
54
+ const IDLE_TIMEOUT_MS = 2 * 60 * 60 * 1000; // 2h no transcript activity → exit
55
+ const PARENT_CHECK_MS = 10 * 1000; // 10s parent liveness check
56
+ const FALLBACK_HARVEST_MS = 5 * 60 * 1000; // 5 min safety net if no resolution detected
57
+ const MIN_BUFFER_CHARS = 500; // skip trivial buffers
58
+ const MAX_DELTA_BYTES = 200 * 1024; // 200KB cap per read
59
+ const STARTUP_RETRY_MS = 10 * 1000;
60
+ const STARTUP_MAX_RETRIES = 12;
61
+
62
+ // ── Resolution signal patterns ────────────────────────────────────────────────
63
+ // Bash tool calls that indicate work completion
64
+ const RESOLUTION_COMMANDS = [
65
+ /npm\s+test/,
66
+ /npm\s+run\s+test/,
67
+ /jest/,
68
+ /pytest/,
69
+ /ruff\s+check/,
70
+ /eslint/,
71
+ /tsc\s+--noEmit/,
72
+ /go\s+test/,
73
+ /cargo\s+test/,
74
+ /make\s+test/,
75
+ /sam\s+build/,
76
+ /sam\s+deploy/,
77
+ /npm\s+run\s+build/,
78
+ /npm\s+run\s+deploy/,
79
+ ];
80
+
81
+ // Completion phrases in assistant messages
82
+ const COMPLETION_PHRASES = [
83
+ /all\s+(?:tests?\s+)?pass/i,
84
+ /successfully\s+(?:deployed|built|completed|created|updated)/i,
85
+ /changes?\s+(?:look|are)\s+good/i,
86
+ /(?:done|finished|completed)[.!]\s*$/im,
87
+ /ready\s+(?:to\s+)?(?:commit|deploy|merge|push|review)/i,
88
+ /implementation\s+is\s+(?:complete|done|ready)/i,
89
+ /that\s+(?:should\s+)?fix/i,
90
+ ];
91
+
92
+ // ── State ─────────────────────────────────────────────────────────────────────
93
+ let byteOffset = 0;
94
+ let jsonlPath = null;
95
+ let sessionId = null;
96
+ let buffer = ''; // accumulated unparsed transcript text
97
+ let pollHandle = null;
98
+ let parentCheckHandle = null;
99
+ let cooldownTimer = null;
100
+ let lastHarvestTime = Date.now();
101
+ let lastActivityTime = Date.now();
102
+ let resolutionDetected = false;
103
+
104
+ // ── Breadcrumb ────────────────────────────────────────────────────────────────
105
+
106
+ /**
107
+ * Path to the breadcrumb file written by session-start.
108
+ * Contains session_id and transcript_path, updated on /clear.
109
+ */
110
+ function breadcrumbPath() {
111
+ return path.join(PROJECT_CWD, '.mindmeld', 'current-session.json');
112
+ }
113
+
114
+ /**
115
+ * Read the breadcrumb to get current session info and transcript path.
116
+ */
117
+ async function readBreadcrumb() {
118
+ try {
119
+ const content = await fs.readFile(breadcrumbPath(), 'utf-8');
120
+ return JSON.parse(content);
121
+ } catch (err) {
122
+ return null;
123
+ }
124
+ }
125
+
126
+ // ── Transcript discovery ──────────────────────────────────────────────────────
127
+
128
+ function encodeProjectDir(cwd) {
129
+ return cwd.replace(/\//g, '-');
130
+ }
131
+
132
+ /**
133
+ * Find the active JSONL session file.
134
+ * Priority: breadcrumb transcript_path → most recently modified .jsonl
135
+ */
136
+ async function findSessionFile() {
137
+ // Check breadcrumb first
138
+ const crumb = await readBreadcrumb();
139
+ if (crumb?.transcriptPath) {
140
+ try {
141
+ await fs.access(crumb.transcriptPath);
142
+ sessionId = crumb.sessionId || sessionId;
143
+ return crumb.transcriptPath;
144
+ } catch (err) {
145
+ // Breadcrumb points to missing file, fall through
146
+ }
147
+ }
148
+
149
+ if (crumb?.sessionId) {
150
+ sessionId = crumb.sessionId;
151
+ }
152
+
153
+ // Fall back to most recently modified JSONL
154
+ const projectDir = path.join(os.homedir(), '.claude', 'projects', encodeProjectDir(PROJECT_CWD));
155
+ let files;
156
+ try {
157
+ files = await fs.readdir(projectDir);
158
+ } catch (err) {
159
+ return null;
160
+ }
161
+
162
+ const jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
163
+ if (jsonlFiles.length === 0) return null;
164
+
165
+ let newest = null;
166
+ let newestMtime = 0;
167
+ for (const file of jsonlFiles) {
168
+ const fullPath = path.join(projectDir, file);
169
+ try {
170
+ const stat = await fs.stat(fullPath);
171
+ if (stat.mtimeMs > newestMtime) {
172
+ newestMtime = stat.mtimeMs;
173
+ newest = fullPath;
174
+ }
175
+ } catch (err) {
176
+ // Skip
177
+ }
178
+ }
179
+
180
+ return newest;
181
+ }
182
+
183
+ // ── JSONL parsing ─────────────────────────────────────────────────────────────
184
+
185
+ /**
186
+ * Parse JSONL entries from raw text. Returns array of parsed entry objects
187
+ * for resolution detection, plus accumulated transcript text.
188
+ */
189
+ function parseJsonlEntries(rawText) {
190
+ const lines = rawText.split('\n').filter(l => l.trim());
191
+ const entries = [];
192
+ const textParts = [];
193
+
194
+ for (const line of lines) {
195
+ try {
196
+ const entry = JSON.parse(line);
197
+ entries.push(entry);
198
+
199
+ const content = entry.message?.content || entry.content;
200
+ if (!content) continue;
201
+
202
+ if (typeof content === 'string') {
203
+ textParts.push(content);
204
+ } else if (Array.isArray(content)) {
205
+ for (const block of content) {
206
+ if (block.type === 'text' && block.text) {
207
+ textParts.push(block.text);
208
+ }
209
+ // Also capture tool_use for resolution detection
210
+ if (block.type === 'tool_use' && block.input) {
211
+ const input = typeof block.input === 'string' ? block.input : JSON.stringify(block.input);
212
+ textParts.push(input);
213
+ }
214
+ }
215
+ }
216
+ } catch (e) {
217
+ // Skip partial lines
218
+ }
219
+ }
220
+
221
+ return { entries, transcript: textParts.join('\n\n') };
222
+ }
223
+
224
+ // ── Delta reading ─────────────────────────────────────────────────────────────
225
+
226
+ async function readDelta() {
227
+ if (!jsonlPath) return null;
228
+
229
+ let stat;
230
+ try {
231
+ stat = await fs.stat(jsonlPath);
232
+ } catch (err) {
233
+ return null;
234
+ }
235
+
236
+ if (stat.size <= byteOffset) return null;
237
+ lastActivityTime = Date.now();
238
+
239
+ const readSize = Math.min(stat.size - byteOffset, MAX_DELTA_BYTES);
240
+ const buf = Buffer.alloc(readSize);
241
+
242
+ const fh = await fs.open(jsonlPath, 'r');
243
+ try {
244
+ await fh.read(buf, 0, readSize, byteOffset);
245
+ } finally {
246
+ await fh.close();
247
+ }
248
+
249
+ const raw = buf.toString('utf-8');
250
+
251
+ // Skip partial first line if mid-file
252
+ let usable = raw;
253
+ if (byteOffset > 0) {
254
+ const firstNewline = raw.indexOf('\n');
255
+ if (firstNewline > 0) {
256
+ usable = raw.substring(firstNewline + 1);
257
+ }
258
+ }
259
+
260
+ // Advance to end of last complete line
261
+ const lastNewline = raw.lastIndexOf('\n');
262
+ if (lastNewline >= 0) {
263
+ byteOffset += lastNewline + 1;
264
+ } else {
265
+ return null;
266
+ }
267
+
268
+ return usable;
269
+ }
270
+
271
+ // ── Resolution detection ──────────────────────────────────────────────────────
272
+
273
+ /**
274
+ * Scan entries for signals that the agent just finished a unit of work.
275
+ */
276
+ function detectResolution(entries, transcriptText) {
277
+ // Check for resolution command patterns in tool calls
278
+ for (const entry of entries) {
279
+ const content = entry.message?.content || entry.content;
280
+ if (!Array.isArray(content)) continue;
281
+
282
+ for (const block of content) {
283
+ if (block.type === 'tool_use' && block.name === 'Bash') {
284
+ const cmd = block.input?.command || '';
285
+ for (const pattern of RESOLUTION_COMMANDS) {
286
+ if (pattern.test(cmd)) {
287
+ return { type: 'command', signal: cmd.substring(0, 80) };
288
+ }
289
+ }
290
+ }
291
+ // TaskUpdate with status "completed"
292
+ if (block.type === 'tool_use' && block.name === 'TaskUpdate') {
293
+ const status = block.input?.status;
294
+ if (status === 'completed') {
295
+ return { type: 'task_complete', signal: block.input?.id || 'unknown' };
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ // Check for completion phrases in recent text
302
+ for (const phrase of COMPLETION_PHRASES) {
303
+ if (phrase.test(transcriptText)) {
304
+ return { type: 'phrase', signal: transcriptText.match(phrase)?.[0] || '' };
305
+ }
306
+ }
307
+
308
+ return null;
309
+ }
310
+
311
+ // ── Harvest ───────────────────────────────────────────────────────────────────
312
+
313
+ async function extract() {
314
+ if (!buffer || buffer.length < MIN_BUFFER_CHARS) {
315
+ buffer = '';
316
+ resolutionDetected = false;
317
+ return;
318
+ }
319
+
320
+ const harvestText = buffer.length > MAX_DELTA_BYTES
321
+ ? buffer.substring(buffer.length - MAX_DELTA_BYTES)
322
+ : buffer;
323
+
324
+ console.error(`[MindMeld:Watcher] Extracting patterns (${harvestText.length} chars)`);
325
+
326
+ try {
327
+ const result = await harvestPatterns({
328
+ sessionId: sessionId || 'unknown',
329
+ transcript: harvestText,
330
+ source: 'watcher'
331
+ });
332
+
333
+ if (result && !result.skipped) {
334
+ console.error(`[MindMeld:Watcher] Harvest: ${result.patternsDetected || 0} patterns, ${result.violations || 0} violations, ${result.reinforced || 0} reinforced`);
335
+ }
336
+ } catch (err) {
337
+ console.error(`[MindMeld:Watcher] Harvest error (non-fatal): ${err.message}`);
338
+ }
339
+
340
+ buffer = '';
341
+ resolutionDetected = false;
342
+ lastHarvestTime = Date.now();
343
+ }
344
+
345
+ // ── Transcript switch (clear detection) ───────────────────────────────────────
346
+
347
+ /**
348
+ * Check if the breadcrumb has been updated with a new transcript path.
349
+ * This happens when session-start fires again after /clear.
350
+ */
351
+ async function checkTranscriptSwitch() {
352
+ const crumb = await readBreadcrumb();
353
+ if (!crumb?.transcriptPath) return false;
354
+
355
+ // No current path yet — initial setup, not a switch
356
+ if (!jsonlPath) return false;
357
+
358
+ // Normalize for comparison
359
+ const currentBasename = path.basename(jsonlPath);
360
+ const crumbBasename = path.basename(crumb.transcriptPath);
361
+
362
+ if (currentBasename !== crumbBasename) {
363
+ console.error(`[MindMeld:Watcher] Transcript switch detected: ${currentBasename} → ${crumbBasename}`);
364
+
365
+ // Flush remaining buffer from old transcript
366
+ if (buffer.length >= MIN_BUFFER_CHARS) {
367
+ console.error('[MindMeld:Watcher] Flushing buffer from previous transcript');
368
+ await extract();
369
+ }
370
+
371
+ // Reset state for new transcript
372
+ try {
373
+ await fs.access(crumb.transcriptPath);
374
+ jsonlPath = crumb.transcriptPath;
375
+ sessionId = crumb.sessionId || sessionId;
376
+ byteOffset = 0; // Read from start of new transcript
377
+ buffer = '';
378
+ resolutionDetected = false;
379
+
380
+ // Set offset to current size if we don't want pre-existing content
381
+ const stat = await fs.stat(jsonlPath);
382
+ byteOffset = stat.size;
383
+
384
+ console.error(`[MindMeld:Watcher] Now watching: ${path.basename(jsonlPath)}`);
385
+ return true;
386
+ } catch (err) {
387
+ console.error(`[MindMeld:Watcher] New transcript not accessible: ${err.message}`);
388
+ return false;
389
+ }
390
+ }
391
+
392
+ return false;
393
+ }
394
+
395
+ // ── Parent process liveness ───────────────────────────────────────────────────
396
+
397
+ function isParentAlive() {
398
+ if (!PARENT_PID || isNaN(PARENT_PID)) return true; // Can't check, assume alive
399
+ try {
400
+ process.kill(PARENT_PID, 0); // Signal 0 = existence check
401
+ return true;
402
+ } catch (err) {
403
+ return false; // ESRCH = process gone
404
+ }
405
+ }
406
+
407
+ // ── Stop file detection ───────────────────────────────────────────────────────
408
+
409
+ function stopFilePath() {
410
+ return path.join(PROJECT_CWD, '.mindmeld', 'watcher.stop');
411
+ }
412
+
413
+ async function checkStopFile() {
414
+ try {
415
+ await fs.access(stopFilePath());
416
+ await fs.unlink(stopFilePath()).catch(() => {});
417
+ return true;
418
+ } catch (err) {
419
+ return false;
420
+ }
421
+ }
422
+
423
+ // ── PID file ──────────────────────────────────────────────────────────────────
424
+
425
+ async function writePidFile() {
426
+ try {
427
+ const pidPath = path.join(PROJECT_CWD, '.mindmeld', 'watcher.pid');
428
+ await fs.writeFile(pidPath, String(process.pid));
429
+ } catch (err) {
430
+ console.error(`[MindMeld:Watcher] Could not write PID file: ${err.message}`);
431
+ }
432
+ }
433
+
434
+ // ── Poll tick ─────────────────────────────────────────────────────────────────
435
+
436
+ async function poll() {
437
+ try {
438
+ // Check for transcript switch (breadcrumb updated by session-start after /clear)
439
+ await checkTranscriptSwitch();
440
+
441
+ // Read new lines from transcript
442
+ const delta = await readDelta();
443
+ if (delta) {
444
+ const { entries, transcript } = parseJsonlEntries(delta);
445
+ if (transcript) {
446
+ buffer += transcript + '\n\n';
447
+ }
448
+
449
+ // Check for resolution signals in new entries
450
+ if (!resolutionDetected) {
451
+ const resolution = detectResolution(entries, transcript);
452
+ if (resolution) {
453
+ resolutionDetected = true;
454
+ console.error(`[MindMeld:Watcher] Resolution detected (${resolution.type}): ${resolution.signal}`);
455
+
456
+ // Start cooldown — extract after 60s
457
+ if (cooldownTimer) clearTimeout(cooldownTimer);
458
+ cooldownTimer = setTimeout(() => {
459
+ cooldownTimer = null;
460
+ extract();
461
+ }, COOLDOWN_MS);
462
+ }
463
+ }
464
+ }
465
+
466
+ // Fallback: if no resolution detected but buffer is accumulating, harvest on timer
467
+ if (!resolutionDetected && !cooldownTimer && buffer.length >= MIN_BUFFER_CHARS) {
468
+ const sinceLastHarvest = Date.now() - lastHarvestTime;
469
+ if (sinceLastHarvest >= FALLBACK_HARVEST_MS) {
470
+ console.error('[MindMeld:Watcher] Fallback harvest (no resolution signal)');
471
+ await extract();
472
+ }
473
+ }
474
+ } catch (err) {
475
+ console.error(`[MindMeld:Watcher] Poll error (non-fatal): ${err.message}`);
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Parent liveness + idle + stop file checks.
481
+ */
482
+ async function livenessCheck() {
483
+ // Parent process died (terminal closed)
484
+ if (!isParentAlive()) {
485
+ console.error('[MindMeld:Watcher] Parent process gone, extracting and exiting');
486
+ await extract();
487
+ cleanup();
488
+ return;
489
+ }
490
+
491
+ // Stop file written by session-end
492
+ if (await checkStopFile()) {
493
+ console.error('[MindMeld:Watcher] Stop file detected, extracting and exiting');
494
+ await extract();
495
+ cleanup();
496
+ return;
497
+ }
498
+
499
+ // Idle timeout
500
+ if (Date.now() - lastActivityTime > IDLE_TIMEOUT_MS) {
501
+ console.error('[MindMeld:Watcher] Idle for 2h, extracting and exiting');
502
+ await extract();
503
+ cleanup();
504
+ return;
505
+ }
506
+ }
507
+
508
+ // ── Cleanup ───────────────────────────────────────────────────────────────────
509
+
510
+ function cleanup() {
511
+ if (pollHandle) {
512
+ clearInterval(pollHandle);
513
+ pollHandle = null;
514
+ }
515
+ if (parentCheckHandle) {
516
+ clearInterval(parentCheckHandle);
517
+ parentCheckHandle = null;
518
+ }
519
+ if (cooldownTimer) {
520
+ clearTimeout(cooldownTimer);
521
+ cooldownTimer = null;
522
+ }
523
+
524
+ // Remove PID file
525
+ try {
526
+ fsSync.unlinkSync(path.join(PROJECT_CWD, '.mindmeld', 'watcher.pid'));
527
+ } catch (err) {
528
+ // Already gone
529
+ }
530
+
531
+ process.exit(0);
532
+ }
533
+
534
+ // ── Main ──────────────────────────────────────────────────────────────────────
535
+
536
+ async function main() {
537
+ console.error(`[MindMeld:Watcher] Starting for ${path.basename(PROJECT_CWD)} (parent PID: ${PARENT_PID || 'unknown'})`);
538
+
539
+ await writePidFile();
540
+
541
+ // Find the JSONL file, retrying if it doesn't exist yet
542
+ for (let attempt = 0; attempt < STARTUP_MAX_RETRIES; attempt++) {
543
+ jsonlPath = await findSessionFile();
544
+ if (jsonlPath) break;
545
+ await new Promise(r => setTimeout(r, STARTUP_RETRY_MS));
546
+ }
547
+
548
+ if (!jsonlPath) {
549
+ console.error('[MindMeld:Watcher] Could not find session JSONL file after retries, exiting');
550
+ cleanup();
551
+ return;
552
+ }
553
+
554
+ // Read session ID from breadcrumb if not set
555
+ if (!sessionId) {
556
+ const crumb = await readBreadcrumb();
557
+ sessionId = crumb?.sessionId || 'unknown';
558
+ }
559
+
560
+ console.error(`[MindMeld:Watcher] Watching: ${path.basename(jsonlPath)} (session: ${sessionId})`);
561
+
562
+ // Start offset at current file size — only capture new activity
563
+ try {
564
+ const stat = await fs.stat(jsonlPath);
565
+ byteOffset = stat.size;
566
+ } catch (err) {
567
+ byteOffset = 0;
568
+ }
569
+
570
+ // Start polling
571
+ pollHandle = setInterval(poll, POLL_MS);
572
+ parentCheckHandle = setInterval(livenessCheck, PARENT_CHECK_MS);
573
+ }
574
+
575
+ process.on('SIGTERM', async () => {
576
+ console.error('[MindMeld:Watcher] SIGTERM received, extracting and exiting');
577
+ await extract();
578
+ cleanup();
579
+ });
580
+ process.on('SIGINT', cleanup);
581
+
582
+ main().catch(err => {
583
+ console.error(`[MindMeld:Watcher] Fatal: ${err.message}`);
584
+ cleanup();
585
+ });
package/package.json CHANGED
@@ -1,20 +1,22 @@
1
1
  {
2
2
  "name": "@equilateral_ai/mindmeld",
3
- "version": "3.5.2",
3
+ "version": "4.0.1",
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": {
7
- "mindmeld": "./scripts/init-project.js"
7
+ "mindmeld": "scripts/init-project.js"
8
8
  },
9
9
  "files": [
10
- "src/",
10
+ "src/index.js",
11
+ "src/core/AuthManager.js",
12
+ "src/core/PatternValidator.js",
13
+ "src/core/LLMPatternDetector.js",
14
+ "src/client/dbShim.js",
15
+ "src/handlers/helpers/dbOperations.js",
16
+ "src/utils/piiMask.js",
11
17
  "hooks/",
12
18
  "scripts/init-project.js",
13
19
  "scripts/auth-login.js",
14
- "scripts/inject.js",
15
- "scripts/harvest.js",
16
- "scripts/repo-analyzer.js",
17
- "scripts/mcp-bridge.js",
18
20
  "README.md"
19
21
  ],
20
22
  "publishConfig": {
@@ -43,7 +45,9 @@
43
45
  "test:benchmark": "node scripts/test-claude-hooks.js --benchmark",
44
46
  "test:smoke": "jest --selectProjects smoke --testTimeout=15000",
45
47
  "test:e2e": "cd e2e && npx playwright test",
46
- "test:e2e:admin": "cd frontend/admin && npx playwright test"
48
+ "test:e2e:admin": "cd frontend/admin && npx playwright test",
49
+ "prepack": "mkdir -p src/handlers/helpers && cp src/client/dbShim.js src/handlers/helpers/dbOperations.js",
50
+ "postpack": "git checkout -- src/handlers/helpers/dbOperations.js"
47
51
  },
48
52
  "claudeCode": {
49
53
  "hooks": {
@@ -79,15 +83,17 @@
79
83
  },
80
84
  "homepage": "https://mindmeld.dev",
81
85
  "dependencies": {
82
- "@aws-sdk/client-bedrock-runtime": "^3.460.0",
83
- "@aws-sdk/client-ssm": "^3.985.0",
84
- "js-yaml": "^4.1.1",
85
- "pg": "^8.18.0"
86
+ "js-yaml": "^4.1.1"
87
+ },
88
+ "optionalDependencies": {
89
+ "@aws-sdk/client-bedrock-runtime": "^3.460.0"
86
90
  },
87
91
  "devDependencies": {
88
92
  "@aws-sdk/client-s3": "^3.460.0",
89
93
  "@aws-sdk/client-ses": "^3.985.0",
90
- "jest": "^29.7.0"
94
+ "dotenv": "^17.4.2",
95
+ "jest": "^29.7.0",
96
+ "pg": "^8.18.0"
91
97
  },
92
98
  "engines": {
93
99
  "node": ">=18.0.0"
@@ -702,14 +702,9 @@ if (args.command === 'init') {
702
702
  process.exit(1);
703
703
  });
704
704
  } else if (args.command === 'inject') {
705
- const { inject, showInjectHelp } = require('./inject');
706
- if (args.help) { showInjectHelp(); process.exit(0); }
707
- inject({ format: args.format, path: args.projectPath })
708
- .then(() => process.exit(0))
709
- .catch(error => {
710
- console.error('\n❌ Error:', error.message);
711
- process.exit(1);
712
- });
705
+ console.error('The inject subcommand has been removed in v4.0.0.');
706
+ console.error('Standards injection is now handled automatically by the session-start hook via the MindMeld API.');
707
+ process.exit(1);
713
708
  } else if (args.command === 'harvest') {
714
709
  const { harvest, showHarvestHelp } = require('./harvest');
715
710
  if (args.help) { showHarvestHelp(); process.exit(0); }
@@ -741,8 +736,9 @@ if (args.command === 'init') {
741
736
  process.exit(1);
742
737
  });
743
738
  } else if (args.command === 'standards') {
744
- // Delegate to standards.js
745
- require('./standards');
739
+ console.error('The standards subcommand has been removed in v4.0.0.');
740
+ console.error('Configure standards at https://app.mindmeld.dev');
741
+ process.exit(1);
746
742
  } else if (args.command === 'open') {
747
743
  openWebApp(args.projectPath || 'dashboard')
748
744
  .then(() => process.exit(0))
@@ -751,19 +747,9 @@ if (args.command === 'init') {
751
747
  process.exit(1);
752
748
  });
753
749
  } else if (args.command === 'mcp') {
754
- const { startBridge, resolveToken, showMcpHelp } = require('./mcp-bridge');
755
- if (args.help) { showMcpHelp(); process.exit(0); }
756
- const token = resolveToken(args.token);
757
- if (!token) {
758
- console.error('\nMindMeld MCP bridge requires an API token.\n');
759
- console.error('Set one of:');
760
- console.error(' 1. MINDMELD_TOKEN environment variable');
761
- console.error(' 2. --token CLI argument');
762
- console.error(' 3. Save to ~/.mindmeld/api-token\n');
763
- console.error('Create a token at: https://app.mindmeld.dev/api-tokens\n');
764
- process.exit(1);
765
- }
766
- startBridge(token);
750
+ console.error('The mcp subcommand has been removed in v4.0.0.');
751
+ console.error('Use the MindMeld MCP server directly via Claude Code settings.');
752
+ process.exit(1);
767
753
  } else {
768
754
  console.error(`Unknown command: ${args.command}`);
769
755
  console.error('Run "mindmeld --help" for usage.');