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.
Files changed (106) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +57 -85
  3. package/lib/dashboard-automations.js +130 -0
  4. package/lib/dashboard-git.js +254 -0
  5. package/lib/dashboard-inbox.js +64 -0
  6. package/lib/dashboard-protocol.js +1 -0
  7. package/lib/dashboard-server.js +114 -924
  8. package/lib/dashboard-session.js +136 -0
  9. package/lib/dashboard-status.js +72 -0
  10. package/lib/dashboard-terminal.js +354 -0
  11. package/lib/dashboard-websocket.js +88 -0
  12. package/lib/drivers/codex-driver.ts +4 -4
  13. package/lib/logger.js +106 -0
  14. package/package.json +4 -2
  15. package/scripts/agileflow-configure.js +2 -2
  16. package/scripts/agileflow-welcome.js +409 -434
  17. package/scripts/claude-tmux.sh +80 -2
  18. package/scripts/context-loader.js +4 -9
  19. package/scripts/lib/browser-qa-evidence.js +409 -0
  20. package/scripts/lib/browser-qa-status.js +192 -0
  21. package/scripts/lib/command-prereqs.js +280 -0
  22. package/scripts/lib/configure-detect.js +92 -2
  23. package/scripts/lib/configure-features.js +295 -1
  24. package/scripts/lib/context-formatter.js +468 -233
  25. package/scripts/lib/context-loader.js +27 -15
  26. package/scripts/lib/damage-control-utils.js +8 -1
  27. package/scripts/lib/feature-catalog.js +321 -0
  28. package/scripts/lib/portable-tasks-cli.js +274 -0
  29. package/scripts/lib/portable-tasks.js +479 -0
  30. package/scripts/lib/signal-detectors.js +1 -1
  31. package/scripts/lib/team-events.js +86 -1
  32. package/scripts/obtain-context.js +28 -4
  33. package/scripts/smart-detect.js +17 -0
  34. package/scripts/strip-ai-attribution.js +63 -0
  35. package/scripts/team-manager.js +7 -2
  36. package/scripts/welcome-deferred.js +437 -0
  37. package/src/core/agents/browser-qa.md +328 -0
  38. package/src/core/agents/perf-analyzer-assets.md +174 -0
  39. package/src/core/agents/perf-analyzer-bundle.md +165 -0
  40. package/src/core/agents/perf-analyzer-caching.md +160 -0
  41. package/src/core/agents/perf-analyzer-compute.md +165 -0
  42. package/src/core/agents/perf-analyzer-memory.md +182 -0
  43. package/src/core/agents/perf-analyzer-network.md +157 -0
  44. package/src/core/agents/perf-analyzer-queries.md +155 -0
  45. package/src/core/agents/perf-analyzer-rendering.md +156 -0
  46. package/src/core/agents/perf-consensus.md +280 -0
  47. package/src/core/agents/security-analyzer-api.md +199 -0
  48. package/src/core/agents/security-analyzer-auth.md +160 -0
  49. package/src/core/agents/security-analyzer-authz.md +168 -0
  50. package/src/core/agents/security-analyzer-deps.md +147 -0
  51. package/src/core/agents/security-analyzer-infra.md +176 -0
  52. package/src/core/agents/security-analyzer-injection.md +148 -0
  53. package/src/core/agents/security-analyzer-input.md +191 -0
  54. package/src/core/agents/security-analyzer-secrets.md +175 -0
  55. package/src/core/agents/security-consensus.md +276 -0
  56. package/src/core/agents/test-analyzer-assertions.md +181 -0
  57. package/src/core/agents/test-analyzer-coverage.md +183 -0
  58. package/src/core/agents/test-analyzer-fragility.md +185 -0
  59. package/src/core/agents/test-analyzer-integration.md +155 -0
  60. package/src/core/agents/test-analyzer-maintenance.md +173 -0
  61. package/src/core/agents/test-analyzer-mocking.md +178 -0
  62. package/src/core/agents/test-analyzer-patterns.md +189 -0
  63. package/src/core/agents/test-analyzer-structure.md +177 -0
  64. package/src/core/agents/test-consensus.md +294 -0
  65. package/src/core/commands/{legal/audit.md → audit/legal.md} +13 -13
  66. package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
  67. package/src/core/commands/audit/performance.md +443 -0
  68. package/src/core/commands/audit/security.md +443 -0
  69. package/src/core/commands/audit/test.md +442 -0
  70. package/src/core/commands/babysit.md +505 -463
  71. package/src/core/commands/browser-qa.md +240 -0
  72. package/src/core/commands/configure.md +8 -8
  73. package/src/core/commands/research/ask.md +42 -9
  74. package/src/core/commands/research/import.md +14 -8
  75. package/src/core/commands/research/list.md +17 -16
  76. package/src/core/commands/research/synthesize.md +8 -8
  77. package/src/core/commands/research/view.md +28 -4
  78. package/src/core/commands/whats-new.md +2 -2
  79. package/src/core/experts/devops/expertise.yaml +13 -2
  80. package/src/core/experts/documentation/expertise.yaml +26 -4
  81. package/src/core/profiles/COMPARISON.md +170 -0
  82. package/src/core/profiles/README.md +178 -0
  83. package/src/core/profiles/claude-code.yaml +111 -0
  84. package/src/core/profiles/codex.yaml +103 -0
  85. package/src/core/profiles/cursor.yaml +134 -0
  86. package/src/core/profiles/examples.js +250 -0
  87. package/src/core/profiles/loader.js +235 -0
  88. package/src/core/profiles/windsurf.yaml +159 -0
  89. package/src/core/teams/logic-audit.json +6 -0
  90. package/src/core/teams/perf-audit.json +71 -0
  91. package/src/core/teams/security-audit.json +71 -0
  92. package/src/core/teams/test-audit.json +71 -0
  93. package/src/core/templates/browser-qa-spec.yaml +94 -0
  94. package/src/core/templates/command-prerequisites.yaml +169 -0
  95. package/src/core/templates/damage-control-patterns.yaml +9 -0
  96. package/tools/cli/installers/ide/_base-ide.js +33 -3
  97. package/tools/cli/installers/ide/claude-code.js +2 -69
  98. package/tools/cli/installers/ide/codex.js +9 -9
  99. package/tools/cli/installers/ide/cursor.js +165 -4
  100. package/tools/cli/installers/ide/windsurf.js +237 -6
  101. package/tools/cli/lib/content-transformer.js +234 -9
  102. package/tools/cli/lib/docs-setup.js +1 -1
  103. package/tools/cli/lib/ide-generator.js +357 -0
  104. package/tools/cli/lib/ide-registry.js +2 -2
  105. package/scripts/tmux-task-name.sh +0 -105
  106. package/scripts/tmux-task-watcher.sh +0 -344
