@everstateai/mcp 1.3.12 → 1.3.13

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.
@@ -95,6 +95,59 @@ export async function checkAndApplyUpdates(): Promise<UpdateCheckResult> {
95
95
  result.needsUpdate = true;
96
96
  }
97
97
 
98
+ // Install gotcha-check hook if not present
99
+ const gotchaCheckPath = `${hooksDir}/gotcha-check.js`;
100
+ if (!fs.existsSync(gotchaCheckPath)) {
101
+ try {
102
+ const { getGotchaCheckHook } = await import('./hooks/templates.js');
103
+ fs.mkdirSync(hooksDir, { recursive: true });
104
+ fs.writeFileSync(gotchaCheckPath, getGotchaCheckHook());
105
+ fs.chmodSync(gotchaCheckPath, '755');
106
+ result.updates.push('gotcha-check.js installed');
107
+ result.needsUpdate = true;
108
+
109
+ // Register PreToolUse hook for Edit and Write
110
+ const gotchaRegistered = await ensureGotchaHookRegistration(gotchaCheckPath);
111
+ if (gotchaRegistered) {
112
+ result.updates.push('PreToolUse gotcha hook registered');
113
+ }
114
+ } catch (error) {
115
+ result.errors.push(`Failed to install gotcha-check.js: ${String(error)}`);
116
+ }
117
+ }
118
+
119
+ // Install implicit-progress hook if not present
120
+ const implicitProgressPath = `${hooksDir}/implicit-progress.js`;
121
+ if (!fs.existsSync(implicitProgressPath)) {
122
+ try {
123
+ const { getImplicitProgressHook } = await import('./hooks/templates.js');
124
+ fs.mkdirSync(hooksDir, { recursive: true });
125
+ fs.writeFileSync(implicitProgressPath, getImplicitProgressHook());
126
+ fs.chmodSync(implicitProgressPath, '755');
127
+ result.updates.push('implicit-progress.js installed');
128
+ result.needsUpdate = true;
129
+
130
+ // Register PostToolUse hook for Edit, Write, and Bash
131
+ const progressRegistered = await ensureImplicitProgressRegistration(implicitProgressPath);
132
+ if (progressRegistered) {
133
+ result.updates.push('PostToolUse implicit-progress hook registered');
134
+ }
135
+ } catch (error) {
136
+ result.errors.push(`Failed to install implicit-progress.js: ${String(error)}`);
137
+ }
138
+ }
139
+
140
+ // Migrate Stop→SessionEnd and ensure PreCompact/permissions in known projects
141
+ try {
142
+ const migrateResult = await migrateProjectSettings(versionFile);
143
+ if (migrateResult.length > 0) {
144
+ result.updates.push(...migrateResult);
145
+ result.needsUpdate = true;
146
+ }
147
+ } catch (error) {
148
+ result.errors.push(`Project migration failed: ${String(error)}`);
149
+ }
150
+
98
151
  // Update MCP proxy version in version file
99
152
  const currentVersion = getMcpProxyVersion();
100
153
  if (versionFile.components.mcpProxy !== currentVersion) {
@@ -326,3 +379,213 @@ async function ensureHookRegistration(syncTodosPath: string): Promise<boolean> {
326
379
  }
327
380
  }
328
381
 
