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.
- package/README.md +3 -5
- package/package.json +1 -1
- package/scripts/agileflow-welcome.js +172 -9
- package/scripts/check-update.js +325 -0
- package/scripts/generators/agent-registry.js +43 -55
- package/scripts/generators/command-registry.js +42 -26
- package/scripts/generators/skill-registry.js +55 -28
- package/scripts/lib/frontmatter-parser.js +82 -0
- package/src/core/commands/whats-new.md +94 -0
- package/tools/cli/installers/ide/_base-ide.js +55 -0
- package/tools/cli/installers/ide/claude-code.js +0 -54
- package/tools/cli/installers/ide/cursor.js +21 -51
- package/tools/cli/installers/ide/windsurf.js +20 -50
- package/tools/cli/lib/content-injector.js +25 -48
- package/tools/cli/lib/npm-utils.js +36 -9
- package/tools/cli/lib/ui.js +24 -3
|
@@ -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
|
-
*
|
|
15
|
-
*
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
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'))
|
|
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
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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,
|
|
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
|
-
*
|
|
15
|
-
* @param {string}
|
|
16
|
-
* @returns {object} Frontmatter object
|
|
20
|
+
* Log debug messages when DEBUG_REGISTRY=1
|
|
21
|
+
* @param {string} message - Message to log
|
|
17
22
|
*/
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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'))
|
|
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,
|
|
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
|
-
*
|
|
15
|
-
* @param {string}
|
|
16
|
-
* @returns {object} Frontmatter object
|
|
20
|
+
* Log debug messages when DEBUG_REGISTRY=1
|
|
21
|
+
* @param {string} message - Message to log
|
|
17
22
|
*/
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
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,
|
|
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
|