@@ -0,0 +1,134 @@
1
+ # Cursor IDE Capability Profile
2
+ # v2.5 (Feb 2026) - Closest competitor to Claude Code
3
+ # Reference: Cross-IDE Compatibility Findings (20260220)
4
+
5
+ ide:
6
+ id: cursor
7
+ name: Cursor
8
+ displayName: Cursor
9
+ description: AI-native code editor with async subagents and hooks
10
+ configDir: .cursor
11
+ instructionsFile: CURSOR.md
12
+ fileFormat: markdown
13
+ commandPrefix: "/"
14
+ agentPrefix: "agileflow-"
15
+ marketplaceId: cursor.com/marketplace
16
+
17
+ paths:
18
+ commands: .cursor/commands/
19
+ agents: .cursor/agents/
20
+ skills: null # Uses agents instead
21
+ rules: .cursor/rules/
22
+ hooks: .cursor/hooks.json
23
+ mcp: .cursor/mcp.json
24
+
25
+ capabilities:
26
+ core:
27
+ fileOperations: true # read_file, edit_file, ApplyPatch
28
+ shell: true # run_terminal_cmd, YOLO mode for auto-execution
29
+ fileSearch: true # codebase_search, grep_search, file_search, list_dir
30
+ interactiveInput: true # Conversational only (no structured AskUserQuestion)
31
+ subAgents: true # Async subagents with nesting
32
+ nestedSubAgents: true # Sub-agents can spawn sub-sub-agents
33
+ planning:
34
+ planMode: true # Native plan mode with Markdown plans
35
+ planModeEditable: true # User can edit plans during execution
36
+ lifecycle:
37
+ hooks: true # Beta - 6 hook events
38
+ hookEvents:
39
+ - beforeSubmitPrompt
40
+ - beforeShellExecution
41
+ - beforeMCPExecution
42
+ - beforeReadFile
43
+ - afterFileEdit
44
+ - stop
45
+ external:
46
+ webSearch: true # Native web_search + @web syntax + MCP
47
+ webFetch: true # Can fetch URLs
48
+ mcp: true # Full MCP support
49
+ mcpToolLimit: 40 # 40-tool cap (binding constraint)
50
+ browser: true # Native browser tool (v2.0+)
51
+ collaboration:
52
+ taskTracking: false # Plan mode checklists only (not persistent)
53
+ persistentTasks: false
54
+ worktrees: false # Background agents use branches instead
55
+
56
+ toolNames:
57
+ # Interactive
58
+ askUser: null # Conversational only, no AskUserQuestion
59
+ # Delegation
60
+ spawnAgent: null # Use native async subagents in .cursor/agents/
61
+ # Planning
62
+ planModeEnter: null # Native, activated via interface
63
+ planModeExit: null # Native
64
+ # Execution
65
+ bash: run_terminal_cmd
66
+ # Files
67
+ read: read_file
68
+ write: null # Use ApplyPatch or edit_file
69
+ edit: edit_file
70
+ # Search
71
+ glob: file_search
72
+ grep: grep_search
73
+ # Web
74
+ webSearch: web_search
75
+ webFetch: null # Use web_search with @web
76
+ # Browser
77
+ browser: native_browser
78
+
79
+ frontmatter:
80
+ agents:
81
+ format: yaml
82
+ fields:
83
+ - name
84
+ - description
85
+ - model
86
+ - readonly
87
+ - is_background
88
+ commands:
89
+ format: markdown # Slash commands
90
+ rules:
91
+ format: mdc # MDC format (YAML: description, globs, alwaysApply)
92
+
93
+ subAgentConfig:
94
+ format: yaml
95
+ fields:
96
+ name: "Agent display name"
97
+ description: "What this agent does"
98
+ model: "claude-3-5-sonnet or gpt-4-turbo"
99
+ readonly: "boolean - read-only workspace"
100
+ is_background: "boolean - runs without user interaction"
101
+ async: true
102
+ supportsNesting: true
103
+ resultsAreAccessible: true
104
+
105
+ limits:
106
+ maxFileSize: 0 # 0 = unlimited
107
+ maxCommandLength: 0 # 0 = unlimited
108
+ maxToolsPerMCP: 40 # Hard limit
109
+ maxBackgroundAgents: 5 # Practical limit
110
+
111
+ installation:
112
+ supportsFileOps: true
113
+ supportsHooks: true # Beta support
114
+ supportsMCP: true
115
+ commandsRequireApproval: false
116
+ agentFilesCanBeNestedDirs: false
117
+ rulesFormat: MDC # Modern YAML-based rule format
118
+
119
+ features:
120
+ marketplace: "Plugin marketplace at cursor.com/marketplace"
121
+ backendAgents: "Can run agents in background without UI blocking"
122
+ asyncModel: "Agents don't wait for each other - true parallelism"
123
+ hookBeta: "6 hooks in beta; more events coming in Wave 2.6"
124
+
125
+ notes:
126
+ - "Cursor v2.5 now rivals Claude Code with async subagents"
127
+ - "Agents are native; no separate Task tool needed"
128
+ - "Conversational input instead of structured menus (AskUserQuestion replacement)"
129
+ - "40-tool MCP limit is binding constraint for multi-IDE installations"
130
+ - "MDC rule format is modern YAML-based (legacy .cursorrules still supported)"
131
+ - "Plan mode fully built-in; no explicit enter/exit needed"
132
+ - "Hook events have different names but similar functionality to Claude Code"
133
+ - "CAVEAT: Task tools (TaskCreate/TaskUpdate) bypass PreToolUse/PostToolUse hooks"
134
+ - "Background agents enable true parallel task execution"
@@ -0,0 +1,250 @@
1
+ /**
2
+ * IDE Capability Profile - Usage Examples
3
+ *
4
+ * This file demonstrates how to use the profile loader
5
+ * in installers, generators, and feature-detection code.
6
+ *
7
+ * Run: node examples.js
8
+ */
9
+
10
+ const loader = require('./loader');
11
+
12
+ console.log('IDE Capability Profile - Usage Examples\n');
13
+ console.log('='.repeat(60) + '\n');
14
+
15
+ // ============================================================
16
+ // Example 1: Load a single profile
17
+ // ============================================================
18
+ console.log('1. LOAD A SINGLE PROFILE\n');
19
+
20
+ const claudeCodeProfile = loader.load('claude-code');
21
+ console.log(`Loaded: ${claudeCodeProfile.ide.displayName}`);
22
+ console.log(`Config dir: ${claudeCodeProfile.paths.commands}`);
23
+ console.log(`Instructions: ${claudeCodeProfile.paths.instructionsFile}`);
24
+ console.log();
25
+
26
+ // ============================================================
27
+ // Example 2: Load all profiles
28
+ // ============================================================
29
+ console.log('2. LOAD ALL PROFILES\n');
30
+
31
+ const allProfiles = loader.loadAll();
32
+ console.log('Available IDEs:');
33
+ Object.keys(allProfiles).sort().forEach(ide => {
34
+ const profile = allProfiles[ide];
35
+ console.log(` - ${profile.ide.displayName}`);
36
+ });
37
+ console.log();
38
+
39
+ // ============================================================
40
+ // Example 3: List available profiles
41
+ // ============================================================
42
+ console.log('3. LIST AVAILABLE PROFILES\n');
43
+
44
+ const available = loader.listAvailable();
45
+ console.log(`Supported IDEs: ${available.join(', ')}`);
46
+ console.log();
47
+
48
+ // ============================================================
49
+ // Example 4: Check if IDE is supported
50
+ // ============================================================
51
+ console.log('4. CHECK IDE SUPPORT\n');
52
+
53
+ ['claude-code', 'cursor', 'unknown-ide'].forEach(ide => {
54
+ const supported = loader.isSupported(ide);
55
+ console.log(`${ide}: ${supported ? '✓ supported' : '✗ not supported'}`);
56
+ });
57
+ console.log();
58
+
59
+ // ============================================================
60
+ // Example 5: Check specific capability
61
+ // ============================================================
62
+ console.log('5. CHECK SPECIFIC CAPABILITIES\n');
63
+
64
+ const capabilities = [
65
+ { ide: 'claude-code', group: 'core', cap: 'planMode' },
66
+ { ide: 'cursor', group: 'core', cap: 'planMode' },
67
+ { ide: 'windsurf', group: 'collaboration', cap: 'taskTracking' },
68
+ { ide: 'codex', group: 'planning', cap: 'planMode' },
69
+ ];
70
+
71
+ capabilities.forEach(({ ide, group, cap }) => {
72
+ const has = loader.hasCapability(ide, group, cap);
73
+ const status = has ? '✅' : '❌';
74
+ console.log(`${ide}: ${cap} ${status}`);
75
+ });
76
+ console.log();
77
+
78
+ // ============================================================
79
+ // Example 6: Get tool name mapping
80
+ // ============================================================
81
+ console.log('6. TOOL NAME MAPPINGS (IDE-specific equivalents)\n');
82
+
83
+ const toolAlias = 'bash';
84
+ console.log(`Standard tool alias: "${toolAlias}"`);
85
+ console.log('IDE-specific names:');
86
+ available.forEach(ide => {
87
+ const toolName = loader.getToolName(ide, toolAlias);
88
+ console.log(` ${ide}: ${toolName || '(not available)'}`);
89
+ });
90
+ console.log();
91
+
92
+ // ============================================================
93
+ // Example 7: Get all capabilities for an IDE
94
+ // ============================================================
95
+ console.log('7. ALL CAPABILITIES FOR CURSOR\n');
96
+
97
+ const cursorCaps = loader.getAllCapabilities('cursor');
98
+ const enabledCaps = Object.entries(cursorCaps)
99
+ .filter(([_, enabled]) => enabled)
100
+ .map(([name, _]) => name);
101
+
102
+ console.log(`Cursor has ${enabledCaps.length} capabilities enabled:`);
103
+ enabledCaps.slice(0, 8).forEach(cap => console.log(` ✓ ${cap}`));
104
+ console.log(` ... and ${enabledCaps.length - 8} more`);
105
+ console.log();
106
+
107
+ // ============================================================
108
+ // Example 8: Find IDEs with a capability
109
+ // ============================================================
110
+ console.log('8. FIND IDEs WITH SPECIFIC CAPABILITY\n');
111
+
112
+ const idsWithPlanMode = loader.findIDEsWithCapability('planning', 'planMode');
113
+ console.log(`IDEs with plan mode: ${idsWithPlanMode.join(', ')}`);
114
+
115
+ const idsWithTasks = loader.findIDEsWithCapability('collaboration', 'taskTracking');
116
+ console.log(`IDEs with task tracking: ${idsWithTasks.join(', ')}`);
117
+
118
+ const idsWithMCP = loader.findIDEsWithCapability('external', 'mcp');
119
+ console.log(`IDEs with MCP: ${idsWithMCP.join(', ')}`);
120
+ console.log();
121
+
122
+ // ============================================================
123
+ // Example 9: Compare capability across IDEs
124
+ // ============================================================
125
+ console.log('9. COMPARE CAPABILITY ACROSS ALL IDEs\n');
126
+
127
+ const subAgentComparison = loader.compareCapability('core', 'subAgents');
128
+ console.log('Sub-agent support:');
129
+ Object.entries(subAgentComparison).forEach(([ide, supported]) => {
130
+ console.log(` ${ide}: ${supported ? '✅' : '❌'}`);
131
+ });
132
+ console.log();
133
+
134
+ // ============================================================
135
+ // Example 10: Design pattern - Feature detection in installer
136
+ // ============================================================
137
+ console.log('10. DESIGN PATTERN: FEATURE-BASED INSTALLATION\n');
138
+
139
+ function installForIDE(ideId) {
140
+ console.log(`Installing for ${ideId}...\n`);
141
+
142
+ const profile = loader.load(ideId);
143
+
144
+ // Install commands if supported
145
+ if (profile.paths.commands) {
146
+ console.log(` → Install commands to: ${profile.paths.commands}`);
147
+ }
148
+
149
+ // Install hooks if supported
150
+ if (loader.hasCapability(ideId, 'lifecycle', 'hooks')) {
151
+ console.log(` → Install lifecycle hooks (${profile.capabilities.lifecycle.hookEvents.length} events)`);
152
+ }
153
+
154
+ // Install MCP if supported
155
+ if (loader.hasCapability(ideId, 'external', 'mcp')) {
156
+ const limit = profile.capabilities.external.mcpToolLimit;
157
+ if (limit === 0) {
158
+ console.log(` → Install MCP tools (unlimited)`);
159
+ } else {
160
+ console.log(` → Install MCP tools (cap: ${limit})`);
161
+ }
162
+ }
163
+
164
+ // Install plan mode prompts if supported
165
+ if (loader.hasCapability(ideId, 'planning', 'planMode')) {
166
+ console.log(` → Install plan mode instructions`);
167
+ }
168
+
169
+ console.log();
170
+ }
171
+
172
+ installForIDE('claude-code');
173
+ installForIDE('windsurf');
174
+ installForIDE('codex');
175
+
176
+ // ============================================================
177
+ // Example 11: Design pattern - Capability fallback chain
178
+ // ============================================================
179
+ console.log('11. DESIGN PATTERN: CAPABILITY FALLBACK\n');
180
+
181
+ function getInteractiveInputMethod(ideId) {
182
+ const profile = loader.load(ideId);
183
+
184
+ if (loader.hasCapability(ideId, 'core', 'interactiveInput')) {
185
+ if (loader.getToolName(ideId, 'askUser') === 'AskUserQuestion') {
186
+ return 'Structured menus (AskUserQuestion)';
187
+ } else if (loader.getToolName(ideId, 'askUser')) {
188
+ return `Text input (${loader.getToolName(ideId, 'askUser')})`;
189
+ }
190
+ }
191
+
192
+ return 'Conversational clarification';
193
+ }
194
+
195
+ console.log('Interactive input methods by IDE:');
196
+ available.forEach(ide => {
197
+ const method = getInteractiveInputMethod(ide);
198
+ console.log(` ${ide}: ${method}`);
199
+ });
200
+ console.log();
201
+
202
+ // ============================================================
203
+ // Example 12: Design pattern - Multi-IDE constraint analysis
204
+ // ============================================================
205
+ console.log('12. DESIGN PATTERN: MULTI-IDE CONSTRAINT ANALYSIS\n');
206
+
207
+ console.log('Planning a multi-IDE deployment...\n');
208
+
209
+ const allCaps = loader.loadAll();
210
+ const mpcConstraints = [];
211
+
212
+ available.forEach(ide => {
213
+ const limit = allCaps[ide].capabilities.external.mcpToolLimit;
214
+ if (limit > 0) {
215
+ mpcConstraints.push({ ide, limit });
216
+ }
217
+ });
218
+
219
+ mpcConstraints.sort((a, b) => a.limit - b.limit);
220
+
221
+ if (mpcConstraints.length > 0) {
222
+ const minLimit = mpcConstraints[0].limit;
223
+ console.log(`Binding constraint (most restrictive IDE): ${minLimit} MCP tools`);
224
+ console.log(`Affected IDEs:`);
225
+ mpcConstraints.forEach(({ ide, limit }) => {
226
+ const marker = limit === minLimit ? '🔴 (CONSTRAINT)' : '🟢';
227
+ console.log(` ${marker} ${ide}: ${limit} tools`);
228
+ });
229
+ }
230
+ console.log();
231
+
232
+ // ============================================================
233
+ // Summary
234
+ // ============================================================
235
+ console.log('='.repeat(60));
236
+ console.log('Profile System Ready for Use');
237
+ console.log('='.repeat(60));
238
+ console.log(`
239
+ Available utilities:
240
+ loader.load(ideId) - Load single profile
241
+ loader.loadAll() - Load all profiles
242
+ loader.listAvailable() - List IDE names
243
+ loader.isSupported(ideId) - Check if IDE supported
244
+ loader.hasCapability(ideId, group, cap) - Check feature support
245
+ loader.getToolName(ideId, alias) - Get IDE-specific tool name
246
+ loader.getAllCapabilities(ideId) - Get all IDE capabilities
247
+ loader.findIDEsWithCapability(group, cap) - Find IDEs with feature
248
+ loader.compareCapability(group, cap) - Compare across all IDEs
249
+ loader.clearCache() - Clear cached profiles
250
+ `);
@@ -0,0 +1,235 @@
1
+ /**
2
+ * IDE Capability Profile Loader
3
+ *
4
+ * Loads YAML capability profiles with validation and caching.
5
+ * Used by installers, generators, and feature-detection code.
6
+ *
7
+ * Usage:
8
+ * const loader = require('./profiles/loader');
9
+ * const profile = loader.load('claude-code');
10
+ * if (profile.capabilities.core.planMode) { ... }
11
+ */
12
+
13
+ const yaml = require('js-yaml');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const PROFILES_DIR = __dirname;
18
+ const CACHE = {};
19
+
20
+ /**
21
+ * Load a profile by IDE name
22
+ * @param {string} ideId - IDE identifier (claude-code, cursor, windsurf, codex)
23
+ * @returns {Object} Profile object
24
+ * @throws {Error} If profile not found or invalid YAML
25
+ */
26
+ function load(ideId) {
27
+ if (CACHE[ideId]) {
28
+ return CACHE[ideId];
29
+ }
30
+
31
+ const filePath = path.join(PROFILES_DIR, `${ideId}.yaml`);
32
+
33
+ if (!fs.existsSync(filePath)) {
34
+ throw new Error(`Profile not found: ${ideId}`);
35
+ }
36
+
37
+ try {
38
+ const content = fs.readFileSync(filePath, 'utf8');
39
+ const profile = yaml.load(content);
40
+
41
+ // Validate profile structure
42
+ validateProfile(profile, ideId);
43
+
44
+ CACHE[ideId] = profile;
45
+ return profile;
46
+ } catch (e) {
47
+ if (e instanceof yaml.YAMLException) {
48
+ throw new Error(`Invalid YAML in profile ${ideId}: ${e.message}`);
49
+ }
50
+ throw e;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Load all available profiles
56
+ * @returns {Object} Map of IDE ID to profile
57
+ */
58
+ function loadAll() {
59
+ const profiles = {};
60
+ const files = fs.readdirSync(PROFILES_DIR)
61
+ .filter(f => f.endsWith('.yaml'));
62
+
63
+ files.forEach(file => {
64
+ const ideId = file.replace('.yaml', '');
65
+ try {
66
+ profiles[ideId] = load(ideId);
67
+ } catch (e) {
68
+ console.error(`Failed to load profile ${ideId}: ${e.message}`);
69
+ }
70
+ });
71
+
72
+ return profiles;
73
+ }
74
+
75
+ /**
76
+ * Get list of available profiles
77
+ * @returns {Array<string>} List of IDE IDs
78
+ */
79
+ function listAvailable() {
80
+ return fs.readdirSync(PROFILES_DIR)
81
+ .filter(f => f.endsWith('.yaml'))
82
+ .map(f => f.replace('.yaml', ''))
83
+ .sort();
84
+ }
85
+
86
+ /**
87
+ * Check if IDE is supported
88
+ * @param {string} ideId - IDE identifier
89
+ * @returns {boolean} True if IDE profile exists
90
+ */
91
+ function isSupported(ideId) {
92
+ return fs.existsSync(path.join(PROFILES_DIR, `${ideId}.yaml`));
93
+ }
94
+
95
+ /**
96
+ * Validate profile structure
97
+ * @private
98
+ * @param {Object} profile - Profile object
99
+ * @param {string} ideId - IDE identifier
100
+ * @throws {Error} If validation fails
101
+ */
102
+ function validateProfile(profile, ideId) {
103
+ const required = ['ide', 'paths', 'capabilities', 'toolNames'];
104
+
105
+ for (const field of required) {
106
+ if (!profile[field]) {
107
+ throw new Error(`Missing required field: ${field}`);
108
+ }
109
+ }
110
+
111
+ // Validate ide object
112
+ if (!profile.ide.id || !profile.ide.name) {
113
+ throw new Error('ide object must have id and name fields');
114
+ }
115
+
116
+ // Validate capabilities structure
117
+ const validCapGroups = ['core', 'planning', 'lifecycle', 'external', 'collaboration'];
118
+ for (const group of Object.keys(profile.capabilities)) {
119
+ if (!validCapGroups.includes(group)) {
120
+ console.warn(`Unknown capability group in ${ideId}: ${group}`);
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get capability status for an IDE
127
+ * @param {string} ideId - IDE identifier
128
+ * @param {string} group - Capability group (core, planning, lifecycle, external, collaboration)
129
+ * @param {string} capability - Capability name
130
+ * @returns {boolean|null} True if supported, false if not, null if unknown
131
+ */
132
+ function hasCapability(ideId, group, capability) {
133
+ const profile = load(ideId);
134
+ const groupCapabilities = profile.capabilities[group];
135
+
136
+ if (!groupCapabilities) {
137
+ return null;
138
+ }
139
+
140
+ return groupCapabilities[capability] === true;
141
+ }
142
+
143
+ /**
144
+ * Get tool name for an IDE
145
+ * Returns the tool name used by the IDE, or null if not supported
146
+ *
147
+ * @param {string} ideId - IDE identifier
148
+ * @param {string} toolAlias - Standard tool alias (askUser, bash, read, etc.)
149
+ * @returns {string|null} IDE-specific tool name or null
150
+ */
151
+ function getToolName(ideId, toolAlias) {
152
+ const profile = load(ideId);
153
+ return profile.toolNames[toolAlias] || null;
154
+ }
155
+
156
+ /**
157
+ * Get all capabilities for an IDE (flattened)
158
+ * @param {string} ideId - IDE identifier
159
+ * @returns {Object} Map of capability name to boolean
160
+ */
161
+ function getAllCapabilities(ideId) {
162
+ const profile = load(ideId);
163
+ const capabilities = {};
164
+
165
+ for (const [group, groupCaps] of Object.entries(profile.capabilities)) {
166
+ if (typeof groupCaps === 'object' && !Array.isArray(groupCaps)) {
167
+ for (const [cap, value] of Object.entries(groupCaps)) {
168
+ if (typeof value === 'boolean') {
169
+ capabilities[`${group}.${cap}`] = value;
170
+ }
171
+ }
172
+ }
173
+ }
174
+
175
+ return capabilities;
176
+ }
177
+
178
+ /**
179
+ * Find all IDEs supporting a capability
180
+ * @param {string} group - Capability group
181
+ * @param {string} capability - Capability name
182
+ * @returns {Array<string>} List of IDE IDs that support this capability
183
+ */
184
+ function findIDEsWithCapability(group, capability) {
185
+ const ides = [];
186
+ const all = loadAll();
187
+
188
+ for (const [ideId, profile] of Object.entries(all)) {
189
+ if (profile.capabilities[group] && profile.capabilities[group][capability] === true) {
190
+ ides.push(ideId);
191
+ }
192
+ }
193
+
194
+ return ides.sort();
195
+ }
196
+
197
+ /**
198
+ * Get comparison table for a specific capability across all IDEs
199
+ * @param {string} group - Capability group
200
+ * @param {string} capability - Capability name
201
+ * @returns {Object} Map of IDE ID to boolean
202
+ */
203
+ function compareCapability(group, capability) {
204
+ const result = {};
205
+ const all = loadAll();
206
+
207
+ for (const [ideId, profile] of Object.entries(all)) {
208
+ result[ideId] = profile.capabilities[group] && profile.capabilities[group][capability] === true;
209
+ }
210
+
211
+ return result;
212
+ }
213
+
214
+ /**
215
+ * Clear profile cache
216
+ * Useful for testing or reloading profiles after updates
217
+ */
218
+ function clearCache() {
219
+ for (const key of Object.keys(CACHE)) {
220
+ delete CACHE[key];
221
+ }
222
+ }
223
+
224
+ module.exports = {
225
+ load,
226
+ loadAll,
227
+ listAvailable,
228
+ isSupported,
229
+ hasCapability,
230
+ getToolName,
231
+ getAllCapabilities,
232
+ findIDEsWithCapability,
233
+ compareCapability,
234
+ clearCache,
235
+ };