wave-agent-sdk 0.17.5 → 0.17.6

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.
Files changed (38) hide show
  1. package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
  2. package/dist/managers/MemoryRuleManager.js +30 -13
  3. package/dist/managers/hookManager.d.ts.map +1 -1
  4. package/dist/managers/hookManager.js +3 -1
  5. package/dist/managers/lspManager.d.ts.map +1 -1
  6. package/dist/managers/lspManager.js +12 -4
  7. package/dist/managers/mcpManager.d.ts.map +1 -1
  8. package/dist/managers/mcpManager.js +13 -6
  9. package/dist/managers/skillManager.d.ts +3 -0
  10. package/dist/managers/skillManager.d.ts.map +1 -1
  11. package/dist/managers/skillManager.js +69 -54
  12. package/dist/services/MarketplaceService.d.ts.map +1 -1
  13. package/dist/services/MarketplaceService.js +12 -4
  14. package/dist/services/memory.d.ts.map +1 -1
  15. package/dist/services/memory.js +39 -5
  16. package/dist/services/pluginLoader.d.ts.map +1 -1
  17. package/dist/services/pluginLoader.js +30 -7
  18. package/dist/types/skills.d.ts +1 -0
  19. package/dist/types/skills.d.ts.map +1 -1
  20. package/dist/utils/customCommands.d.ts.map +1 -1
  21. package/dist/utils/customCommands.js +11 -9
  22. package/dist/utils/skillParser.d.ts.map +1 -1
  23. package/dist/utils/skillParser.js +3 -1
  24. package/dist/utils/subagentParser.d.ts.map +1 -1
  25. package/dist/utils/subagentParser.js +18 -7
  26. package/package.json +1 -1
  27. package/src/managers/MemoryRuleManager.ts +29 -14
  28. package/src/managers/hookManager.ts +6 -1
  29. package/src/managers/lspManager.ts +23 -5
  30. package/src/managers/mcpManager.ts +24 -7
  31. package/src/managers/skillManager.ts +90 -57
  32. package/src/services/MarketplaceService.ts +22 -4
  33. package/src/services/memory.ts +43 -6
  34. package/src/services/pluginLoader.ts +35 -7
  35. package/src/types/skills.ts +1 -0
  36. package/src/utils/customCommands.ts +17 -12
  37. package/src/utils/skillParser.ts +3 -1
  38. package/src/utils/subagentParser.ts +22 -8
