memorix 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 path5 of [configPath, globalPath]) {
2222
- if (existsSync3(path5)) {
2580
+ for (const path6 of [configPath, globalPath]) {
2581
+ if (existsSync3(path6)) {
2223
2582
  try {
2224
- const content = readFileSync(path5, "utf-8");
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
- return { mcpConfigs, workflows, rulesCount };
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 rules. Action "scan": detect all workspace configs. Action "migrate": generate configs for target agent (preview only). Action "apply": migrate AND write configs to disk with backup/rollback.',
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") }]