agileflow 3.1.0 → 3.2.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/CHANGELOG.md +5 -0
- package/README.md +57 -85
- package/lib/dashboard-automations.js +130 -0
- package/lib/dashboard-git.js +254 -0
- package/lib/dashboard-inbox.js +64 -0
- package/lib/dashboard-protocol.js +1 -0
- package/lib/dashboard-server.js +114 -924
- package/lib/dashboard-session.js +136 -0
- package/lib/dashboard-status.js +72 -0
- package/lib/dashboard-terminal.js +354 -0
- package/lib/dashboard-websocket.js +88 -0
- package/lib/drivers/codex-driver.ts +4 -4
- package/lib/logger.js +106 -0
- package/package.json +4 -2
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +409 -434
- package/scripts/claude-tmux.sh +80 -2
- package/scripts/context-loader.js +4 -9
- package/scripts/lib/command-prereqs.js +280 -0
- package/scripts/lib/configure-detect.js +92 -2
- package/scripts/lib/configure-features.js +295 -1
- package/scripts/lib/context-formatter.js +468 -233
- package/scripts/lib/context-loader.js +27 -15
- package/scripts/lib/damage-control-utils.js +8 -1
- package/scripts/lib/feature-catalog.js +321 -0
- package/scripts/lib/portable-tasks-cli.js +274 -0
- package/scripts/lib/portable-tasks.js +479 -0
- package/scripts/lib/signal-detectors.js +1 -1
- package/scripts/lib/team-events.js +86 -1
- package/scripts/obtain-context.js +28 -4
- package/scripts/smart-detect.js +17 -0
- package/scripts/strip-ai-attribution.js +63 -0
- package/scripts/team-manager.js +7 -2
- package/scripts/welcome-deferred.js +437 -0
- package/src/core/agents/perf-analyzer-assets.md +174 -0
- package/src/core/agents/perf-analyzer-bundle.md +165 -0
- package/src/core/agents/perf-analyzer-caching.md +160 -0
- package/src/core/agents/perf-analyzer-compute.md +165 -0
- package/src/core/agents/perf-analyzer-memory.md +182 -0
- package/src/core/agents/perf-analyzer-network.md +157 -0
- package/src/core/agents/perf-analyzer-queries.md +155 -0
- package/src/core/agents/perf-analyzer-rendering.md +156 -0
- package/src/core/agents/perf-consensus.md +280 -0
- package/src/core/agents/security-analyzer-api.md +199 -0
- package/src/core/agents/security-analyzer-auth.md +160 -0
- package/src/core/agents/security-analyzer-authz.md +168 -0
- package/src/core/agents/security-analyzer-deps.md +147 -0
- package/src/core/agents/security-analyzer-infra.md +176 -0
- package/src/core/agents/security-analyzer-injection.md +148 -0
- package/src/core/agents/security-analyzer-input.md +191 -0
- package/src/core/agents/security-analyzer-secrets.md +175 -0
- package/src/core/agents/security-consensus.md +276 -0
- package/src/core/agents/test-analyzer-assertions.md +181 -0
- package/src/core/agents/test-analyzer-coverage.md +183 -0
- package/src/core/agents/test-analyzer-fragility.md +185 -0
- package/src/core/agents/test-analyzer-integration.md +155 -0
- package/src/core/agents/test-analyzer-maintenance.md +173 -0
- package/src/core/agents/test-analyzer-mocking.md +178 -0
- package/src/core/agents/test-analyzer-patterns.md +189 -0
- package/src/core/agents/test-analyzer-structure.md +177 -0
- package/src/core/agents/test-consensus.md +294 -0
- package/src/core/commands/{legal/audit.md → audit/legal.md} +13 -13
- package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
- package/src/core/commands/audit/performance.md +443 -0
- package/src/core/commands/audit/security.md +443 -0
- package/src/core/commands/audit/test.md +442 -0
- package/src/core/commands/babysit.md +505 -463
- package/src/core/commands/configure.md +8 -8
- package/src/core/commands/research/ask.md +42 -9
- package/src/core/commands/research/import.md +14 -8
- package/src/core/commands/research/list.md +17 -16
- package/src/core/commands/research/synthesize.md +8 -8
- package/src/core/commands/research/view.md +28 -4
- package/src/core/commands/whats-new.md +2 -2
- package/src/core/experts/devops/expertise.yaml +13 -2
- package/src/core/experts/documentation/expertise.yaml +26 -4
- package/src/core/profiles/COMPARISON.md +170 -0
- package/src/core/profiles/README.md +178 -0
- package/src/core/profiles/claude-code.yaml +111 -0
- package/src/core/profiles/codex.yaml +103 -0
- package/src/core/profiles/cursor.yaml +134 -0
- package/src/core/profiles/examples.js +250 -0
- package/src/core/profiles/loader.js +235 -0
- package/src/core/profiles/windsurf.yaml +159 -0
- package/src/core/teams/logic-audit.json +6 -0
- package/src/core/teams/perf-audit.json +71 -0
- package/src/core/teams/security-audit.json +71 -0
- package/src/core/teams/test-audit.json +71 -0
- package/src/core/templates/command-prerequisites.yaml +169 -0
- package/src/core/templates/damage-control-patterns.yaml +9 -0
- package/tools/cli/installers/ide/_base-ide.js +33 -3
- package/tools/cli/installers/ide/claude-code.js +2 -69
- package/tools/cli/installers/ide/codex.js +9 -9
- package/tools/cli/installers/ide/cursor.js +165 -4
- package/tools/cli/installers/ide/windsurf.js +237 -6
- package/tools/cli/lib/content-transformer.js +234 -9
- package/tools/cli/lib/docs-setup.js +1 -1
- package/tools/cli/lib/ide-generator.js +357 -0
- package/tools/cli/lib/ide-registry.js +2 -2
- package/scripts/tmux-task-name.sh +0 -105
- package/scripts/tmux-task-watcher.sh +0 -344
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AgileFlow CLI - Cursor IDE Installer
|
|
3
3
|
*
|
|
4
|
-
* Installs AgileFlow commands for Cursor IDE.
|
|
5
|
-
* Cursor uses
|
|
4
|
+
* Installs AgileFlow commands, agents, and hooks for Cursor IDE.
|
|
5
|
+
* Cursor uses:
|
|
6
|
+
* - Plain Markdown files in .cursor/commands/ for slash commands
|
|
7
|
+
* - Markdown files in .cursor/agents/ for subagents (with YAML frontmatter)
|
|
8
|
+
* - .cursor/hooks.json for lifecycle hooks
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
const path = require('node:path');
|
|
@@ -27,10 +30,162 @@ class CursorSetup extends BaseIdeSetup {
|
|
|
27
30
|
* @param {Object} options - Setup options
|
|
28
31
|
*/
|
|
29
32
|
async setup(projectDir, agileflowDir, options = {}) {
|
|
30
|
-
|
|
33
|
+
// Use standard setup for commands and agents
|
|
34
|
+
const result = await this.setupStandard(projectDir, agileflowDir, {
|
|
31
35
|
targetSubdir: this.commandsDir,
|
|
32
36
|
agileflowFolder: 'AgileFlow',
|
|
33
37
|
});
|
|
38
|
+
|
|
39
|
+
const { ideDir, agileflowTargetDir } = result;
|
|
40
|
+
const agentsSource = path.join(agileflowDir, 'agents');
|
|
41
|
+
|
|
42
|
+
// Cursor specific: Install agents as spawnable subagents (.cursor/agents/AgileFlow/)
|
|
43
|
+
// This allows Cursor's async subagent spawning feature
|
|
44
|
+
const spawnableAgentsDir = path.join(ideDir, 'agents', 'AgileFlow');
|
|
45
|
+
|
|
46
|
+
// Clean existing spawnable agents directory to prevent duplicates during update
|
|
47
|
+
if (await fs.pathExists(spawnableAgentsDir)) {
|
|
48
|
+
await fs.remove(spawnableAgentsDir);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const agentInstallResult = await this.installCommandsRecursive(
|
|
52
|
+
agentsSource,
|
|
53
|
+
spawnableAgentsDir,
|
|
54
|
+
agileflowDir,
|
|
55
|
+
false
|
|
56
|
+
);
|
|
57
|
+
console.log(chalk.dim(` - Spawnable agents: .cursor/agents/AgileFlow/`));
|
|
58
|
+
|
|
59
|
+
// Cursor specific: Setup damage control hooks
|
|
60
|
+
await this.setupDamageControlHooks(projectDir, agileflowDir, ideDir, options);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...result,
|
|
64
|
+
agents: agentInstallResult.commands,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Setup damage control hooks for Cursor
|
|
70
|
+
* Maps Claude Code's PreToolUse hooks to Cursor's lifecycle events:
|
|
71
|
+
* - beforeShellExecution (for bash commands)
|
|
72
|
+
* - afterFileEdit (for file edits)
|
|
73
|
+
*
|
|
74
|
+
* @param {string} projectDir - Project directory
|
|
75
|
+
* @param {string} agileflowDir - AgileFlow installation directory
|
|
76
|
+
* @param {string} cursorDir - .cursor directory path
|
|
77
|
+
* @param {Object} options - Setup options
|
|
78
|
+
*/
|
|
79
|
+
async setupDamageControlHooks(projectDir, agileflowDir, cursorDir, options = {}) {
|
|
80
|
+
if (options.skipDamageControl) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const hooksPath = path.join(cursorDir, 'hooks.json');
|
|
85
|
+
let hooks = [];
|
|
86
|
+
|
|
87
|
+
// Load existing hooks if they exist
|
|
88
|
+
if (fs.existsSync(hooksPath)) {
|
|
89
|
+
try {
|
|
90
|
+
const content = await fs.readFile(hooksPath, 'utf8');
|
|
91
|
+
hooks = JSON.parse(content);
|
|
92
|
+
if (!Array.isArray(hooks)) {
|
|
93
|
+
hooks = [];
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
hooks = [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Define damage control hooks for Cursor
|
|
101
|
+
// Cursor's hooks.json format: Array of {event, command, args, order}
|
|
102
|
+
const damageControlHooks = [
|
|
103
|
+
{
|
|
104
|
+
event: 'beforeShellExecution',
|
|
105
|
+
command: 'node',
|
|
106
|
+
args: ['$CURSOR_PROJECT_DIR/.cursor/hooks/damage-control/bash-tool-damage-control.js'],
|
|
107
|
+
order: 1,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
event: 'afterFileEdit',
|
|
111
|
+
command: 'node',
|
|
112
|
+
args: ['$CURSOR_PROJECT_DIR/.cursor/hooks/damage-control/edit-tool-damage-control.js'],
|
|
113
|
+
order: 1,
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
// Check if damage control hooks already exist and merge
|
|
118
|
+
let hasUpdates = false;
|
|
119
|
+
for (const newHook of damageControlHooks) {
|
|
120
|
+
const existingIdx = hooks.findIndex(
|
|
121
|
+
h =>
|
|
122
|
+
h.event === newHook.event &&
|
|
123
|
+
h.command === newHook.command &&
|
|
124
|
+
(h.args?.join(' ') === newHook.args?.join(' ') ||
|
|
125
|
+
(h.args && h.args.some(arg => arg.includes('damage-control'))))
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (existingIdx === -1) {
|
|
129
|
+
hooks.push(newHook);
|
|
130
|
+
hasUpdates = true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Only write if we have updates or if file didn't exist
|
|
135
|
+
if (hasUpdates || !fs.existsSync(hooksPath)) {
|
|
136
|
+
await fs.ensureDir(path.dirname(hooksPath));
|
|
137
|
+
await fs.writeFile(hooksPath, JSON.stringify(hooks, null, 2));
|
|
138
|
+
console.log(chalk.dim(` - Damage control: hooks enabled`));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Copy damage control scripts
|
|
142
|
+
await this.setupDamageControlScripts(agileflowDir, cursorDir);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Copy damage control scripts to .cursor/hooks/damage-control/
|
|
147
|
+
* @param {string} agileflowDir - AgileFlow installation directory
|
|
148
|
+
* @param {string} cursorDir - .cursor directory path
|
|
149
|
+
*/
|
|
150
|
+
async setupDamageControlScripts(agileflowDir, cursorDir) {
|
|
151
|
+
const damageControlSource = path.join(agileflowDir, 'scripts', 'damage-control');
|
|
152
|
+
const damageControlTarget = path.join(cursorDir, 'hooks', 'damage-control');
|
|
153
|
+
|
|
154
|
+
if (!fs.existsSync(damageControlSource)) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await this.ensureDir(damageControlTarget);
|
|
159
|
+
|
|
160
|
+
// Copy hook scripts
|
|
161
|
+
const scripts = [
|
|
162
|
+
'bash-tool-damage-control.js',
|
|
163
|
+
'edit-tool-damage-control.js',
|
|
164
|
+
'write-tool-damage-control.js',
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
for (const script of scripts) {
|
|
168
|
+
const src = path.join(damageControlSource, script);
|
|
169
|
+
const dest = path.join(damageControlTarget, script);
|
|
170
|
+
if (fs.existsSync(src)) {
|
|
171
|
+
await fs.copy(src, dest);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Copy lib/damage-control-utils.js (required by hook scripts)
|
|
176
|
+
const libSource = path.join(agileflowDir, 'scripts', 'lib', 'damage-control-utils.js');
|
|
177
|
+
const libTarget = path.join(cursorDir, 'hooks', 'lib', 'damage-control-utils.js');
|
|
178
|
+
if (fs.existsSync(libSource)) {
|
|
179
|
+
await this.ensureDir(path.dirname(libTarget));
|
|
180
|
+
await fs.copy(libSource, libTarget);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Copy patterns.yaml (preserve existing)
|
|
184
|
+
const patternsSource = path.join(damageControlSource, 'patterns.yaml');
|
|
185
|
+
const patternsTarget = path.join(damageControlTarget, 'patterns.yaml');
|
|
186
|
+
if (fs.existsSync(patternsSource) && !fs.existsSync(patternsTarget)) {
|
|
187
|
+
await fs.copy(patternsSource, patternsTarget);
|
|
188
|
+
}
|
|
34
189
|
}
|
|
35
190
|
|
|
36
191
|
/**
|
|
@@ -45,11 +200,17 @@ class CursorSetup extends BaseIdeSetup {
|
|
|
45
200
|
console.log(chalk.dim(` Removed old AgileFlow rules from ${this.displayName}`));
|
|
46
201
|
}
|
|
47
202
|
|
|
48
|
-
// Remove .cursor/commands/
|
|
203
|
+
// Remove .cursor/commands/AgileFlow (for re-installation)
|
|
49
204
|
const commandsPath = path.join(projectDir, this.configDir, this.commandsDir, 'AgileFlow');
|
|
50
205
|
if (await this.exists(commandsPath)) {
|
|
51
206
|
await fs.remove(commandsPath);
|
|
52
207
|
}
|
|
208
|
+
|
|
209
|
+
// Remove .cursor/agents/AgileFlow (for re-installation)
|
|
210
|
+
const agentsPath = path.join(projectDir, this.configDir, 'agents', 'AgileFlow');
|
|
211
|
+
if (await this.exists(agentsPath)) {
|
|
212
|
+
await fs.remove(agentsPath);
|
|
213
|
+
}
|
|
53
214
|
}
|
|
54
215
|
}
|
|
55
216
|
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AgileFlow CLI - Windsurf IDE Installer
|
|
3
3
|
*
|
|
4
|
-
* Installs AgileFlow
|
|
5
|
-
*
|
|
4
|
+
* Installs AgileFlow for Windsurf IDE:
|
|
5
|
+
* - Commands as workflows to .windsurf/workflows/agileflow/
|
|
6
|
+
* - Agents as skills to .windsurf/skills/agileflow-<NAME>/SKILL.md
|
|
7
|
+
* - Damage control hooks to .windsurf/hooks.json
|
|
8
|
+
*
|
|
9
|
+
* Windsurf supports 11 lifecycle events and 12,000 character limit per workflow.
|
|
10
|
+
* Skills follow agentskills.io specification with YAML frontmatter.
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
const path = require('node:path');
|
|
9
14
|
const fs = require('fs-extra');
|
|
10
15
|
const chalk = require('chalk');
|
|
16
|
+
const { yaml } = require('../../../../lib/yaml-utils');
|
|
11
17
|
const { BaseIdeSetup } = require('./_base-ide');
|
|
18
|
+
const {
|
|
19
|
+
getFrontmatter,
|
|
20
|
+
stripFrontmatter,
|
|
21
|
+
replaceReferences,
|
|
22
|
+
IDE_REPLACEMENTS,
|
|
23
|
+
} = require('../../lib/content-transformer');
|
|
12
24
|
|
|
13
25
|
/**
|
|
14
26
|
* Windsurf IDE setup handler
|
|
@@ -20,6 +32,187 @@ class WindsurfSetup extends BaseIdeSetup {
|
|
|
20
32
|
this.workflowsDir = 'workflows';
|
|
21
33
|
}
|
|
22
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Convert an AgileFlow agent markdown file to Windsurf SKILL.md format
|
|
37
|
+
* Windsurf skills follow the agentskills.io specification with YAML frontmatter
|
|
38
|
+
*
|
|
39
|
+
* @param {string} content - Original agent markdown content
|
|
40
|
+
* @param {string} agentName - Agent name (e.g., 'database')
|
|
41
|
+
* @returns {string} Windsurf SKILL.md content
|
|
42
|
+
*/
|
|
43
|
+
convertAgentToSkill(content, agentName) {
|
|
44
|
+
// Extract frontmatter using content-transformer
|
|
45
|
+
const frontmatter = getFrontmatter(content);
|
|
46
|
+
const description = frontmatter.description || `AgileFlow ${agentName} agent`;
|
|
47
|
+
|
|
48
|
+
// Create SKILL.md with YAML frontmatter (agentskills.io spec)
|
|
49
|
+
const skillFrontmatter = yaml
|
|
50
|
+
.dump({
|
|
51
|
+
name: `agileflow-${agentName}`,
|
|
52
|
+
description: description,
|
|
53
|
+
})
|
|
54
|
+
.trim();
|
|
55
|
+
|
|
56
|
+
// Remove original frontmatter from content using content-transformer
|
|
57
|
+
let bodyContent = stripFrontmatter(content);
|
|
58
|
+
|
|
59
|
+
// Add Windsurf-specific header
|
|
60
|
+
const windsurfHeader = `# AgileFlow: ${agentName.charAt(0).toUpperCase() + agentName.slice(1)} Skill
|
|
61
|
+
|
|
62
|
+
> Use this skill via \`@agileflow-${agentName}\` or /cascade
|
|
63
|
+
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
// Replace Claude-specific references using content-transformer
|
|
67
|
+
bodyContent = replaceReferences(bodyContent, IDE_REPLACEMENTS.windsurf);
|
|
68
|
+
|
|
69
|
+
// Add Windsurf-specific replacements
|
|
70
|
+
bodyContent = replaceReferences(bodyContent, {
|
|
71
|
+
'Task tool': 'workflow chaining',
|
|
72
|
+
AskUserQuestion: 'numbered list prompt',
|
|
73
|
+
'.claude/agents/agileflow': '.windsurf/skills/agileflow',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Replace /agileflow: prefix for Windsurf workflow chaining
|
|
77
|
+
// e.g., /agileflow:story:list → /agileflow-story-list
|
|
78
|
+
bodyContent = bodyContent.replace(/\/agileflow:([a-zA-Z0-9:_-]+)/g, (_match, rest) => {
|
|
79
|
+
return '/agileflow-' + rest.replace(/:/g, '-');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Warn if content exceeds Windsurf's 12,000 character limit
|
|
83
|
+
const totalLength = skillFrontmatter.length + windsurfHeader.length + bodyContent.length;
|
|
84
|
+
if (totalLength > 12000) {
|
|
85
|
+
console.warn(
|
|
86
|
+
chalk.yellow(
|
|
87
|
+
` ⚠ Skill '${agentName}' exceeds 12,000 character limit (${totalLength} chars). Consider splitting.`
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `---
|
|
93
|
+
${skillFrontmatter}
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
${windsurfHeader}${bodyContent}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Install AgileFlow agents as Windsurf skills
|
|
101
|
+
* Skills are installed to .windsurf/skills/agileflow-{name}/SKILL.md
|
|
102
|
+
*
|
|
103
|
+
* @param {string} projectDir - Project directory
|
|
104
|
+
* @param {string} agileflowDir - AgileFlow installation directory
|
|
105
|
+
* @returns {Promise<number>} Number of skills installed
|
|
106
|
+
*/
|
|
107
|
+
async installSkills(projectDir, agileflowDir) {
|
|
108
|
+
const agentsSource = path.join(agileflowDir, 'agents');
|
|
109
|
+
const skillsTarget = path.join(projectDir, this.configDir, 'skills');
|
|
110
|
+
|
|
111
|
+
if (!(await this.exists(agentsSource))) {
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let skillCount = 0;
|
|
116
|
+
const agents = await this.scanDirectory(agentsSource, '.md');
|
|
117
|
+
|
|
118
|
+
for (const agent of agents) {
|
|
119
|
+
const content = await this.readFile(agent.path);
|
|
120
|
+
const skillContent = this.convertAgentToSkill(content, agent.name);
|
|
121
|
+
|
|
122
|
+
// Create skill directory: .windsurf/skills/agileflow-{name}/
|
|
123
|
+
const skillDir = path.join(skillsTarget, `agileflow-${agent.name}`);
|
|
124
|
+
await this.ensureDir(skillDir);
|
|
125
|
+
|
|
126
|
+
// Write SKILL.md
|
|
127
|
+
await this.writeFile(path.join(skillDir, 'SKILL.md'), skillContent);
|
|
128
|
+
skillCount++;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return skillCount;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Setup damage control hooks for Windsurf
|
|
136
|
+
* Maps Claude Code's PreToolUse hooks to Windsurf's lifecycle events:
|
|
137
|
+
* - pre_run_command (for bash commands)
|
|
138
|
+
* - post_write_code (for file edits)
|
|
139
|
+
* - pre_mcp_tool_use (for MCP tools)
|
|
140
|
+
*
|
|
141
|
+
* @param {string} projectDir - Project directory
|
|
142
|
+
* @param {string} agileflowDir - AgileFlow installation directory
|
|
143
|
+
* @param {string} windsurfDir - .windsurf directory path
|
|
144
|
+
* @param {Object} options - Setup options
|
|
145
|
+
*/
|
|
146
|
+
async setupDamageControlHooks(projectDir, agileflowDir, windsurfDir, options = {}) {
|
|
147
|
+
if (options.skipDamageControl) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const hooksPath = path.join(windsurfDir, 'hooks.json');
|
|
152
|
+
let hooks = [];
|
|
153
|
+
|
|
154
|
+
// Load existing hooks if they exist
|
|
155
|
+
if (fs.existsSync(hooksPath)) {
|
|
156
|
+
try {
|
|
157
|
+
const content = await fs.readFile(hooksPath, 'utf8');
|
|
158
|
+
const parsed = JSON.parse(content);
|
|
159
|
+
if (!Array.isArray(parsed)) {
|
|
160
|
+
console.warn(
|
|
161
|
+
'[AgileFlow] hooks.json exists but is not an array, preserving existing file'
|
|
162
|
+
);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
hooks = parsed;
|
|
166
|
+
} catch (e) {
|
|
167
|
+
console.warn(
|
|
168
|
+
`[AgileFlow] hooks.json exists but is malformed (${e.message}), preserving existing file`
|
|
169
|
+
);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Define damage control hooks for Windsurf
|
|
175
|
+
// Windsurf's hooks.json format differs from Cursor - it uses event, command, args
|
|
176
|
+
const damageControlHooks = [
|
|
177
|
+
{
|
|
178
|
+
event: 'pre_run_command',
|
|
179
|
+
command: 'node',
|
|
180
|
+
args: ['.agileflow/scripts/damage-control/damage-control-bash.js'],
|
|
181
|
+
description: 'AgileFlow damage control for shell commands',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
event: 'post_write_code',
|
|
185
|
+
command: 'node',
|
|
186
|
+
args: ['.agileflow/scripts/damage-control/damage-control-edit.js'],
|
|
187
|
+
description: 'AgileFlow damage control for file edits',
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
// Check if damage control hooks already exist and merge
|
|
192
|
+
let hasUpdates = false;
|
|
193
|
+
for (const newHook of damageControlHooks) {
|
|
194
|
+
const existingIdx = hooks.findIndex(
|
|
195
|
+
h =>
|
|
196
|
+
h.event === newHook.event &&
|
|
197
|
+
h.command === newHook.command &&
|
|
198
|
+
(h.args?.join(' ') === newHook.args?.join(' ') ||
|
|
199
|
+
(h.args && h.args.some(arg => arg.includes('damage-control'))))
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
if (existingIdx === -1) {
|
|
203
|
+
hooks.push(newHook);
|
|
204
|
+
hasUpdates = true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Only write if we have updates or if file didn't exist
|
|
209
|
+
if (hasUpdates || !fs.existsSync(hooksPath)) {
|
|
210
|
+
await fs.ensureDir(path.dirname(hooksPath));
|
|
211
|
+
await fs.writeFile(hooksPath, JSON.stringify(hooks, null, 2));
|
|
212
|
+
console.log(chalk.dim(` - Damage control: hooks enabled`));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
23
216
|
/**
|
|
24
217
|
* Setup Windsurf IDE configuration
|
|
25
218
|
* @param {string} projectDir - Project directory
|
|
@@ -27,12 +220,37 @@ class WindsurfSetup extends BaseIdeSetup {
|
|
|
27
220
|
* @param {Object} options - Setup options
|
|
28
221
|
*/
|
|
29
222
|
async setup(projectDir, agileflowDir, options = {}) {
|
|
30
|
-
|
|
223
|
+
console.log(chalk.hex('#e8683a')(` Setting up ${this.displayName}...`));
|
|
224
|
+
|
|
225
|
+
// Note: cleanup is handled inside setupStandard(), no need to call explicitly
|
|
226
|
+
|
|
227
|
+
// 1. Install workflows using standard setup
|
|
228
|
+
const workflowsResult = await this.setupStandard(projectDir, agileflowDir, {
|
|
31
229
|
targetSubdir: this.workflowsDir,
|
|
32
230
|
agileflowFolder: 'agileflow',
|
|
33
231
|
commandLabel: 'workflows',
|
|
34
232
|
agentLabel: 'agent workflows',
|
|
35
233
|
});
|
|
234
|
+
|
|
235
|
+
// 2. Install agents as skills
|
|
236
|
+
const skillCount = await this.installSkills(projectDir, agileflowDir);
|
|
237
|
+
if (skillCount > 0) {
|
|
238
|
+
console.log(chalk.dim(` - ${skillCount} skills installed to .windsurf/skills/`));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 3. Setup damage control hooks
|
|
242
|
+
const windsurfDir = path.join(projectDir, this.configDir);
|
|
243
|
+
await this.setupDamageControlHooks(projectDir, agileflowDir, windsurfDir, options);
|
|
244
|
+
|
|
245
|
+
console.log(chalk.green(` ✓ ${this.displayName} configured:`));
|
|
246
|
+
console.log(chalk.dim(` - Workflows: .windsurf/workflows/agileflow/`));
|
|
247
|
+
console.log(chalk.dim(` - Skills: .windsurf/skills/agileflow-*/`));
|
|
248
|
+
console.log(chalk.dim(` - Hooks: .windsurf/hooks.json`));
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
...workflowsResult,
|
|
252
|
+
skills: skillCount,
|
|
253
|
+
};
|
|
36
254
|
}
|
|
37
255
|
|
|
38
256
|
/**
|
|
@@ -40,11 +258,24 @@ class WindsurfSetup extends BaseIdeSetup {
|
|
|
40
258
|
* @param {string} projectDir - Project directory
|
|
41
259
|
*/
|
|
42
260
|
async cleanup(projectDir) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
261
|
+
// Remove old workflows directory
|
|
262
|
+
const workflowsPath = path.join(projectDir, this.configDir, this.workflowsDir, 'agileflow');
|
|
263
|
+
if (await this.exists(workflowsPath)) {
|
|
264
|
+
await fs.remove(workflowsPath);
|
|
46
265
|
console.log(chalk.dim(` Removed old AgileFlow workflows from ${this.displayName}`));
|
|
47
266
|
}
|
|
267
|
+
|
|
268
|
+
// Remove old skills directories
|
|
269
|
+
const skillsDir = path.join(projectDir, this.configDir, 'skills');
|
|
270
|
+
if (await this.exists(skillsDir)) {
|
|
271
|
+
const entries = await fs.readdir(skillsDir);
|
|
272
|
+
for (const entry of entries) {
|
|
273
|
+
if (entry.startsWith('agileflow-')) {
|
|
274
|
+
await fs.remove(path.join(skillsDir, entry));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
console.log(chalk.dim(` Removed old AgileFlow skills from ${this.displayName}`));
|
|
278
|
+
}
|
|
48
279
|
}
|
|
49
280
|
}
|
|
50
281
|
|