agileflow 3.1.0 → 3.2.1
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 +10 -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/browser-qa-evidence.js +409 -0
- package/scripts/lib/browser-qa-status.js +192 -0
- 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/browser-qa.md +328 -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/browser-qa.md +240 -0
- 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/browser-qa-spec.yaml +94 -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
|
@@ -71,7 +71,8 @@ class BaseIdeSetup {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
// Replace standalone "docs" word (not followed by .)
|
|
74
|
-
|
|
74
|
+
// Use replacement function to avoid $ in docsFolder being interpreted as regex backreference
|
|
75
|
+
result = result.replace(/\bdocs\b(?!\.)/g, () => this.docsFolder);
|
|
75
76
|
|
|
76
77
|
return result;
|
|
77
78
|
}
|
|
@@ -371,15 +372,38 @@ class BaseIdeSetup {
|
|
|
371
372
|
/**
|
|
372
373
|
* Recursively install markdown files from source to target directory
|
|
373
374
|
* Handles content injection and docs reference replacement.
|
|
375
|
+
* Optionally applies IDE-specific transformations via ideTransform callback.
|
|
376
|
+
*
|
|
374
377
|
* @param {string} sourceDir - Source directory path
|
|
375
378
|
* @param {string} targetDir - Target directory path
|
|
376
379
|
* @param {string} agileflowDir - AgileFlow installation directory (for dynamic content)
|
|
377
380
|
* @param {boolean} injectDynamic - Whether to inject dynamic content (only for top-level commands)
|
|
381
|
+
* @param {Function} [ideTransform] - Optional IDE transformation function: (content, filename) => string
|
|
378
382
|
* @returns {Promise<{commands: number, subdirs: number}>} Count of installed items
|
|
379
383
|
* @throws {CommandInstallationError} If command installation fails
|
|
380
384
|
* @throws {FilePermissionError} If permission denied
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* // Without transformation (backward compatible)
|
|
388
|
+
* await installer.installCommandsRecursive(src, target, agileflow, true);
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* // With IDE transformation
|
|
392
|
+
* await installer.installCommandsRecursive(
|
|
393
|
+
* src,
|
|
394
|
+
* target,
|
|
395
|
+
* agileflow,
|
|
396
|
+
* true,
|
|
397
|
+
* (content, filename) => ideGenerator.generateForIde(content, 'cursor')
|
|
398
|
+
* );
|
|
381
399
|
*/
|
|
382
|
-
async installCommandsRecursive(
|
|
400
|
+
async installCommandsRecursive(
|
|
401
|
+
sourceDir,
|
|
402
|
+
targetDir,
|
|
403
|
+
agileflowDir,
|
|
404
|
+
injectDynamic = false,
|
|
405
|
+
ideTransform = null
|
|
406
|
+
) {
|
|
383
407
|
let commandCount = 0;
|
|
384
408
|
let subdirCount = 0;
|
|
385
409
|
|
|
@@ -419,6 +443,11 @@ class BaseIdeSetup {
|
|
|
419
443
|
// Replace docs/ references with custom folder name
|
|
420
444
|
content = this.replaceDocsReferences(content);
|
|
421
445
|
|
|
446
|
+
// Apply IDE-specific transformation if provided
|
|
447
|
+
if (ideTransform && typeof ideTransform === 'function') {
|
|
448
|
+
content = ideTransform(content, entry.name);
|
|
449
|
+
}
|
|
450
|
+
|
|
422
451
|
await this.writeFile(targetPath, content);
|
|
423
452
|
commandCount++;
|
|
424
453
|
} catch (error) {
|
|
@@ -437,7 +466,8 @@ class BaseIdeSetup {
|
|
|
437
466
|
sourcePath,
|
|
438
467
|
targetPath,
|
|
439
468
|
agileflowDir,
|
|
440
|
-
false // Don't inject dynamic content in subdirectories
|
|
469
|
+
false, // Don't inject dynamic content in subdirectories
|
|
470
|
+
ideTransform // Pass ideTransform to recursive calls
|
|
441
471
|
);
|
|
442
472
|
commandCount += subResult.commands;
|
|
443
473
|
subdirCount += 1 + subResult.subdirs;
|
|
@@ -61,12 +61,9 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|
|
61
61
|
// Claude Code specific: Setup damage control hooks
|
|
62
62
|
await this.setupDamageControl(projectDir, agileflowDir, ideDir, options);
|
|
63
63
|
|
|
64
|
-
// Claude Code specific: Setup SessionStart hooks (welcome, archive, context-loader
|
|
64
|
+
// Claude Code specific: Setup SessionStart hooks (welcome, archive, context-loader)
|
|
65
65
|
await this.setupSessionStartHooks(projectDir, agileflowDir, ideDir, options);
|
|
66
66
|
|
|
67
|
-
// Claude Code specific: Setup Stop hooks (tmux-task-watcher cleanup)
|
|
68
|
-
await this.setupStopHooks(projectDir, agileflowDir, ideDir, options);
|
|
69
|
-
|
|
70
67
|
return result;
|
|
71
68
|
}
|
|
72
69
|
|
|
@@ -259,12 +256,6 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|
|
259
256
|
'node $CLAUDE_PROJECT_DIR/.agileflow/scripts/context-loader.js 2>/dev/null || true',
|
|
260
257
|
timeout: 5000,
|
|
261
258
|
},
|
|
262
|
-
{
|
|
263
|
-
type: 'command',
|
|
264
|
-
command:
|
|
265
|
-
'bash $CLAUDE_PROJECT_DIR/.agileflow/scripts/tmux-task-watcher.sh 2>/dev/null || true',
|
|
266
|
-
timeout: 5000,
|
|
267
|
-
},
|
|
268
259
|
];
|
|
269
260
|
|
|
270
261
|
// Check if SessionStart hooks already exist
|
|
@@ -294,65 +285,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|
|
294
285
|
|
|
295
286
|
// Write settings
|
|
296
287
|
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
297
|
-
console.log(
|
|
298
|
-
chalk.dim(` - SessionStart hooks: welcome, archive, context-loader, tmux-task-watcher`)
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Setup Stop hooks (tmux-task-watcher cleanup)
|
|
304
|
-
* @param {string} projectDir - Project directory
|
|
305
|
-
* @param {string} agileflowDir - AgileFlow installation directory
|
|
306
|
-
* @param {string} claudeDir - .claude directory path
|
|
307
|
-
* @param {Object} options - Setup options
|
|
308
|
-
*/
|
|
309
|
-
async setupStopHooks(projectDir, agileflowDir, claudeDir, options = {}) {
|
|
310
|
-
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
311
|
-
let settings = {};
|
|
312
|
-
|
|
313
|
-
if (fs.existsSync(settingsPath)) {
|
|
314
|
-
try {
|
|
315
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
316
|
-
} catch (e) {
|
|
317
|
-
settings = {};
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (!settings.hooks) settings.hooks = {};
|
|
322
|
-
if (!settings.hooks.Stop) settings.hooks.Stop = [];
|
|
323
|
-
|
|
324
|
-
const stopHooks = [
|
|
325
|
-
{
|
|
326
|
-
type: 'command',
|
|
327
|
-
command:
|
|
328
|
-
'bash $CLAUDE_PROJECT_DIR/.agileflow/scripts/tmux-task-watcher.sh stop 2>/dev/null || true',
|
|
329
|
-
timeout: 3000,
|
|
330
|
-
},
|
|
331
|
-
];
|
|
332
|
-
|
|
333
|
-
const existingEntry = settings.hooks.Stop.find(
|
|
334
|
-
h => h.matcher === '' || h.matcher === undefined
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
if (existingEntry) {
|
|
338
|
-
if (!existingEntry.hooks) existingEntry.hooks = [];
|
|
339
|
-
for (const newHook of stopHooks) {
|
|
340
|
-
const alreadyExists = existingEntry.hooks.some(
|
|
341
|
-
h => h.command && h.command.includes('tmux-task-watcher')
|
|
342
|
-
);
|
|
343
|
-
if (!alreadyExists) {
|
|
344
|
-
existingEntry.hooks.push(newHook);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
} else {
|
|
348
|
-
settings.hooks.Stop.push({
|
|
349
|
-
matcher: '',
|
|
350
|
-
hooks: stopHooks,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
355
|
-
console.log(chalk.dim(` - Stop hooks: tmux-task-watcher cleanup`));
|
|
288
|
+
console.log(chalk.dim(` - SessionStart hooks: welcome, archive, context-loader`));
|
|
356
289
|
}
|
|
357
290
|
|
|
358
291
|
/**
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AgileFlow CLI - OpenAI Codex
|
|
2
|
+
* AgileFlow CLI - OpenAI Codex Installer
|
|
3
3
|
*
|
|
4
|
-
* Installs AgileFlow for OpenAI Codex
|
|
4
|
+
* Installs AgileFlow for OpenAI Codex:
|
|
5
5
|
* - Agents become Codex Skills (.codex/skills/agileflow-NAME/)
|
|
6
6
|
* - Commands become Codex Prompts (~/.codex/prompts/agileflow-NAME.md)
|
|
7
7
|
* - AGENTS.md provides project instructions at repo root
|
|
8
8
|
*
|
|
9
9
|
* @see https://developers.openai.com/codex/
|
|
10
|
-
* @see ADR-0002: Codex
|
|
10
|
+
* @see ADR-0002: OpenAI Codex Integration Strategy
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const path = require('node:path');
|
|
@@ -24,11 +24,11 @@ const {
|
|
|
24
24
|
} = require('../../lib/content-transformer');
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* OpenAI Codex
|
|
27
|
+
* OpenAI Codex setup handler
|
|
28
28
|
*/
|
|
29
29
|
class CodexSetup extends BaseIdeSetup {
|
|
30
30
|
constructor() {
|
|
31
|
-
super('codex', 'OpenAI Codex
|
|
31
|
+
super('codex', 'OpenAI Codex', false);
|
|
32
32
|
// Per-repo config directory
|
|
33
33
|
this.configDir = '.codex';
|
|
34
34
|
// User-level Codex home (can be overridden by $CODEX_HOME)
|
|
@@ -44,12 +44,12 @@ class CodexSetup extends BaseIdeSetup {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Detect if Codex
|
|
47
|
+
* Detect if OpenAI Codex is installed/configured
|
|
48
48
|
* @param {string} projectDir - Project directory
|
|
49
49
|
* @returns {Promise<boolean>}
|
|
50
50
|
*/
|
|
51
51
|
async detect(projectDir) {
|
|
52
|
-
// Check if Codex home exists (user has Codex
|
|
52
|
+
// Check if Codex home exists (user has OpenAI Codex)
|
|
53
53
|
const codexHomeExists = await this.exists(this.codexHome);
|
|
54
54
|
// Check if project has .codex/ or AGENTS.md
|
|
55
55
|
const projectCodexExists = await this.exists(path.join(projectDir, this.configDir));
|
|
@@ -229,7 +229,7 @@ ${codexHeader}${bodyContent}`;
|
|
|
229
229
|
|
|
230
230
|
const content = `# AGENTS.md
|
|
231
231
|
|
|
232
|
-
> Project instructions for Codex
|
|
232
|
+
> Project instructions for OpenAI Codex with AgileFlow integration
|
|
233
233
|
|
|
234
234
|
## Project Commands
|
|
235
235
|
|
|
@@ -334,7 +334,7 @@ This directory contains: [describe purpose]
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
/**
|
|
337
|
-
* Setup Codex
|
|
337
|
+
* Setup OpenAI Codex configuration
|
|
338
338
|
* @param {string} projectDir - Project directory
|
|
339
339
|
* @param {string} agileflowDir - AgileFlow installation directory
|
|
340
340
|
* @param {Object} options - Setup options
|
|
@@ -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
|
|