wave-agent-sdk 0.17.4 → 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.
- package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
- package/dist/managers/MemoryRuleManager.js +30 -13
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +3 -1
- package/dist/managers/lspManager.d.ts.map +1 -1
- package/dist/managers/lspManager.js +12 -4
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +13 -6
- package/dist/managers/skillManager.d.ts +3 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +69 -54
- package/dist/services/MarketplaceService.d.ts +15 -0
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +95 -6
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +39 -5
- package/dist/services/pluginLoader.d.ts.map +1 -1
- package/dist/services/pluginLoader.js +30 -7
- package/dist/types/marketplace.d.ts +1 -0
- package/dist/types/marketplace.d.ts.map +1 -1
- package/dist/types/skills.d.ts +1 -0
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/utils/customCommands.js +11 -9
- package/dist/utils/skillParser.d.ts.map +1 -1
- package/dist/utils/skillParser.js +3 -1
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +18 -7
- package/package.json +1 -1
- package/src/managers/MemoryRuleManager.ts +29 -14
- package/src/managers/hookManager.ts +6 -1
- package/src/managers/lspManager.ts +23 -5
- package/src/managers/mcpManager.ts +24 -7
- package/src/managers/skillManager.ts +90 -57
- package/src/services/MarketplaceService.ts +152 -6
- package/src/services/memory.ts +43 -6
- package/src/services/pluginLoader.ts +35 -7
- package/src/types/marketplace.ts +1 -0
- package/src/types/skills.ts +1 -0
- package/src/utils/customCommands.ts +17 -12
- package/src/utils/skillParser.ts +3 -1
- package/src/utils/subagentParser.ts +22 -8
|
@@ -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:
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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:
|
|
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}
|
|
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}
|
|
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;
|
|
@@ -68,6 +68,9 @@ export class MarketplaceService {
|
|
|
68
68
|
|
|
69
69
|
this.ensureDirectoryStructure();
|
|
70
70
|
this.runMigration();
|
|
71
|
+
// Stored dynamically to avoid widening keyof MarketplaceService
|
|
72
|
+
(this as Record<string, unknown>)._seedComplete =
|
|
73
|
+
this.seedBuiltinMarketplace();
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
/**
|
|
@@ -121,6 +124,63 @@ export class MarketplaceService {
|
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Seeds the builtin marketplace on first startup.
|
|
129
|
+
* Adds it to user-scope settings and marks the cache as seeded.
|
|
130
|
+
*/
|
|
131
|
+
private async seedBuiltinMarketplace(): Promise<void> {
|
|
132
|
+
try {
|
|
133
|
+
const cache = await this.getCacheRegistry();
|
|
134
|
+
if (cache?.builtinSeeded) return;
|
|
135
|
+
|
|
136
|
+
const builtin = MarketplaceService.BUILTIN_MARKETPLACE;
|
|
137
|
+
|
|
138
|
+
// Add to user-scope settings if not already in any scope
|
|
139
|
+
const scoped = this.configurationService.getMergedMarketplaces(
|
|
140
|
+
this.workdir,
|
|
141
|
+
);
|
|
142
|
+
if (!scoped[builtin.name]) {
|
|
143
|
+
await this.configurationService.addMarketplaceToScope(
|
|
144
|
+
this.workdir,
|
|
145
|
+
"user",
|
|
146
|
+
builtin.name,
|
|
147
|
+
{ source: builtin.source, autoUpdate: builtin.autoUpdate },
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Update cache: add entry + set builtinSeeded flag
|
|
152
|
+
await this.updateCacheMarketplace(builtin.name, {
|
|
153
|
+
source: builtin.source,
|
|
154
|
+
autoUpdate: builtin.autoUpdate,
|
|
155
|
+
isBuiltin: true,
|
|
156
|
+
declaredScope: "builtin",
|
|
157
|
+
});
|
|
158
|
+
await this.setBuiltinSeeded(true);
|
|
159
|
+
} catch {
|
|
160
|
+
// Seeding failure should not block startup
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Sets the builtinSeeded flag in the cache registry, preserving all other fields.
|
|
166
|
+
*/
|
|
167
|
+
private async setBuiltinSeeded(value: boolean): Promise<void> {
|
|
168
|
+
const registry = await this.getCacheRegistry();
|
|
169
|
+
const tmpPath = `${this.knownMarketplacesPath}.tmp`;
|
|
170
|
+
await fs.writeFile(
|
|
171
|
+
tmpPath,
|
|
172
|
+
JSON.stringify(
|
|
173
|
+
{
|
|
174
|
+
builtinSeeded: value,
|
|
175
|
+
marketplaces: registry?.marketplaces ?? [],
|
|
176
|
+
},
|
|
177
|
+
null,
|
|
178
|
+
2,
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
await fs.rename(tmpPath, this.knownMarketplacesPath);
|
|
182
|
+
}
|
|
183
|
+
|
|
124
184
|
/**
|
|
125
185
|
* Check if a lock file is stale by reading its PID and checking if the process is alive.
|
|
126
186
|
* Returns true if the lock is stale and safe to remove.
|
|
@@ -258,7 +318,17 @@ export class MarketplaceService {
|
|
|
258
318
|
});
|
|
259
319
|
}
|
|
260
320
|
const tmpPath = `${this.knownMarketplacesPath}.tmp`;
|
|
261
|
-
await fs.writeFile(
|
|
321
|
+
await fs.writeFile(
|
|
322
|
+
tmpPath,
|
|
323
|
+
JSON.stringify(
|
|
324
|
+
{
|
|
325
|
+
builtinSeeded: registry?.builtinSeeded ?? false,
|
|
326
|
+
marketplaces,
|
|
327
|
+
},
|
|
328
|
+
null,
|
|
329
|
+
2,
|
|
330
|
+
),
|
|
331
|
+
);
|
|
262
332
|
await fs.rename(tmpPath, this.knownMarketplacesPath);
|
|
263
333
|
}
|
|
264
334
|
|
|
@@ -272,7 +342,14 @@ export class MarketplaceService {
|
|
|
272
342
|
const tmpPath = `${this.knownMarketplacesPath}.tmp`;
|
|
273
343
|
await fs.writeFile(
|
|
274
344
|
tmpPath,
|
|
275
|
-
JSON.stringify(
|
|
345
|
+
JSON.stringify(
|
|
346
|
+
{
|
|
347
|
+
builtinSeeded: registry?.builtinSeeded ?? false,
|
|
348
|
+
marketplaces: filtered,
|
|
349
|
+
},
|
|
350
|
+
null,
|
|
351
|
+
2,
|
|
352
|
+
),
|
|
276
353
|
);
|
|
277
354
|
await fs.rename(tmpPath, this.knownMarketplacesPath);
|
|
278
355
|
}
|
|
@@ -313,13 +390,21 @@ export class MarketplaceService {
|
|
|
313
390
|
async loadMarketplaceManifest(
|
|
314
391
|
marketplacePath: string,
|
|
315
392
|
): Promise<MarketplaceManifest> {
|
|
316
|
-
|
|
393
|
+
let manifestPath = path.join(
|
|
317
394
|
marketplacePath,
|
|
318
395
|
".wave-plugin",
|
|
319
396
|
"marketplace.json",
|
|
320
397
|
);
|
|
321
398
|
if (!existsSync(manifestPath)) {
|
|
322
|
-
|
|
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
|
+
}
|
|
323
408
|
}
|
|
324
409
|
const content = await fs.readFile(manifestPath, "utf-8");
|
|
325
410
|
const manifest = JSON.parse(content);
|
|
@@ -536,6 +621,9 @@ export class MarketplaceService {
|
|
|
536
621
|
const targetScope =
|
|
537
622
|
scope || this.getMarketplaceDeclaringSource(name) || "user";
|
|
538
623
|
|
|
624
|
+
// Uninstall all plugins from this marketplace
|
|
625
|
+
await this.uninstallPluginsByMarketplace(name);
|
|
626
|
+
|
|
539
627
|
if (targetScope !== "builtin") {
|
|
540
628
|
await this.configurationService.removeMarketplaceFromScope(
|
|
541
629
|
this.workdir,
|
|
@@ -549,6 +637,54 @@ export class MarketplaceService {
|
|
|
549
637
|
});
|
|
550
638
|
}
|
|
551
639
|
|
|
640
|
+
/**
|
|
641
|
+
* Uninstalls all plugins belonging to a given marketplace.
|
|
642
|
+
* Removes them from installed_plugins.json, deletes cache files,
|
|
643
|
+
* and cleans up enabledPlugins entries from all scopes.
|
|
644
|
+
*/
|
|
645
|
+
private async uninstallPluginsByMarketplace(
|
|
646
|
+
marketplaceName: string,
|
|
647
|
+
): Promise<void> {
|
|
648
|
+
const installedRegistry = await this.getInstalledPlugins();
|
|
649
|
+
const pluginsFromMarketplace = installedRegistry.plugins.filter(
|
|
650
|
+
(p) => p.marketplace === marketplaceName,
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
if (pluginsFromMarketplace.length === 0) return;
|
|
654
|
+
|
|
655
|
+
// Remove all plugins from this marketplace from the registry
|
|
656
|
+
installedRegistry.plugins = installedRegistry.plugins.filter(
|
|
657
|
+
(p) => p.marketplace !== marketplaceName,
|
|
658
|
+
);
|
|
659
|
+
await this.saveInstalledPlugins(installedRegistry);
|
|
660
|
+
|
|
661
|
+
// Delete cache files that are no longer referenced
|
|
662
|
+
const remainingCachePaths = new Set(
|
|
663
|
+
installedRegistry.plugins.map((p) => p.cachePath),
|
|
664
|
+
);
|
|
665
|
+
for (const plugin of pluginsFromMarketplace) {
|
|
666
|
+
if (
|
|
667
|
+
!remainingCachePaths.has(plugin.cachePath) &&
|
|
668
|
+
existsSync(plugin.cachePath)
|
|
669
|
+
) {
|
|
670
|
+
await fs.rm(plugin.cachePath, { recursive: true, force: true });
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Remove enabledPlugins entries from all scopes
|
|
675
|
+
const scopes: Scope[] = ["user", "project", "local"];
|
|
676
|
+
for (const plugin of pluginsFromMarketplace) {
|
|
677
|
+
const pluginId = `${plugin.name}@${plugin.marketplace}`;
|
|
678
|
+
for (const s of scopes) {
|
|
679
|
+
await this.configurationService.removeEnabledPlugin(
|
|
680
|
+
this.workdir,
|
|
681
|
+
s,
|
|
682
|
+
pluginId,
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
552
688
|
/**
|
|
553
689
|
* Updates a specific marketplace or all marketplaces
|
|
554
690
|
*/
|
|
@@ -771,13 +907,23 @@ export class MarketplaceService {
|
|
|
771
907
|
pluginSrcPath = path.resolve(marketplacePath, pluginEntry.source);
|
|
772
908
|
}
|
|
773
909
|
|
|
774
|
-
|
|
910
|
+
let pluginManifestPath = path.join(
|
|
775
911
|
pluginSrcPath,
|
|
776
912
|
".wave-plugin",
|
|
777
913
|
"plugin.json",
|
|
778
914
|
);
|
|
779
915
|
if (!existsSync(pluginManifestPath)) {
|
|
780
|
-
|
|
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
|
+
}
|
|
781
927
|
}
|
|
782
928
|
|
|
783
929
|
const pluginManifestContent = await fs.readFile(
|
package/src/services/memory.ts
CHANGED
|
@@ -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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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 "";
|
|
@@ -23,11 +23,13 @@ export class PluginLoader {
|
|
|
23
23
|
*/
|
|
24
24
|
static async loadManifest(pluginPath: string): Promise<PluginManifest> {
|
|
25
25
|
const dotWavePluginPath = path.join(pluginPath, ".wave-plugin");
|
|
26
|
-
const manifestPath = path.join(dotWavePluginPath, "plugin.json");
|
|
27
26
|
|
|
28
|
-
//
|
|
27
|
+
// Check if .wave-plugin/ directory exists
|
|
28
|
+
let wavePluginDirExists = false;
|
|
29
29
|
try {
|
|
30
30
|
const entries = await fs.readdir(dotWavePluginPath);
|
|
31
|
+
wavePluginDirExists = true;
|
|
32
|
+
// T018: Ensure plugin.json is the only file in .wave-plugin/
|
|
31
33
|
const misplaced = entries.filter((e) => e !== "plugin.json");
|
|
32
34
|
if (misplaced.length > 0) {
|
|
33
35
|
throw new Error(
|
|
@@ -39,15 +41,39 @@ export class PluginLoader {
|
|
|
39
41
|
error instanceof Error &&
|
|
40
42
|
(error as { code?: string }).code === "ENOENT"
|
|
41
43
|
) {
|
|
44
|
+
// .wave-plugin/ doesn't exist, will try .claude-plugin/ fallback below
|
|
45
|
+
} else {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (wavePluginDirExists) {
|
|
51
|
+
const manifestPath = path.join(dotWavePluginPath, "plugin.json");
|
|
52
|
+
try {
|
|
53
|
+
const content = await fs.readFile(manifestPath, "utf-8");
|
|
54
|
+
const manifest = JSON.parse(content) as PluginManifest;
|
|
55
|
+
this.validateManifest(manifest);
|
|
56
|
+
return manifest;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (
|
|
59
|
+
error instanceof Error &&
|
|
60
|
+
(error as { code?: string }).code === "ENOENT"
|
|
61
|
+
) {
|
|
62
|
+
throw new Error(`Plugin manifest not found at ${manifestPath}`);
|
|
63
|
+
}
|
|
42
64
|
throw new Error(
|
|
43
|
-
`
|
|
65
|
+
`Failed to load plugin manifest at ${manifestPath}: ${
|
|
66
|
+
error instanceof Error ? error.message : String(error)
|
|
67
|
+
}`,
|
|
44
68
|
);
|
|
45
69
|
}
|
|
46
|
-
throw error;
|
|
47
70
|
}
|
|
48
71
|
|
|
72
|
+
// Fallback to .claude-plugin/
|
|
73
|
+
const dotClaudePluginPath = path.join(pluginPath, ".claude-plugin");
|
|
74
|
+
const claudeManifestPath = path.join(dotClaudePluginPath, "plugin.json");
|
|
49
75
|
try {
|
|
50
|
-
const content = await fs.readFile(
|
|
76
|
+
const content = await fs.readFile(claudeManifestPath, "utf-8");
|
|
51
77
|
const manifest = JSON.parse(content) as PluginManifest;
|
|
52
78
|
this.validateManifest(manifest);
|
|
53
79
|
return manifest;
|
|
@@ -56,10 +82,12 @@ export class PluginLoader {
|
|
|
56
82
|
error instanceof Error &&
|
|
57
83
|
(error as { code?: string }).code === "ENOENT"
|
|
58
84
|
) {
|
|
59
|
-
throw new Error(
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Plugin manifest not found at ${dotWavePluginPath} or ${dotClaudePluginPath}`,
|
|
87
|
+
);
|
|
60
88
|
}
|
|
61
89
|
throw new Error(
|
|
62
|
-
`Failed to load plugin manifest at ${
|
|
90
|
+
`Failed to load plugin manifest at ${claudeManifestPath}: ${
|
|
63
91
|
error instanceof Error ? error.message : String(error)
|
|
64
92
|
}`,
|
|
65
93
|
);
|
package/src/types/marketplace.ts
CHANGED
package/src/types/skills.ts
CHANGED
|
@@ -70,21 +70,26 @@ export function scanCommandsDirectory(dirPath: string): CustomSlashCommand[] {
|
|
|
70
70
|
* Load all custom slash commands from both project and user directories
|
|
71
71
|
*/
|
|
72
72
|
export function loadCustomSlashCommands(workdir: string): CustomSlashCommand[] {
|
|
73
|
-
const
|
|
74
|
-
|
|
73
|
+
const userClaudeCommands = scanCommandsDirectory(
|
|
74
|
+
join(homedir(), ".claude", "commands"),
|
|
75
|
+
);
|
|
76
|
+
const userWaveCommands = scanCommandsDirectory(getUserCommandsDir());
|
|
77
|
+
const projectClaudeCommands = scanCommandsDirectory(
|
|
78
|
+
join(workdir, ".claude", "commands"),
|
|
79
|
+
);
|
|
80
|
+
const projectWaveCommands = scanCommandsDirectory(
|
|
81
|
+
getProjectCommandsDir(workdir),
|
|
82
|
+
);
|
|
75
83
|
|
|
76
|
-
// Project commands take precedence over user commands with the same name
|
|
77
84
|
const commandMap = new Map<string, CustomSlashCommand>();
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
// Write in priority order: lowest first, highest last (overwrites)
|
|
86
|
+
for (const command of [
|
|
87
|
+
...userClaudeCommands,
|
|
88
|
+
...userWaveCommands,
|
|
89
|
+
...projectClaudeCommands,
|
|
90
|
+
...projectWaveCommands,
|
|
91
|
+
]) {
|
|
81
92
|
commandMap.set(command.id, command);
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
// Add project commands (will overwrite user commands with same name)
|
|
85
|
-
for (const command of projectCommands) {
|
|
86
|
-
commandMap.set(command.id, command);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
94
|
return Array.from(commandMap.values());
|
|
90
95
|
}
|
package/src/utils/skillParser.ts
CHANGED
|
@@ -55,7 +55,9 @@ export function parseSkillFile(
|
|
|
55
55
|
const skillPath = basePath || dirname(filePath);
|
|
56
56
|
const skillType =
|
|
57
57
|
skillPath.includes("/.wave/skills") ||
|
|
58
|
-
skillPath.includes("\\.wave\\skills")
|
|
58
|
+
skillPath.includes("\\.wave\\skills") ||
|
|
59
|
+
skillPath.includes("/.claude/skills") ||
|
|
60
|
+
skillPath.includes("\\.claude\\skills")
|
|
59
61
|
? "project"
|
|
60
62
|
: "personal";
|
|
61
63
|
|