memorix 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -5
- package/dist/cli/index.js +337 -29
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +263 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,8 +44,26 @@ Memorix stores and indexes project knowledge (architecture decisions, bug fixes,
|
|
|
44
44
|
- **Rules Parser**: 4 format adapters (Cursor `.mdc`, Claude Code `CLAUDE.md`, Codex `SKILL.md`, Windsurf `.windsurfrules`)
|
|
45
45
|
- **Rules Syncer**: Scan → Deduplicate → Conflict detection → Cross-format generation
|
|
46
46
|
- **Workspace Sync**: MCP config migration + workflow sync across agents
|
|
47
|
+
- **Skills Sync**: Scan `.codex/skills/`, `.cursor/skills/`, `.windsurf/skills/`, `.claude/skills/` → copy entire skill folders across agents (no format conversion needed — SKILL.md is a universal standard)
|
|
47
48
|
- **Apply with Safety**: Backup → Atomic write → Auto-rollback on failure
|
|
48
49
|
|
|
50
|
+
### P3 — Auto-Memory Hooks
|
|
51
|
+
|
|
52
|
+
- **Hook Events**: `user_prompt`, `post_response`, `post_edit`, `post_command`, `post_tool`, `session_end`
|
|
53
|
+
- **Agent Normalizer**: Maps Windsurf/Cursor/Claude/Codex native events to unified hook events
|
|
54
|
+
- **Pattern Detection**: Auto-detects decisions, errors, gotchas, configurations, learnings, implementations
|
|
55
|
+
- **Cooldown Filtering**: Prevents duplicate storage within configurable time windows
|
|
56
|
+
- **Noise Filtering**: Skips trivial commands (`ls`, `cat`, `pwd`, etc.)
|
|
57
|
+
- **Agent Rules**: Auto-installs `.windsurf/rules/memorix.md` (or equivalent) to guide agents in proactive memory management
|
|
58
|
+
- **One-Command Install**: `memorix hooks install` sets up hooks + rules for your agent
|
|
59
|
+
|
|
60
|
+
### Context Continuity
|
|
61
|
+
|
|
62
|
+
- **Session Start**: Agent rules instruct AI to search memories before responding
|
|
63
|
+
- **During Session**: Auto-capture decisions, bugs, gotchas via hooks + agent-driven `memorix_store`
|
|
64
|
+
- **Session End**: Agent stores a "handoff note" summarizing progress and next steps
|
|
65
|
+
- **Result**: Start a new session and your AI already knows everything — no re-explaining needed
|
|
66
|
+
|
|
49
67
|
### P5 — Intelligence (Competitor-Inspired)
|
|
50
68
|
|
|
51
69
|
- **Access Tracking**: `accessCount` + `lastAccessedAt` on every search hit (from mcp-memory-service)
|
|
@@ -115,7 +133,7 @@ npm install memorix
|
|
|
115
133
|
| `memorix_detail` | L3 | Full observation details | ~500-1000/result |
|
|
116
134
|
| `memorix_retention` | Analytics | Memory decay & retention status | — |
|
|
117
135
|
| `memorix_rules_sync` | Rules | Scan, dedup, convert rules across agents | — |
|
|
118
|
-
| `memorix_workspace_sync` | Workspace | Scan/migrate MCP configs across agents | — |
|
|
136
|
+
| `memorix_workspace_sync` | Workspace | Scan/migrate MCP configs, workflows, and skills across agents | — |
|
|
119
137
|
|
|
120
138
|
#### MCP Official Compatible
|
|
121
139
|
|
|
@@ -158,9 +176,16 @@ npm install memorix
|
|
|
158
176
|
│ └─────────────────────────────────────┘ │
|
|
159
177
|
│ │
|
|
160
178
|
│ ┌────────────────────────────────────┐ │
|
|
161
|
-
│ │
|
|
179
|
+
│ │ Rules & Skills Syncer │ │
|
|
162
180
|
│ │ Cursor│Claude│Codex│Windsurf │ │
|
|
163
|
-
│ │ scan
|
|
181
|
+
│ │ rules: scan→dedup→conflict→gen │ │
|
|
182
|
+
│ │ skills: scan→copy (no convert) │ │
|
|
183
|
+
│ └────────────────────────────────────┘ │
|
|
184
|
+
│ │
|
|
185
|
+
│ ┌────────────────────────────────────┐ │
|
|
186
|
+
│ │ Auto-Memory Hooks │ │
|
|
187
|
+
│ │ normalize→detect→filter→store │ │
|
|
188
|
+
│ │ + agent rules (context cont.) │ │
|
|
164
189
|
│ └────────────────────────────────────┘ │
|
|
165
190
|
└──────────────────────────────────────────────┘
|
|
166
191
|
```
|
|
@@ -179,7 +204,7 @@ npm install memorix
|
|
|
179
204
|
| Entity extraction | Regex patterns | MemCP |
|
|
180
205
|
| Rule parsing | `gray-matter` | — |
|
|
181
206
|
| Build | `tsup` | — |
|
|
182
|
-
| Test | `vitest` |
|
|
207
|
+
| Test | `vitest` | 219 tests |
|
|
183
208
|
|
|
184
209
|
## Optional: Enable Vector Search
|
|
185
210
|
|
|
@@ -200,7 +225,7 @@ npm install
|
|
|
200
225
|
# Build
|
|
201
226
|
npm run build
|
|
202
227
|
|
|
203
|
-
# Run tests (
|
|
228
|
+
# Run tests (219 tests)
|
|
204
229
|
npm test
|
|
205
230
|
|
|
206
231
|
# Type check
|
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, conflicts: skillConflicts } = this.scanSkills();
|
|
2315
|
+
return { mcpConfigs, workflows, rulesCount, skills, skillConflicts };
|
|
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: [], conflicts: [], copied: [], skipped: [] }
|
|
2314
2327
|
};
|
|
2315
2328
|
const allServers = /* @__PURE__ */ new Map();
|
|
2316
2329
|
for (const servers of Object.values(scan.mcpConfigs)) {
|
|
@@ -2347,9 +2360,103 @@ var init_engine2 = __esm({
|
|
|
2347
2360
|
}
|
|
2348
2361
|
} catch {
|
|
2349
2362
|
}
|
|
2363
|
+
result.skills.scanned = scan.skills;
|
|
2364
|
+
result.skills.conflicts = scan.skillConflicts;
|
|
2350
2365
|
return result;
|
|
2351
2366
|
}
|
|
2352
2367
|
// ---- Private helpers ----
|
|
2368
|
+
/** Skills directories per agent */
|
|
2369
|
+
static SKILLS_DIRS = {
|
|
2370
|
+
codex: [".codex/skills", ".agents/skills"],
|
|
2371
|
+
cursor: [".cursor/skills", ".cursor/skills-cursor"],
|
|
2372
|
+
windsurf: [".windsurf/skills"],
|
|
2373
|
+
"claude-code": [".claude/skills"]
|
|
2374
|
+
};
|
|
2375
|
+
/** Get the target skills directory for an agent */
|
|
2376
|
+
getTargetSkillsDir(target) {
|
|
2377
|
+
const dirs = _WorkspaceSyncEngine.SKILLS_DIRS[target];
|
|
2378
|
+
return join6(this.projectRoot, dirs[0]);
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Scan all agent skills directories and collect unique skills.
|
|
2382
|
+
*/
|
|
2383
|
+
scanSkills() {
|
|
2384
|
+
const skills = [];
|
|
2385
|
+
const conflicts = [];
|
|
2386
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2387
|
+
const home = homedir5();
|
|
2388
|
+
for (const [agent, dirs] of Object.entries(_WorkspaceSyncEngine.SKILLS_DIRS)) {
|
|
2389
|
+
for (const dir of dirs) {
|
|
2390
|
+
const paths = [
|
|
2391
|
+
join6(this.projectRoot, dir),
|
|
2392
|
+
join6(home, dir)
|
|
2393
|
+
];
|
|
2394
|
+
for (const skillsRoot of paths) {
|
|
2395
|
+
if (!existsSync3(skillsRoot)) continue;
|
|
2396
|
+
try {
|
|
2397
|
+
const entries = readdirSync(skillsRoot, { withFileTypes: true });
|
|
2398
|
+
for (const entry of entries) {
|
|
2399
|
+
if (!entry.isDirectory()) continue;
|
|
2400
|
+
const skillMd = join6(skillsRoot, entry.name, "SKILL.md");
|
|
2401
|
+
if (!existsSync3(skillMd)) continue;
|
|
2402
|
+
let description = "";
|
|
2403
|
+
try {
|
|
2404
|
+
const content = readFileSync(skillMd, "utf-8");
|
|
2405
|
+
const match = content.match(/^---[\s\S]*?description:\s*["']?(.+?)["']?\s*$/m);
|
|
2406
|
+
if (match) description = match[1];
|
|
2407
|
+
} catch {
|
|
2408
|
+
}
|
|
2409
|
+
const newEntry = {
|
|
2410
|
+
name: entry.name,
|
|
2411
|
+
description,
|
|
2412
|
+
sourcePath: join6(skillsRoot, entry.name),
|
|
2413
|
+
sourceAgent: agent
|
|
2414
|
+
};
|
|
2415
|
+
const existing = seen.get(entry.name);
|
|
2416
|
+
if (existing) {
|
|
2417
|
+
if (existing.sourceAgent !== agent) {
|
|
2418
|
+
conflicts.push({
|
|
2419
|
+
name: entry.name,
|
|
2420
|
+
kept: existing,
|
|
2421
|
+
skipped: newEntry
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
continue;
|
|
2425
|
+
}
|
|
2426
|
+
seen.set(entry.name, newEntry);
|
|
2427
|
+
skills.push(newEntry);
|
|
2428
|
+
}
|
|
2429
|
+
} catch {
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
return { skills, conflicts };
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Copy skills to a target agent's skills directory.
|
|
2438
|
+
* Returns list of copied skill names.
|
|
2439
|
+
*/
|
|
2440
|
+
copySkills(skills, target) {
|
|
2441
|
+
const targetDir = this.getTargetSkillsDir(target);
|
|
2442
|
+
const copied = [];
|
|
2443
|
+
const skipped = [];
|
|
2444
|
+
for (const skill of skills) {
|
|
2445
|
+
if (skill.sourceAgent === target) continue;
|
|
2446
|
+
const dest = join6(targetDir, skill.name);
|
|
2447
|
+
if (existsSync3(dest)) {
|
|
2448
|
+
skipped.push(`${skill.name} (already exists in ${target})`);
|
|
2449
|
+
continue;
|
|
2450
|
+
}
|
|
2451
|
+
try {
|
|
2452
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
2453
|
+
cpSync(skill.sourcePath, dest, { recursive: true });
|
|
2454
|
+
copied.push(skill.name);
|
|
2455
|
+
} catch {
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
return { copied, skipped };
|
|
2459
|
+
}
|
|
2353
2460
|
scanWorkflows() {
|
|
2354
2461
|
const workflows = [];
|
|
2355
2462
|
const wfDir = join6(this.projectRoot, ".windsurf", "workflows");
|
|
@@ -2384,12 +2491,37 @@ var init_engine2 = __esm({
|
|
|
2384
2491
|
...syncResult.workflows.generated
|
|
2385
2492
|
];
|
|
2386
2493
|
const applyResult = await applier.apply(filesToWrite);
|
|
2494
|
+
let skillResult = { copied: [], skipped: [] };
|
|
2495
|
+
if (syncResult.skills.scanned.length > 0) {
|
|
2496
|
+
skillResult = this.copySkills(syncResult.skills.scanned, target);
|
|
2497
|
+
}
|
|
2387
2498
|
const lines = [];
|
|
2388
2499
|
if (applyResult.success) {
|
|
2389
2500
|
lines.push(`\u2705 Applied ${applyResult.filesWritten.length} file(s) for ${target}`);
|
|
2390
2501
|
for (const f of applyResult.filesWritten) {
|
|
2391
2502
|
lines.push(` \u2192 ${f}`);
|
|
2392
2503
|
}
|
|
2504
|
+
if (skillResult.copied.length > 0) {
|
|
2505
|
+
lines.push(`
|
|
2506
|
+
\u{1F9E9} Copied ${skillResult.copied.length} skill(s):`);
|
|
2507
|
+
for (const sk of skillResult.copied) {
|
|
2508
|
+
lines.push(` \u2192 ${sk}`);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
if (skillResult.skipped.length > 0) {
|
|
2512
|
+
lines.push(`
|
|
2513
|
+
\u23ED\uFE0F Skipped ${skillResult.skipped.length} skill(s):`);
|
|
2514
|
+
for (const sk of skillResult.skipped) {
|
|
2515
|
+
lines.push(` \u2192 ${sk}`);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
if (syncResult.skills.conflicts.length > 0) {
|
|
2519
|
+
lines.push(`
|
|
2520
|
+
\u26A0\uFE0F Name conflicts (${syncResult.skills.conflicts.length}):`);
|
|
2521
|
+
for (const c of syncResult.skills.conflicts) {
|
|
2522
|
+
lines.push(` \u2192 "${c.name}": kept ${c.kept.sourceAgent}, skipped ${c.skipped.sourceAgent}`);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2393
2525
|
if (applyResult.backups.length > 0) {
|
|
2394
2526
|
lines.push(`
|
|
2395
2527
|
\u{1F4E6} Backups created (${applyResult.backups.length}):`);
|
|
@@ -2438,10 +2570,33 @@ __export(installers_exports, {
|
|
|
2438
2570
|
import * as fs3 from "fs/promises";
|
|
2439
2571
|
import * as path5 from "path";
|
|
2440
2572
|
import * as os2 from "os";
|
|
2573
|
+
import { createRequire } from "module";
|
|
2574
|
+
function resolveHookCommand() {
|
|
2575
|
+
if (process.platform === "win32") {
|
|
2576
|
+
try {
|
|
2577
|
+
const devPath = path5.resolve(import.meta.dirname ?? __dirname, "../../cli/index.js");
|
|
2578
|
+
try {
|
|
2579
|
+
const fsStat = __require("fs");
|
|
2580
|
+
if (fsStat.existsSync(devPath)) {
|
|
2581
|
+
return `node ${devPath.replace(/\\/g, "/")}`;
|
|
2582
|
+
}
|
|
2583
|
+
} catch {
|
|
2584
|
+
}
|
|
2585
|
+
const require_ = createRequire(import.meta.url);
|
|
2586
|
+
const pkgPath = require_.resolve("memorix/package.json");
|
|
2587
|
+
const cliPath = path5.join(path5.dirname(pkgPath), "dist", "cli", "index.js");
|
|
2588
|
+
return `node ${cliPath.replace(/\\/g, "/")}`;
|
|
2589
|
+
} catch {
|
|
2590
|
+
return "memorix";
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
return "memorix";
|
|
2594
|
+
}
|
|
2441
2595
|
function generateClaudeConfig() {
|
|
2596
|
+
const cmd = `${resolveHookCommand()} hook`;
|
|
2442
2597
|
const hookEntry = {
|
|
2443
2598
|
type: "command",
|
|
2444
|
-
command:
|
|
2599
|
+
command: cmd,
|
|
2445
2600
|
timeout: 10
|
|
2446
2601
|
};
|
|
2447
2602
|
return {
|
|
@@ -2455,9 +2610,10 @@ function generateClaudeConfig() {
|
|
|
2455
2610
|
};
|
|
2456
2611
|
}
|
|
2457
2612
|
function generateWindsurfConfig() {
|
|
2613
|
+
const cmd = `${resolveHookCommand()} hook`;
|
|
2458
2614
|
const hookEntry = {
|
|
2459
|
-
command:
|
|
2460
|
-
|
|
2615
|
+
command: cmd,
|
|
2616
|
+
show_output: false
|
|
2461
2617
|
};
|
|
2462
2618
|
return {
|
|
2463
2619
|
hooks: {
|
|
@@ -2470,16 +2626,17 @@ function generateWindsurfConfig() {
|
|
|
2470
2626
|
};
|
|
2471
2627
|
}
|
|
2472
2628
|
function generateCursorConfig() {
|
|
2629
|
+
const cmd = `${resolveHookCommand()} hook`;
|
|
2473
2630
|
return {
|
|
2474
2631
|
hooks: {
|
|
2475
2632
|
beforeSubmitPrompt: {
|
|
2476
|
-
command:
|
|
2633
|
+
command: cmd
|
|
2477
2634
|
},
|
|
2478
2635
|
afterFileEdit: {
|
|
2479
|
-
command:
|
|
2636
|
+
command: cmd
|
|
2480
2637
|
},
|
|
2481
2638
|
stop: {
|
|
2482
|
-
command:
|
|
2639
|
+
command: cmd
|
|
2483
2640
|
}
|
|
2484
2641
|
}
|
|
2485
2642
|
};
|
|
@@ -2495,7 +2652,7 @@ filePattern: "**/*"
|
|
|
2495
2652
|
Run the memorix hook command to analyze changes and store relevant memories:
|
|
2496
2653
|
|
|
2497
2654
|
\`\`\`bash
|
|
2498
|
-
|
|
2655
|
+
${resolveHookCommand()} hook
|
|
2499
2656
|
\`\`\`
|
|
2500
2657
|
`;
|
|
2501
2658
|
}
|
|
@@ -2619,6 +2776,7 @@ async function installHooks(agent, projectRoot, global = false) {
|
|
|
2619
2776
|
events.push("post_edit");
|
|
2620
2777
|
break;
|
|
2621
2778
|
}
|
|
2779
|
+
await installAgentRules(agent, projectRoot);
|
|
2622
2780
|
return {
|
|
2623
2781
|
agent,
|
|
2624
2782
|
configPath,
|
|
@@ -2626,6 +2784,79 @@ async function installHooks(agent, projectRoot, global = false) {
|
|
|
2626
2784
|
generated: typeof generated === "string" ? { content: generated } : generated
|
|
2627
2785
|
};
|
|
2628
2786
|
}
|
|
2787
|
+
async function installAgentRules(agent, projectRoot) {
|
|
2788
|
+
const rulesContent = getAgentRulesContent();
|
|
2789
|
+
let rulesPath;
|
|
2790
|
+
switch (agent) {
|
|
2791
|
+
case "windsurf":
|
|
2792
|
+
rulesPath = path5.join(projectRoot, ".windsurf", "rules", "memorix.md");
|
|
2793
|
+
break;
|
|
2794
|
+
case "cursor":
|
|
2795
|
+
rulesPath = path5.join(projectRoot, ".cursor", "rules", "memorix.mdc");
|
|
2796
|
+
break;
|
|
2797
|
+
case "claude":
|
|
2798
|
+
case "copilot":
|
|
2799
|
+
rulesPath = path5.join(projectRoot, ".github", "copilot-instructions.md");
|
|
2800
|
+
break;
|
|
2801
|
+
default:
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
try {
|
|
2805
|
+
await fs3.mkdir(path5.dirname(rulesPath), { recursive: true });
|
|
2806
|
+
try {
|
|
2807
|
+
await fs3.access(rulesPath);
|
|
2808
|
+
} catch {
|
|
2809
|
+
await fs3.writeFile(rulesPath, rulesContent, "utf-8");
|
|
2810
|
+
}
|
|
2811
|
+
} catch {
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
function getAgentRulesContent() {
|
|
2815
|
+
return `# Memorix \u2014 Automatic Memory Rules
|
|
2816
|
+
|
|
2817
|
+
You have access to Memorix memory tools. Follow these rules to maintain persistent context across sessions.
|
|
2818
|
+
|
|
2819
|
+
## Session Start \u2014 Load Context
|
|
2820
|
+
|
|
2821
|
+
At the **beginning of every conversation**, before responding to the user:
|
|
2822
|
+
|
|
2823
|
+
1. Call \`memorix_search\` with query related to the user's first message or the current project
|
|
2824
|
+
2. If results are found, use them to understand the current project state, recent decisions, and pending tasks
|
|
2825
|
+
3. Reference relevant memories naturally in your response
|
|
2826
|
+
|
|
2827
|
+
This ensures you already know the project context without the user re-explaining.
|
|
2828
|
+
|
|
2829
|
+
## During Session \u2014 Capture Important Context
|
|
2830
|
+
|
|
2831
|
+
Proactively call \`memorix_store\` when any of the following happen:
|
|
2832
|
+
|
|
2833
|
+
- **Architecture decision**: You or the user decide on a technology, pattern, or approach
|
|
2834
|
+
- **Bug fix**: A bug is identified and resolved \u2014 store the root cause and fix
|
|
2835
|
+
- **Gotcha/pitfall**: Something unexpected or tricky is discovered
|
|
2836
|
+
- **Configuration change**: Environment, port, path, or tooling changes
|
|
2837
|
+
|
|
2838
|
+
Use appropriate types: \`decision\`, \`problem-solution\`, \`gotcha\`, \`what-changed\`, \`discovery\`.
|
|
2839
|
+
|
|
2840
|
+
## Session End \u2014 Store Summary
|
|
2841
|
+
|
|
2842
|
+
When the conversation is ending or the user says goodbye:
|
|
2843
|
+
|
|
2844
|
+
1. Call \`memorix_store\` with type \`session-request\` to record:
|
|
2845
|
+
- What was accomplished in this session
|
|
2846
|
+
- Current project state
|
|
2847
|
+
- Pending tasks or next steps
|
|
2848
|
+
- Any unresolved issues
|
|
2849
|
+
|
|
2850
|
+
This creates a "handoff note" for the next session.
|
|
2851
|
+
|
|
2852
|
+
## Guidelines
|
|
2853
|
+
|
|
2854
|
+
- **Don't store trivial information** (greetings, acknowledgments, simple file reads)
|
|
2855
|
+
- **Do store anything you'd want to know if you lost all context**
|
|
2856
|
+
- **Use concise titles** and structured facts
|
|
2857
|
+
- **Include file paths** in filesModified when relevant
|
|
2858
|
+
`;
|
|
2859
|
+
}
|
|
2629
2860
|
async function uninstallHooks(agent, projectRoot, global = false) {
|
|
2630
2861
|
const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
|
|
2631
2862
|
try {
|
|
@@ -2669,13 +2900,10 @@ async function getHookStatus(projectRoot) {
|
|
|
2669
2900
|
}
|
|
2670
2901
|
return results;
|
|
2671
2902
|
}
|
|
2672
|
-
var HOOK_COMMAND, HOOK_ARGS;
|
|
2673
2903
|
var init_installers = __esm({
|
|
2674
2904
|
"src/hooks/installers/index.ts"() {
|
|
2675
2905
|
"use strict";
|
|
2676
2906
|
init_esm_shims();
|
|
2677
|
-
HOOK_COMMAND = "memorix";
|
|
2678
|
-
HOOK_ARGS = ["hook"];
|
|
2679
2907
|
}
|
|
2680
2908
|
});
|
|
2681
2909
|
|
|
@@ -3271,7 +3499,7 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
3271
3499
|
"memorix_workspace_sync",
|
|
3272
3500
|
{
|
|
3273
3501
|
title: "Workspace Sync",
|
|
3274
|
-
description: 'Migrate your entire workspace environment between AI agents. Syncs MCP server configs, workflows, and
|
|
3502
|
+
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.',
|
|
3275
3503
|
inputSchema: {
|
|
3276
3504
|
action: z.enum(["scan", "migrate", "apply"]).describe('Action: "scan" to detect configs, "migrate" to preview, "apply" to write to disk'),
|
|
3277
3505
|
target: z.enum(AGENT_TARGETS).optional().describe("Target agent for migration (required for migrate)")
|
|
@@ -3301,6 +3529,20 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
3301
3529
|
}
|
|
3302
3530
|
lines2.push("", `### Rules`);
|
|
3303
3531
|
lines2.push(`- ${scan.rulesCount} rule(s) detected across all agents`);
|
|
3532
|
+
lines2.push("", `### Skills`);
|
|
3533
|
+
if (scan.skills.length > 0) {
|
|
3534
|
+
for (const sk of scan.skills) {
|
|
3535
|
+
lines2.push(`- **${sk.name}** (${sk.sourceAgent}): ${sk.description || "(no description)"}`);
|
|
3536
|
+
}
|
|
3537
|
+
} else {
|
|
3538
|
+
lines2.push("- No skills found");
|
|
3539
|
+
}
|
|
3540
|
+
if (scan.skillConflicts.length > 0) {
|
|
3541
|
+
lines2.push("", `### \u26A0\uFE0F Skill Name Conflicts`);
|
|
3542
|
+
for (const c of scan.skillConflicts) {
|
|
3543
|
+
lines2.push(`- **${c.name}**: kept from ${c.kept.sourceAgent}, duplicate in ${c.skipped.sourceAgent}`);
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3304
3546
|
return {
|
|
3305
3547
|
content: [{ type: "text", text: lines2.join("\n") }]
|
|
3306
3548
|
};
|
|
@@ -3338,6 +3580,12 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
3338
3580
|
if (result.rules.generated > 0) {
|
|
3339
3581
|
lines.push(`### Rules`, `- ${result.rules.generated} rule file(s) generated`);
|
|
3340
3582
|
}
|
|
3583
|
+
if (result.skills.scanned.length > 0) {
|
|
3584
|
+
lines.push("### Skills", `- ${result.skills.scanned.length} skill(s) found, ready to copy:`);
|
|
3585
|
+
for (const sk of result.skills.scanned) {
|
|
3586
|
+
lines.push(` - **${sk.name}** (from ${sk.sourceAgent})`);
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3341
3589
|
lines.push("", '> Review the generated configs above. Use action "apply" to write them to disk.');
|
|
3342
3590
|
return {
|
|
3343
3591
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
@@ -3844,9 +4092,10 @@ var init_pattern_detector = __esm({
|
|
|
3844
4092
|
{
|
|
3845
4093
|
type: "configuration",
|
|
3846
4094
|
keywords: [
|
|
3847
|
-
/\b(config(ured?|uration)?|setting|environment
|
|
3848
|
-
/(
|
|
3849
|
-
/\b(gradle|webpack|vite|tsconfig|package\.json|docker)\b/i
|
|
4095
|
+
/\b(config(ured?|uration)?|setting|environment|\.env)\b/i,
|
|
4096
|
+
/(配置|环境变量|端口配置|设置项|安装配置)/,
|
|
4097
|
+
/\b(gradle|webpack|vite|tsconfig|package\.json|docker|nginx)\b/i,
|
|
4098
|
+
/\b(port\s*[:=]|listen\s+\d|bind\s+\d|DATABASE_URL|API_KEY)\b/i
|
|
3850
4099
|
],
|
|
3851
4100
|
minLength: 80,
|
|
3852
4101
|
baseConfidence: 0.7
|
|
@@ -3974,24 +4223,75 @@ async function handleHookEvent(input) {
|
|
|
3974
4223
|
observation: buildObservation(input, extractContent(input)),
|
|
3975
4224
|
output: defaultOutput
|
|
3976
4225
|
};
|
|
3977
|
-
case "post_edit":
|
|
3978
|
-
|
|
3979
|
-
|
|
4226
|
+
case "post_edit": {
|
|
4227
|
+
const editKey = `post_edit:${input.filePath ?? "general"}`;
|
|
4228
|
+
if (isInCooldown(editKey)) {
|
|
4229
|
+
return { observation: null, output: defaultOutput };
|
|
4230
|
+
}
|
|
4231
|
+
const editContent = extractContent(input);
|
|
4232
|
+
if (editContent.length < MIN_EDIT_LENGTH) {
|
|
4233
|
+
return { observation: null, output: defaultOutput };
|
|
4234
|
+
}
|
|
4235
|
+
const editPattern = detectBestPattern(editContent, 0.6);
|
|
4236
|
+
if (!editPattern) {
|
|
4237
|
+
return { observation: null, output: defaultOutput };
|
|
4238
|
+
}
|
|
4239
|
+
markTriggered(editKey);
|
|
4240
|
+
return {
|
|
4241
|
+
observation: buildObservation(input, editContent),
|
|
4242
|
+
output: defaultOutput
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
case "post_command": {
|
|
4246
|
+
if (input.command && NOISE_COMMANDS.some((r) => r.test(input.command))) {
|
|
4247
|
+
return { observation: null, output: defaultOutput };
|
|
4248
|
+
}
|
|
4249
|
+
const cmdKey = `post_command:${input.command ?? "general"}`;
|
|
4250
|
+
if (isInCooldown(cmdKey)) {
|
|
4251
|
+
return { observation: null, output: defaultOutput };
|
|
4252
|
+
}
|
|
4253
|
+
const cmdContent = input.commandOutput || extractContent(input);
|
|
4254
|
+
if (cmdContent.length < MIN_STORE_LENGTH) {
|
|
4255
|
+
return { observation: null, output: defaultOutput };
|
|
4256
|
+
}
|
|
4257
|
+
detectBestPattern(cmdContent);
|
|
4258
|
+
markTriggered(cmdKey);
|
|
4259
|
+
return {
|
|
4260
|
+
observation: buildObservation(input, cmdContent),
|
|
4261
|
+
output: defaultOutput
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
case "post_tool": {
|
|
4265
|
+
const toolKey = `post_tool:${input.toolName ?? "general"}`;
|
|
4266
|
+
if (isInCooldown(toolKey)) {
|
|
4267
|
+
return { observation: null, output: defaultOutput };
|
|
4268
|
+
}
|
|
4269
|
+
const toolContent = extractContent(input);
|
|
4270
|
+
if (toolContent.length < MIN_STORE_LENGTH) {
|
|
4271
|
+
return { observation: null, output: defaultOutput };
|
|
4272
|
+
}
|
|
4273
|
+
const toolPattern = detectBestPattern(toolContent);
|
|
4274
|
+
if (!toolPattern) {
|
|
4275
|
+
return { observation: null, output: defaultOutput };
|
|
4276
|
+
}
|
|
4277
|
+
markTriggered(toolKey);
|
|
4278
|
+
return {
|
|
4279
|
+
observation: buildObservation(input, toolContent),
|
|
4280
|
+
output: defaultOutput
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
3980
4283
|
case "post_response":
|
|
3981
4284
|
case "user_prompt": {
|
|
3982
|
-
const
|
|
3983
|
-
if (isInCooldown(
|
|
4285
|
+
const promptKey = `${input.event}:${input.sessionId ?? "general"}`;
|
|
4286
|
+
if (isInCooldown(promptKey)) {
|
|
3984
4287
|
return { observation: null, output: defaultOutput };
|
|
3985
4288
|
}
|
|
3986
4289
|
const content = extractContent(input);
|
|
3987
4290
|
if (content.length < MIN_STORE_LENGTH) {
|
|
3988
4291
|
return { observation: null, output: defaultOutput };
|
|
3989
4292
|
}
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
return { observation: null, output: defaultOutput };
|
|
3993
|
-
}
|
|
3994
|
-
markTriggered(cooldownKey);
|
|
4293
|
+
detectBestPattern(content);
|
|
4294
|
+
markTriggered(promptKey);
|
|
3995
4295
|
return {
|
|
3996
4296
|
observation: buildObservation(input, content),
|
|
3997
4297
|
output: defaultOutput
|
|
@@ -4034,7 +4334,7 @@ async function runHook() {
|
|
|
4034
4334
|
}
|
|
4035
4335
|
process.stdout.write(JSON.stringify(output));
|
|
4036
4336
|
}
|
|
4037
|
-
var cooldowns, COOLDOWN_MS, MIN_STORE_LENGTH, MAX_CONTENT_LENGTH;
|
|
4337
|
+
var cooldowns, COOLDOWN_MS, MIN_STORE_LENGTH, MIN_EDIT_LENGTH, NOISE_COMMANDS, MAX_CONTENT_LENGTH;
|
|
4038
4338
|
var init_handler = __esm({
|
|
4039
4339
|
"src/hooks/handler.ts"() {
|
|
4040
4340
|
"use strict";
|
|
@@ -4044,6 +4344,14 @@ var init_handler = __esm({
|
|
|
4044
4344
|
cooldowns = /* @__PURE__ */ new Map();
|
|
4045
4345
|
COOLDOWN_MS = 3e4;
|
|
4046
4346
|
MIN_STORE_LENGTH = 100;
|
|
4347
|
+
MIN_EDIT_LENGTH = 30;
|
|
4348
|
+
NOISE_COMMANDS = [
|
|
4349
|
+
/^(ls|dir|cd|pwd|echo|cat|type|head|tail|wc|find|which|where|whoami)\b/i,
|
|
4350
|
+
/^(Get-Content|Test-Path|Get-Item|Get-ChildItem|Set-Location|Write-Host)\b/i,
|
|
4351
|
+
/^(Start-Sleep|Select-String|Select-Object|Format-Table|Measure-Object)\b/i,
|
|
4352
|
+
/^(mkdir|rm|cp|mv|touch|chmod|chown)\b/i,
|
|
4353
|
+
/^(node -[ep]|python -c)\b/i
|
|
4354
|
+
];
|
|
4047
4355
|
MAX_CONTENT_LENGTH = 4e3;
|
|
4048
4356
|
}
|
|
4049
4357
|
});
|