@@ -1 +1 @@
1
- {"version":3,"file":"customCommands.d.ts","sourceRoot":"","sources":["../../src/utils/customCommands.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAK5D;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAwC3E;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAkB7E"}
1
+ {"version":3,"file":"customCommands.d.ts","sourceRoot":"","sources":["../../src/utils/customCommands.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAK5D;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAwC3E;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAuB7E"}
@@ -60,16 +60,18 @@ export function scanCommandsDirectory(dirPath) {
60
60
  * Load all custom slash commands from both project and user directories
61
61
  */
62
62
  export function loadCustomSlashCommands(workdir) {
63
- const projectCommands = scanCommandsDirectory(getProjectCommandsDir(workdir));
64
- const userCommands = scanCommandsDirectory(getUserCommandsDir());
65
- // Project commands take precedence over user commands with the same name
63
+ const userClaudeCommands = scanCommandsDirectory(join(homedir(), ".claude", "commands"));
64
+ const userWaveCommands = scanCommandsDirectory(getUserCommandsDir());
65
+ const projectClaudeCommands = scanCommandsDirectory(join(workdir, ".claude", "commands"));
66
+ const projectWaveCommands = scanCommandsDirectory(getProjectCommandsDir(workdir));
66
67
  const commandMap = new Map();
67
- // Add user commands first
68
- for (const command of userCommands) {
69
- commandMap.set(command.id, command);
70
- }
71
- // Add project commands (will overwrite user commands with same name)
72
- for (const command of projectCommands) {
68
+ // Write in priority order: lowest first, highest last (overwrites)
69
+ for (const command of [
70
+ ...userClaudeCommands,
71
+ ...userWaveCommands,
72
+ ...projectClaudeCommands,
73
+ ...projectWaveCommands,
74
+ ]) {
73
75
  commandMap.set(command.id, command);
74
76
  }
75
77
  return Array.from(commandMap.values());
@@ -1 +1 @@
1
- {"version":3,"file":"skillParser.d.ts","sourceRoot":"","sources":["../../src/utils/skillParser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EAEjB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,iBAAsB,GAC9B,eAAe,CAgGjB;AAyDD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAwCvE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAa5E"}
1
+ {"version":3,"file":"skillParser.d.ts","sourceRoot":"","sources":["../../src/utils/skillParser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EAEjB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,iBAAsB,GAC9B,eAAe,CAkGjB;AAyDD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAwCvE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAa5E"}
@@ -36,7 +36,9 @@ export function parseSkillFile(filePath, options = {}) {
36
36
  // Determine skill type and path
37
37
  const skillPath = basePath || dirname(filePath);
38
38
  const skillType = skillPath.includes("/.wave/skills") ||
39
- skillPath.includes("\\.wave\\skills")
39
+ skillPath.includes("\\.wave\\skills") ||
40
+ skillPath.includes("/.claude/skills") ||
41
+ skillPath.includes("\\.claude\\skills")
40
42
  ? "project"
41
43
  : "personal";
42
44
  // Extract allowed tools
@@ -1 +1 @@
1
- {"version":3,"file":"subagentParser.d.ts","sourceRoot":"","sources":["../../src/utils/subagentParser.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAmKD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,MAAM,GACjB,qBAAqB,CAEvB;AAqCD;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAsBlC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAGvC"}
1
+ {"version":3,"file":"subagentParser.d.ts","sourceRoot":"","sources":["../../src/utils/subagentParser.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAmKD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,MAAM,GACjB,qBAAqB,CAEvB;AAqCD;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAoClC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAGvC"}
@@ -160,17 +160,28 @@ function scanSubagentDirectory(dirPath, scope) {
160
160
  * Load all subagent configurations from project and user directories, plus built-in subagents
161
161
  */
162
162
  export async function loadSubagentConfigurations(workdir) {
163
- const projectDir = join(workdir, ".wave", "agents");
164
- const userDir = join(process.env.HOME || "~", ".wave", "agents");
163
+ const projectWaveDir = join(workdir, ".wave", "agents");
164
+ const projectClaudeDir = join(workdir, ".claude", "agents");
165
+ const userWaveDir = join(process.env.HOME || "~", ".wave", "agents");
166
+ const userClaudeDir = join(process.env.HOME || "~", ".claude", "agents");
165
167
  const builtinDir = getBuiltinSubagentsDir();
166
168
  // Load configurations from all sources
167
169
  const builtinConfigs = scanSubagentDirectory(builtinDir, "builtin");
168
- const projectConfigs = scanSubagentDirectory(projectDir, "project");
169
- const userConfigs = scanSubagentDirectory(userDir, "user");
170
- // Merge configurations, with project configs taking highest precedence
170
+ const userClaudeConfigs = scanSubagentDirectory(userClaudeDir, "user");
171
+ const userWaveConfigs = scanSubagentDirectory(userWaveDir, "user");
172
+ const projectClaudeConfigs = scanSubagentDirectory(projectClaudeDir, "project");
173
+ const projectWaveConfigs = scanSubagentDirectory(projectWaveDir, "project");
174
+ // Merge configurations, with .wave taking priority over .claude
171
175
  const configMap = new Map();
172
- // Process in reverse priority order (built-in first, then user, then project)
173
- for (const config of [...builtinConfigs, ...userConfigs, ...projectConfigs]) {
176
+ // Merge order: builtin userClaude userWave projectClaude projectWave
177
+ // Later writes override earlier ones, so .wave takes priority over .claude
178
+ for (const config of [
179
+ ...builtinConfigs,
180
+ ...userClaudeConfigs,
181
+ ...userWaveConfigs,
182
+ ...projectClaudeConfigs,
183
+ ...projectWaveConfigs,
184
+ ]) {
174
185
  configMap.set(config.name, config);
175
186
  }
176
187
  return Array.from(configMap.values()).sort((a, b) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "0.17.5",
3
+ "version": "0.17.6",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
@@ -39,23 +39,26 @@ export class MemoryRuleManager {
39
39
  * Scans .wave/rules and ~/.wave/rules for memory rule files.
40
40
  */
41
41
  async discoverRules(): Promise<void> {
42
- const projectRulesDir = path.join(this.workdir, ".wave", "rules");
43
- const userRulesDir = path.join(os.homedir(), ".wave", "rules");
42
+ const projectWaveRulesDir = path.join(this.workdir, ".wave", "rules");
43
+ const projectClaudeRulesDir = path.join(this.workdir, ".claude", "rules");
44
+ const userWaveRulesDir = path.join(os.homedir(), ".wave", "rules");
45
+ const userClaudeRulesDir = path.join(os.homedir(), ".claude", "rules");
44
46
 
45
47
  logger.debug(`Scanning for modular memory rules...`);
46
- logger.debug(` User rules directory: ${userRulesDir}`);
47
- logger.debug(` Project rules directory: ${projectRulesDir}`);
48
+ logger.debug(` User rules directory: ${userWaveRulesDir}`);
49
+ logger.debug(` Project rules directory: ${projectWaveRulesDir}`);
48
50
 
49
51
  const newRules: Record<string, MemoryRule> = {};
50
52
 
51
- // Discover user rules first, then project rules so project rules can override if needed
52
- // (though IDs are based on file path, so they shouldn't collide unless same path)
53
- await this.scanDirectory(userRulesDir, "user", newRules);
54
- await this.scanDirectory(projectRulesDir, "project", newRules);
53
+ // Scan order: userClaude userWave projectClaude projectWave
54
+ // Later writes override, so .wave takes priority over .claude
55
+ await this.scanDirectory(userClaudeRulesDir, "user", newRules);
56
+ await this.scanDirectory(userWaveRulesDir, "user", newRules);
57
+ await this.scanDirectory(projectClaudeRulesDir, "project", newRules);
58
+ await this.scanDirectory(projectWaveRulesDir, "project", newRules);
55
59
 
56
60
  this.state.rules = newRules;
57
61
  const ruleCount = Object.keys(newRules).length;
58
- // Removed verbose logging of all discovered rules
59
62
  logger.debug(`Discovered ${ruleCount} modular memory rules`);
60
63
  }
61
64
 
@@ -124,11 +127,23 @@ export class MemoryRuleManager {
124
127
  try {
125
128
  const content = await fs.readFile(filePath, "utf-8");
126
129
  const rule = this.service.parseRule(content, filePath, source);
127
- // Use relative path from rules root as ID to allow project rules to override user rules
128
- const rulesRoot =
129
- source === "project"
130
- ? path.join(this.workdir, ".wave", "rules")
131
- : path.join(os.homedir(), ".wave", "rules");
130
+
131
+ // Determine rulesRoot dynamically based on actual file path
132
+ let rulesRoot: string;
133
+ if (source === "project") {
134
+ if (filePath.includes(path.join(".claude", "rules"))) {
135
+ rulesRoot = path.join(this.workdir, ".claude", "rules");
136
+ } else {
137
+ rulesRoot = path.join(this.workdir, ".wave", "rules");
138
+ }
139
+ } else {
140
+ if (filePath.includes(path.join(".claude", "rules"))) {
141
+ rulesRoot = path.join(os.homedir(), ".claude", "rules");
142
+ } else {
143
+ rulesRoot = path.join(os.homedir(), ".wave", "rules");
144
+ }
145
+ }
146
+
132
147
  const relativeId = path.relative(rulesRoot, filePath);
133
148
  rule.id = relativeId;
134
149
  registry[rule.id] = rule;
@@ -183,16 +183,21 @@ export class HookManager {
183
183
  env: {
184
184
  ...("env" in context ? (context.env ?? {}) : {}),
185
185
  WAVE_PLUGIN_ROOT: hookCommand.pluginRoot,
186
+ CLAUDE_PLUGIN_ROOT: hookCommand.pluginRoot,
186
187
  },
187
188
  }
188
189
  : context;
189
190
 
190
- // Substitute ${WAVE_PLUGIN_ROOT} in the command string (same pattern as Claude Code)
191
+ // Substitute ${WAVE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_ROOT} in the command string
191
192
  if (hookCommand.pluginRoot) {
192
193
  command = command.replace(
193
194
  /\$\{WAVE_PLUGIN_ROOT\}/g,
194
195
  hookCommand.pluginRoot,
195
196
  );
197
+ command = command.replace(
198
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
199
+ hookCommand.pluginRoot,
200
+ );
196
201
  }
197
202
 
198
203
  if (hookCommand.async) {
@@ -92,22 +92,40 @@ export class LspManager implements ILspManager {
92
92
  try {
93
93
  const env = { ...process.env, ...config.env };
94
94
 
95
- // For plugin servers, substitute ${WAVE_PLUGIN_ROOT} in command/args/env
95
+ // For plugin servers, substitute ${WAVE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_ROOT}
96
96
  let command = config.command;
97
97
  let args = config.args || [];
98
98
  if (config.pluginRoot) {
99
99
  env.WAVE_PLUGIN_ROOT = config.pluginRoot;
100
+ env.CLAUDE_PLUGIN_ROOT = config.pluginRoot;
100
101
  command = command.replace(/\$\{WAVE_PLUGIN_ROOT\}/g, config.pluginRoot);
101
- args = args.map((arg) =>
102
- arg.replace(/\$\{WAVE_PLUGIN_ROOT\}/g, config.pluginRoot!),
102
+ command = command.replace(
103
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
104
+ config.pluginRoot,
103
105
  );
104
- // Also expand WAVE_PLUGIN_ROOT in user-provided env values
106
+ args = args.map((arg) => {
107
+ let result = arg.replace(
108
+ /\$\{WAVE_PLUGIN_ROOT\}/g,
109
+ config.pluginRoot!,
110
+ );
111
+ result = result.replace(
112
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
113
+ config.pluginRoot!,
114
+ );
115
+ return result;
116
+ });
117
+ // Also expand plugin root in user-provided env values
105
118
  if (config.env) {
106
119
  for (const [key, value] of Object.entries(config.env)) {
107
- env[key] = value.replace(
120
+ let expanded = value.replace(
108
121
  /\$\{WAVE_PLUGIN_ROOT\}/g,
109
122
  config.pluginRoot!,
110
123
  );
124
+ expanded = expanded.replace(
125
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
126
+ config.pluginRoot!,
127
+ );
128
+ env[key] = expanded;
111
129
  }
112
130
  }
113
131
  }
@@ -40,7 +40,7 @@ export interface McpManagerOptions {
40
40
  * Expand environment variables in a string value.
41
41
  * Supports ${VAR} and ${VAR:-default} patterns.
42
42
  */
43
- const WAVE_TEMPLATE_VARS = ["WAVE_PLUGIN_ROOT"];
43
+ const WAVE_TEMPLATE_VARS = ["WAVE_PLUGIN_ROOT", "CLAUDE_PLUGIN_ROOT"];
44
44
 
45
45
  export function expandEnvVars(value: string): string {
46
46
  return value.replace(/\$\{([^}]+)\}/g, (_match, expr: string) => {
@@ -440,25 +440,42 @@ export class McpManager {
440
440
  ...(server.config.env || {}),
441
441
  };
442
442
 
443
- // For plugin servers, substitute ${WAVE_PLUGIN_ROOT} in command/args/env
444
- // (same pattern as Claude Code's substitutePluginVariables)
443
+ // For plugin servers, substitute ${WAVE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_ROOT}
445
444
  let command = server.config.command;
446
445
  let args = server.config.args || [];
447
446
  if (server.config.pluginRoot) {
448
447
  env.WAVE_PLUGIN_ROOT = server.config.pluginRoot;
448
+ env.CLAUDE_PLUGIN_ROOT = server.config.pluginRoot;
449
449
  command = command.replace(
450
450
  /\$\{WAVE_PLUGIN_ROOT\}/g,
451
451
  server.config.pluginRoot,
452
452
  );
453
- args = args.map((arg) =>
454
- arg.replace(/\$\{WAVE_PLUGIN_ROOT\}/g, server.config.pluginRoot!),
453
+ command = command.replace(
454
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
455
+ server.config.pluginRoot,
455
456
  );
456
- // Also expand WAVE_PLUGIN_ROOT in user-provided env values
457
+ args = args.map((arg) => {
458
+ let result = arg.replace(
459
+ /\$\{WAVE_PLUGIN_ROOT\}/g,
460
+ server.config.pluginRoot!,
461
+ );
462
+ result = result.replace(
463
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
464
+ server.config.pluginRoot!,
465
+ );
466
+ return result;
467
+ });
468
+ // Also expand plugin root in user-provided env values
457
469
  for (const [key, value] of Object.entries(server.config.env || {})) {
458
- env[key] = value.replace(
470
+ let expanded = value.replace(
459
471
  /\$\{WAVE_PLUGIN_ROOT\}/g,
460
472
  server.config.pluginRoot!,
461
473
  );
474
+ expanded = expanded.replace(
475
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
476
+ server.config.pluginRoot!,
477
+ );
478
+ env[key] = expanded;
462
479
  }
463
480
  }
464
481
  transport = new StdioClientTransport({
@@ -29,6 +29,7 @@ import { logger } from "../utils/globalLogger.js";
29
29
  */
30
30
  export class SkillManager extends EventEmitter {
31
31
  private personalSkillsPath: string;
32
+ private personalClaudeSkillsPath: string;
32
33
  private scanTimeout: number;
33
34
  private workdir: string;
34
35
 
@@ -47,6 +48,8 @@ export class SkillManager extends EventEmitter {
47
48
  super();
48
49
  this.personalSkillsPath =
49
50
  options.personalSkillsPath || join(homedir(), ".wave", "skills");
51
+ this.personalClaudeSkillsPath =
52
+ options.personalClaudeSkillsPath || join(homedir(), ".claude", "skills");
50
53
  this.scanTimeout = options.scanTimeout || 5000;
51
54
  this.workdir = options.workdir || process.cwd();
52
55
  this.watchEnabled = options.watch ?? false;
@@ -127,7 +130,9 @@ export class SkillManager extends EventEmitter {
127
130
 
128
131
  const pathsToWatch = [
129
132
  this.personalSkillsPath,
133
+ this.personalClaudeSkillsPath,
130
134
  join(this.workdir, ".wave", "skills"),
135
+ join(this.workdir, ".claude", "skills"),
131
136
  ];
132
137
 
133
138
  logger?.debug(`Setting up skill watcher for: ${pathsToWatch.join(", ")}`);
@@ -220,6 +225,11 @@ export class SkillManager extends EventEmitter {
220
225
  "builtin",
221
226
  );
222
227
 
228
+ const personalClaudeCollection = await this.discoverSkillCollection(
229
+ this.personalClaudeSkillsPath,
230
+ "personal",
231
+ );
232
+
223
233
  const personalCollection = await this.discoverSkillCollection(
224
234
  this.personalSkillsPath,
225
235
  "personal",
@@ -232,10 +242,14 @@ export class SkillManager extends EventEmitter {
232
242
 
233
243
  return {
234
244
  builtinSkills: builtinCollection.skills,
235
- personalSkills: personalCollection.skills,
245
+ personalSkills: new Map([
246
+ ...personalClaudeCollection.skills,
247
+ ...personalCollection.skills, // .wave overrides .claude
248
+ ]),
236
249
  projectSkills: projectCollection.skills,
237
250
  errors: [
238
251
  ...builtinCollection.errors,
252
+ ...personalClaudeCollection.errors,
239
253
  ...personalCollection.errors,
240
254
  ...projectCollection.errors,
241
255
  ],
@@ -256,77 +270,88 @@ export class SkillManager extends EventEmitter {
256
270
  errors: [],
257
271
  };
258
272
 
259
- let skillsPath: string;
260
- if (type === "personal") {
261
- skillsPath = basePath;
262
- } else if (type === "builtin") {
263
- skillsPath = basePath;
273
+ if (type === "project") {
274
+ // Scan .claude/skills first, then .wave/skills (wave overrides)
275
+ const claudePath = join(basePath, ".claude", "skills");
276
+ const wavePath = join(basePath, ".wave", "skills");
277
+ await this.scanSkillPath(claudePath, collection);
278
+ await this.scanSkillPath(wavePath, collection);
264
279
  } else {
265
- skillsPath = join(basePath, ".wave", "skills");
280
+ await this.scanSkillPath(basePath, collection);
266
281
  }
267
282
 
283
+ return collection;
284
+ }
285
+
286
+ private async scanSkillPath(
287
+ skillsPath: string,
288
+ collection: SkillCollection,
289
+ ): Promise<void> {
268
290
  try {
269
291
  const skillDirs = await this.findSkillDirectories(skillsPath);
270
292
  logger?.debug(
271
293
  `Found ${skillDirs.length} potential skill directories in ${skillsPath}`,
272
294
  );
295
+ await this.processSkillDirs(skillDirs, collection);
296
+ } catch (error) {
297
+ logger?.debug(
298
+ `Could not scan ${skillsPath}: ${error instanceof Error ? error.message : String(error)}`,
299
+ );
300
+ }
301
+ }
273
302
 
274
- for (const skillDir of skillDirs) {
275
- try {
276
- const skillFilePath = join(skillDir, "SKILL.md");
303
+ private async processSkillDirs(
304
+ skillDirs: string[],
305
+ collection: SkillCollection,
306
+ ): Promise<void> {
307
+ for (const skillDir of skillDirs) {
308
+ try {
309
+ const skillFilePath = join(skillDir, "SKILL.md");
277
310
 
278
- // Check if SKILL.md exists
279
- try {
280
- await stat(skillFilePath);
281
- } catch {
282
- continue; // Skip directories without SKILL.md
283
- }
284
-
285
- const parsed = parseSkillFile(skillFilePath, {
286
- basePath: skillDir,
287
- validateMetadata: true,
288
- });
311
+ // Check if SKILL.md exists
312
+ try {
313
+ await stat(skillFilePath);
314
+ } catch {
315
+ continue; // Skip directories without SKILL.md
316
+ }
289
317
 
290
- if (parsed.isValid) {
291
- // Override the skill type with the collection type
292
- const skillMetadata: SkillMetadata = {
293
- ...parsed.skillMetadata,
294
- type,
295
- };
296
-
297
- // Create full skill object with content
298
- const skill: Skill = {
299
- ...skillMetadata,
300
- content: parsed.content,
301
- frontmatter: parsed.frontmatter,
302
- isValid: parsed.isValid,
303
- errors: parsed.validationErrors,
304
- };
305
-
306
- collection.skills.set(skillMetadata.name, skillMetadata);
307
- // Store the full skill content in the manager's skillContent map
308
- this.skillContent.set(skillMetadata.name, skill);
309
- } else {
310
- collection.errors.push({
311
- skillPath: skillDir,
312
- message: parsed.validationErrors.join("; "),
313
- });
314
- }
315
- } catch (error) {
318
+ const parsed = parseSkillFile(skillFilePath, {
319
+ basePath: skillDir,
320
+ validateMetadata: true,
321
+ });
322
+
323
+ if (parsed.isValid) {
324
+ // Override the skill type with the collection type
325
+ const skillMetadata: SkillMetadata = {
326
+ ...parsed.skillMetadata,
327
+ type: collection.type,
328
+ };
329
+
330
+ // Create full skill object with content
331
+ const skill: Skill = {
332
+ ...skillMetadata,
333
+ content: parsed.content,
334
+ frontmatter: parsed.frontmatter,
335
+ isValid: parsed.isValid,
336
+ errors: parsed.validationErrors,
337
+ };
338
+
339
+ collection.skills.set(skillMetadata.name, skillMetadata);
340
+ // Store the full skill content in the manager's skillContent map
341
+ this.skillContent.set(skillMetadata.name, skill);
342
+ } else {
316
343
  collection.errors.push({
317
344
  skillPath: skillDir,
318
- message: `Failed to process skill: ${error instanceof Error ? error.message : String(error)}`,
345
+ message: parsed.validationErrors.join("; "),
319
346
  });
320
347
  }
348
+ } catch (error) {
349
+ collection.errors.push({
350
+ skillPath: skillDir,
351
+ message: `Failed to process skill: ${error instanceof Error ? error.message : String(error)}`,
352
+ });
321
353
  }
322
- } catch (error) {
323
- logger?.debug(
324
- `Could not scan ${skillsPath}: ${error instanceof Error ? error.message : String(error)}`,
325
- );
326
- // Not an error - the directory might not exist yet
327
354
  }
328
-
329
- return collection;
330
355
  }
331
356
 
332
357
  /**
@@ -450,15 +475,23 @@ export class SkillManager extends EventEmitter {
450
475
  // 1. Substitute parameters ($1, $ARGUMENTS, etc.)
451
476
  mainContent = substituteCommandParameters(mainContent, argsString);
452
477
 
453
- // 2. Substitute ${WAVE_SKILL_DIR} with the skill's directory path
478
+ // 2. Substitute ${WAVE_SKILL_DIR} and ${CLAUDE_SKILL_DIR}
454
479
  mainContent = mainContent.replace(/\$\{WAVE_SKILL_DIR\}/g, skill.skillPath);
480
+ mainContent = mainContent.replace(
481
+ /\$\{CLAUDE_SKILL_DIR\}/g,
482
+ skill.skillPath,
483
+ );
455
484
 
456
- // 3. Substitute ${WAVE_PLUGIN_ROOT} with the skill's plugin root path
485
+ // 3. Substitute ${WAVE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_ROOT}
457
486
  if (skill.pluginRoot) {
458
487
  mainContent = mainContent.replace(
459
488
  /\$\{WAVE_PLUGIN_ROOT\}/g,
460
489
  skill.pluginRoot,
461
490
  );
491
+ mainContent = mainContent.replace(
492
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
493
+ skill.pluginRoot,
494
+ );
462
495
  }
463
496
 
464
497
  return skillPath + mainContent;
@@ -390,13 +390,21 @@ export class MarketplaceService {
390
390
  async loadMarketplaceManifest(
391
391
  marketplacePath: string,
392
392
  ): Promise<MarketplaceManifest> {
393
- const manifestPath = path.join(
393
+ let manifestPath = path.join(
394
394
  marketplacePath,
395
395
  ".wave-plugin",
396
396
  "marketplace.json",
397
397
  );
398
398
  if (!existsSync(manifestPath)) {
399
- throw new Error(`Marketplace manifest not found at ${manifestPath}`);
399
+ // Fallback to .claude-plugin/marketplace.json
400
+ manifestPath = path.join(
401
+ marketplacePath,
402
+ ".claude-plugin",
403
+ "marketplace.json",
404
+ );
405
+ if (!existsSync(manifestPath)) {
406
+ throw new Error(`Marketplace manifest not found at ${manifestPath}`);
407
+ }
400
408
  }
401
409
  const content = await fs.readFile(manifestPath, "utf-8");
402
410
  const manifest = JSON.parse(content);
@@ -899,13 +907,23 @@ export class MarketplaceService {
899
907
  pluginSrcPath = path.resolve(marketplacePath, pluginEntry.source);
900
908
  }
901
909
 
902
- const pluginManifestPath = path.join(
910
+ let pluginManifestPath = path.join(
903
911
  pluginSrcPath,
904
912
  ".wave-plugin",
905
913
  "plugin.json",
906
914
  );
907
915
  if (!existsSync(pluginManifestPath)) {
908
- throw new Error(`Plugin manifest not found at ${pluginManifestPath}`);
916
+ // Fallback to .claude-plugin/plugin.json
917
+ pluginManifestPath = path.join(
918
+ pluginSrcPath,
919
+ ".claude-plugin",
920
+ "plugin.json",
921
+ );
922
+ if (!existsSync(pluginManifestPath)) {
923
+ throw new Error(
924
+ `Plugin manifest not found at ${pluginManifestPath}`,
925
+ );
926
+ }
909
927
  }
910
928
 
911
929
  const pluginManifestContent = await fs.readFile(
@@ -130,6 +130,27 @@ export class MemoryService {
130
130
  userMemoryFile: USER_MEMORY_FILE,
131
131
  contentLength: content.length,
132
132
  });
133
+
134
+ // If the file is still the default empty template, fallback to ~/.claude/AGENTS.md
135
+ const defaultTemplate =
136
+ "# User Memory\n\nThis is the user-level memory file, recording important information and context across projects.\n\n";
137
+ if (content === defaultTemplate) {
138
+ const claudeMemoryPath = path.join(homedir(), ".claude", "AGENTS.md");
139
+ try {
140
+ const claudeContent = await fs.readFile(claudeMemoryPath, "utf-8");
141
+ logger.debug(
142
+ "User memory content read from ~/.claude/AGENTS.md fallback",
143
+ {
144
+ claudeMemoryPath,
145
+ contentLength: claudeContent.length,
146
+ },
147
+ );
148
+ return claudeContent;
149
+ } catch {
150
+ // CLAUDE.md doesn't exist or can't be read, return the default template
151
+ }
152
+ }
153
+
133
154
  return content;
134
155
  } catch (error) {
135
156
  logger.error("Failed to read user memory:", error);
@@ -139,8 +160,6 @@ export class MemoryService {
139
160
 
140
161
  async readMemoryFile(workdir: string): Promise<string> {
141
162
  const memoryFilePath = path.join(workdir, "AGENTS.md");
142
-
143
- // Direct file access
144
163
  try {
145
164
  const content = await fs.readFile(memoryFilePath, "utf-8");
146
165
  logger.debug("Memory file read successfully via direct file access", {
@@ -150,10 +169,28 @@ export class MemoryService {
150
169
  return content;
151
170
  } catch (error) {
152
171
  if ((error as NodeJS.ErrnoException).code === "ENOENT") {
153
- logger.debug("Memory file does not exist, returning empty content", {
154
- memoryFilePath,
155
- });
156
- return "";
172
+ // Fallback to CLAUDE.md
173
+ const claudeMemoryPath = path.join(workdir, "CLAUDE.md");
174
+ try {
175
+ const content = await fs.readFile(claudeMemoryPath, "utf-8");
176
+ logger.debug("Memory file read from CLAUDE.md fallback", {
177
+ memoryFilePath: claudeMemoryPath,
178
+ contentLength: content.length,
179
+ });
180
+ return content;
181
+ } catch (claudeError) {
182
+ if ((claudeError as NodeJS.ErrnoException).code === "ENOENT") {
183
+ logger.debug("Neither AGENTS.md nor CLAUDE.md found", {
184
+ memoryFilePath,
185
+ });
186
+ return "";
187
+ }
188
+ logger.error("Failed to read CLAUDE.md fallback", {
189
+ memoryFilePath: claudeMemoryPath,
190
+ error: claudeError,
191
+ });
192
+ return "";
193
+ }
157
194
  }
158
195
  logger.error("Failed to read memory file", { memoryFilePath, error });
159
196
  return "";