memorix 0.2.2 → 0.3.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.
package/dist/cli/index.js CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
4
10
  var __esm = (fn, res) => function __init() {
5
11
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
12
  };
@@ -12,9 +18,13 @@ var __export = (target, all) => {
12
18
  // node_modules/tsup/assets/esm_shims.js
13
19
  import path from "path";
14
20
  import { fileURLToPath } from "url";
21
+ var getFilename, getDirname, __dirname;
15
22
  var init_esm_shims = __esm({
16
23
  "node_modules/tsup/assets/esm_shims.js"() {
17
24
  "use strict";
25
+ getFilename = () => fileURLToPath(import.meta.url);
26
+ getDirname = () => path.dirname(getFilename());
27
+ __dirname = /* @__PURE__ */ getDirname();
18
28
  }
19
29
  });
20
30
 
@@ -2236,8 +2246,9 @@ var init_applier = __esm({
2236
2246
  });
2237
2247
 
2238
2248
  // src/workspace/engine.ts
2239
- import { readFileSync, readdirSync, existsSync as existsSync3 } from "fs";
2249
+ import { readFileSync, readdirSync, existsSync as existsSync3, cpSync, mkdirSync as mkdirSync2 } from "fs";
2240
2250
  import { join as join6 } from "path";
2251
+ import { homedir as homedir5 } from "os";
2241
2252
  var WorkspaceSyncEngine;
2242
2253
  var init_engine2 = __esm({
2243
2254
  "src/workspace/engine.ts"() {
@@ -2251,7 +2262,7 @@ var init_engine2 = __esm({
2251
2262
  init_syncer();
2252
2263
  init_sanitizer();
2253
2264
  init_applier();
2254
- WorkspaceSyncEngine = class {
2265
+ WorkspaceSyncEngine = class _WorkspaceSyncEngine {
2255
2266
  constructor(projectRoot) {
2256
2267
  this.projectRoot = projectRoot;
2257
2268
  this.adapters = /* @__PURE__ */ new Map([
@@ -2300,7 +2311,8 @@ var init_engine2 = __esm({
2300
2311
  rulesCount = rules.length;
2301
2312
  } catch {
2302
2313
  }
2303
- return { mcpConfigs, workflows, rulesCount };
2314
+ const skills = this.scanSkills();
2315
+ return { mcpConfigs, workflows, rulesCount, skills };
2304
2316
  }
2305
2317
  /**
2306
2318
  * Migrate workspace configs to a target agent format.
@@ -2310,7 +2322,8 @@ var init_engine2 = __esm({
2310
2322
  const result = {
2311
2323
  mcpServers: { scanned: [], generated: [] },
2312
2324
  workflows: { scanned: [], generated: [] },
2313
- rules: { scanned: 0, generated: 0 }
2325
+ rules: { scanned: 0, generated: 0 },
2326
+ skills: { scanned: [], copied: [] }
2314
2327
  };
2315
2328
  const allServers = /* @__PURE__ */ new Map();
2316
2329
  for (const servers of Object.values(scan.mcpConfigs)) {
@@ -2347,9 +2360,85 @@ var init_engine2 = __esm({
2347
2360
  }
2348
2361
  } catch {
2349
2362
  }
2363
+ result.skills.scanned = scan.skills;
2350
2364
  return result;
2351
2365
  }
2352
2366
  // ---- Private helpers ----
2367
+ /** Skills directories per agent */
2368
+ static SKILLS_DIRS = {
2369
+ codex: [".codex/skills", ".agents/skills"],
2370
+ cursor: [".cursor/skills", ".cursor/skills-cursor"],
2371
+ windsurf: [".windsurf/skills"],
2372
+ "claude-code": [".claude/skills"]
2373
+ };
2374
+ /** Get the target skills directory for an agent */
2375
+ getTargetSkillsDir(target) {
2376
+ const dirs = _WorkspaceSyncEngine.SKILLS_DIRS[target];
2377
+ return join6(this.projectRoot, dirs[0]);
2378
+ }
2379
+ /**
2380
+ * Scan all agent skills directories and collect unique skills.
2381
+ */
2382
+ scanSkills() {
2383
+ const skills = [];
2384
+ const seen = /* @__PURE__ */ new Set();
2385
+ const home = homedir5();
2386
+ for (const [agent, dirs] of Object.entries(_WorkspaceSyncEngine.SKILLS_DIRS)) {
2387
+ for (const dir of dirs) {
2388
+ const paths = [
2389
+ join6(this.projectRoot, dir),
2390
+ join6(home, dir)
2391
+ ];
2392
+ for (const skillsRoot of paths) {
2393
+ if (!existsSync3(skillsRoot)) continue;
2394
+ try {
2395
+ const entries = readdirSync(skillsRoot, { withFileTypes: true });
2396
+ for (const entry of entries) {
2397
+ if (!entry.isDirectory()) continue;
2398
+ if (seen.has(entry.name)) continue;
2399
+ const skillMd = join6(skillsRoot, entry.name, "SKILL.md");
2400
+ if (!existsSync3(skillMd)) continue;
2401
+ let description = "";
2402
+ try {
2403
+ const content = readFileSync(skillMd, "utf-8");
2404
+ const match = content.match(/^---[\s\S]*?description:\s*["']?(.+?)["']?\s*$/m);
2405
+ if (match) description = match[1];
2406
+ } catch {
2407
+ }
2408
+ seen.add(entry.name);
2409
+ skills.push({
2410
+ name: entry.name,
2411
+ description,
2412
+ sourcePath: join6(skillsRoot, entry.name),
2413
+ sourceAgent: agent
2414
+ });
2415
+ }
2416
+ } catch {
2417
+ }
2418
+ }
2419
+ }
2420
+ }
2421
+ return skills;
2422
+ }
2423
+ /**
2424
+ * Copy skills to a target agent's skills directory.
2425
+ * Returns list of copied skill names.
2426
+ */
2427
+ copySkills(skills, target) {
2428
+ const targetDir = this.getTargetSkillsDir(target);
2429
+ const copied = [];
2430
+ for (const skill of skills) {
2431
+ const dest = join6(targetDir, skill.name);
2432
+ if (existsSync3(dest)) continue;
2433
+ try {
2434
+ mkdirSync2(targetDir, { recursive: true });
2435
+ cpSync(skill.sourcePath, dest, { recursive: true });
2436
+ copied.push(skill.name);
2437
+ } catch {
2438
+ }
2439
+ }
2440
+ return copied;
2441
+ }
2353
2442
  scanWorkflows() {
2354
2443
  const workflows = [];
2355
2444
  const wfDir = join6(this.projectRoot, ".windsurf", "workflows");
@@ -2384,12 +2473,23 @@ var init_engine2 = __esm({
2384
2473
  ...syncResult.workflows.generated
2385
2474
  ];
2386
2475
  const applyResult = await applier.apply(filesToWrite);
2476
+ let copiedSkills = [];
2477
+ if (syncResult.skills.scanned.length > 0) {
2478
+ copiedSkills = this.copySkills(syncResult.skills.scanned, target);
2479
+ }
2387
2480
  const lines = [];
2388
2481
  if (applyResult.success) {
2389
2482
  lines.push(`\u2705 Applied ${applyResult.filesWritten.length} file(s) for ${target}`);
2390
2483
  for (const f of applyResult.filesWritten) {
2391
2484
  lines.push(` \u2192 ${f}`);
2392
2485
  }
2486
+ if (copiedSkills.length > 0) {
2487
+ lines.push(`
2488
+ \u{1F9E9} Copied ${copiedSkills.length} skill(s):`);
2489
+ for (const sk of copiedSkills) {
2490
+ lines.push(` \u2192 ${sk}`);
2491
+ }
2492
+ }
2393
2493
  if (applyResult.backups.length > 0) {
2394
2494
  lines.push(`
2395
2495
  \u{1F4E6} Backups created (${applyResult.backups.length}):`);
@@ -2427,143 +2527,508 @@ var init_engine2 = __esm({
2427
2527
  }
2428
2528
  });
2429
2529
 
2430
- // src/memory/retention.ts
2431
- var retention_exports = {};
2432
- __export(retention_exports, {
2433
- calculateRelevance: () => calculateRelevance,
2434
- getArchiveCandidates: () => getArchiveCandidates,
2435
- getImportanceLevel: () => getImportanceLevel,
2436
- getRetentionSummary: () => getRetentionSummary,
2437
- getRetentionZone: () => getRetentionZone,
2438
- isImmune: () => isImmune,
2439
- rankByRelevance: () => rankByRelevance
2530
+ // src/hooks/installers/index.ts
2531
+ var installers_exports = {};
2532
+ __export(installers_exports, {
2533
+ detectInstalledAgents: () => detectInstalledAgents,
2534
+ getHookStatus: () => getHookStatus,
2535
+ installHooks: () => installHooks,
2536
+ uninstallHooks: () => uninstallHooks
2440
2537
  });
2441
- function isImmune(doc) {
2442
- const importance = getImportanceLevel(doc);
2443
- if (importance === "critical" || importance === "high") return true;
2444
- if ((doc.accessCount ?? 0) >= MIN_ACCESS_FOR_IMMUNITY) return true;
2445
- const concepts = doc.concepts?.split(", ").map((c) => c.toLowerCase()) ?? [];
2446
- return concepts.some((c) => PROTECTED_TAGS.has(c));
2538
+ import * as fs3 from "fs/promises";
2539
+ import * as path5 from "path";
2540
+ import * as os2 from "os";
2541
+ import { createRequire } from "module";
2542
+ function resolveHookCommand() {
2543
+ if (process.platform === "win32") {
2544
+ try {
2545
+ const devPath = path5.resolve(import.meta.dirname ?? __dirname, "../../cli/index.js");
2546
+ try {
2547
+ const fsStat = __require("fs");
2548
+ if (fsStat.existsSync(devPath)) {
2549
+ return `node ${devPath.replace(/\\/g, "/")}`;
2550
+ }
2551
+ } catch {
2552
+ }
2553
+ const require_ = createRequire(import.meta.url);
2554
+ const pkgPath = require_.resolve("memorix/package.json");
2555
+ const cliPath = path5.join(path5.dirname(pkgPath), "dist", "cli", "index.js");
2556
+ return `node ${cliPath.replace(/\\/g, "/")}`;
2557
+ } catch {
2558
+ return "memorix";
2559
+ }
2560
+ }
2561
+ return "memorix";
2447
2562
  }
2448
- function getImportanceLevel(doc) {
2449
- return TYPE_IMPORTANCE[doc.type] ?? "medium";
2563
+ function generateClaudeConfig() {
2564
+ const cmd = `${resolveHookCommand()} hook`;
2565
+ const hookEntry = {
2566
+ type: "command",
2567
+ command: cmd,
2568
+ timeout: 10
2569
+ };
2570
+ return {
2571
+ hooks: {
2572
+ SessionStart: [hookEntry],
2573
+ PostToolUse: [hookEntry],
2574
+ UserPromptSubmit: [hookEntry],
2575
+ PreCompact: [hookEntry],
2576
+ Stop: [hookEntry]
2577
+ }
2578
+ };
2450
2579
  }
2451
- function calculateRelevance(doc, referenceTime) {
2452
- const now = referenceTime ?? /* @__PURE__ */ new Date();
2453
- const importance = getImportanceLevel(doc);
2454
- const base = BASE_IMPORTANCE[importance];
2455
- const retention = RETENTION_DAYS[importance];
2456
- const createdAt = new Date(doc.createdAt);
2457
- const ageDays = Math.max(0, (now.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24));
2458
- const decayFactor = Math.exp(-ageDays / retention);
2459
- const accessCount = doc.accessCount ?? 0;
2460
- const accessBoost = Math.min(2, 1 + 0.1 * accessCount);
2461
- let totalScore = base * decayFactor * accessBoost;
2462
- const immune = isImmune(doc);
2463
- if (immune) {
2464
- totalScore = Math.max(totalScore, 0.5);
2465
- }
2580
+ function generateWindsurfConfig() {
2581
+ const cmd = `${resolveHookCommand()} hook`;
2582
+ const hookEntry = {
2583
+ command: cmd,
2584
+ show_output: false
2585
+ };
2466
2586
  return {
2467
- observationId: doc.observationId,
2468
- totalScore,
2469
- baseImportance: base,
2470
- decayFactor,
2471
- accessBoost,
2472
- ageDays,
2473
- isImmune: immune
2587
+ hooks: {
2588
+ post_write_code: [hookEntry],
2589
+ post_run_command: [hookEntry],
2590
+ post_mcp_tool_use: [hookEntry],
2591
+ pre_user_prompt: [hookEntry],
2592
+ post_cascade_response: [hookEntry]
2593
+ }
2474
2594
  };
2475
2595
  }
2476
- function rankByRelevance(docs, referenceTime) {
2477
- return docs.map((doc) => calculateRelevance(doc, referenceTime)).sort((a, b) => b.totalScore - a.totalScore);
2596
+ function generateCursorConfig() {
2597
+ const cmd = `${resolveHookCommand()} hook`;
2598
+ return {
2599
+ hooks: {
2600
+ beforeSubmitPrompt: {
2601
+ command: cmd
2602
+ },
2603
+ afterFileEdit: {
2604
+ command: cmd
2605
+ },
2606
+ stop: {
2607
+ command: cmd
2608
+ }
2609
+ }
2610
+ };
2478
2611
  }
2479
- function getRetentionZone(doc, referenceTime) {
2480
- const now = referenceTime ?? /* @__PURE__ */ new Date();
2481
- const importance = getImportanceLevel(doc);
2482
- const retention = RETENTION_DAYS[importance];
2483
- const createdAt = new Date(doc.createdAt);
2484
- const ageDays = (now.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24);
2485
- if (doc.lastAccessedAt) {
2486
- const lastAccess = new Date(doc.lastAccessedAt);
2487
- const daysSinceAccess = (now.getTime() - lastAccess.getTime()) / (1e3 * 60 * 60 * 24);
2488
- if (daysSinceAccess < 7) return "active";
2489
- }
2490
- if (isImmune(doc)) return "active";
2491
- if (ageDays > retention) return "archive-candidate";
2492
- if (ageDays > retention * 0.5) return "stale";
2493
- return "active";
2612
+ function generateKiroHookFile() {
2613
+ return `---
2614
+ title: Memorix Auto-Memory
2615
+ description: Automatically record development context for cross-agent memory sharing
2616
+ event: file_saved
2617
+ filePattern: "**/*"
2618
+ ---
2619
+
2620
+ Run the memorix hook command to analyze changes and store relevant memories:
2621
+
2622
+ \`\`\`bash
2623
+ ${resolveHookCommand()} hook
2624
+ \`\`\`
2625
+ `;
2494
2626
  }
2495
- function getArchiveCandidates(docs, referenceTime) {
2496
- return docs.filter((doc) => getRetentionZone(doc, referenceTime) === "archive-candidate");
2627
+ function getProjectConfigPath(agent, projectRoot) {
2628
+ switch (agent) {
2629
+ case "claude":
2630
+ case "copilot":
2631
+ return path5.join(projectRoot, ".github", "hooks", "memorix.json");
2632
+ case "windsurf":
2633
+ return path5.join(projectRoot, ".windsurf", "hooks.json");
2634
+ case "cursor":
2635
+ return path5.join(projectRoot, ".cursor", "hooks.json");
2636
+ case "kiro":
2637
+ return path5.join(projectRoot, ".kiro", "hooks", "memorix.hook.md");
2638
+ case "codex":
2639
+ return path5.join(projectRoot, ".codex", "hooks.json");
2640
+ default:
2641
+ return path5.join(projectRoot, ".memorix", "hooks.json");
2642
+ }
2497
2643
  }
2498
- function getRetentionSummary(docs, referenceTime) {
2499
- let active = 0;
2500
- let stale = 0;
2501
- let archiveCandidates = 0;
2502
- let immune = 0;
2503
- for (const doc of docs) {
2504
- const zone = getRetentionZone(doc, referenceTime);
2505
- if (zone === "active") active++;
2506
- else if (zone === "stale") stale++;
2507
- else archiveCandidates++;
2508
- if (isImmune(doc)) immune++;
2644
+ function getGlobalConfigPath(agent) {
2645
+ const home = os2.homedir();
2646
+ switch (agent) {
2647
+ case "claude":
2648
+ case "copilot":
2649
+ return path5.join(home, ".claude", "settings.json");
2650
+ case "windsurf":
2651
+ return path5.join(home, ".codeium", "windsurf", "hooks.json");
2652
+ case "cursor":
2653
+ return path5.join(home, ".cursor", "hooks.json");
2654
+ default:
2655
+ return path5.join(home, ".memorix", "hooks.json");
2509
2656
  }
2510
- return { active, stale, archiveCandidates, immune };
2511
2657
  }
2512
- var RETENTION_DAYS, BASE_IMPORTANCE, TYPE_IMPORTANCE, PROTECTED_TAGS, MIN_ACCESS_FOR_IMMUNITY;
2513
- var init_retention = __esm({
2514
- "src/memory/retention.ts"() {
2515
- "use strict";
2516
- init_esm_shims();
2517
- RETENTION_DAYS = {
2518
- critical: 365,
2519
- high: 180,
2520
- medium: 90,
2521
- low: 30
2522
- };
2523
- BASE_IMPORTANCE = {
2524
- critical: 1,
2525
- high: 0.8,
2526
- medium: 0.5,
2527
- low: 0.3
2528
- };
2529
- TYPE_IMPORTANCE = {
2530
- gotcha: "high",
2531
- decision: "high",
2532
- "trade-off": "high",
2533
- "problem-solution": "medium",
2534
- "how-it-works": "medium",
2535
- "what-changed": "medium",
2536
- "why-it-exists": "medium",
2537
- discovery: "medium",
2538
- "session-request": "low"
2539
- };
2540
- PROTECTED_TAGS = /* @__PURE__ */ new Set(["keep", "important", "pinned", "critical"]);
2541
- MIN_ACCESS_FOR_IMMUNITY = 3;
2658
+ async function detectInstalledAgents() {
2659
+ const agents = [];
2660
+ const home = os2.homedir();
2661
+ const claudeDir = path5.join(home, ".claude");
2662
+ try {
2663
+ await fs3.access(claudeDir);
2664
+ agents.push("claude");
2665
+ } catch {
2542
2666
  }
2543
- });
2544
-
2545
- // src/server.ts
2546
- var server_exports = {};
2547
- __export(server_exports, {
2548
- createMemorixServer: () => createMemorixServer
2549
- });
2550
- import { watch } from "fs";
2551
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2552
- import { z } from "zod";
2553
- async function createMemorixServer(cwd) {
2554
- const project = detectProject(cwd);
2555
- const projectDir2 = await getProjectDataDir(project.id);
2556
- const graphManager = new KnowledgeGraphManager(projectDir2);
2557
- await graphManager.init();
2558
- await initObservations(projectDir2);
2559
- const reindexed = await reindexObservations();
2560
- if (reindexed > 0) {
2561
- console.error(`[memorix] Reindexed ${reindexed} observations for project: ${project.id}`);
2667
+ const windsurfDir = path5.join(home, ".codeium", "windsurf");
2668
+ try {
2669
+ await fs3.access(windsurfDir);
2670
+ agents.push("windsurf");
2671
+ } catch {
2562
2672
  }
2563
- console.error(`[memorix] Project: ${project.id} (${project.name})`);
2564
- console.error(`[memorix] Data dir: ${projectDir2}`);
2565
- const observationsFile = projectDir2 + "/observations.json";
2566
- let reloadDebounce = null;
2673
+ const cursorDir = path5.join(home, ".cursor");
2674
+ try {
2675
+ await fs3.access(cursorDir);
2676
+ agents.push("cursor");
2677
+ } catch {
2678
+ }
2679
+ if (!agents.includes("claude")) {
2680
+ const vscodeDir = path5.join(home, ".vscode");
2681
+ try {
2682
+ await fs3.access(vscodeDir);
2683
+ agents.push("copilot");
2684
+ } catch {
2685
+ }
2686
+ }
2687
+ const kiroConfig = path5.join(home, ".kiro");
2688
+ try {
2689
+ await fs3.access(kiroConfig);
2690
+ agents.push("kiro");
2691
+ } catch {
2692
+ }
2693
+ return agents;
2694
+ }
2695
+ async function installHooks(agent, projectRoot, global = false) {
2696
+ const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
2697
+ let generated;
2698
+ switch (agent) {
2699
+ case "claude":
2700
+ case "copilot":
2701
+ generated = generateClaudeConfig();
2702
+ break;
2703
+ case "windsurf":
2704
+ generated = generateWindsurfConfig();
2705
+ break;
2706
+ case "cursor":
2707
+ generated = generateCursorConfig();
2708
+ break;
2709
+ case "kiro":
2710
+ generated = generateKiroHookFile();
2711
+ break;
2712
+ default:
2713
+ generated = generateClaudeConfig();
2714
+ }
2715
+ await fs3.mkdir(path5.dirname(configPath), { recursive: true });
2716
+ if (agent === "kiro") {
2717
+ await fs3.writeFile(configPath, generated, "utf-8");
2718
+ } else {
2719
+ let existing = {};
2720
+ try {
2721
+ const content = await fs3.readFile(configPath, "utf-8");
2722
+ existing = JSON.parse(content);
2723
+ } catch {
2724
+ }
2725
+ const merged = {
2726
+ ...existing,
2727
+ ...generated
2728
+ };
2729
+ await fs3.writeFile(configPath, JSON.stringify(merged, null, 2), "utf-8");
2730
+ }
2731
+ const events = [];
2732
+ switch (agent) {
2733
+ case "claude":
2734
+ case "copilot":
2735
+ events.push("session_start", "post_tool", "user_prompt", "pre_compact", "session_end");
2736
+ break;
2737
+ case "windsurf":
2738
+ events.push("post_edit", "post_command", "post_tool", "user_prompt", "post_response");
2739
+ break;
2740
+ case "cursor":
2741
+ events.push("user_prompt", "post_edit", "session_end");
2742
+ break;
2743
+ case "kiro":
2744
+ events.push("post_edit");
2745
+ break;
2746
+ }
2747
+ await installAgentRules(agent, projectRoot);
2748
+ return {
2749
+ agent,
2750
+ configPath,
2751
+ events,
2752
+ generated: typeof generated === "string" ? { content: generated } : generated
2753
+ };
2754
+ }
2755
+ async function installAgentRules(agent, projectRoot) {
2756
+ const rulesContent = getAgentRulesContent();
2757
+ let rulesPath;
2758
+ switch (agent) {
2759
+ case "windsurf":
2760
+ rulesPath = path5.join(projectRoot, ".windsurf", "rules", "memorix.md");
2761
+ break;
2762
+ case "cursor":
2763
+ rulesPath = path5.join(projectRoot, ".cursor", "rules", "memorix.mdc");
2764
+ break;
2765
+ case "claude":
2766
+ case "copilot":
2767
+ rulesPath = path5.join(projectRoot, ".github", "copilot-instructions.md");
2768
+ break;
2769
+ default:
2770
+ return;
2771
+ }
2772
+ try {
2773
+ await fs3.mkdir(path5.dirname(rulesPath), { recursive: true });
2774
+ try {
2775
+ await fs3.access(rulesPath);
2776
+ } catch {
2777
+ await fs3.writeFile(rulesPath, rulesContent, "utf-8");
2778
+ }
2779
+ } catch {
2780
+ }
2781
+ }
2782
+ function getAgentRulesContent() {
2783
+ return `# Memorix \u2014 Automatic Memory Rules
2784
+
2785
+ You have access to Memorix memory tools. Follow these rules to maintain persistent context across sessions.
2786
+
2787
+ ## Session Start \u2014 Load Context
2788
+
2789
+ At the **beginning of every conversation**, before responding to the user:
2790
+
2791
+ 1. Call \`memorix_search\` with query related to the user's first message or the current project
2792
+ 2. If results are found, use them to understand the current project state, recent decisions, and pending tasks
2793
+ 3. Reference relevant memories naturally in your response
2794
+
2795
+ This ensures you already know the project context without the user re-explaining.
2796
+
2797
+ ## During Session \u2014 Capture Important Context
2798
+
2799
+ Proactively call \`memorix_store\` when any of the following happen:
2800
+
2801
+ - **Architecture decision**: You or the user decide on a technology, pattern, or approach
2802
+ - **Bug fix**: A bug is identified and resolved \u2014 store the root cause and fix
2803
+ - **Gotcha/pitfall**: Something unexpected or tricky is discovered
2804
+ - **Configuration change**: Environment, port, path, or tooling changes
2805
+
2806
+ Use appropriate types: \`decision\`, \`problem-solution\`, \`gotcha\`, \`what-changed\`, \`discovery\`.
2807
+
2808
+ ## Session End \u2014 Store Summary
2809
+
2810
+ When the conversation is ending or the user says goodbye:
2811
+
2812
+ 1. Call \`memorix_store\` with type \`session-request\` to record:
2813
+ - What was accomplished in this session
2814
+ - Current project state
2815
+ - Pending tasks or next steps
2816
+ - Any unresolved issues
2817
+
2818
+ This creates a "handoff note" for the next session.
2819
+
2820
+ ## Guidelines
2821
+
2822
+ - **Don't store trivial information** (greetings, acknowledgments, simple file reads)
2823
+ - **Do store anything you'd want to know if you lost all context**
2824
+ - **Use concise titles** and structured facts
2825
+ - **Include file paths** in filesModified when relevant
2826
+ `;
2827
+ }
2828
+ async function uninstallHooks(agent, projectRoot, global = false) {
2829
+ const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
2830
+ try {
2831
+ if (agent === "kiro") {
2832
+ await fs3.unlink(configPath);
2833
+ } else {
2834
+ const content = await fs3.readFile(configPath, "utf-8");
2835
+ const config = JSON.parse(content);
2836
+ delete config.hooks;
2837
+ if (Object.keys(config).length === 0) {
2838
+ await fs3.unlink(configPath);
2839
+ } else {
2840
+ await fs3.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
2841
+ }
2842
+ }
2843
+ return true;
2844
+ } catch {
2845
+ return false;
2846
+ }
2847
+ }
2848
+ async function getHookStatus(projectRoot) {
2849
+ const results = [];
2850
+ const agents = ["claude", "copilot", "windsurf", "cursor", "kiro", "codex"];
2851
+ for (const agent of agents) {
2852
+ const projectPath = getProjectConfigPath(agent, projectRoot);
2853
+ const globalPath = getGlobalConfigPath(agent);
2854
+ let installed = false;
2855
+ let usedPath = projectPath;
2856
+ try {
2857
+ await fs3.access(projectPath);
2858
+ installed = true;
2859
+ } catch {
2860
+ try {
2861
+ await fs3.access(globalPath);
2862
+ installed = true;
2863
+ usedPath = globalPath;
2864
+ } catch {
2865
+ }
2866
+ }
2867
+ results.push({ agent, installed, configPath: usedPath });
2868
+ }
2869
+ return results;
2870
+ }
2871
+ var init_installers = __esm({
2872
+ "src/hooks/installers/index.ts"() {
2873
+ "use strict";
2874
+ init_esm_shims();
2875
+ }
2876
+ });
2877
+
2878
+ // src/memory/retention.ts
2879
+ var retention_exports = {};
2880
+ __export(retention_exports, {
2881
+ calculateRelevance: () => calculateRelevance,
2882
+ getArchiveCandidates: () => getArchiveCandidates,
2883
+ getImportanceLevel: () => getImportanceLevel,
2884
+ getRetentionSummary: () => getRetentionSummary,
2885
+ getRetentionZone: () => getRetentionZone,
2886
+ isImmune: () => isImmune,
2887
+ rankByRelevance: () => rankByRelevance
2888
+ });
2889
+ function isImmune(doc) {
2890
+ const importance = getImportanceLevel(doc);
2891
+ if (importance === "critical" || importance === "high") return true;
2892
+ if ((doc.accessCount ?? 0) >= MIN_ACCESS_FOR_IMMUNITY) return true;
2893
+ const concepts = doc.concepts?.split(", ").map((c) => c.toLowerCase()) ?? [];
2894
+ return concepts.some((c) => PROTECTED_TAGS.has(c));
2895
+ }
2896
+ function getImportanceLevel(doc) {
2897
+ return TYPE_IMPORTANCE[doc.type] ?? "medium";
2898
+ }
2899
+ function calculateRelevance(doc, referenceTime) {
2900
+ const now = referenceTime ?? /* @__PURE__ */ new Date();
2901
+ const importance = getImportanceLevel(doc);
2902
+ const base = BASE_IMPORTANCE[importance];
2903
+ const retention = RETENTION_DAYS[importance];
2904
+ const createdAt = new Date(doc.createdAt);
2905
+ const ageDays = Math.max(0, (now.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24));
2906
+ const decayFactor = Math.exp(-ageDays / retention);
2907
+ const accessCount = doc.accessCount ?? 0;
2908
+ const accessBoost = Math.min(2, 1 + 0.1 * accessCount);
2909
+ let totalScore = base * decayFactor * accessBoost;
2910
+ const immune = isImmune(doc);
2911
+ if (immune) {
2912
+ totalScore = Math.max(totalScore, 0.5);
2913
+ }
2914
+ return {
2915
+ observationId: doc.observationId,
2916
+ totalScore,
2917
+ baseImportance: base,
2918
+ decayFactor,
2919
+ accessBoost,
2920
+ ageDays,
2921
+ isImmune: immune
2922
+ };
2923
+ }
2924
+ function rankByRelevance(docs, referenceTime) {
2925
+ return docs.map((doc) => calculateRelevance(doc, referenceTime)).sort((a, b) => b.totalScore - a.totalScore);
2926
+ }
2927
+ function getRetentionZone(doc, referenceTime) {
2928
+ const now = referenceTime ?? /* @__PURE__ */ new Date();
2929
+ const importance = getImportanceLevel(doc);
2930
+ const retention = RETENTION_DAYS[importance];
2931
+ const createdAt = new Date(doc.createdAt);
2932
+ const ageDays = (now.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24);
2933
+ if (doc.lastAccessedAt) {
2934
+ const lastAccess = new Date(doc.lastAccessedAt);
2935
+ const daysSinceAccess = (now.getTime() - lastAccess.getTime()) / (1e3 * 60 * 60 * 24);
2936
+ if (daysSinceAccess < 7) return "active";
2937
+ }
2938
+ if (isImmune(doc)) return "active";
2939
+ if (ageDays > retention) return "archive-candidate";
2940
+ if (ageDays > retention * 0.5) return "stale";
2941
+ return "active";
2942
+ }
2943
+ function getArchiveCandidates(docs, referenceTime) {
2944
+ return docs.filter((doc) => getRetentionZone(doc, referenceTime) === "archive-candidate");
2945
+ }
2946
+ function getRetentionSummary(docs, referenceTime) {
2947
+ let active = 0;
2948
+ let stale = 0;
2949
+ let archiveCandidates = 0;
2950
+ let immune = 0;
2951
+ for (const doc of docs) {
2952
+ const zone = getRetentionZone(doc, referenceTime);
2953
+ if (zone === "active") active++;
2954
+ else if (zone === "stale") stale++;
2955
+ else archiveCandidates++;
2956
+ if (isImmune(doc)) immune++;
2957
+ }
2958
+ return { active, stale, archiveCandidates, immune };
2959
+ }
2960
+ var RETENTION_DAYS, BASE_IMPORTANCE, TYPE_IMPORTANCE, PROTECTED_TAGS, MIN_ACCESS_FOR_IMMUNITY;
2961
+ var init_retention = __esm({
2962
+ "src/memory/retention.ts"() {
2963
+ "use strict";
2964
+ init_esm_shims();
2965
+ RETENTION_DAYS = {
2966
+ critical: 365,
2967
+ high: 180,
2968
+ medium: 90,
2969
+ low: 30
2970
+ };
2971
+ BASE_IMPORTANCE = {
2972
+ critical: 1,
2973
+ high: 0.8,
2974
+ medium: 0.5,
2975
+ low: 0.3
2976
+ };
2977
+ TYPE_IMPORTANCE = {
2978
+ gotcha: "high",
2979
+ decision: "high",
2980
+ "trade-off": "high",
2981
+ "problem-solution": "medium",
2982
+ "how-it-works": "medium",
2983
+ "what-changed": "medium",
2984
+ "why-it-exists": "medium",
2985
+ discovery: "medium",
2986
+ "session-request": "low"
2987
+ };
2988
+ PROTECTED_TAGS = /* @__PURE__ */ new Set(["keep", "important", "pinned", "critical"]);
2989
+ MIN_ACCESS_FOR_IMMUNITY = 3;
2990
+ }
2991
+ });
2992
+
2993
+ // src/server.ts
2994
+ var server_exports = {};
2995
+ __export(server_exports, {
2996
+ createMemorixServer: () => createMemorixServer
2997
+ });
2998
+ import { watch } from "fs";
2999
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3000
+ import { z } from "zod";
3001
+ async function createMemorixServer(cwd) {
3002
+ const project = detectProject(cwd);
3003
+ const projectDir2 = await getProjectDataDir(project.id);
3004
+ const graphManager = new KnowledgeGraphManager(projectDir2);
3005
+ await graphManager.init();
3006
+ await initObservations(projectDir2);
3007
+ const reindexed = await reindexObservations();
3008
+ if (reindexed > 0) {
3009
+ console.error(`[memorix] Reindexed ${reindexed} observations for project: ${project.id}`);
3010
+ }
3011
+ console.error(`[memorix] Project: ${project.id} (${project.name})`);
3012
+ console.error(`[memorix] Data dir: ${projectDir2}`);
3013
+ try {
3014
+ const { getHookStatus: getHookStatus2, installHooks: installHooks2, detectInstalledAgents: detectInstalledAgents2 } = await Promise.resolve().then(() => (init_installers(), installers_exports));
3015
+ const workDir = cwd ?? process.cwd();
3016
+ const statuses = await getHookStatus2(workDir);
3017
+ const anyInstalled = statuses.some((s) => s.installed);
3018
+ if (!anyInstalled) {
3019
+ const agents = await detectInstalledAgents2();
3020
+ for (const agent of agents) {
3021
+ try {
3022
+ const config = await installHooks2(agent, workDir);
3023
+ console.error(`[memorix] Auto-installed hooks for ${agent} \u2192 ${config.configPath}`);
3024
+ } catch {
3025
+ }
3026
+ }
3027
+ }
3028
+ } catch {
3029
+ }
3030
+ const observationsFile = projectDir2 + "/observations.json";
3031
+ let reloadDebounce = null;
2567
3032
  try {
2568
3033
  watch(observationsFile, () => {
2569
3034
  if (reloadDebounce) clearTimeout(reloadDebounce);
@@ -3002,7 +3467,7 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
3002
3467
  "memorix_workspace_sync",
3003
3468
  {
3004
3469
  title: "Workspace Sync",
3005
- description: 'Migrate your entire workspace environment between AI agents. Syncs MCP server configs, workflows, and rules. Action "scan": detect all workspace configs. Action "migrate": generate configs for target agent (preview only). Action "apply": migrate AND write configs to disk with backup/rollback.',
3470
+ description: 'Migrate your entire workspace environment between AI agents. Syncs MCP server configs, workflows, rules, and skills. Action "scan": detect all workspace configs. Action "migrate": generate configs for target agent (preview only). Action "apply": migrate AND write configs to disk with backup/rollback.',
3006
3471
  inputSchema: {
3007
3472
  action: z.enum(["scan", "migrate", "apply"]).describe('Action: "scan" to detect configs, "migrate" to preview, "apply" to write to disk'),
3008
3473
  target: z.enum(AGENT_TARGETS).optional().describe("Target agent for migration (required for migrate)")
@@ -3032,6 +3497,14 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
3032
3497
  }
3033
3498
  lines2.push("", `### Rules`);
3034
3499
  lines2.push(`- ${scan.rulesCount} rule(s) detected across all agents`);
3500
+ lines2.push("", `### Skills`);
3501
+ if (scan.skills.length > 0) {
3502
+ for (const sk of scan.skills) {
3503
+ lines2.push(`- **${sk.name}** (${sk.sourceAgent}): ${sk.description || "(no description)"}`);
3504
+ }
3505
+ } else {
3506
+ lines2.push("- No skills found");
3507
+ }
3035
3508
  return {
3036
3509
  content: [{ type: "text", text: lines2.join("\n") }]
3037
3510
  };
@@ -3069,6 +3542,12 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
3069
3542
  if (result.rules.generated > 0) {
3070
3543
  lines.push(`### Rules`, `- ${result.rules.generated} rule file(s) generated`);
3071
3544
  }
3545
+ if (result.skills.scanned.length > 0) {
3546
+ lines.push("### Skills", `- ${result.skills.scanned.length} skill(s) found, ready to copy:`);
3547
+ for (const sk of result.skills.scanned) {
3548
+ lines.push(` - **${sk.name}** (from ${sk.sourceAgent})`);
3549
+ }
3550
+ }
3072
3551
  lines.push("", '> Review the generated configs above. Use action "apply" to write them to disk.');
3073
3552
  return {
3074
3553
  content: [{ type: "text", text: lines.join("\n") }]
@@ -3575,9 +4054,10 @@ var init_pattern_detector = __esm({
3575
4054
  {
3576
4055
  type: "configuration",
3577
4056
  keywords: [
3578
- /\b(config(ured?|uration)?|setting|environment|env\b|\.env|path|port)\b/i,
3579
- /(配置|环境|路径|端口|设置|安装)/,
3580
- /\b(gradle|webpack|vite|tsconfig|package\.json|docker)\b/i
4057
+ /\b(config(ured?|uration)?|setting|environment|\.env)\b/i,
4058
+ /(配置|环境变量|端口配置|设置项|安装配置)/,
4059
+ /\b(gradle|webpack|vite|tsconfig|package\.json|docker|nginx)\b/i,
4060
+ /\b(port\s*[:=]|listen\s+\d|bind\s+\d|DATABASE_URL|API_KEY)\b/i
3581
4061
  ],
3582
4062
  minLength: 80,
3583
4063
  baseConfidence: 0.7
@@ -3702,27 +4182,78 @@ async function handleHookEvent(input) {
3702
4182
  };
3703
4183
  case "session_end":
3704
4184
  return {
3705
- observation: buildObservation(input, extractContent(input)),
4185
+ observation: buildObservation(input, extractContent(input)),
4186
+ output: defaultOutput
4187
+ };
4188
+ case "post_edit": {
4189
+ const editKey = `post_edit:${input.filePath ?? "general"}`;
4190
+ if (isInCooldown(editKey)) {
4191
+ return { observation: null, output: defaultOutput };
4192
+ }
4193
+ const editContent = extractContent(input);
4194
+ if (editContent.length < MIN_EDIT_LENGTH) {
4195
+ return { observation: null, output: defaultOutput };
4196
+ }
4197
+ const editPattern = detectBestPattern(editContent, 0.6);
4198
+ if (!editPattern) {
4199
+ return { observation: null, output: defaultOutput };
4200
+ }
4201
+ markTriggered(editKey);
4202
+ return {
4203
+ observation: buildObservation(input, editContent),
4204
+ output: defaultOutput
4205
+ };
4206
+ }
4207
+ case "post_command": {
4208
+ if (input.command && NOISE_COMMANDS.some((r) => r.test(input.command))) {
4209
+ return { observation: null, output: defaultOutput };
4210
+ }
4211
+ const cmdKey = `post_command:${input.command ?? "general"}`;
4212
+ if (isInCooldown(cmdKey)) {
4213
+ return { observation: null, output: defaultOutput };
4214
+ }
4215
+ const cmdContent = input.commandOutput || extractContent(input);
4216
+ if (cmdContent.length < MIN_STORE_LENGTH) {
4217
+ return { observation: null, output: defaultOutput };
4218
+ }
4219
+ detectBestPattern(cmdContent);
4220
+ markTriggered(cmdKey);
4221
+ return {
4222
+ observation: buildObservation(input, cmdContent),
4223
+ output: defaultOutput
4224
+ };
4225
+ }
4226
+ case "post_tool": {
4227
+ const toolKey = `post_tool:${input.toolName ?? "general"}`;
4228
+ if (isInCooldown(toolKey)) {
4229
+ return { observation: null, output: defaultOutput };
4230
+ }
4231
+ const toolContent = extractContent(input);
4232
+ if (toolContent.length < MIN_STORE_LENGTH) {
4233
+ return { observation: null, output: defaultOutput };
4234
+ }
4235
+ const toolPattern = detectBestPattern(toolContent);
4236
+ if (!toolPattern) {
4237
+ return { observation: null, output: defaultOutput };
4238
+ }
4239
+ markTriggered(toolKey);
4240
+ return {
4241
+ observation: buildObservation(input, toolContent),
3706
4242
  output: defaultOutput
3707
4243
  };
3708
- case "post_edit":
3709
- case "post_command":
3710
- case "post_tool":
4244
+ }
3711
4245
  case "post_response":
3712
4246
  case "user_prompt": {
3713
- const cooldownKey = `${input.event}:${input.filePath ?? input.command ?? "general"}`;
3714
- if (isInCooldown(cooldownKey)) {
4247
+ const promptKey = `${input.event}:${input.sessionId ?? "general"}`;
4248
+ if (isInCooldown(promptKey)) {
3715
4249
  return { observation: null, output: defaultOutput };
3716
4250
  }
3717
4251
  const content = extractContent(input);
3718
4252
  if (content.length < MIN_STORE_LENGTH) {
3719
4253
  return { observation: null, output: defaultOutput };
3720
4254
  }
3721
- const pattern = detectBestPattern(content);
3722
- if (!pattern) {
3723
- return { observation: null, output: defaultOutput };
3724
- }
3725
- markTriggered(cooldownKey);
4255
+ detectBestPattern(content);
4256
+ markTriggered(promptKey);
3726
4257
  return {
3727
4258
  observation: buildObservation(input, content),
3728
4259
  output: defaultOutput
@@ -3765,7 +4296,7 @@ async function runHook() {
3765
4296
  }
3766
4297
  process.stdout.write(JSON.stringify(output));
3767
4298
  }
3768
- var cooldowns, COOLDOWN_MS, MIN_STORE_LENGTH, MAX_CONTENT_LENGTH;
4299
+ var cooldowns, COOLDOWN_MS, MIN_STORE_LENGTH, MIN_EDIT_LENGTH, NOISE_COMMANDS, MAX_CONTENT_LENGTH;
3769
4300
  var init_handler = __esm({
3770
4301
  "src/hooks/handler.ts"() {
3771
4302
  "use strict";
@@ -3775,6 +4306,14 @@ var init_handler = __esm({
3775
4306
  cooldowns = /* @__PURE__ */ new Map();
3776
4307
  COOLDOWN_MS = 3e4;
3777
4308
  MIN_STORE_LENGTH = 100;
4309
+ MIN_EDIT_LENGTH = 30;
4310
+ NOISE_COMMANDS = [
4311
+ /^(ls|dir|cd|pwd|echo|cat|type|head|tail|wc|find|which|where|whoami)\b/i,
4312
+ /^(Get-Content|Test-Path|Get-Item|Get-ChildItem|Set-Location|Write-Host)\b/i,
4313
+ /^(Start-Sleep|Select-String|Select-Object|Format-Table|Measure-Object)\b/i,
4314
+ /^(mkdir|rm|cp|mv|touch|chmod|chown)\b/i,
4315
+ /^(node -[ep]|python -c)\b/i
4316
+ ];
3778
4317
  MAX_CONTENT_LENGTH = 4e3;
3779
4318
  }
3780
4319
  });
@@ -3803,258 +4342,6 @@ var init_hook = __esm({
3803
4342
  }
3804
4343
  });
3805
4344
 
3806
- // src/hooks/installers/index.ts
3807
- var installers_exports = {};
3808
- __export(installers_exports, {
3809
- detectInstalledAgents: () => detectInstalledAgents,
3810
- getHookStatus: () => getHookStatus,
3811
- installHooks: () => installHooks,
3812
- uninstallHooks: () => uninstallHooks
3813
- });
3814
- import * as fs3 from "fs/promises";
3815
- import * as path5 from "path";
3816
- import * as os2 from "os";
3817
- function generateClaudeConfig() {
3818
- const hookEntry = {
3819
- type: "command",
3820
- command: `${HOOK_COMMAND} ${HOOK_ARGS.join(" ")}`,
3821
- timeout: 10
3822
- };
3823
- return {
3824
- hooks: {
3825
- SessionStart: [hookEntry],
3826
- PostToolUse: [hookEntry],
3827
- UserPromptSubmit: [hookEntry],
3828
- PreCompact: [hookEntry],
3829
- Stop: [hookEntry]
3830
- }
3831
- };
3832
- }
3833
- function generateWindsurfConfig() {
3834
- const hookEntry = {
3835
- command: `${HOOK_COMMAND} ${HOOK_ARGS.join(" ")}`,
3836
- timeout: 10
3837
- };
3838
- return {
3839
- hooks: {
3840
- post_write_code: [hookEntry],
3841
- post_run_command: [hookEntry],
3842
- post_mcp_tool_use: [hookEntry],
3843
- pre_user_prompt: [hookEntry],
3844
- post_cascade_response: [hookEntry]
3845
- }
3846
- };
3847
- }
3848
- function generateCursorConfig() {
3849
- return {
3850
- hooks: {
3851
- beforeSubmitPrompt: {
3852
- command: `${HOOK_COMMAND} ${HOOK_ARGS.join(" ")}`
3853
- },
3854
- afterFileEdit: {
3855
- command: `${HOOK_COMMAND} ${HOOK_ARGS.join(" ")}`
3856
- },
3857
- stop: {
3858
- command: `${HOOK_COMMAND} ${HOOK_ARGS.join(" ")}`
3859
- }
3860
- }
3861
- };
3862
- }
3863
- function generateKiroHookFile() {
3864
- return `---
3865
- title: Memorix Auto-Memory
3866
- description: Automatically record development context for cross-agent memory sharing
3867
- event: file_saved
3868
- filePattern: "**/*"
3869
- ---
3870
-
3871
- Run the memorix hook command to analyze changes and store relevant memories:
3872
-
3873
- \`\`\`bash
3874
- memorix hook
3875
- \`\`\`
3876
- `;
3877
- }
3878
- function getProjectConfigPath(agent, projectRoot) {
3879
- switch (agent) {
3880
- case "claude":
3881
- case "copilot":
3882
- return path5.join(projectRoot, ".github", "hooks", "memorix.json");
3883
- case "windsurf":
3884
- return path5.join(projectRoot, ".windsurf", "hooks.json");
3885
- case "cursor":
3886
- return path5.join(projectRoot, ".cursor", "hooks.json");
3887
- case "kiro":
3888
- return path5.join(projectRoot, ".kiro", "hooks", "memorix.hook.md");
3889
- case "codex":
3890
- return path5.join(projectRoot, ".codex", "hooks.json");
3891
- default:
3892
- return path5.join(projectRoot, ".memorix", "hooks.json");
3893
- }
3894
- }
3895
- function getGlobalConfigPath(agent) {
3896
- const home = os2.homedir();
3897
- switch (agent) {
3898
- case "claude":
3899
- case "copilot":
3900
- return path5.join(home, ".claude", "settings.json");
3901
- case "windsurf":
3902
- return path5.join(home, ".codeium", "windsurf", "hooks.json");
3903
- case "cursor":
3904
- return path5.join(home, ".cursor", "hooks.json");
3905
- default:
3906
- return path5.join(home, ".memorix", "hooks.json");
3907
- }
3908
- }
3909
- async function detectInstalledAgents() {
3910
- const agents = [];
3911
- const home = os2.homedir();
3912
- const claudeDir = path5.join(home, ".claude");
3913
- try {
3914
- await fs3.access(claudeDir);
3915
- agents.push("claude");
3916
- } catch {
3917
- }
3918
- const windsurfDir = path5.join(home, ".codeium", "windsurf");
3919
- try {
3920
- await fs3.access(windsurfDir);
3921
- agents.push("windsurf");
3922
- } catch {
3923
- }
3924
- const cursorDir = path5.join(home, ".cursor");
3925
- try {
3926
- await fs3.access(cursorDir);
3927
- agents.push("cursor");
3928
- } catch {
3929
- }
3930
- if (!agents.includes("claude")) {
3931
- const vscodeDir = path5.join(home, ".vscode");
3932
- try {
3933
- await fs3.access(vscodeDir);
3934
- agents.push("copilot");
3935
- } catch {
3936
- }
3937
- }
3938
- const kiroConfig = path5.join(home, ".kiro");
3939
- try {
3940
- await fs3.access(kiroConfig);
3941
- agents.push("kiro");
3942
- } catch {
3943
- }
3944
- return agents;
3945
- }
3946
- async function installHooks(agent, projectRoot, global = false) {
3947
- const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
3948
- let generated;
3949
- switch (agent) {
3950
- case "claude":
3951
- case "copilot":
3952
- generated = generateClaudeConfig();
3953
- break;
3954
- case "windsurf":
3955
- generated = generateWindsurfConfig();
3956
- break;
3957
- case "cursor":
3958
- generated = generateCursorConfig();
3959
- break;
3960
- case "kiro":
3961
- generated = generateKiroHookFile();
3962
- break;
3963
- default:
3964
- generated = generateClaudeConfig();
3965
- }
3966
- await fs3.mkdir(path5.dirname(configPath), { recursive: true });
3967
- if (agent === "kiro") {
3968
- await fs3.writeFile(configPath, generated, "utf-8");
3969
- } else {
3970
- let existing = {};
3971
- try {
3972
- const content = await fs3.readFile(configPath, "utf-8");
3973
- existing = JSON.parse(content);
3974
- } catch {
3975
- }
3976
- const merged = {
3977
- ...existing,
3978
- ...generated
3979
- };
3980
- await fs3.writeFile(configPath, JSON.stringify(merged, null, 2), "utf-8");
3981
- }
3982
- const events = [];
3983
- switch (agent) {
3984
- case "claude":
3985
- case "copilot":
3986
- events.push("session_start", "post_tool", "user_prompt", "pre_compact", "session_end");
3987
- break;
3988
- case "windsurf":
3989
- events.push("post_edit", "post_command", "post_tool", "user_prompt", "post_response");
3990
- break;
3991
- case "cursor":
3992
- events.push("user_prompt", "post_edit", "session_end");
3993
- break;
3994
- case "kiro":
3995
- events.push("post_edit");
3996
- break;
3997
- }
3998
- return {
3999
- agent,
4000
- configPath,
4001
- events,
4002
- generated: typeof generated === "string" ? { content: generated } : generated
4003
- };
4004
- }
4005
- async function uninstallHooks(agent, projectRoot, global = false) {
4006
- const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
4007
- try {
4008
- if (agent === "kiro") {
4009
- await fs3.unlink(configPath);
4010
- } else {
4011
- const content = await fs3.readFile(configPath, "utf-8");
4012
- const config = JSON.parse(content);
4013
- delete config.hooks;
4014
- if (Object.keys(config).length === 0) {
4015
- await fs3.unlink(configPath);
4016
- } else {
4017
- await fs3.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
4018
- }
4019
- }
4020
- return true;
4021
- } catch {
4022
- return false;
4023
- }
4024
- }
4025
- async function getHookStatus(projectRoot) {
4026
- const results = [];
4027
- const agents = ["claude", "copilot", "windsurf", "cursor", "kiro", "codex"];
4028
- for (const agent of agents) {
4029
- const projectPath = getProjectConfigPath(agent, projectRoot);
4030
- const globalPath = getGlobalConfigPath(agent);
4031
- let installed = false;
4032
- let usedPath = projectPath;
4033
- try {
4034
- await fs3.access(projectPath);
4035
- installed = true;
4036
- } catch {
4037
- try {
4038
- await fs3.access(globalPath);
4039
- installed = true;
4040
- usedPath = globalPath;
4041
- } catch {
4042
- }
4043
- }
4044
- results.push({ agent, installed, configPath: usedPath });
4045
- }
4046
- return results;
4047
- }
4048
- var HOOK_COMMAND, HOOK_ARGS;
4049
- var init_installers = __esm({
4050
- "src/hooks/installers/index.ts"() {
4051
- "use strict";
4052
- init_esm_shims();
4053
- HOOK_COMMAND = "memorix";
4054
- HOOK_ARGS = ["hook"];
4055
- }
4056
- });
4057
-
4058
4345
  // src/cli/commands/hooks-install.ts
4059
4346
  var hooks_install_exports = {};
4060
4347
  __export(hooks_install_exports, {