agileflow 2.55.0 → 2.57.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.
@@ -5,58 +5,25 @@
5
5
  *
6
6
  * Scans agents/ directory and extracts metadata from frontmatter.
7
7
  * Returns structured agent registry for use in generators.
8
+ *
9
+ * Set DEBUG_REGISTRY=1 for verbose logging of skipped files.
8
10
  */
9
11
 
10
12
  const fs = require('fs');
11
13
  const path = require('path');
14
+ const { extractFrontmatter, normalizeTools } = require('../lib/frontmatter-parser');
15
+
16
+ // Debug mode: set DEBUG_REGISTRY=1 to see why files are skipped
17
+ const DEBUG = process.env.DEBUG_REGISTRY === '1';
12
18
 
13
19
  /**
14
- * Extract YAML frontmatter from markdown file
15
- * Handles multi-line values like tools arrays
16
- * @param {string} filePath - Path to markdown file
17
- * @returns {object} Frontmatter object
20
+ * Log debug messages when DEBUG_REGISTRY=1
21
+ * @param {string} message - Message to log
18
22
  */
19
- function extractFrontmatter(filePath) {
20
- const content = fs.readFileSync(filePath, 'utf-8');
21
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
22
-
23
- if (!frontmatterMatch) {
24
- return {};
25
- }
26
-
27
- const frontmatter = {};
28
- const lines = frontmatterMatch[1].split('\n');
29
- let currentKey = null;
30
- let currentArray = null;
31
-
32
- for (const line of lines) {
33
- // Handle array items (lines starting with -)
34
- if (line.trim().startsWith('-')) {
35
- if (currentArray) {
36
- currentArray.push(line.trim().substring(1).trim());
37
- }
38
- continue;
39
- }
40
-
41
- // Handle key-value pairs
42
- const match = line.match(/^(\w+):\s*(.*)$/);
43
- if (match) {
44
- const [, key, value] = match;
45
- currentKey = key;
46
-
47
- // If value is empty, it's likely an array
48
- if (!value) {
49
- currentArray = [];
50
- frontmatter[key] = currentArray;
51
- } else {
52
- // Remove quotes if present
53
- frontmatter[key] = value.replace(/^["']|["']$/g, '');
54
- currentArray = null;
55
- }
56
- }
23
+ function debugLog(message) {
24
+ if (DEBUG) {
25
+ console.error(`[agent-registry] ${message}`);
57
26
  }
58
-
59
- return frontmatter;
60
27
  }
61
28
 
62
29
  /**
@@ -93,25 +60,40 @@ function categorizeAgent(name, description) {
93
60
  */
94
61
  function scanAgents(agentsDir) {
95
62
  const agents = [];
96
- const files = fs.readdirSync(agentsDir);
63
+ const skipped = [];
64
+
65
+ let files;
66
+ try {
67
+ files = fs.readdirSync(agentsDir);
68
+ } catch (err) {
69
+ debugLog(`Failed to read directory: ${err.message}`);
70
+ return agents;
71
+ }
72
+
73
+ debugLog(`Scanning ${files.length} files in ${agentsDir}`);
97
74
 
98
75
  for (const file of files) {
99
- if (!file.endsWith('.md')) continue;
76
+ if (!file.endsWith('.md')) {
77
+ debugLog(`Skipping non-md file: ${file}`);
78
+ continue;
79
+ }
100
80
 
101
81
  const filePath = path.join(agentsDir, file);
102
82
  const frontmatter = extractFrontmatter(filePath);
103
83
  const name = file.replace('.md', '');
104
84
 
105
- // Parse tools array if it exists
106
- let tools = [];
107
- if (frontmatter.tools) {
108
- if (Array.isArray(frontmatter.tools)) {
109
- tools = frontmatter.tools;
110
- } else if (typeof frontmatter.tools === 'string') {
111
- tools = frontmatter.tools.split(',').map(t => t.trim());
112
- }
85
+ // Check if frontmatter was extracted successfully
86
+ if (Object.keys(frontmatter).length === 0) {
87
+ skipped.push({ file, reason: 'no frontmatter or parse error' });
88
+ debugLog(`Skipping ${file}: no frontmatter found`);
89
+ continue;
113
90
  }
114
91
 
92
+ // Normalize tools field (handles array or comma-separated string)
93
+ const tools = normalizeTools(frontmatter.tools);
94
+
95
+ debugLog(`Loaded ${file}: name="${frontmatter.name || name}", tools=${tools.length}`);
96
+
115
97
  agents.push({
116
98
  name,
117
99
  file,
@@ -133,6 +115,12 @@ function scanAgents(agentsDir) {
133
115
  return a.name.localeCompare(b.name);
134
116
  });
135
117
 
118
+ if (skipped.length > 0) {
119
+ debugLog(`Skipped ${skipped.length} files: ${skipped.map(s => s.file).join(', ')}`);
120
+ }
121
+
122
+ debugLog(`Found ${agents.length} agents`);
123
+
136
124
  return agents;
137
125
  }
138
126
 
@@ -159,7 +147,7 @@ function main() {
159
147
  }
160
148
 
161
149
  // Export for use in other scripts
162
- module.exports = { scanAgents, extractFrontmatter, categorizeAgent };
150
+ module.exports = { scanAgents, categorizeAgent };
163
151
 
164
152
  // Run if called directly
165
153
  if (require.main === module) {
@@ -5,37 +5,25 @@
5
5
  *
6
6
  * Scans commands/ directory and extracts metadata from frontmatter.
7
7
  * Returns structured command registry for use in generators.
8
+ *
9
+ * Set DEBUG_REGISTRY=1 for verbose logging of skipped files.
8
10
  */
9
11
 
10
12
  const fs = require('fs');
11
13
  const path = require('path');
14
+ const { extractFrontmatter } = require('../lib/frontmatter-parser');
15
+
16
+ // Debug mode: set DEBUG_REGISTRY=1 to see why files are skipped
17
+ const DEBUG = process.env.DEBUG_REGISTRY === '1';
12
18
 
13
19
  /**
14
- * Extract frontmatter from markdown file
15
- * @param {string} filePath - Path to markdown file
16
- * @returns {object} Frontmatter object
20
+ * Log debug messages when DEBUG_REGISTRY=1
21
+ * @param {string} message - Message to log
17
22
  */
18
- function extractFrontmatter(filePath) {
19
- const content = fs.readFileSync(filePath, 'utf-8');
20
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
21
-
22
- if (!frontmatterMatch) {
23
- return {};
23
+ function debugLog(message) {
24
+ if (DEBUG) {
25
+ console.error(`[command-registry] ${message}`);
24
26
  }
25
-
26
- const frontmatter = {};
27
- const lines = frontmatterMatch[1].split('\n');
28
-
29
- for (const line of lines) {
30
- const match = line.match(/^(\w+):\s*(.+)$/);
31
- if (match) {
32
- const [, key, value] = match;
33
- // Remove quotes if present
34
- frontmatter[key] = value.replace(/^["']|["']$/g, '');
35
- }
36
- }
37
-
38
- return frontmatter;
39
27
  }
40
28
 
41
29
  /**
@@ -74,15 +62,37 @@ function categorizeCommand(name, description) {
74
62
  */
75
63
  function scanCommands(commandsDir) {
76
64
  const commands = [];
77
- const files = fs.readdirSync(commandsDir);
65
+ const skipped = [];
66
+
67
+ let files;
68
+ try {
69
+ files = fs.readdirSync(commandsDir);
70
+ } catch (err) {
71
+ debugLog(`Failed to read directory: ${err.message}`);
72
+ return commands;
73
+ }
74
+
75
+ debugLog(`Scanning ${files.length} files in ${commandsDir}`);
78
76
 
79
77
  for (const file of files) {
80
- if (!file.endsWith('.md')) continue;
78
+ if (!file.endsWith('.md')) {
79
+ debugLog(`Skipping non-md file: ${file}`);
80
+ continue;
81
+ }
81
82
 
82
83
  const filePath = path.join(commandsDir, file);
83
84
  const frontmatter = extractFrontmatter(filePath);
84
85
  const name = file.replace('.md', '');
85
86
 
87
+ // Check if frontmatter was extracted successfully
88
+ if (Object.keys(frontmatter).length === 0) {
89
+ skipped.push({ file, reason: 'no frontmatter or parse error' });
90
+ debugLog(`Skipping ${file}: no frontmatter found`);
91
+ continue;
92
+ }
93
+
94
+ debugLog(`Loaded ${file}: description="${frontmatter.description || '(none)'}"`);
95
+
86
96
  commands.push({
87
97
  name,
88
98
  file,
@@ -101,6 +111,12 @@ function scanCommands(commandsDir) {
101
111
  return a.name.localeCompare(b.name);
102
112
  });
103
113
 
114
+ if (skipped.length > 0) {
115
+ debugLog(`Skipped ${skipped.length} files: ${skipped.map(s => s.file).join(', ')}`);
116
+ }
117
+
118
+ debugLog(`Found ${commands.length} commands`);
119
+
104
120
  return commands;
105
121
  }
106
122
 
@@ -127,7 +143,7 @@ function main() {
127
143
  }
128
144
 
129
145
  // Export for use in other scripts
130
- module.exports = { scanCommands, extractFrontmatter, categorizeCommand };
146
+ module.exports = { scanCommands, categorizeCommand };
131
147
 
132
148
  // Run if called directly
133
149
  if (require.main === module) {
@@ -5,37 +5,25 @@
5
5
  *
6
6
  * Scans skills/ directory and extracts metadata from SKILL.md frontmatter.
7
7
  * Returns structured skill registry for use in generators.
8
+ *
9
+ * Set DEBUG_REGISTRY=1 for verbose logging of skipped files.
8
10
  */
9
11
 
10
12
  const fs = require('fs');
11
13
  const path = require('path');
14
+ const { extractFrontmatter } = require('../lib/frontmatter-parser');
15
+
16
+ // Debug mode: set DEBUG_REGISTRY=1 to see why files are skipped
17
+ const DEBUG = process.env.DEBUG_REGISTRY === '1';
12
18
 
13
19
  /**
14
- * Extract YAML frontmatter from markdown file
15
- * @param {string} filePath - Path to markdown file
16
- * @returns {object} Frontmatter object
20
+ * Log debug messages when DEBUG_REGISTRY=1
21
+ * @param {string} message - Message to log
17
22
  */
18
- function extractFrontmatter(filePath) {
19
- const content = fs.readFileSync(filePath, 'utf-8');
20
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
21
-
22
- if (!frontmatterMatch) {
23
- return {};
24
- }
25
-
26
- const frontmatter = {};
27
- const lines = frontmatterMatch[1].split('\n');
28
-
29
- for (const line of lines) {
30
- const match = line.match(/^(\w+):\s*(.+)$/);
31
- if (match) {
32
- const [, key, value] = match;
33
- // Remove quotes if present
34
- frontmatter[key] = value.replace(/^["']|["']$/g, '');
35
- }
23
+ function debugLog(message) {
24
+ if (DEBUG) {
25
+ console.error(`[skill-registry] ${message}`);
36
26
  }
37
-
38
- return frontmatter;
39
27
  }
40
28
 
41
29
  /**
@@ -73,25 +61,58 @@ function categorizeSkill(name, description) {
73
61
  */
74
62
  function scanSkills(skillsDir) {
75
63
  const skills = [];
64
+ const skipped = [];
65
+
66
+ let skillDirs;
67
+ try {
68
+ skillDirs = fs.readdirSync(skillsDir);
69
+ } catch (err) {
70
+ debugLog(`Failed to read directory: ${err.message}`);
71
+ return skills;
72
+ }
76
73
 
77
- // Each skill is in its own directory with a SKILL.md file
78
- const skillDirs = fs.readdirSync(skillsDir);
74
+ debugLog(`Scanning ${skillDirs.length} entries in ${skillsDir}`);
79
75
 
80
76
  for (const skillDir of skillDirs) {
81
77
  const skillPath = path.join(skillsDir, skillDir);
82
78
 
83
79
  // Skip if not a directory
84
- if (!fs.statSync(skillPath).isDirectory()) continue;
80
+ let stat;
81
+ try {
82
+ stat = fs.statSync(skillPath);
83
+ } catch (err) {
84
+ debugLog(`Failed to stat ${skillDir}: ${err.message}`);
85
+ continue;
86
+ }
87
+
88
+ if (!stat.isDirectory()) {
89
+ debugLog(`Skipping non-directory: ${skillDir}`);
90
+ continue;
91
+ }
85
92
 
86
93
  const skillFile = path.join(skillPath, 'SKILL.md');
87
94
 
88
95
  // Skip if SKILL.md doesn't exist
89
- if (!fs.existsSync(skillFile)) continue;
96
+ if (!fs.existsSync(skillFile)) {
97
+ skipped.push({ dir: skillDir, reason: 'no SKILL.md file' });
98
+ debugLog(`Skipping ${skillDir}: no SKILL.md found`);
99
+ continue;
100
+ }
90
101
 
91
102
  const frontmatter = extractFrontmatter(skillFile);
103
+
104
+ // Check if frontmatter was extracted successfully
105
+ if (Object.keys(frontmatter).length === 0) {
106
+ skipped.push({ dir: skillDir, reason: 'no frontmatter or parse error' });
107
+ debugLog(`Skipping ${skillDir}: no frontmatter in SKILL.md`);
108
+ continue;
109
+ }
110
+
92
111
  const name = frontmatter.name || skillDir;
93
112
  const description = frontmatter.description || '';
94
113
 
114
+ debugLog(`Loaded ${skillDir}: name="${name}"`);
115
+
95
116
  skills.push({
96
117
  name,
97
118
  directory: skillDir,
@@ -110,6 +131,12 @@ function scanSkills(skillsDir) {
110
131
  return a.name.localeCompare(b.name);
111
132
  });
112
133
 
134
+ if (skipped.length > 0) {
135
+ debugLog(`Skipped ${skipped.length} directories: ${skipped.map(s => s.dir).join(', ')}`);
136
+ }
137
+
138
+ debugLog(`Found ${skills.length} skills`);
139
+
113
140
  return skills;
114
141
  }
115
142
 
@@ -136,7 +163,7 @@ function main() {
136
163
  }
137
164
 
138
165
  // Export for use in other scripts
139
- module.exports = { scanSkills, extractFrontmatter, categorizeSkill };
166
+ module.exports = { scanSkills, categorizeSkill };
140
167
 
141
168
  // Run if called directly
142
169
  if (require.main === module) {
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Frontmatter Parser - Shared YAML frontmatter extraction
5
+ *
6
+ * Consolidates frontmatter parsing logic used across:
7
+ * - command-registry.js
8
+ * - agent-registry.js
9
+ * - skill-registry.js
10
+ * - content-injector.js
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const yaml = require('js-yaml');
15
+
16
+ /**
17
+ * Parse YAML frontmatter from markdown content
18
+ * @param {string} content - Markdown content with frontmatter
19
+ * @returns {object} Parsed frontmatter object, or empty object if none found
20
+ */
21
+ function parseFrontmatter(content) {
22
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
23
+
24
+ if (!match) {
25
+ return {};
26
+ }
27
+
28
+ try {
29
+ const parsed = yaml.load(match[1]);
30
+ // Return empty object if yaml.load returns null/undefined
31
+ return parsed && typeof parsed === 'object' ? parsed : {};
32
+ } catch (err) {
33
+ // Return empty object on parse error (invalid YAML)
34
+ return {};
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Extract frontmatter from a markdown file
40
+ * @param {string} filePath - Path to markdown file
41
+ * @returns {object} Parsed frontmatter object, or empty object if none/error
42
+ */
43
+ function extractFrontmatter(filePath) {
44
+ try {
45
+ const content = fs.readFileSync(filePath, 'utf-8');
46
+ return parseFrontmatter(content);
47
+ } catch (err) {
48
+ // Return empty object on file read error
49
+ return {};
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Extract markdown body (content after frontmatter)
55
+ * @param {string} content - Full markdown content
56
+ * @returns {string} Content after frontmatter, or original if no frontmatter
57
+ */
58
+ function extractBody(content) {
59
+ const match = content.match(/^---\n[\s\S]*?\n---\n?([\s\S]*)/);
60
+ return match ? match[1].trim() : content.trim();
61
+ }
62
+
63
+ /**
64
+ * Normalize tools field - handles array or comma-separated string
65
+ * @param {string|Array} tools - Tools field from frontmatter
66
+ * @returns {Array} Array of tool names
67
+ */
68
+ function normalizeTools(tools) {
69
+ if (!tools) return [];
70
+ if (Array.isArray(tools)) return tools;
71
+ if (typeof tools === 'string') {
72
+ return tools.split(',').map(t => t.trim()).filter(Boolean);
73
+ }
74
+ return [];
75
+ }
76
+
77
+ module.exports = {
78
+ parseFrontmatter,
79
+ extractFrontmatter,
80
+ extractBody,
81
+ normalizeTools,
82
+ };
@@ -0,0 +1,94 @@
1
+ ---
2
+ description: Show what's new in AgileFlow
3
+ ---
4
+
5
+ # /agileflow:whats-new
6
+
7
+ Display recent AgileFlow updates and version history.
8
+
9
+ ---
10
+
11
+ ## Prompt
12
+
13
+ ROLE: AgileFlow Version Reporter
14
+
15
+ **STEP 1: Get current version and changelog**
16
+
17
+ Read the AgileFlow CHANGELOG.md:
18
+
19
+ ```bash
20
+ cat .agileflow/CHANGELOG.md 2>/dev/null || echo "Changelog not found"
21
+ ```
22
+
23
+ Also check the installed version:
24
+ ```bash
25
+ cat .agileflow/package.json 2>/dev/null | grep '"version"' || echo "Version unknown"
26
+ ```
27
+
28
+ **STEP 2: Parse and display**
29
+
30
+ Parse the CHANGELOG.md and display:
31
+
32
+ 1. **Current installed version** at the top
33
+ 2. **Last 3-5 versions** with their changes
34
+ 3. Use clear formatting with headers and bullets
35
+
36
+ **FORMAT:**
37
+
38
+ ```
39
+ ╭─────────────────────────────────────────────────────╮
40
+ │ AgileFlow Changelog │
41
+ │ Current: v2.57.0 │
42
+ ╰─────────────────────────────────────────────────────╯
43
+
44
+ ## v2.57.0 (2025-12-27)
45
+
46
+ ### Added
47
+ • Auto-update system with configurable check frequency
48
+ • Update notifications in welcome message and status line
49
+ • Changelog display after updates
50
+
51
+ ---
52
+
53
+ ## v2.56.0 (2025-12-27)
54
+
55
+ ### Added
56
+ • Dynamic IDE discovery - Codex CLI now in setup
57
+
58
+ ### Changed
59
+ • Replaced hardcoded IDE list with dynamic loading
60
+
61
+ ---
62
+
63
+ ## v2.55.0 (2025-12-26)
64
+
65
+ ### Changed
66
+ • Consolidated code improvements and debugging
67
+
68
+ ---
69
+
70
+ 📖 Full changelog: https://github.com/projectquestorg/AgileFlow/blob/main/packages/cli/CHANGELOG.md
71
+ 🔄 Check for updates: npx agileflow update
72
+ ⚙️ Configure auto-update: /agileflow:configure --auto-update
73
+ ```
74
+
75
+ **STEP 3: Check for updates**
76
+
77
+ After displaying, check if an update is available:
78
+
79
+ ```bash
80
+ npm view agileflow version 2>/dev/null
81
+ ```
82
+
83
+ If a newer version exists, show:
84
+ ```
85
+ ⚡ Update available: v2.57.0 → v2.58.0
86
+ Run: npx agileflow update
87
+ ```
88
+
89
+ **RULES:**
90
+ - Show max 5 versions (most recent first)
91
+ - Use clear section headers
92
+ - Include dates if available
93
+ - Always show the GitHub link for full history
94
+ - Mention update availability if applicable
@@ -179,6 +179,61 @@ class BaseIdeSetup {
179
179
 
180
180
  return results;
181
181
  }
182
+
183
+ /**
184
+ * Recursively install markdown files from source to target directory
185
+ * Handles content injection and docs reference replacement.
186
+ * @param {string} sourceDir - Source directory path
187
+ * @param {string} targetDir - Target directory path
188
+ * @param {string} agileflowDir - AgileFlow installation directory (for dynamic content)
189
+ * @param {boolean} injectDynamic - Whether to inject dynamic content (only for top-level commands)
190
+ * @returns {Promise<{commands: number, subdirs: number}>} Count of installed items
191
+ */
192
+ async installCommandsRecursive(sourceDir, targetDir, agileflowDir, injectDynamic = false) {
193
+ let commandCount = 0;
194
+ let subdirCount = 0;
195
+
196
+ if (!(await this.exists(sourceDir))) {
197
+ return { commands: 0, subdirs: 0 };
198
+ }
199
+
200
+ await this.ensureDir(targetDir);
201
+
202
+ const entries = await fs.readdir(sourceDir, { withFileTypes: true });
203
+
204
+ for (const entry of entries) {
205
+ const sourcePath = path.join(sourceDir, entry.name);
206
+ const targetPath = path.join(targetDir, entry.name);
207
+
208
+ if (entry.isFile() && entry.name.endsWith('.md')) {
209
+ // Read and process .md file
210
+ let content = await this.readFile(sourcePath);
211
+
212
+ // Inject dynamic content if enabled (for top-level commands)
213
+ if (injectDynamic) {
214
+ content = this.injectDynamicContent(content, agileflowDir);
215
+ }
216
+
217
+ // Replace docs/ references with custom folder name
218
+ content = this.replaceDocsReferences(content);
219
+
220
+ await this.writeFile(targetPath, content);
221
+ commandCount++;
222
+ } else if (entry.isDirectory()) {
223
+ // Recursively process subdirectory
224
+ const subResult = await this.installCommandsRecursive(
225
+ sourcePath,
226
+ targetPath,
227
+ agileflowDir,
228
+ false // Don't inject dynamic content in subdirectories
229
+ );
230
+ commandCount += subResult.commands;
231
+ subdirCount += 1 + subResult.subdirs;
232
+ }
233
+ }
234
+
235
+ return { commands: commandCount, subdirs: subdirCount };
236
+ }
182
237
  }
183
238
 
184
239
  module.exports = { BaseIdeSetup };
@@ -19,60 +19,6 @@ class ClaudeCodeSetup extends BaseIdeSetup {
19
19
  this.commandsDir = 'commands';
20
20
  }
21
21
 
22
- /**
23
- * Recursively install commands from a source directory
24
- * @param {string} sourceDir - Source directory path
25
- * @param {string} targetDir - Target directory path
26
- * @param {string} agileflowDir - AgileFlow installation directory (for dynamic content)
27
- * @param {boolean} injectDynamic - Whether to inject dynamic content (only for top-level commands)
28
- * @returns {Promise<{commands: number, subdirs: number}>} Count of installed items
29
- */
30
- async installCommandsRecursive(sourceDir, targetDir, agileflowDir, injectDynamic = false) {
31
- let commandCount = 0;
32
- let subdirCount = 0;
33
-
34
- if (!(await this.exists(sourceDir))) {
35
- return { commands: 0, subdirs: 0 };
36
- }
37
-
38
- await this.ensureDir(targetDir);
39
-
40
- const entries = await fs.readdir(sourceDir, { withFileTypes: true });
41
-
42
- for (const entry of entries) {
43
- const sourcePath = path.join(sourceDir, entry.name);
44
- const targetPath = path.join(targetDir, entry.name);
45
-
46
- if (entry.isFile() && entry.name.endsWith('.md')) {
47
- // Read and process .md file
48
- let content = await this.readFile(sourcePath);
49
-
50
- // Inject dynamic content if enabled (for top-level commands)
51
- if (injectDynamic) {
52
- content = this.injectDynamicContent(content, agileflowDir);
53
- }
54
-
55
- // Replace docs/ references with custom folder name
56
- content = this.replaceDocsReferences(content);
57
-
58
- await this.writeFile(targetPath, content);
59
- commandCount++;
60
- } else if (entry.isDirectory()) {
61
- // Recursively process subdirectory
62
- const subResult = await this.installCommandsRecursive(
63
- sourcePath,
64
- targetPath,
65
- agileflowDir,
66
- false // Don't inject dynamic content in subdirectories
67
- );
68
- commandCount += subResult.commands;
69
- subdirCount += 1 + subResult.subdirs;
70
- }
71
- }
72
-
73
- return { commands: commandCount, subdirs: subdirCount };
74
- }
75
-
76
22
  /**
77
23
  * Setup Claude Code IDE configuration
78
24
  * @param {string} projectDir - Project directory