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/README.md +30 -5
- package/dist/cli/index.js +683 -396
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +487 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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/
|
|
2431
|
-
var
|
|
2432
|
-
__export(
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
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
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
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
|
|
2449
|
-
|
|
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
|
|
2452
|
-
const
|
|
2453
|
-
const
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
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
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
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
|
|
2477
|
-
|
|
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
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
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
|
|
2496
|
-
|
|
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
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
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
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
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
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
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
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3709
|
-
case "post_command":
|
|
3710
|
-
case "post_tool":
|
|
4244
|
+
}
|
|
3711
4245
|
case "post_response":
|
|
3712
4246
|
case "user_prompt": {
|
|
3713
|
-
const
|
|
3714
|
-
if (isInCooldown(
|
|
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
|
-
|
|
3722
|
-
|
|
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, {
|