wave-agent-sdk 0.10.1 → 0.10.2
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/agent.d.ts +6 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +2 -0
- package/dist/core/session.d.ts +1 -1
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +1 -1
- package/dist/managers/backgroundTaskManager.d.ts +3 -0
- package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
- package/dist/managers/backgroundTaskManager.js +3 -0
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +33 -4
- package/dist/managers/skillManager.d.ts +17 -2
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +93 -21
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +7 -0
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +1 -0
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +1 -0
- package/dist/services/fileWatcher.d.ts.map +1 -1
- package/dist/services/fileWatcher.js +27 -15
- package/dist/services/session.d.ts +9 -2
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +77 -7
- package/dist/tools/bashTool.js +3 -3
- package/dist/tools/exitPlanMode.d.ts.map +1 -1
- package/dist/tools/exitPlanMode.js +3 -1
- package/dist/tools/grepTool.d.ts.map +1 -1
- package/dist/tools/grepTool.js +1 -1
- package/dist/tools/lspTool.d.ts.map +1 -1
- package/dist/tools/lspTool.js +47 -15
- package/dist/tools/taskOutputTool.d.ts.map +1 -1
- package/dist/tools/taskOutputTool.js +22 -5
- package/dist/tools/types.d.ts +1 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types/agent.d.ts +2 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/configuration.d.ts +2 -0
- package/dist/types/configuration.d.ts.map +1 -1
- package/dist/types/skills.d.ts +4 -2
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/utils/configPaths.d.ts +6 -0
- package/dist/utils/configPaths.d.ts.map +1 -1
- package/dist/utils/configPaths.js +23 -2
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +4 -1
- package/dist/utils/gitUtils.d.ts +6 -0
- package/dist/utils/gitUtils.d.ts.map +1 -1
- package/dist/utils/gitUtils.js +22 -0
- package/package.json +1 -1
- package/src/agent.ts +18 -2
- package/src/builtin-skills/settings/HOOKS.md +95 -0
- package/src/builtin-skills/settings/SKILL.md +86 -0
- package/src/core/session.ts +1 -0
- package/src/managers/backgroundTaskManager.ts +11 -1
- package/src/managers/permissionManager.ts +33 -4
- package/src/managers/skillManager.ts +111 -25
- package/src/managers/slashCommandManager.ts +8 -0
- package/src/prompts/index.ts +1 -0
- package/src/services/configurationService.ts +1 -0
- package/src/services/fileWatcher.ts +33 -17
- package/src/services/session.ts +83 -7
- package/src/tools/bashTool.ts +3 -3
- package/src/tools/exitPlanMode.ts +3 -1
- package/src/tools/grepTool.ts +2 -1
- package/src/tools/lspTool.ts +99 -9
- package/src/tools/taskOutputTool.ts +25 -6
- package/src/tools/types.ts +2 -0
- package/src/types/agent.ts +2 -0
- package/src/types/configuration.ts +2 -0
- package/src/types/skills.ts +4 -2
- package/src/utils/configPaths.ts +28 -2
- package/src/utils/containerSetup.ts +4 -1
- package/src/utils/gitUtils.ts +22 -0
package/dist/utils/gitUtils.js
CHANGED
|
@@ -58,6 +58,28 @@ export function getGitCommonDir(cwd) {
|
|
|
58
58
|
return getGitRepoRoot(cwd);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Get the main repository root directory (the first worktree in the list)
|
|
63
|
+
* @param cwd Working directory
|
|
64
|
+
* @returns Main repository root path
|
|
65
|
+
*/
|
|
66
|
+
export function getGitMainRepoRoot(cwd) {
|
|
67
|
+
try {
|
|
68
|
+
const output = execSync("git worktree list --porcelain", {
|
|
69
|
+
cwd,
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
72
|
+
}).trim();
|
|
73
|
+
const lines = output.split("\n");
|
|
74
|
+
if (lines.length > 0 && lines[0].startsWith("worktree ")) {
|
|
75
|
+
return lines[0].substring("worktree ".length).trim();
|
|
76
|
+
}
|
|
77
|
+
return getGitRepoRoot(cwd);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return getGitRepoRoot(cwd);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
61
83
|
/**
|
|
62
84
|
* Get the default remote branch (e.g., origin/main)
|
|
63
85
|
* @param cwd Working directory
|
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -235,7 +235,14 @@ export class Agent {
|
|
|
235
235
|
public getBackgroundShellOutput(
|
|
236
236
|
id: string,
|
|
237
237
|
filter?: string,
|
|
238
|
-
): {
|
|
238
|
+
): {
|
|
239
|
+
stdout: string;
|
|
240
|
+
stderr: string;
|
|
241
|
+
status: string;
|
|
242
|
+
outputPath?: string;
|
|
243
|
+
type: string;
|
|
244
|
+
exitCode?: number;
|
|
245
|
+
} | null {
|
|
239
246
|
return this.backgroundTaskManager.getOutput(id, filter);
|
|
240
247
|
}
|
|
241
248
|
|
|
@@ -248,7 +255,14 @@ export class Agent {
|
|
|
248
255
|
public getBackgroundTaskOutput(
|
|
249
256
|
id: string,
|
|
250
257
|
filter?: string,
|
|
251
|
-
): {
|
|
258
|
+
): {
|
|
259
|
+
stdout: string;
|
|
260
|
+
stderr: string;
|
|
261
|
+
status: string;
|
|
262
|
+
outputPath?: string;
|
|
263
|
+
type: string;
|
|
264
|
+
exitCode?: number;
|
|
265
|
+
} | null {
|
|
252
266
|
return this.backgroundTaskManager.getOutput(id, filter);
|
|
253
267
|
}
|
|
254
268
|
|
|
@@ -456,6 +470,8 @@ export class Agent {
|
|
|
456
470
|
}
|
|
457
471
|
// Cleanup subagent manager
|
|
458
472
|
this.subagentManager.cleanup();
|
|
473
|
+
// Cleanup skill manager
|
|
474
|
+
await this.skillManager.destroy();
|
|
459
475
|
// Cleanup live configuration reload
|
|
460
476
|
try {
|
|
461
477
|
await this.liveConfigManager.shutdown();
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Wave Hooks Configuration
|
|
2
|
+
|
|
3
|
+
Hooks allow you to automate tasks when certain events occur in Wave. This document provides detailed guidance on how to configure complex hooks in `settings.json`.
|
|
4
|
+
|
|
5
|
+
## Hook Events
|
|
6
|
+
|
|
7
|
+
Wave supports the following hook events:
|
|
8
|
+
|
|
9
|
+
- `WorktreeCreate`: Triggered when a new worktree is created.
|
|
10
|
+
- `TaskStart`: Triggered when a task starts.
|
|
11
|
+
- `TaskComplete`: Triggered when a task is completed.
|
|
12
|
+
- `TaskError`: Triggered when a task fails.
|
|
13
|
+
- `SessionStart`: Triggered when a new session starts.
|
|
14
|
+
- `SessionEnd`: Triggered when a session ends.
|
|
15
|
+
|
|
16
|
+
## Hook Configuration Structure
|
|
17
|
+
|
|
18
|
+
Hooks are configured in the `hooks` field of `settings.json`. Each event can have multiple hook configurations.
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"hooks": {
|
|
23
|
+
"WorktreeCreate": [
|
|
24
|
+
{
|
|
25
|
+
"command": "pnpm install",
|
|
26
|
+
"description": "Install dependencies in new worktree",
|
|
27
|
+
"blocking": true,
|
|
28
|
+
"timeout": 300000
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"TaskComplete": [
|
|
32
|
+
{
|
|
33
|
+
"command": "pnpm test",
|
|
34
|
+
"description": "Run tests after task completion",
|
|
35
|
+
"blocking": false
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Hook Configuration Fields
|
|
43
|
+
|
|
44
|
+
- `command`: The shell command to execute.
|
|
45
|
+
- `description`: A brief description of the hook's purpose.
|
|
46
|
+
- `blocking`: (Optional) Whether the hook should block the main agent's execution (default: `false`).
|
|
47
|
+
- `timeout`: (Optional) Maximum execution time in milliseconds (default: `60000`).
|
|
48
|
+
- `env`: (Optional) Environment variables specific to this hook.
|
|
49
|
+
- `cwd`: (Optional) Working directory for the hook command.
|
|
50
|
+
|
|
51
|
+
## Advanced Hook Examples
|
|
52
|
+
|
|
53
|
+
### 1. Conditional Hooks
|
|
54
|
+
You can use shell logic within the `command` field to create conditional hooks.
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"hooks": {
|
|
58
|
+
"TaskComplete": [
|
|
59
|
+
{
|
|
60
|
+
"command": "if [ \"$WAVE_TASK_STATUS\" = \"completed\" ]; then pnpm lint; fi",
|
|
61
|
+
"description": "Run linting only on successful task completion"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Hook Chaining
|
|
69
|
+
You can chain multiple commands in a single hook or define multiple hooks for the same event.
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"hooks": {
|
|
73
|
+
"WorktreeCreate": [
|
|
74
|
+
{
|
|
75
|
+
"command": "pnpm install && pnpm build",
|
|
76
|
+
"description": "Install and build in new worktree"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. Using Environment Variables
|
|
84
|
+
Wave provides several environment variables to hooks:
|
|
85
|
+
- `WAVE_PROJECT_DIR`: The root directory of the project.
|
|
86
|
+
- `WAVE_SESSION_ID`: The current session ID.
|
|
87
|
+
- `WAVE_TASK_ID`: The current task ID (if applicable).
|
|
88
|
+
- `WAVE_TASK_STATUS`: The status of the task (for `TaskComplete` and `TaskError`).
|
|
89
|
+
|
|
90
|
+
## Best Practices
|
|
91
|
+
|
|
92
|
+
- **Keep hooks fast**: Long-running hooks can slow down your workflow, especially if they are `blocking`.
|
|
93
|
+
- **Use descriptive names**: Help yourself and others understand what each hook does.
|
|
94
|
+
- **Test your hooks**: Run the commands manually first to ensure they work as expected.
|
|
95
|
+
- **Use local overrides**: For machine-specific hooks, use `.wave/settings.local.json`.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: settings
|
|
3
|
+
description: Manage Wave settings and get guidance on settings.json
|
|
4
|
+
allowed-tools: Bash, Read, Write
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Wave Settings Skill
|
|
8
|
+
|
|
9
|
+
This skill helps you manage your Wave configuration and provides guidance on how to use `settings.json`.
|
|
10
|
+
|
|
11
|
+
## What is `settings.json`?
|
|
12
|
+
|
|
13
|
+
`settings.json` is the central configuration file for Wave. It allows you to customize hooks, environment variables, tool permissions, and more.
|
|
14
|
+
|
|
15
|
+
Wave looks for `settings.json` in three scopes:
|
|
16
|
+
1. **User Scope**: Global settings for all projects. Located at `~/.wave/settings.json`.
|
|
17
|
+
2. **Project Scope**: Settings specific to the current project. Located at `.wave/settings.json` in your project root.
|
|
18
|
+
3. **Local Scope**: Local overrides for the current project (not committed to git). Located at `.wave/settings.local.json`.
|
|
19
|
+
|
|
20
|
+
## Common Settings
|
|
21
|
+
|
|
22
|
+
### 1. Hooks
|
|
23
|
+
Hooks allow you to automate tasks when certain events occur (e.g., `WorktreeCreate`, `TaskStart`).
|
|
24
|
+
For detailed hook configuration, see [HOOKS.md](${WAVE_SKILL_DIR}/HOOKS.md).
|
|
25
|
+
|
|
26
|
+
### 2. Environment Variables
|
|
27
|
+
Set environment variables that will be available to all tools and hooks.
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"env": {
|
|
31
|
+
"NODE_ENV": "development",
|
|
32
|
+
"API_KEY": "your-api-key"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 3. Permissions
|
|
38
|
+
Manage tool permissions and define the "Safe Zone".
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"permissions": {
|
|
42
|
+
"allow": ["Bash", "Read"],
|
|
43
|
+
"deny": ["Write"],
|
|
44
|
+
"defaultMode": "interactive",
|
|
45
|
+
"additionalDirectories": ["/tmp/wave-exports"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 4. Enabled Plugins
|
|
51
|
+
Enable or disable specific plugins.
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"enabledPlugins": {
|
|
55
|
+
"git-plugin": true,
|
|
56
|
+
"experimental-plugin": false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 5. Model and Token Configuration
|
|
62
|
+
Define which AI models Wave should use and set token limits via environment variables.
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"env": {
|
|
66
|
+
"WAVE_MODEL": "gemini-3-flash",
|
|
67
|
+
"WAVE_FAST_MODEL": "gemini-2.5-flash",
|
|
68
|
+
"WAVE_MAX_INPUT_TOKENS": "100000",
|
|
69
|
+
"WAVE_MAX_OUTPUT_TOKENS": "4096"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 6. Other Settings
|
|
75
|
+
- `language`: Preferred language for agent communication (e.g., `"en"`, `"zh"`).
|
|
76
|
+
- `autoMemoryEnabled`: Enable or disable auto-memory (default: `true`).
|
|
77
|
+
|
|
78
|
+
## How to use this skill
|
|
79
|
+
|
|
80
|
+
You can ask me to:
|
|
81
|
+
- "Show my current settings"
|
|
82
|
+
- "Update my project settings to enable auto-memory"
|
|
83
|
+
- "How do I configure a post-commit hook?"
|
|
84
|
+
- "What are the available permission modes?"
|
|
85
|
+
|
|
86
|
+
I will guide you through the process and ensure your configuration is valid.
|
package/src/core/session.ts
CHANGED
|
@@ -293,7 +293,14 @@ export class BackgroundTaskManager {
|
|
|
293
293
|
public getOutput(
|
|
294
294
|
id: string,
|
|
295
295
|
filter?: string,
|
|
296
|
-
): {
|
|
296
|
+
): {
|
|
297
|
+
stdout: string;
|
|
298
|
+
stderr: string;
|
|
299
|
+
status: string;
|
|
300
|
+
outputPath?: string;
|
|
301
|
+
type: string;
|
|
302
|
+
exitCode?: number;
|
|
303
|
+
} | null {
|
|
297
304
|
const task = this.tasks.get(id);
|
|
298
305
|
if (!task) {
|
|
299
306
|
return null;
|
|
@@ -323,6 +330,9 @@ export class BackgroundTaskManager {
|
|
|
323
330
|
stdout,
|
|
324
331
|
stderr,
|
|
325
332
|
status: task.status,
|
|
333
|
+
outputPath: task.outputPath,
|
|
334
|
+
type: task.type,
|
|
335
|
+
exitCode: task.exitCode,
|
|
326
336
|
};
|
|
327
337
|
}
|
|
328
338
|
|
|
@@ -34,7 +34,19 @@ import {
|
|
|
34
34
|
import { Container } from "../utils/container.js";
|
|
35
35
|
import { ConfigurationService } from "../services/configurationService.js";
|
|
36
36
|
|
|
37
|
-
const SAFE_COMMANDS = [
|
|
37
|
+
const SAFE_COMMANDS = [
|
|
38
|
+
"cd",
|
|
39
|
+
"ls",
|
|
40
|
+
"pwd",
|
|
41
|
+
"true",
|
|
42
|
+
"false",
|
|
43
|
+
"grep",
|
|
44
|
+
"rg",
|
|
45
|
+
"cat",
|
|
46
|
+
"head",
|
|
47
|
+
"tail",
|
|
48
|
+
"wc",
|
|
49
|
+
];
|
|
38
50
|
|
|
39
51
|
const DEFAULT_ALLOWED_RULES = [
|
|
40
52
|
"Bash(git status*)",
|
|
@@ -69,7 +81,12 @@ const DEFAULT_ALLOWED_RULES = [
|
|
|
69
81
|
"Bash(whoami*)",
|
|
70
82
|
"Bash(date*)",
|
|
71
83
|
"Bash(uptime*)",
|
|
72
|
-
"Bash(
|
|
84
|
+
"Bash(grep*)",
|
|
85
|
+
"Bash(rg*)",
|
|
86
|
+
"Bash(cat*)",
|
|
87
|
+
"Bash(head*)",
|
|
88
|
+
"Bash(tail*)",
|
|
89
|
+
"Bash(wc*)",
|
|
73
90
|
];
|
|
74
91
|
|
|
75
92
|
import { logger } from "../utils/globalLogger.js";
|
|
@@ -649,7 +666,13 @@ export class PermissionManager {
|
|
|
649
666
|
cmd === "pwd" ||
|
|
650
667
|
cmd === "true" ||
|
|
651
668
|
cmd === "false" ||
|
|
652
|
-
cmd === "ls"
|
|
669
|
+
cmd === "ls" ||
|
|
670
|
+
cmd === "grep" ||
|
|
671
|
+
cmd === "rg" ||
|
|
672
|
+
cmd === "cat" ||
|
|
673
|
+
cmd === "head" ||
|
|
674
|
+
cmd === "tail" ||
|
|
675
|
+
cmd === "wc"
|
|
653
676
|
) {
|
|
654
677
|
return true;
|
|
655
678
|
}
|
|
@@ -751,7 +774,13 @@ export class PermissionManager {
|
|
|
751
774
|
cmd === "pwd" ||
|
|
752
775
|
cmd === "true" ||
|
|
753
776
|
cmd === "false" ||
|
|
754
|
-
cmd === "ls"
|
|
777
|
+
cmd === "ls" ||
|
|
778
|
+
cmd === "grep" ||
|
|
779
|
+
cmd === "rg" ||
|
|
780
|
+
cmd === "cat" ||
|
|
781
|
+
cmd === "head" ||
|
|
782
|
+
cmd === "tail" ||
|
|
783
|
+
cmd === "wc"
|
|
755
784
|
) {
|
|
756
785
|
isSafe = true;
|
|
757
786
|
} else {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readdir, stat } from "fs/promises";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { homedir } from "os";
|
|
4
|
+
import { EventEmitter } from "events";
|
|
5
|
+
import { FileWatcherService } from "../services/fileWatcher.js";
|
|
4
6
|
import type {
|
|
5
7
|
SkillManagerOptions,
|
|
6
8
|
SkillMetadata,
|
|
@@ -17,6 +19,7 @@ import {
|
|
|
17
19
|
replaceBashCommandsWithOutput,
|
|
18
20
|
executeBashCommands,
|
|
19
21
|
} from "../utils/markdownParser.js";
|
|
22
|
+
import { getBuiltinSkillsDir } from "../utils/configPaths.js";
|
|
20
23
|
|
|
21
24
|
import { Container } from "../utils/container.js";
|
|
22
25
|
import { logger } from "../utils/globalLogger.js";
|
|
@@ -24,7 +27,7 @@ import { logger } from "../utils/globalLogger.js";
|
|
|
24
27
|
/**
|
|
25
28
|
* Manages skill discovery and loading
|
|
26
29
|
*/
|
|
27
|
-
export class SkillManager {
|
|
30
|
+
export class SkillManager extends EventEmitter {
|
|
28
31
|
private personalSkillsPath: string;
|
|
29
32
|
private scanTimeout: number;
|
|
30
33
|
private workdir: string;
|
|
@@ -32,15 +35,19 @@ export class SkillManager {
|
|
|
32
35
|
private skillMetadata = new Map<string, SkillMetadata>();
|
|
33
36
|
private skillContent = new Map<string, Skill>();
|
|
34
37
|
private initialized = false;
|
|
38
|
+
private fileWatcher: FileWatcherService | null = null;
|
|
39
|
+
private watchEnabled: boolean;
|
|
35
40
|
|
|
36
41
|
constructor(
|
|
37
42
|
private container: Container,
|
|
38
43
|
options: SkillManagerOptions = {},
|
|
39
44
|
) {
|
|
45
|
+
super();
|
|
40
46
|
this.personalSkillsPath =
|
|
41
47
|
options.personalSkillsPath || join(homedir(), ".wave", "skills");
|
|
42
48
|
this.scanTimeout = options.scanTimeout || 5000;
|
|
43
49
|
this.workdir = options.workdir || process.cwd();
|
|
50
|
+
this.watchEnabled = options.watch ?? false;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
/**
|
|
@@ -50,26 +57,10 @@ export class SkillManager {
|
|
|
50
57
|
logger?.debug("Initializing SkillManager...");
|
|
51
58
|
|
|
52
59
|
try {
|
|
53
|
-
|
|
54
|
-
this.skillMetadata.clear();
|
|
55
|
-
this.skillContent.clear();
|
|
60
|
+
await this.refreshSkills();
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Store discovered skill metadata
|
|
60
|
-
discovery.personalSkills.forEach((skill, name) => {
|
|
61
|
-
this.skillMetadata.set(name, skill);
|
|
62
|
-
});
|
|
63
|
-
discovery.projectSkills.forEach((skill, name) => {
|
|
64
|
-
this.skillMetadata.set(name, skill);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Log any discovery errors
|
|
68
|
-
if (discovery.errors.length > 0) {
|
|
69
|
-
logger?.warn(`Found ${discovery.errors.length} skill discovery errors`);
|
|
70
|
-
discovery.errors.forEach((error) => {
|
|
71
|
-
logger?.warn(`Skill error in ${error.skillPath}: ${error.message}`);
|
|
72
|
-
});
|
|
62
|
+
if (this.watchEnabled && !this.fileWatcher) {
|
|
63
|
+
await this.setupWatcher();
|
|
73
64
|
}
|
|
74
65
|
|
|
75
66
|
this.initialized = true;
|
|
@@ -82,6 +73,85 @@ export class SkillManager {
|
|
|
82
73
|
}
|
|
83
74
|
}
|
|
84
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Refresh skills by re-discovering them
|
|
78
|
+
*/
|
|
79
|
+
private async refreshSkills(): Promise<void> {
|
|
80
|
+
// Clear existing data before discovery
|
|
81
|
+
this.skillMetadata.clear();
|
|
82
|
+
this.skillContent.clear();
|
|
83
|
+
|
|
84
|
+
const discovery = await this.discoverSkills();
|
|
85
|
+
|
|
86
|
+
// Store discovered skill metadata
|
|
87
|
+
discovery.builtinSkills.forEach((skill, name) => {
|
|
88
|
+
this.skillMetadata.set(name, skill);
|
|
89
|
+
});
|
|
90
|
+
discovery.personalSkills.forEach((skill, name) => {
|
|
91
|
+
this.skillMetadata.set(name, skill);
|
|
92
|
+
});
|
|
93
|
+
discovery.projectSkills.forEach((skill, name) => {
|
|
94
|
+
this.skillMetadata.set(name, skill);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Log any discovery errors
|
|
98
|
+
if (discovery.errors.length > 0) {
|
|
99
|
+
logger?.warn(`Found ${discovery.errors.length} skill discovery errors`);
|
|
100
|
+
discovery.errors.forEach((error) => {
|
|
101
|
+
logger?.warn(`Skill error in ${error.skillPath}: ${error.message}`);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.emit("refreshed", Array.from(this.skillMetadata.values()));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Setup file watcher for skill directories
|
|
110
|
+
*/
|
|
111
|
+
private async setupWatcher(): Promise<void> {
|
|
112
|
+
if (this.fileWatcher) {
|
|
113
|
+
await this.fileWatcher.cleanup();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.fileWatcher = new FileWatcherService(logger);
|
|
117
|
+
|
|
118
|
+
const pathsToWatch = [
|
|
119
|
+
this.personalSkillsPath,
|
|
120
|
+
join(this.workdir, ".wave", "skills"),
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
logger?.debug(`Setting up skill watcher for: ${pathsToWatch.join(", ")}`);
|
|
124
|
+
|
|
125
|
+
for (const pathToWatch of pathsToWatch) {
|
|
126
|
+
await this.fileWatcher.watchFile(pathToWatch, async (event) => {
|
|
127
|
+
if (
|
|
128
|
+
event.path.endsWith("SKILL.md") ||
|
|
129
|
+
event.type === "delete" ||
|
|
130
|
+
event.type === "create"
|
|
131
|
+
) {
|
|
132
|
+
logger?.debug(
|
|
133
|
+
`Skill change detected (${event.type}): ${event.path}. Refreshing skills...`,
|
|
134
|
+
);
|
|
135
|
+
try {
|
|
136
|
+
await this.refreshSkills();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger?.error("Failed to refresh skills after change:", error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Cleanup resources
|
|
147
|
+
*/
|
|
148
|
+
async destroy(): Promise<void> {
|
|
149
|
+
if (this.fileWatcher) {
|
|
150
|
+
await this.fileWatcher.cleanup();
|
|
151
|
+
this.fileWatcher = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
85
155
|
/**
|
|
86
156
|
* Check if the skill manager is initialized
|
|
87
157
|
*/
|
|
@@ -132,9 +202,14 @@ export class SkillManager {
|
|
|
132
202
|
}
|
|
133
203
|
|
|
134
204
|
/**
|
|
135
|
-
* Discover skills in
|
|
205
|
+
* Discover skills in builtin, personal and project directories
|
|
136
206
|
*/
|
|
137
207
|
private async discoverSkills(): Promise<SkillDiscoveryResult> {
|
|
208
|
+
const builtinCollection = await this.discoverSkillCollection(
|
|
209
|
+
getBuiltinSkillsDir(),
|
|
210
|
+
"builtin",
|
|
211
|
+
);
|
|
212
|
+
|
|
138
213
|
const personalCollection = await this.discoverSkillCollection(
|
|
139
214
|
this.personalSkillsPath,
|
|
140
215
|
"personal",
|
|
@@ -146,9 +221,14 @@ export class SkillManager {
|
|
|
146
221
|
);
|
|
147
222
|
|
|
148
223
|
return {
|
|
224
|
+
builtinSkills: builtinCollection.skills,
|
|
149
225
|
personalSkills: personalCollection.skills,
|
|
150
226
|
projectSkills: projectCollection.skills,
|
|
151
|
-
errors: [
|
|
227
|
+
errors: [
|
|
228
|
+
...builtinCollection.errors,
|
|
229
|
+
...personalCollection.errors,
|
|
230
|
+
...projectCollection.errors,
|
|
231
|
+
],
|
|
152
232
|
};
|
|
153
233
|
}
|
|
154
234
|
|
|
@@ -157,7 +237,7 @@ export class SkillManager {
|
|
|
157
237
|
*/
|
|
158
238
|
private async discoverSkillCollection(
|
|
159
239
|
basePath: string,
|
|
160
|
-
type: "personal" | "project",
|
|
240
|
+
type: "personal" | "project" | "builtin",
|
|
161
241
|
): Promise<SkillCollection> {
|
|
162
242
|
const collection: SkillCollection = {
|
|
163
243
|
type,
|
|
@@ -166,8 +246,14 @@ export class SkillManager {
|
|
|
166
246
|
errors: [],
|
|
167
247
|
};
|
|
168
248
|
|
|
169
|
-
|
|
170
|
-
|
|
249
|
+
let skillsPath: string;
|
|
250
|
+
if (type === "personal") {
|
|
251
|
+
skillsPath = basePath;
|
|
252
|
+
} else if (type === "builtin") {
|
|
253
|
+
skillsPath = basePath;
|
|
254
|
+
} else {
|
|
255
|
+
skillsPath = join(basePath, ".wave", "skills");
|
|
256
|
+
}
|
|
171
257
|
|
|
172
258
|
try {
|
|
173
259
|
const skillDirs = await this.findSkillDirectories(skillsPath);
|
|
@@ -42,6 +42,14 @@ export class SlashCommandManager {
|
|
|
42
42
|
public initialize(): void {
|
|
43
43
|
this.initializeBuiltinCommands();
|
|
44
44
|
this.loadCustomCommands();
|
|
45
|
+
|
|
46
|
+
// Listen for skill refreshes and update skill commands
|
|
47
|
+
const skillManager = this.container.get<SkillManager>("SkillManager");
|
|
48
|
+
if (skillManager) {
|
|
49
|
+
skillManager.on("refreshed", (skills: SkillMetadata[]) => {
|
|
50
|
+
this.registerSkillCommands(skills);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
private get messageManager(): MessageManager {
|
package/src/prompts/index.ts
CHANGED
|
@@ -256,6 +256,7 @@ Usage notes:
|
|
|
256
256
|
- Avoid listing every component or file structure that can be easily discovered.
|
|
257
257
|
- Don't include generic development practices.
|
|
258
258
|
- If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include the important parts.
|
|
259
|
+
- Do NOT include rules from .wave/rules/ as they are automatically loaded by the system.
|
|
259
260
|
- If there is a README.md, make sure to include the important parts.
|
|
260
261
|
- Do not make up information such as "Common Development Tasks", "Tips for Development", "Support and Documentation" unless this is expressly included in other files that you read.
|
|
261
262
|
- Be sure to prefix the file with the following text:
|