memorix 0.2.3 → 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 +299 -29
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +225 -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 = 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}):`);
|
|
@@ -2438,10 +2538,33 @@ __export(installers_exports, {
|
|
|
2438
2538
|
import * as fs3 from "fs/promises";
|
|
2439
2539
|
import * as path5 from "path";
|
|
2440
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";
|
|
2562
|
+
}
|
|
2441
2563
|
function generateClaudeConfig() {
|
|
2564
|
+
const cmd = `${resolveHookCommand()} hook`;
|
|
2442
2565
|
const hookEntry = {
|
|
2443
2566
|
type: "command",
|
|
2444
|
-
command:
|
|
2567
|
+
command: cmd,
|
|
2445
2568
|
timeout: 10
|
|
2446
2569
|
};
|
|
2447
2570
|
return {
|
|
@@ -2455,9 +2578,10 @@ function generateClaudeConfig() {
|
|
|
2455
2578
|
};
|
|
2456
2579
|
}
|
|
2457
2580
|
function generateWindsurfConfig() {
|
|
2581
|
+
const cmd = `${resolveHookCommand()} hook`;
|
|
2458
2582
|
const hookEntry = {
|
|
2459
|
-
command:
|
|
2460
|
-
|
|
2583
|
+
command: cmd,
|
|
2584
|
+
show_output: false
|
|
2461
2585
|
};
|
|
2462
2586
|
return {
|
|
2463
2587
|
hooks: {
|
|
@@ -2470,16 +2594,17 @@ function generateWindsurfConfig() {
|
|
|
2470
2594
|
};
|
|
2471
2595
|
}
|
|
2472
2596
|
function generateCursorConfig() {
|
|
2597
|
+
const cmd = `${resolveHookCommand()} hook`;
|
|
2473
2598
|
return {
|
|
2474
2599
|
hooks: {
|
|
2475
2600
|
beforeSubmitPrompt: {
|
|
2476
|
-
command:
|
|
2601
|
+
command: cmd
|
|
2477
2602
|
},
|
|
2478
2603
|
afterFileEdit: {
|
|
2479
|
-
command:
|
|
2604
|
+
command: cmd
|
|
2480
2605
|
},
|
|
2481
2606
|
stop: {
|
|
2482
|
-
command:
|
|
2607
|
+
command: cmd
|
|
2483
2608
|
}
|
|
2484
2609
|
}
|
|
2485
2610
|
};
|
|
@@ -2495,7 +2620,7 @@ filePattern: "**/*"
|
|
|
2495
2620
|
Run the memorix hook command to analyze changes and store relevant memories:
|
|
2496
2621
|
|
|
2497
2622
|
\`\`\`bash
|
|
2498
|
-
|
|
2623
|
+
${resolveHookCommand()} hook
|
|
2499
2624
|
\`\`\`
|
|
2500
2625
|
`;
|
|
2501
2626
|
}
|
|
@@ -2619,6 +2744,7 @@ async function installHooks(agent, projectRoot, global = false) {
|
|
|
2619
2744
|
events.push("post_edit");
|
|
2620
2745
|
break;
|
|
2621
2746
|
}
|
|
2747
|
+
await installAgentRules(agent, projectRoot);
|
|
2622
2748
|
return {
|
|
2623
2749
|
agent,
|
|
2624
2750
|
configPath,
|
|
@@ -2626,6 +2752,79 @@ async function installHooks(agent, projectRoot, global = false) {
|
|
|
2626
2752
|
generated: typeof generated === "string" ? { content: generated } : generated
|
|
2627
2753
|
};
|
|
2628
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
|
+
}
|
|
2629
2828
|
async function uninstallHooks(agent, projectRoot, global = false) {
|
|
2630
2829
|
const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
|
|
2631
2830
|
try {
|
|
@@ -2669,13 +2868,10 @@ async function getHookStatus(projectRoot) {
|
|
|
2669
2868
|
}
|
|
2670
2869
|
return results;
|
|
2671
2870
|
}
|
|
2672
|
-
var HOOK_COMMAND, HOOK_ARGS;
|
|
2673
2871
|
var init_installers = __esm({
|
|
2674
2872
|
"src/hooks/installers/index.ts"() {
|
|
2675
2873
|
"use strict";
|
|
2676
2874
|
init_esm_shims();
|
|
2677
|
-
HOOK_COMMAND = "memorix";
|
|
2678
|
-
HOOK_ARGS = ["hook"];
|
|
2679
2875
|
}
|
|
2680
2876
|
});
|
|
2681
2877
|
|
|
@@ -3271,7 +3467,7 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
3271
3467
|
"memorix_workspace_sync",
|
|
3272
3468
|
{
|
|
3273
3469
|
title: "Workspace Sync",
|
|
3274
|
-
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.',
|
|
3275
3471
|
inputSchema: {
|
|
3276
3472
|
action: z.enum(["scan", "migrate", "apply"]).describe('Action: "scan" to detect configs, "migrate" to preview, "apply" to write to disk'),
|
|
3277
3473
|
target: z.enum(AGENT_TARGETS).optional().describe("Target agent for migration (required for migrate)")
|
|
@@ -3301,6 +3497,14 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
3301
3497
|
}
|
|
3302
3498
|
lines2.push("", `### Rules`);
|
|
3303
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
|
+
}
|
|
3304
3508
|
return {
|
|
3305
3509
|
content: [{ type: "text", text: lines2.join("\n") }]
|
|
3306
3510
|
};
|
|
@@ -3338,6 +3542,12 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
3338
3542
|
if (result.rules.generated > 0) {
|
|
3339
3543
|
lines.push(`### Rules`, `- ${result.rules.generated} rule file(s) generated`);
|
|
3340
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
|
+
}
|
|
3341
3551
|
lines.push("", '> Review the generated configs above. Use action "apply" to write them to disk.');
|
|
3342
3552
|
return {
|
|
3343
3553
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
@@ -3844,9 +4054,10 @@ var init_pattern_detector = __esm({
|
|
|
3844
4054
|
{
|
|
3845
4055
|
type: "configuration",
|
|
3846
4056
|
keywords: [
|
|
3847
|
-
/\b(config(ured?|uration)?|setting|environment
|
|
3848
|
-
/(
|
|
3849
|
-
/\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
|
|
3850
4061
|
],
|
|
3851
4062
|
minLength: 80,
|
|
3852
4063
|
baseConfidence: 0.7
|
|
@@ -3974,24 +4185,75 @@ async function handleHookEvent(input) {
|
|
|
3974
4185
|
observation: buildObservation(input, extractContent(input)),
|
|
3975
4186
|
output: defaultOutput
|
|
3976
4187
|
};
|
|
3977
|
-
case "post_edit":
|
|
3978
|
-
|
|
3979
|
-
|
|
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),
|
|
4242
|
+
output: defaultOutput
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
3980
4245
|
case "post_response":
|
|
3981
4246
|
case "user_prompt": {
|
|
3982
|
-
const
|
|
3983
|
-
if (isInCooldown(
|
|
4247
|
+
const promptKey = `${input.event}:${input.sessionId ?? "general"}`;
|
|
4248
|
+
if (isInCooldown(promptKey)) {
|
|
3984
4249
|
return { observation: null, output: defaultOutput };
|
|
3985
4250
|
}
|
|
3986
4251
|
const content = extractContent(input);
|
|
3987
4252
|
if (content.length < MIN_STORE_LENGTH) {
|
|
3988
4253
|
return { observation: null, output: defaultOutput };
|
|
3989
4254
|
}
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
return { observation: null, output: defaultOutput };
|
|
3993
|
-
}
|
|
3994
|
-
markTriggered(cooldownKey);
|
|
4255
|
+
detectBestPattern(content);
|
|
4256
|
+
markTriggered(promptKey);
|
|
3995
4257
|
return {
|
|
3996
4258
|
observation: buildObservation(input, content),
|
|
3997
4259
|
output: defaultOutput
|
|
@@ -4034,7 +4296,7 @@ async function runHook() {
|
|
|
4034
4296
|
}
|
|
4035
4297
|
process.stdout.write(JSON.stringify(output));
|
|
4036
4298
|
}
|
|
4037
|
-
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;
|
|
4038
4300
|
var init_handler = __esm({
|
|
4039
4301
|
"src/hooks/handler.ts"() {
|
|
4040
4302
|
"use strict";
|
|
@@ -4044,6 +4306,14 @@ var init_handler = __esm({
|
|
|
4044
4306
|
cooldowns = /* @__PURE__ */ new Map();
|
|
4045
4307
|
COOLDOWN_MS = 3e4;
|
|
4046
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
|
+
];
|
|
4047
4317
|
MAX_CONTENT_LENGTH = 4e3;
|
|
4048
4318
|
}
|
|
4049
4319
|
});
|