382
+ /**
383
+ * Ensure PreToolUse gotcha hook is registered in ~/.claude.json for Edit and Write
384
+ */
385
+ async function ensureGotchaHookRegistration(gotchaCheckPath: string): Promise<boolean> {
386
+ const configPath = getClaudeConfigPath();
387
+
388
+ if (!fs.existsSync(configPath)) {
389
+ return false;
390
+ }
391
+
392
+ try {
393
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
394
+
395
+ if (!config.hooks) {
396
+ config.hooks = {};
397
+ }
398
+ if (!config.hooks.PreToolUse) {
399
+ config.hooks.PreToolUse = [];
400
+ }
401
+
402
+ const preToolUse = config.hooks.PreToolUse as Array<{
403
+ matcher: string;
404
+ hooks: Array<{ type?: string; command?: string; timeout?: number }>;
405
+ }>;
406
+
407
+ const expectedCommand = `node ${gotchaCheckPath}`;
408
+ let updated = false;
409
+
410
+ for (const toolName of ['Edit', 'Write']) {
411
+ let entry = preToolUse.find((e) => e.matcher === toolName);
412
+ if (!entry) {
413
+ entry = { matcher: toolName, hooks: [] };
414
+ preToolUse.push(entry);
415
+ }
416
+
417
+ const hasGotchaHook = entry.hooks.some((h) => h.command?.includes('gotcha-check'));
418
+ if (!hasGotchaHook) {
419
+ entry.hooks.push({
420
+ type: 'command',
421
+ command: expectedCommand,
422
+ timeout: 5,
423
+ });
424
+ updated = true;
425
+ }
426
+ }
427
+
428
+ if (updated) {
429
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
430
+ }
431
+ return updated;
432
+ } catch {
433
+ return false;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Ensure PostToolUse implicit-progress hook is registered for Edit, Write, Bash
439
+ */
440
+ async function ensureImplicitProgressRegistration(hookPath: string): Promise<boolean> {
441
+ const configPath = getClaudeConfigPath();
442
+
443
+ if (!fs.existsSync(configPath)) {
444
+ return false;
445
+ }
446
+
447
+ try {
448
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
449
+
450
+ if (!config.hooks) {
451
+ config.hooks = {};
452
+ }
453
+ if (!config.hooks.PostToolUse) {
454
+ config.hooks.PostToolUse = [];
455
+ }
456
+
457
+ const postToolUse = config.hooks.PostToolUse as Array<{
458
+ matcher: string;
459
+ hooks: Array<{ type?: string; command?: string; timeout?: number }>;
460
+ }>;
461
+
462
+ const expectedCommand = `node ${hookPath}`;
463
+ let updated = false;
464
+
465
+ for (const toolName of ['Edit', 'Write', 'Bash']) {
466
+ let entry = postToolUse.find((e) => e.matcher === toolName);
467
+ if (!entry) {
468
+ entry = { matcher: toolName, hooks: [] };
469
+ postToolUse.push(entry);
470
+ }
471
+
472
+ const hasHook = entry.hooks.some((h) => h.command?.includes('implicit-progress'));
473
+ if (!hasHook) {
474
+ entry.hooks.push({
475
+ type: 'command',
476
+ command: expectedCommand,
477
+ timeout: 5,
478
+ });
479
+ updated = true;
480
+ }
481
+ }
482
+
483
+ if (updated) {
484
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
485
+ }
486
+ return updated;
487
+ } catch {
488
+ return false;
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Migrate project-level settings in known projects:
494
+ * 1. Move Stop hooks → SessionEnd (bug fix from older installs)
495
+ * 2. Ensure PreCompact hook is registered
496
+ * 3. Ensure mcp__everstate__* permission is present
497
+ */
498
+ async function migrateProjectSettings(
499
+ versionFile: ReturnType<typeof readVersionFile>
500
+ ): Promise<string[]> {
501
+ const updates: string[] = [];
502
+
503
+ if (!versionFile?.projects) return updates;
504
+
505
+ for (const [projectId, projectInfo] of Object.entries(versionFile.projects)) {
506
+ const projectDir = (projectInfo as any).projectDir;
507
+ if (!projectDir || !fs.existsSync(projectDir)) continue;
508
+
509
+ const settingsPath = `${projectDir}/.claude/settings.local.json`;
510
+ if (!fs.existsSync(settingsPath)) continue;
511
+
512
+ try {
513
+ const content = fs.readFileSync(settingsPath, 'utf8');
514
+ const settings = JSON.parse(content);
515
+ if (!settings.hooks) continue;
516
+
517
+ let modified = false;
518
+
519
+ // 1. Migrate Stop → SessionEnd for everstate hooks
520
+ if (settings.hooks.Stop && Array.isArray(settings.hooks.Stop)) {
521
+ const everstateEntries = settings.hooks.Stop.filter((entry: any) =>
522
+ entry.hooks?.some((h: any) => h.command?.includes('everstate'))
523
+ );
524
+
525
+ if (everstateEntries.length > 0) {
526
+ settings.hooks.Stop = settings.hooks.Stop.filter((entry: any) =>
527
+ !entry.hooks?.some((h: any) => h.command?.includes('everstate'))
528
+ );
529
+
530
+ if (!settings.hooks.SessionEnd) settings.hooks.SessionEnd = [];
531
+ for (const entry of everstateEntries) {
532
+ for (const hook of (entry.hooks || [])) {
533
+ if (hook.command) {
534
+ hook.command = hook.command
535
+ .replace('session-stop.sh', 'session-end.sh')
536
+ .replace('everstate-session-stop', 'everstate-session-end');
537
+ }
538
+ }
539
+ const alreadyExists = settings.hooks.SessionEnd.some((e: any) =>
540
+ e.hooks?.some((h: any) => h.command?.includes('everstate'))
541
+ );
542
+ if (!alreadyExists) {
543
+ settings.hooks.SessionEnd.push(entry);
544
+ }
545
+ }
546
+
547
+ modified = true;
548
+ updates.push(`${projectId}: Migrated Stop→SessionEnd hooks`);
549
+ }
550
+ }
551
+
552
+ // 2. Ensure PreCompact hook is registered
553
+ if (!settings.hooks.PreCompact || !Array.isArray(settings.hooks.PreCompact)) {
554
+ settings.hooks.PreCompact = [];
555
+ }
556
+ const hasPreCompact = settings.hooks.PreCompact.some((entry: any) =>
557
+ entry.hooks?.some((h: any) => h.command?.includes('pre-compact'))
558
+ );
559
+ if (!hasPreCompact) {
560
+ const preCompactPath = `${projectDir}/.claude/hooks/everstate-pre-compact.sh`;
561
+ if (fs.existsSync(preCompactPath)) {
562
+ settings.hooks.PreCompact.push({
563
+ matcher: '*',
564
+ hooks: [{ type: 'command', command: preCompactPath }],
565
+ });
566
+ modified = true;
567
+ updates.push(`${projectId}: Registered PreCompact hook`);
568
+ }
569
+ }
570
+
571
+ // 3. Ensure mcp__everstate__* permission
572
+ if (!settings.permissions) settings.permissions = {};
573
+ if (!settings.permissions.allow) settings.permissions.allow = [];
574
+ const everstatePermission = 'mcp__everstate__*';
575
+ if (!settings.permissions.allow.includes(everstatePermission)) {
576
+ settings.permissions.allow.push(everstatePermission);
577
+ modified = true;
578
+ updates.push(`${projectId}: Added mcp__everstate__* permission`);
579
+ }
580
+
581
+ if (modified) {
582
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
583
+ }
584
+ } catch {
585
+ // Skip projects with invalid settings
586
+ }
587
+ }
588
+
589
+ return updates;
590
+ }
591
+