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/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
|
|
|
@@ -372,6 +382,354 @@ var init_orama_store = __esm({
|
|
|
372
382
|
}
|
|
373
383
|
});
|
|
374
384
|
|
|
385
|
+
// src/hooks/installers/index.ts
|
|
386
|
+
var installers_exports = {};
|
|
387
|
+
__export(installers_exports, {
|
|
388
|
+
detectInstalledAgents: () => detectInstalledAgents,
|
|
389
|
+
getHookStatus: () => getHookStatus,
|
|
390
|
+
installHooks: () => installHooks,
|
|
391
|
+
uninstallHooks: () => uninstallHooks
|
|
392
|
+
});
|
|
393
|
+
import * as fs3 from "fs/promises";
|
|
394
|
+
import * as path5 from "path";
|
|
395
|
+
import * as os2 from "os";
|
|
396
|
+
import { createRequire } from "module";
|
|
397
|
+
function resolveHookCommand() {
|
|
398
|
+
if (process.platform === "win32") {
|
|
399
|
+
try {
|
|
400
|
+
const devPath = path5.resolve(import.meta.dirname ?? __dirname, "../../cli/index.js");
|
|
401
|
+
try {
|
|
402
|
+
const fsStat = __require("fs");
|
|
403
|
+
if (fsStat.existsSync(devPath)) {
|
|
404
|
+
return `node ${devPath.replace(/\\/g, "/")}`;
|
|
405
|
+
}
|
|
406
|
+
} catch {
|
|
407
|
+
}
|
|
408
|
+
const require_ = createRequire(import.meta.url);
|
|
409
|
+
const pkgPath = require_.resolve("memorix/package.json");
|
|
410
|
+
const cliPath = path5.join(path5.dirname(pkgPath), "dist", "cli", "index.js");
|
|
411
|
+
return `node ${cliPath.replace(/\\/g, "/")}`;
|
|
412
|
+
} catch {
|
|
413
|
+
return "memorix";
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return "memorix";
|
|
417
|
+
}
|
|
418
|
+
function generateClaudeConfig() {
|
|
419
|
+
const cmd = `${resolveHookCommand()} hook`;
|
|
420
|
+
const hookEntry = {
|
|
421
|
+
type: "command",
|
|
422
|
+
command: cmd,
|
|
423
|
+
timeout: 10
|
|
424
|
+
};
|
|
425
|
+
return {
|
|
426
|
+
hooks: {
|
|
427
|
+
SessionStart: [hookEntry],
|
|
428
|
+
PostToolUse: [hookEntry],
|
|
429
|
+
UserPromptSubmit: [hookEntry],
|
|
430
|
+
PreCompact: [hookEntry],
|
|
431
|
+
Stop: [hookEntry]
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function generateWindsurfConfig() {
|
|
436
|
+
const cmd = `${resolveHookCommand()} hook`;
|
|
437
|
+
const hookEntry = {
|
|
438
|
+
command: cmd,
|
|
439
|
+
show_output: false
|
|
440
|
+
};
|
|
441
|
+
return {
|
|
442
|
+
hooks: {
|
|
443
|
+
post_write_code: [hookEntry],
|
|
444
|
+
post_run_command: [hookEntry],
|
|
445
|
+
post_mcp_tool_use: [hookEntry],
|
|
446
|
+
pre_user_prompt: [hookEntry],
|
|
447
|
+
post_cascade_response: [hookEntry]
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function generateCursorConfig() {
|
|
452
|
+
const cmd = `${resolveHookCommand()} hook`;
|
|
453
|
+
return {
|
|
454
|
+
hooks: {
|
|
455
|
+
beforeSubmitPrompt: {
|
|
456
|
+
command: cmd
|
|
457
|
+
},
|
|
458
|
+
afterFileEdit: {
|
|
459
|
+
command: cmd
|
|
460
|
+
},
|
|
461
|
+
stop: {
|
|
462
|
+
command: cmd
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function generateKiroHookFile() {
|
|
468
|
+
return `---
|
|
469
|
+
title: Memorix Auto-Memory
|
|
470
|
+
description: Automatically record development context for cross-agent memory sharing
|
|
471
|
+
event: file_saved
|
|
472
|
+
filePattern: "**/*"
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
Run the memorix hook command to analyze changes and store relevant memories:
|
|
476
|
+
|
|
477
|
+
\`\`\`bash
|
|
478
|
+
${resolveHookCommand()} hook
|
|
479
|
+
\`\`\`
|
|
480
|
+
`;
|
|
481
|
+
}
|
|
482
|
+
function getProjectConfigPath(agent, projectRoot) {
|
|
483
|
+
switch (agent) {
|
|
484
|
+
case "claude":
|
|
485
|
+
case "copilot":
|
|
486
|
+
return path5.join(projectRoot, ".github", "hooks", "memorix.json");
|
|
487
|
+
case "windsurf":
|
|
488
|
+
return path5.join(projectRoot, ".windsurf", "hooks.json");
|
|
489
|
+
case "cursor":
|
|
490
|
+
return path5.join(projectRoot, ".cursor", "hooks.json");
|
|
491
|
+
case "kiro":
|
|
492
|
+
return path5.join(projectRoot, ".kiro", "hooks", "memorix.hook.md");
|
|
493
|
+
case "codex":
|
|
494
|
+
return path5.join(projectRoot, ".codex", "hooks.json");
|
|
495
|
+
default:
|
|
496
|
+
return path5.join(projectRoot, ".memorix", "hooks.json");
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function getGlobalConfigPath(agent) {
|
|
500
|
+
const home = os2.homedir();
|
|
501
|
+
switch (agent) {
|
|
502
|
+
case "claude":
|
|
503
|
+
case "copilot":
|
|
504
|
+
return path5.join(home, ".claude", "settings.json");
|
|
505
|
+
case "windsurf":
|
|
506
|
+
return path5.join(home, ".codeium", "windsurf", "hooks.json");
|
|
507
|
+
case "cursor":
|
|
508
|
+
return path5.join(home, ".cursor", "hooks.json");
|
|
509
|
+
default:
|
|
510
|
+
return path5.join(home, ".memorix", "hooks.json");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function detectInstalledAgents() {
|
|
514
|
+
const agents = [];
|
|
515
|
+
const home = os2.homedir();
|
|
516
|
+
const claudeDir = path5.join(home, ".claude");
|
|
517
|
+
try {
|
|
518
|
+
await fs3.access(claudeDir);
|
|
519
|
+
agents.push("claude");
|
|
520
|
+
} catch {
|
|
521
|
+
}
|
|
522
|
+
const windsurfDir = path5.join(home, ".codeium", "windsurf");
|
|
523
|
+
try {
|
|
524
|
+
await fs3.access(windsurfDir);
|
|
525
|
+
agents.push("windsurf");
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
const cursorDir = path5.join(home, ".cursor");
|
|
529
|
+
try {
|
|
530
|
+
await fs3.access(cursorDir);
|
|
531
|
+
agents.push("cursor");
|
|
532
|
+
} catch {
|
|
533
|
+
}
|
|
534
|
+
if (!agents.includes("claude")) {
|
|
535
|
+
const vscodeDir = path5.join(home, ".vscode");
|
|
536
|
+
try {
|
|
537
|
+
await fs3.access(vscodeDir);
|
|
538
|
+
agents.push("copilot");
|
|
539
|
+
} catch {
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const kiroConfig = path5.join(home, ".kiro");
|
|
543
|
+
try {
|
|
544
|
+
await fs3.access(kiroConfig);
|
|
545
|
+
agents.push("kiro");
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
return agents;
|
|
549
|
+
}
|
|
550
|
+
async function installHooks(agent, projectRoot, global = false) {
|
|
551
|
+
const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
|
|
552
|
+
let generated;
|
|
553
|
+
switch (agent) {
|
|
554
|
+
case "claude":
|
|
555
|
+
case "copilot":
|
|
556
|
+
generated = generateClaudeConfig();
|
|
557
|
+
break;
|
|
558
|
+
case "windsurf":
|
|
559
|
+
generated = generateWindsurfConfig();
|
|
560
|
+
break;
|
|
561
|
+
case "cursor":
|
|
562
|
+
generated = generateCursorConfig();
|
|
563
|
+
break;
|
|
564
|
+
case "kiro":
|
|
565
|
+
generated = generateKiroHookFile();
|
|
566
|
+
break;
|
|
567
|
+
default:
|
|
568
|
+
generated = generateClaudeConfig();
|
|
569
|
+
}
|
|
570
|
+
await fs3.mkdir(path5.dirname(configPath), { recursive: true });
|
|
571
|
+
if (agent === "kiro") {
|
|
572
|
+
await fs3.writeFile(configPath, generated, "utf-8");
|
|
573
|
+
} else {
|
|
574
|
+
let existing = {};
|
|
575
|
+
try {
|
|
576
|
+
const content = await fs3.readFile(configPath, "utf-8");
|
|
577
|
+
existing = JSON.parse(content);
|
|
578
|
+
} catch {
|
|
579
|
+
}
|
|
580
|
+
const merged = {
|
|
581
|
+
...existing,
|
|
582
|
+
...generated
|
|
583
|
+
};
|
|
584
|
+
await fs3.writeFile(configPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
585
|
+
}
|
|
586
|
+
const events = [];
|
|
587
|
+
switch (agent) {
|
|
588
|
+
case "claude":
|
|
589
|
+
case "copilot":
|
|
590
|
+
events.push("session_start", "post_tool", "user_prompt", "pre_compact", "session_end");
|
|
591
|
+
break;
|
|
592
|
+
case "windsurf":
|
|
593
|
+
events.push("post_edit", "post_command", "post_tool", "user_prompt", "post_response");
|
|
594
|
+
break;
|
|
595
|
+
case "cursor":
|
|
596
|
+
events.push("user_prompt", "post_edit", "session_end");
|
|
597
|
+
break;
|
|
598
|
+
case "kiro":
|
|
599
|
+
events.push("post_edit");
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
await installAgentRules(agent, projectRoot);
|
|
603
|
+
return {
|
|
604
|
+
agent,
|
|
605
|
+
configPath,
|
|
606
|
+
events,
|
|
607
|
+
generated: typeof generated === "string" ? { content: generated } : generated
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
async function installAgentRules(agent, projectRoot) {
|
|
611
|
+
const rulesContent = getAgentRulesContent();
|
|
612
|
+
let rulesPath;
|
|
613
|
+
switch (agent) {
|
|
614
|
+
case "windsurf":
|
|
615
|
+
rulesPath = path5.join(projectRoot, ".windsurf", "rules", "memorix.md");
|
|
616
|
+
break;
|
|
617
|
+
case "cursor":
|
|
618
|
+
rulesPath = path5.join(projectRoot, ".cursor", "rules", "memorix.mdc");
|
|
619
|
+
break;
|
|
620
|
+
case "claude":
|
|
621
|
+
case "copilot":
|
|
622
|
+
rulesPath = path5.join(projectRoot, ".github", "copilot-instructions.md");
|
|
623
|
+
break;
|
|
624
|
+
default:
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
await fs3.mkdir(path5.dirname(rulesPath), { recursive: true });
|
|
629
|
+
try {
|
|
630
|
+
await fs3.access(rulesPath);
|
|
631
|
+
} catch {
|
|
632
|
+
await fs3.writeFile(rulesPath, rulesContent, "utf-8");
|
|
633
|
+
}
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function getAgentRulesContent() {
|
|
638
|
+
return `# Memorix \u2014 Automatic Memory Rules
|
|
639
|
+
|
|
640
|
+
You have access to Memorix memory tools. Follow these rules to maintain persistent context across sessions.
|
|
641
|
+
|
|
642
|
+
## Session Start \u2014 Load Context
|
|
643
|
+
|
|
644
|
+
At the **beginning of every conversation**, before responding to the user:
|
|
645
|
+
|
|
646
|
+
1. Call \`memorix_search\` with query related to the user's first message or the current project
|
|
647
|
+
2. If results are found, use them to understand the current project state, recent decisions, and pending tasks
|
|
648
|
+
3. Reference relevant memories naturally in your response
|
|
649
|
+
|
|
650
|
+
This ensures you already know the project context without the user re-explaining.
|
|
651
|
+
|
|
652
|
+
## During Session \u2014 Capture Important Context
|
|
653
|
+
|
|
654
|
+
Proactively call \`memorix_store\` when any of the following happen:
|
|
655
|
+
|
|
656
|
+
- **Architecture decision**: You or the user decide on a technology, pattern, or approach
|
|
657
|
+
- **Bug fix**: A bug is identified and resolved \u2014 store the root cause and fix
|
|
658
|
+
- **Gotcha/pitfall**: Something unexpected or tricky is discovered
|
|
659
|
+
- **Configuration change**: Environment, port, path, or tooling changes
|
|
660
|
+
|
|
661
|
+
Use appropriate types: \`decision\`, \`problem-solution\`, \`gotcha\`, \`what-changed\`, \`discovery\`.
|
|
662
|
+
|
|
663
|
+
## Session End \u2014 Store Summary
|
|
664
|
+
|
|
665
|
+
When the conversation is ending or the user says goodbye:
|
|
666
|
+
|
|
667
|
+
1. Call \`memorix_store\` with type \`session-request\` to record:
|
|
668
|
+
- What was accomplished in this session
|
|
669
|
+
- Current project state
|
|
670
|
+
- Pending tasks or next steps
|
|
671
|
+
- Any unresolved issues
|
|
672
|
+
|
|
673
|
+
This creates a "handoff note" for the next session.
|
|
674
|
+
|
|
675
|
+
## Guidelines
|
|
676
|
+
|
|
677
|
+
- **Don't store trivial information** (greetings, acknowledgments, simple file reads)
|
|
678
|
+
- **Do store anything you'd want to know if you lost all context**
|
|
679
|
+
- **Use concise titles** and structured facts
|
|
680
|
+
- **Include file paths** in filesModified when relevant
|
|
681
|
+
`;
|
|
682
|
+
}
|
|
683
|
+
async function uninstallHooks(agent, projectRoot, global = false) {
|
|
684
|
+
const configPath = global ? getGlobalConfigPath(agent) : getProjectConfigPath(agent, projectRoot);
|
|
685
|
+
try {
|
|
686
|
+
if (agent === "kiro") {
|
|
687
|
+
await fs3.unlink(configPath);
|
|
688
|
+
} else {
|
|
689
|
+
const content = await fs3.readFile(configPath, "utf-8");
|
|
690
|
+
const config = JSON.parse(content);
|
|
691
|
+
delete config.hooks;
|
|
692
|
+
if (Object.keys(config).length === 0) {
|
|
693
|
+
await fs3.unlink(configPath);
|
|
694
|
+
} else {
|
|
695
|
+
await fs3.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return true;
|
|
699
|
+
} catch {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async function getHookStatus(projectRoot) {
|
|
704
|
+
const results = [];
|
|
705
|
+
const agents = ["claude", "copilot", "windsurf", "cursor", "kiro", "codex"];
|
|
706
|
+
for (const agent of agents) {
|
|
707
|
+
const projectPath = getProjectConfigPath(agent, projectRoot);
|
|
708
|
+
const globalPath = getGlobalConfigPath(agent);
|
|
709
|
+
let installed = false;
|
|
710
|
+
let usedPath = projectPath;
|
|
711
|
+
try {
|
|
712
|
+
await fs3.access(projectPath);
|
|
713
|
+
installed = true;
|
|
714
|
+
} catch {
|
|
715
|
+
try {
|
|
716
|
+
await fs3.access(globalPath);
|
|
717
|
+
installed = true;
|
|
718
|
+
usedPath = globalPath;
|
|
719
|
+
} catch {
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
results.push({ agent, installed, configPath: usedPath });
|
|
723
|
+
}
|
|
724
|
+
return results;
|
|
725
|
+
}
|
|
726
|
+
var init_installers = __esm({
|
|
727
|
+
"src/hooks/installers/index.ts"() {
|
|
728
|
+
"use strict";
|
|
729
|
+
init_esm_shims();
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
|
|
375
733
|
// src/memory/retention.ts
|
|
376
734
|
var retention_exports = {};
|
|
377
735
|
__export(retention_exports, {
|
|
@@ -1667,8 +2025,9 @@ var RulesSyncer = class {
|
|
|
1667
2025
|
|
|
1668
2026
|
// src/workspace/engine.ts
|
|
1669
2027
|
init_esm_shims();
|
|
1670
|
-
import { readFileSync, readdirSync, existsSync as existsSync3 } from "fs";
|
|
2028
|
+
import { readFileSync, readdirSync, existsSync as existsSync3, cpSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1671
2029
|
import { join as join6 } from "path";
|
|
2030
|
+
import { homedir as homedir5 } from "os";
|
|
1672
2031
|
|
|
1673
2032
|
// src/workspace/mcp-adapters/windsurf.ts
|
|
1674
2033
|
init_esm_shims();
|
|
@@ -2190,7 +2549,7 @@ var WorkspaceSyncApplier = class {
|
|
|
2190
2549
|
};
|
|
2191
2550
|
|
|
2192
2551
|
// src/workspace/engine.ts
|
|
2193
|
-
var WorkspaceSyncEngine = class {
|
|
2552
|
+
var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
|
|
2194
2553
|
constructor(projectRoot) {
|
|
2195
2554
|
this.projectRoot = projectRoot;
|
|
2196
2555
|
this.adapters = /* @__PURE__ */ new Map([
|
|
@@ -2218,10 +2577,10 @@ var WorkspaceSyncEngine = class {
|
|
|
2218
2577
|
for (const [target, adapter] of this.adapters) {
|
|
2219
2578
|
const configPath = adapter.getConfigPath(this.projectRoot);
|
|
2220
2579
|
const globalPath = adapter.getConfigPath();
|
|
2221
|
-
for (const
|
|
2222
|
-
if (existsSync3(
|
|
2580
|
+
for (const path6 of [configPath, globalPath]) {
|
|
2581
|
+
if (existsSync3(path6)) {
|
|
2223
2582
|
try {
|
|
2224
|
-
const content = readFileSync(
|
|
2583
|
+
const content = readFileSync(path6, "utf-8");
|
|
2225
2584
|
const servers = adapter.parse(content);
|
|
2226
2585
|
if (servers.length > 0) {
|
|
2227
2586
|
mcpConfigs[target] = servers;
|
|
@@ -2239,7 +2598,8 @@ var WorkspaceSyncEngine = class {
|
|
|
2239
2598
|
rulesCount = rules.length;
|
|
2240
2599
|
} catch {
|
|
2241
2600
|
}
|
|
2242
|
-
|
|
2601
|
+
const skills = this.scanSkills();
|
|
2602
|
+
return { mcpConfigs, workflows, rulesCount, skills };
|
|
2243
2603
|
}
|
|
2244
2604
|
/**
|
|
2245
2605
|
* Migrate workspace configs to a target agent format.
|
|
@@ -2249,7 +2609,8 @@ var WorkspaceSyncEngine = class {
|
|
|
2249
2609
|
const result = {
|
|
2250
2610
|
mcpServers: { scanned: [], generated: [] },
|
|
2251
2611
|
workflows: { scanned: [], generated: [] },
|
|
2252
|
-
rules: { scanned: 0, generated: 0 }
|
|
2612
|
+
rules: { scanned: 0, generated: 0 },
|
|
2613
|
+
skills: { scanned: [], copied: [] }
|
|
2253
2614
|
};
|
|
2254
2615
|
const allServers = /* @__PURE__ */ new Map();
|
|
2255
2616
|
for (const servers of Object.values(scan.mcpConfigs)) {
|
|
@@ -2286,9 +2647,85 @@ var WorkspaceSyncEngine = class {
|
|
|
2286
2647
|
}
|
|
2287
2648
|
} catch {
|
|
2288
2649
|
}
|
|
2650
|
+
result.skills.scanned = scan.skills;
|
|
2289
2651
|
return result;
|
|
2290
2652
|
}
|
|
2291
2653
|
// ---- Private helpers ----
|
|
2654
|
+
/** Skills directories per agent */
|
|
2655
|
+
static SKILLS_DIRS = {
|
|
2656
|
+
codex: [".codex/skills", ".agents/skills"],
|
|
2657
|
+
cursor: [".cursor/skills", ".cursor/skills-cursor"],
|
|
2658
|
+
windsurf: [".windsurf/skills"],
|
|
2659
|
+
"claude-code": [".claude/skills"]
|
|
2660
|
+
};
|
|
2661
|
+
/** Get the target skills directory for an agent */
|
|
2662
|
+
getTargetSkillsDir(target) {
|
|
2663
|
+
const dirs = _WorkspaceSyncEngine.SKILLS_DIRS[target];
|
|
2664
|
+
return join6(this.projectRoot, dirs[0]);
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* Scan all agent skills directories and collect unique skills.
|
|
2668
|
+
*/
|
|
2669
|
+
scanSkills() {
|
|
2670
|
+
const skills = [];
|
|
2671
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2672
|
+
const home = homedir5();
|
|
2673
|
+
for (const [agent, dirs] of Object.entries(_WorkspaceSyncEngine.SKILLS_DIRS)) {
|
|
2674
|
+
for (const dir of dirs) {
|
|
2675
|
+
const paths = [
|
|
2676
|
+
join6(this.projectRoot, dir),
|
|
2677
|
+
join6(home, dir)
|
|
2678
|
+
];
|
|
2679
|
+
for (const skillsRoot of paths) {
|
|
2680
|
+
if (!existsSync3(skillsRoot)) continue;
|
|
2681
|
+
try {
|
|
2682
|
+
const entries = readdirSync(skillsRoot, { withFileTypes: true });
|
|
2683
|
+
for (const entry of entries) {
|
|
2684
|
+
if (!entry.isDirectory()) continue;
|
|
2685
|
+
if (seen.has(entry.name)) continue;
|
|
2686
|
+
const skillMd = join6(skillsRoot, entry.name, "SKILL.md");
|
|
2687
|
+
if (!existsSync3(skillMd)) continue;
|
|
2688
|
+
let description = "";
|
|
2689
|
+
try {
|
|
2690
|
+
const content = readFileSync(skillMd, "utf-8");
|
|
2691
|
+
const match = content.match(/^---[\s\S]*?description:\s*["']?(.+?)["']?\s*$/m);
|
|
2692
|
+
if (match) description = match[1];
|
|
2693
|
+
} catch {
|
|
2694
|
+
}
|
|
2695
|
+
seen.add(entry.name);
|
|
2696
|
+
skills.push({
|
|
2697
|
+
name: entry.name,
|
|
2698
|
+
description,
|
|
2699
|
+
sourcePath: join6(skillsRoot, entry.name),
|
|
2700
|
+
sourceAgent: agent
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
} catch {
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
return skills;
|
|
2709
|
+
}
|
|
2710
|
+
/**
|
|
2711
|
+
* Copy skills to a target agent's skills directory.
|
|
2712
|
+
* Returns list of copied skill names.
|
|
2713
|
+
*/
|
|
2714
|
+
copySkills(skills, target) {
|
|
2715
|
+
const targetDir = this.getTargetSkillsDir(target);
|
|
2716
|
+
const copied = [];
|
|
2717
|
+
for (const skill of skills) {
|
|
2718
|
+
const dest = join6(targetDir, skill.name);
|
|
2719
|
+
if (existsSync3(dest)) continue;
|
|
2720
|
+
try {
|
|
2721
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
2722
|
+
cpSync(skill.sourcePath, dest, { recursive: true });
|
|
2723
|
+
copied.push(skill.name);
|
|
2724
|
+
} catch {
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
return copied;
|
|
2728
|
+
}
|
|
2292
2729
|
scanWorkflows() {
|
|
2293
2730
|
const workflows = [];
|
|
2294
2731
|
const wfDir = join6(this.projectRoot, ".windsurf", "workflows");
|
|
@@ -2323,12 +2760,23 @@ var WorkspaceSyncEngine = class {
|
|
|
2323
2760
|
...syncResult.workflows.generated
|
|
2324
2761
|
];
|
|
2325
2762
|
const applyResult = await applier.apply(filesToWrite);
|
|
2763
|
+
let copiedSkills = [];
|
|
2764
|
+
if (syncResult.skills.scanned.length > 0) {
|
|
2765
|
+
copiedSkills = this.copySkills(syncResult.skills.scanned, target);
|
|
2766
|
+
}
|
|
2326
2767
|
const lines = [];
|
|
2327
2768
|
if (applyResult.success) {
|
|
2328
2769
|
lines.push(`\u2705 Applied ${applyResult.filesWritten.length} file(s) for ${target}`);
|
|
2329
2770
|
for (const f of applyResult.filesWritten) {
|
|
2330
2771
|
lines.push(` \u2192 ${f}`);
|
|
2331
2772
|
}
|
|
2773
|
+
if (copiedSkills.length > 0) {
|
|
2774
|
+
lines.push(`
|
|
2775
|
+
\u{1F9E9} Copied ${copiedSkills.length} skill(s):`);
|
|
2776
|
+
for (const sk of copiedSkills) {
|
|
2777
|
+
lines.push(` \u2192 ${sk}`);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2332
2780
|
if (applyResult.backups.length > 0) {
|
|
2333
2781
|
lines.push(`
|
|
2334
2782
|
\u{1F4E6} Backups created (${applyResult.backups.length}):`);
|
|
@@ -2388,6 +2836,23 @@ async function createMemorixServer(cwd) {
|
|
|
2388
2836
|
}
|
|
2389
2837
|
console.error(`[memorix] Project: ${project.id} (${project.name})`);
|
|
2390
2838
|
console.error(`[memorix] Data dir: ${projectDir2}`);
|
|
2839
|
+
try {
|
|
2840
|
+
const { getHookStatus: getHookStatus2, installHooks: installHooks2, detectInstalledAgents: detectInstalledAgents2 } = await Promise.resolve().then(() => (init_installers(), installers_exports));
|
|
2841
|
+
const workDir = cwd ?? process.cwd();
|
|
2842
|
+
const statuses = await getHookStatus2(workDir);
|
|
2843
|
+
const anyInstalled = statuses.some((s) => s.installed);
|
|
2844
|
+
if (!anyInstalled) {
|
|
2845
|
+
const agents = await detectInstalledAgents2();
|
|
2846
|
+
for (const agent of agents) {
|
|
2847
|
+
try {
|
|
2848
|
+
const config = await installHooks2(agent, workDir);
|
|
2849
|
+
console.error(`[memorix] Auto-installed hooks for ${agent} \u2192 ${config.configPath}`);
|
|
2850
|
+
} catch {
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
} catch {
|
|
2855
|
+
}
|
|
2391
2856
|
const observationsFile = projectDir2 + "/observations.json";
|
|
2392
2857
|
let reloadDebounce = null;
|
|
2393
2858
|
try {
|
|
@@ -2828,7 +3293,7 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
2828
3293
|
"memorix_workspace_sync",
|
|
2829
3294
|
{
|
|
2830
3295
|
title: "Workspace Sync",
|
|
2831
|
-
description: 'Migrate your entire workspace environment between AI agents. Syncs MCP server configs, workflows, and
|
|
3296
|
+
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.',
|
|
2832
3297
|
inputSchema: {
|
|
2833
3298
|
action: z.enum(["scan", "migrate", "apply"]).describe('Action: "scan" to detect configs, "migrate" to preview, "apply" to write to disk'),
|
|
2834
3299
|
target: z.enum(AGENT_TARGETS).optional().describe("Target agent for migration (required for migrate)")
|
|
@@ -2858,6 +3323,14 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
2858
3323
|
}
|
|
2859
3324
|
lines2.push("", `### Rules`);
|
|
2860
3325
|
lines2.push(`- ${scan.rulesCount} rule(s) detected across all agents`);
|
|
3326
|
+
lines2.push("", `### Skills`);
|
|
3327
|
+
if (scan.skills.length > 0) {
|
|
3328
|
+
for (const sk of scan.skills) {
|
|
3329
|
+
lines2.push(`- **${sk.name}** (${sk.sourceAgent}): ${sk.description || "(no description)"}`);
|
|
3330
|
+
}
|
|
3331
|
+
} else {
|
|
3332
|
+
lines2.push("- No skills found");
|
|
3333
|
+
}
|
|
2861
3334
|
return {
|
|
2862
3335
|
content: [{ type: "text", text: lines2.join("\n") }]
|
|
2863
3336
|
};
|
|
@@ -2895,6 +3368,12 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
|
|
|
2895
3368
|
if (result.rules.generated > 0) {
|
|
2896
3369
|
lines.push(`### Rules`, `- ${result.rules.generated} rule file(s) generated`);
|
|
2897
3370
|
}
|
|
3371
|
+
if (result.skills.scanned.length > 0) {
|
|
3372
|
+
lines.push("### Skills", `- ${result.skills.scanned.length} skill(s) found, ready to copy:`);
|
|
3373
|
+
for (const sk of result.skills.scanned) {
|
|
3374
|
+
lines.push(` - **${sk.name}** (from ${sk.sourceAgent})`);
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
2898
3377
|
lines.push("", '> Review the generated configs above. Use action "apply" to write them to disk.');
|
|
2899
3378
|
return {
|
|
2900
3379
|
content: [{ type: "text", text: lines.join("\n") }]
|