agileflow 2.61.0 → 2.63.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 +9 -9
- package/package.json +1 -1
- package/scripts/lib/counter.js +103 -0
- package/src/core/commands/auto.md +1 -0
- package/src/core/commands/babysit.md +170 -29
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/ci.md +1 -0
- package/src/core/commands/compress.md +1 -0
- package/src/core/commands/deploy.md +1 -0
- package/src/core/commands/help.md +1 -0
- package/src/core/commands/research.md +1 -0
- package/src/core/commands/skill/create.md +566 -0
- package/src/core/commands/skill/delete.md +189 -0
- package/src/core/commands/skill/edit.md +245 -0
- package/src/core/commands/skill/list.md +155 -0
- package/src/core/commands/skill/test.md +249 -0
- package/src/core/commands/template.md +1 -0
- package/src/core/commands/tests.md +1 -0
- package/src/core/commands/update.md +1 -0
- package/src/core/commands/velocity.md +1 -0
- package/src/core/experts/refactor/expertise.yaml +17 -12
- package/src/core/templates/claude-settings.advanced.example.json +1 -1
- package/src/core/templates/claude-settings.example.json +1 -1
- package/tools/cli/commands/list.js +8 -13
- package/tools/cli/commands/uninstall.js +70 -0
- package/tools/cli/commands/update.js +21 -4
- package/tools/cli/installers/core/installer.js +20 -19
- package/tools/cli/installers/ide/_base-ide.js +18 -4
- package/tools/cli/installers/ide/claude-code.js +4 -15
- package/tools/cli/installers/ide/codex.js +9 -13
- package/tools/cli/lib/content-injector.js +162 -31
- package/tools/cli/lib/utils.js +87 -0
- package/src/core/skills/acceptance-criteria-generator/SKILL.md +0 -46
- package/src/core/skills/adr-template/SKILL.md +0 -62
- package/src/core/skills/agileflow-acceptance-criteria/SKILL.md +0 -156
- package/src/core/skills/agileflow-adr/SKILL.md +0 -147
- package/src/core/skills/agileflow-adr/examples/database-choice-example.md +0 -122
- package/src/core/skills/agileflow-adr/templates/adr-template.md +0 -69
- package/src/core/skills/agileflow-commit-messages/SKILL.md +0 -130
- package/src/core/skills/agileflow-commit-messages/reference/bad-examples.md +0 -168
- package/src/core/skills/agileflow-commit-messages/reference/good-examples.md +0 -120
- package/src/core/skills/agileflow-commit-messages/scripts/check-attribution.sh +0 -15
- package/src/core/skills/agileflow-epic-planner/SKILL.md +0 -184
- package/src/core/skills/agileflow-retro-facilitator/SKILL.md +0 -119
- package/src/core/skills/agileflow-retro-facilitator/cookbook/4ls.md +0 -86
- package/src/core/skills/agileflow-retro-facilitator/cookbook/glad-sad-mad.md +0 -79
- package/src/core/skills/agileflow-retro-facilitator/cookbook/start-stop-continue.md +0 -142
- package/src/core/skills/agileflow-retro-facilitator/prompts/action-items.md +0 -83
- package/src/core/skills/agileflow-sprint-planner/SKILL.md +0 -212
- package/src/core/skills/agileflow-story-writer/SKILL.md +0 -163
- package/src/core/skills/agileflow-story-writer/examples/good-story-example.md +0 -63
- package/src/core/skills/agileflow-story-writer/templates/story-template.md +0 -44
- package/src/core/skills/agileflow-tech-debt/SKILL.md +0 -215
- package/src/core/skills/api-documentation-generator/SKILL.md +0 -65
- package/src/core/skills/changelog-entry/SKILL.md +0 -55
- package/src/core/skills/commit-message-formatter/SKILL.md +0 -50
- package/src/core/skills/deployment-guide-generator/SKILL.md +0 -84
- package/src/core/skills/diagram-generator/SKILL.md +0 -65
- package/src/core/skills/error-handler-template/SKILL.md +0 -78
- package/src/core/skills/migration-checklist/SKILL.md +0 -82
- package/src/core/skills/pr-description/SKILL.md +0 -65
- package/src/core/skills/sql-schema-generator/SKILL.md +0 -69
- package/src/core/skills/story-skeleton/SKILL.md +0 -34
- package/src/core/skills/test-case-generator/SKILL.md +0 -63
- package/src/core/skills/type-definitions/SKILL.md +0 -65
- package/src/core/skills/validation-schema-generator/SKILL.md +0 -64
- package/src/core/skills/writing-skills/SKILL.md +0 -352
- package/src/core/skills/writing-skills/testing-skills-with-subagents.md +0 -232
|
@@ -63,22 +63,11 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
|
|
63
63
|
await this.installCommandsRecursive(agentsSource, spawnableAgentsDir, agileflowDir, false);
|
|
64
64
|
console.log(chalk.dim(` - Spawnable agents: .claude/agents/agileflow/`));
|
|
65
65
|
|
|
66
|
-
//
|
|
67
|
-
|
|
66
|
+
// Create skills directory for user-generated skills (.claude/skills/)
|
|
67
|
+
// AgileFlow no longer ships static skills - users generate them via /agileflow:skill:create
|
|
68
68
|
const skillsTargetDir = path.join(claudeDir, 'skills');
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const skillResult = await this.installCommandsRecursive(
|
|
72
|
-
skillsSource,
|
|
73
|
-
skillsTargetDir,
|
|
74
|
-
agileflowDir,
|
|
75
|
-
false
|
|
76
|
-
);
|
|
77
|
-
skillCount = skillResult.commands + skillResult.subdirs;
|
|
78
|
-
if (skillCount > 0) {
|
|
79
|
-
console.log(chalk.dim(` - Skills: .claude/skills/`));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
69
|
+
await this.ensureDir(skillsTargetDir);
|
|
70
|
+
console.log(chalk.dim(` - Skills directory: .claude/skills/ (for user-generated skills)`));
|
|
82
71
|
|
|
83
72
|
const totalCommands = commandResult.commands + agentResult.commands;
|
|
84
73
|
const totalSubdirs =
|
|
@@ -16,6 +16,7 @@ const fs = require('fs-extra');
|
|
|
16
16
|
const chalk = require('chalk');
|
|
17
17
|
const yaml = require('js-yaml');
|
|
18
18
|
const { BaseIdeSetup } = require('./_base-ide');
|
|
19
|
+
const { parseFrontmatter } = require('../../../../scripts/lib/frontmatter-parser');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* OpenAI Codex CLI setup handler
|
|
@@ -59,22 +60,17 @@ class CodexSetup extends BaseIdeSetup {
|
|
|
59
60
|
* @returns {string} Codex SKILL.md content
|
|
60
61
|
*/
|
|
61
62
|
convertAgentToSkill(content, agentName) {
|
|
62
|
-
// Extract frontmatter
|
|
63
|
+
// Extract frontmatter using shared parser
|
|
63
64
|
let description = `AgileFlow ${agentName} agent`;
|
|
64
65
|
let model = 'default';
|
|
65
66
|
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (frontmatter.model) {
|
|
74
|
-
model = frontmatter.model;
|
|
75
|
-
}
|
|
76
|
-
} catch (e) {
|
|
77
|
-
// Ignore YAML parse errors
|
|
67
|
+
const frontmatter = parseFrontmatter(content);
|
|
68
|
+
if (frontmatter && Object.keys(frontmatter).length > 0) {
|
|
69
|
+
if (frontmatter.description) {
|
|
70
|
+
description = frontmatter.description;
|
|
71
|
+
}
|
|
72
|
+
if (frontmatter.model) {
|
|
73
|
+
model = frontmatter.model;
|
|
78
74
|
}
|
|
79
75
|
}
|
|
80
76
|
|
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Content Injector - Dynamic content injection for
|
|
2
|
+
* Content Injector - Dynamic content injection for AgileFlow files
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Supports template variables that get replaced at install time:
|
|
5
|
+
*
|
|
6
|
+
* COUNTS:
|
|
7
|
+
* {{COMMAND_COUNT}} - Total number of commands
|
|
8
|
+
* {{AGENT_COUNT}} - Total number of agents
|
|
9
|
+
* {{SKILL_COUNT}} - Total number of skills
|
|
10
|
+
*
|
|
11
|
+
* LISTS:
|
|
12
|
+
* <!-- {{AGENT_LIST}} --> - Full formatted agent list
|
|
13
|
+
* <!-- {{COMMAND_LIST}} --> - Full formatted command list
|
|
14
|
+
*
|
|
15
|
+
* METADATA:
|
|
16
|
+
* {{VERSION}} - AgileFlow version from package.json
|
|
17
|
+
* {{INSTALL_DATE}} - Date of installation (YYYY-MM-DD)
|
|
18
|
+
*
|
|
19
|
+
* FOLDER REFERENCES:
|
|
20
|
+
* {agileflow_folder} - Name of the agileflow folder (e.g., .agileflow)
|
|
21
|
+
* {project-root} - Project root reference
|
|
6
22
|
*/
|
|
7
23
|
|
|
8
24
|
const fs = require('fs');
|
|
9
25
|
const path = require('path');
|
|
26
|
+
|
|
27
|
+
// Use shared modules
|
|
10
28
|
const { parseFrontmatter, normalizeTools } = require('../../../scripts/lib/frontmatter-parser');
|
|
29
|
+
const { countCommands, countAgents, countSkills, getCounts } = require('../../../scripts/lib/counter');
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// List Generation Functions
|
|
33
|
+
// =============================================================================
|
|
11
34
|
|
|
12
35
|
/**
|
|
13
36
|
* Scan agents directory and generate formatted agent list
|
|
@@ -15,17 +38,16 @@ const { parseFrontmatter, normalizeTools } = require('../../../scripts/lib/front
|
|
|
15
38
|
* @returns {string} Formatted agent list
|
|
16
39
|
*/
|
|
17
40
|
function generateAgentList(agentsDir) {
|
|
41
|
+
if (!fs.existsSync(agentsDir)) return '';
|
|
42
|
+
|
|
18
43
|
const files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
19
44
|
const agents = [];
|
|
20
45
|
|
|
21
46
|
for (const file of files) {
|
|
22
47
|
const filePath = path.join(agentsDir, file);
|
|
23
48
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
24
|
-
|
|
25
|
-
// Parse frontmatter using shared parser
|
|
26
49
|
const frontmatter = parseFrontmatter(content);
|
|
27
50
|
|
|
28
|
-
// Skip if no frontmatter found
|
|
29
51
|
if (!frontmatter || Object.keys(frontmatter).length === 0) {
|
|
30
52
|
continue;
|
|
31
53
|
}
|
|
@@ -38,17 +60,15 @@ function generateAgentList(agentsDir) {
|
|
|
38
60
|
});
|
|
39
61
|
}
|
|
40
62
|
|
|
41
|
-
// Sort alphabetically by name
|
|
42
63
|
agents.sort((a, b) => a.name.localeCompare(b.name));
|
|
43
64
|
|
|
44
|
-
// Generate formatted output
|
|
45
65
|
let output = `**AVAILABLE AGENTS (${agents.length} total)**:\n\n`;
|
|
46
66
|
|
|
47
67
|
agents.forEach((agent, index) => {
|
|
48
68
|
output += `${index + 1}. **${agent.name}** (model: ${agent.model})\n`;
|
|
49
69
|
output += ` - **Purpose**: ${agent.description}\n`;
|
|
50
70
|
output += ` - **Tools**: ${agent.tools.join(', ')}\n`;
|
|
51
|
-
output += ` - **Usage**: \`subagent_type: "
|
|
71
|
+
output += ` - **Usage**: \`subagent_type: "agileflow-${agent.name}"\`\n`;
|
|
52
72
|
output += `\n`;
|
|
53
73
|
});
|
|
54
74
|
|
|
@@ -61,18 +81,18 @@ function generateAgentList(agentsDir) {
|
|
|
61
81
|
* @returns {string} Formatted command list
|
|
62
82
|
*/
|
|
63
83
|
function generateCommandList(commandsDir) {
|
|
64
|
-
|
|
84
|
+
if (!fs.existsSync(commandsDir)) return '';
|
|
85
|
+
|
|
65
86
|
const commands = [];
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
// Scan main commands
|
|
89
|
+
const mainFiles = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
|
|
90
|
+
for (const file of mainFiles) {
|
|
68
91
|
const filePath = path.join(commandsDir, file);
|
|
69
92
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
70
|
-
|
|
71
|
-
// Parse frontmatter using shared parser
|
|
72
93
|
const frontmatter = parseFrontmatter(content);
|
|
73
94
|
const cmdName = path.basename(file, '.md');
|
|
74
95
|
|
|
75
|
-
// Skip if no frontmatter found
|
|
76
96
|
if (!frontmatter || Object.keys(frontmatter).length === 0) {
|
|
77
97
|
continue;
|
|
78
98
|
}
|
|
@@ -84,10 +104,34 @@ function generateCommandList(commandsDir) {
|
|
|
84
104
|
});
|
|
85
105
|
}
|
|
86
106
|
|
|
87
|
-
//
|
|
107
|
+
// Scan subdirectories (e.g., session/)
|
|
108
|
+
const entries = fs.readdirSync(commandsDir, { withFileTypes: true });
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
if (entry.isDirectory()) {
|
|
111
|
+
const subDir = path.join(commandsDir, entry.name);
|
|
112
|
+
const subFiles = fs.readdirSync(subDir).filter(f => f.endsWith('.md'));
|
|
113
|
+
|
|
114
|
+
for (const file of subFiles) {
|
|
115
|
+
const filePath = path.join(subDir, file);
|
|
116
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
117
|
+
const frontmatter = parseFrontmatter(content);
|
|
118
|
+
const cmdName = `${entry.name}:${path.basename(file, '.md')}`;
|
|
119
|
+
|
|
120
|
+
if (!frontmatter || Object.keys(frontmatter).length === 0) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
commands.push({
|
|
125
|
+
name: cmdName,
|
|
126
|
+
description: frontmatter.description || '',
|
|
127
|
+
argumentHint: frontmatter['argument-hint'] || '',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
88
133
|
commands.sort((a, b) => a.name.localeCompare(b.name));
|
|
89
134
|
|
|
90
|
-
// Generate formatted output
|
|
91
135
|
let output = `Available commands (${commands.length} total):\n`;
|
|
92
136
|
|
|
93
137
|
commands.forEach(cmd => {
|
|
@@ -98,33 +142,120 @@ function generateCommandList(commandsDir) {
|
|
|
98
142
|
return output;
|
|
99
143
|
}
|
|
100
144
|
|
|
145
|
+
// =============================================================================
|
|
146
|
+
// Main Injection Function
|
|
147
|
+
// =============================================================================
|
|
148
|
+
|
|
101
149
|
/**
|
|
102
|
-
* Inject
|
|
103
|
-
* @param {string}
|
|
104
|
-
* @param {
|
|
105
|
-
* @param {string}
|
|
106
|
-
* @
|
|
150
|
+
* Inject all template variables into content
|
|
151
|
+
* @param {string} content - Template content with placeholders
|
|
152
|
+
* @param {Object} context - Context for replacements
|
|
153
|
+
* @param {string} context.coreDir - Path to core directory (commands/, agents/, skills/)
|
|
154
|
+
* @param {string} context.agileflowFolder - AgileFlow folder name
|
|
155
|
+
* @param {string} context.version - AgileFlow version
|
|
156
|
+
* @returns {string} Content with all placeholders replaced
|
|
107
157
|
*/
|
|
108
|
-
function injectContent(
|
|
109
|
-
|
|
158
|
+
function injectContent(content, context = {}) {
|
|
159
|
+
const { coreDir, agileflowFolder = '.agileflow', version = 'unknown' } = context;
|
|
160
|
+
|
|
161
|
+
let result = content;
|
|
110
162
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
163
|
+
// Get counts if core directory is available
|
|
164
|
+
let counts = { commands: 0, agents: 0, skills: 0 };
|
|
165
|
+
if (coreDir && fs.existsSync(coreDir)) {
|
|
166
|
+
counts = getCounts(coreDir);
|
|
115
167
|
}
|
|
116
168
|
|
|
117
|
-
// Replace {{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
169
|
+
// Replace count placeholders (both formats: {{X}} and <!-- {{X}} -->)
|
|
170
|
+
result = result.replace(/\{\{COMMAND_COUNT\}\}/g, String(counts.commands));
|
|
171
|
+
result = result.replace(/\{\{AGENT_COUNT\}\}/g, String(counts.agents));
|
|
172
|
+
result = result.replace(/\{\{SKILL_COUNT\}\}/g, String(counts.skills));
|
|
173
|
+
|
|
174
|
+
// Replace metadata placeholders
|
|
175
|
+
result = result.replace(/\{\{VERSION\}\}/g, version);
|
|
176
|
+
result = result.replace(/\{\{INSTALL_DATE\}\}/g, new Date().toISOString().split('T')[0]);
|
|
177
|
+
|
|
178
|
+
// Replace list placeholders (only if core directory available)
|
|
179
|
+
if (coreDir && fs.existsSync(coreDir)) {
|
|
180
|
+
if (result.includes('{{AGENT_LIST}}')) {
|
|
181
|
+
const agentList = generateAgentList(path.join(coreDir, 'agents'));
|
|
182
|
+
result = result.replace(/<!-- \{\{AGENT_LIST\}\} -->/g, agentList);
|
|
183
|
+
result = result.replace(/\{\{AGENT_LIST\}\}/g, agentList);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (result.includes('{{COMMAND_LIST}}')) {
|
|
187
|
+
const commandList = generateCommandList(path.join(coreDir, 'commands'));
|
|
188
|
+
result = result.replace(/<!-- \{\{COMMAND_LIST\}\} -->/g, commandList);
|
|
189
|
+
result = result.replace(/\{\{COMMAND_LIST\}\}/g, commandList);
|
|
190
|
+
}
|
|
121
191
|
}
|
|
122
192
|
|
|
193
|
+
// Replace folder placeholders
|
|
194
|
+
result = result.replace(/\{agileflow_folder\}/g, agileflowFolder);
|
|
195
|
+
result = result.replace(/\{project-root\}/g, '{project-root}'); // Keep as-is for runtime
|
|
196
|
+
|
|
123
197
|
return result;
|
|
124
198
|
}
|
|
125
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Check if content has any template variables
|
|
202
|
+
* @param {string} content - Content to check
|
|
203
|
+
* @returns {boolean} True if content has placeholders
|
|
204
|
+
*/
|
|
205
|
+
function hasPlaceholders(content) {
|
|
206
|
+
const patterns = [
|
|
207
|
+
/\{\{COMMAND_COUNT\}\}/,
|
|
208
|
+
/\{\{AGENT_COUNT\}\}/,
|
|
209
|
+
/\{\{SKILL_COUNT\}\}/,
|
|
210
|
+
/\{\{VERSION\}\}/,
|
|
211
|
+
/\{\{INSTALL_DATE\}\}/,
|
|
212
|
+
/\{\{AGENT_LIST\}\}/,
|
|
213
|
+
/\{\{COMMAND_LIST\}\}/,
|
|
214
|
+
/\{agileflow_folder\}/,
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
return patterns.some(pattern => pattern.test(content));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* List all supported placeholders
|
|
222
|
+
* @returns {Object} Placeholder documentation
|
|
223
|
+
*/
|
|
224
|
+
function getPlaceholderDocs() {
|
|
225
|
+
return {
|
|
226
|
+
counts: {
|
|
227
|
+
'{{COMMAND_COUNT}}': 'Total number of slash commands',
|
|
228
|
+
'{{AGENT_COUNT}}': 'Total number of specialized agents',
|
|
229
|
+
'{{SKILL_COUNT}}': 'Total number of skills',
|
|
230
|
+
},
|
|
231
|
+
lists: {
|
|
232
|
+
'<!-- {{AGENT_LIST}} -->': 'Full formatted agent list with details',
|
|
233
|
+
'<!-- {{COMMAND_LIST}} -->': 'Full formatted command list',
|
|
234
|
+
},
|
|
235
|
+
metadata: {
|
|
236
|
+
'{{VERSION}}': 'AgileFlow version from package.json',
|
|
237
|
+
'{{INSTALL_DATE}}': 'Installation date (YYYY-MM-DD)',
|
|
238
|
+
},
|
|
239
|
+
folders: {
|
|
240
|
+
'{agileflow_folder}': 'Name of the AgileFlow folder',
|
|
241
|
+
'{project-root}': 'Project root reference (kept as-is)',
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
126
246
|
module.exports = {
|
|
247
|
+
// Count functions
|
|
248
|
+
countCommands,
|
|
249
|
+
countAgents,
|
|
250
|
+
countSkills,
|
|
251
|
+
getCounts,
|
|
252
|
+
|
|
253
|
+
// List generation
|
|
127
254
|
generateAgentList,
|
|
128
255
|
generateCommandList,
|
|
256
|
+
|
|
257
|
+
// Main injection
|
|
129
258
|
injectContent,
|
|
259
|
+
hasPlaceholders,
|
|
260
|
+
getPlaceholderDocs,
|
|
130
261
|
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Utilities Module
|
|
3
|
+
*
|
|
4
|
+
* Common utility functions used across the CLI.
|
|
5
|
+
* Consolidates duplicated code from installer.js, doctor.js, etc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Calculate SHA256 hash of data and return as hex string
|
|
13
|
+
* @param {string|Buffer} data - Data to hash
|
|
14
|
+
* @returns {string} Hex-encoded SHA256 hash
|
|
15
|
+
*/
|
|
16
|
+
function sha256Hex(data) {
|
|
17
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert a file path to POSIX format (forward slashes)
|
|
22
|
+
* @param {string} filePath - Path to convert
|
|
23
|
+
* @returns {string} POSIX-style path
|
|
24
|
+
*/
|
|
25
|
+
function toPosixPath(filePath) {
|
|
26
|
+
return filePath.split(path.sep).join('/');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate a safe timestamp string for use in file/folder names
|
|
31
|
+
* @param {Date} date - Date to format (defaults to now)
|
|
32
|
+
* @returns {string} Timestamp like "2025-12-27T10-30-45-123Z"
|
|
33
|
+
*/
|
|
34
|
+
function safeTimestampForPath(date = new Date()) {
|
|
35
|
+
return date.toISOString().replace(/[:.]/g, '-');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Compare two semantic version strings
|
|
40
|
+
* @param {string} v1 - First version (e.g., "2.61.0")
|
|
41
|
+
* @param {string} v2 - Second version (e.g., "2.60.0")
|
|
42
|
+
* @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
43
|
+
*/
|
|
44
|
+
function compareVersions(v1, v2) {
|
|
45
|
+
const parts1 = v1.replace(/^v/, '').split('.').map(Number);
|
|
46
|
+
const parts2 = v2.replace(/^v/, '').split('.').map(Number);
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
49
|
+
const p1 = parts1[i] || 0;
|
|
50
|
+
const p2 = parts2[i] || 0;
|
|
51
|
+
if (p1 < p2) return -1;
|
|
52
|
+
if (p1 > p2) return 1;
|
|
53
|
+
}
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a debug logger function
|
|
59
|
+
* @param {boolean} enabled - Whether debug logging is enabled
|
|
60
|
+
* @param {string} prefix - Optional prefix for log messages
|
|
61
|
+
* @returns {Function} Debug log function
|
|
62
|
+
*/
|
|
63
|
+
function createDebugLogger(enabled, prefix = '') {
|
|
64
|
+
return (...args) => {
|
|
65
|
+
if (enabled) {
|
|
66
|
+
if (prefix) {
|
|
67
|
+
console.log(`[${prefix}]`, ...args);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(...args);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Brand color for AgileFlow (burnt orange/terracotta)
|
|
77
|
+
*/
|
|
78
|
+
const BRAND_COLOR = '#e8683a';
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
sha256Hex,
|
|
82
|
+
toPosixPath,
|
|
83
|
+
safeTimestampForPath,
|
|
84
|
+
compareVersions,
|
|
85
|
+
createDebugLogger,
|
|
86
|
+
BRAND_COLOR,
|
|
87
|
+
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: acceptance-criteria-generator
|
|
3
|
-
description: Generate properly-formatted Given/When/Then acceptance criteria for user stories
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# acceptance-criteria-generator
|
|
7
|
-
|
|
8
|
-
Generate properly-formatted Given/When/Then acceptance criteria.
|
|
9
|
-
|
|
10
|
-
## Activation Keywords
|
|
11
|
-
- "AC", "acceptance criteria", "Given When Then", "given when then", "acceptance", "criteria"
|
|
12
|
-
|
|
13
|
-
## When to Use
|
|
14
|
-
- User is writing acceptance criteria
|
|
15
|
-
- Need to format AC in proper Given/When/Then structure
|
|
16
|
-
- Ensuring clarity and testability of requirements
|
|
17
|
-
|
|
18
|
-
## What This Does
|
|
19
|
-
Takes user's plain English requirements and converts to structured Given/When/Then format:
|
|
20
|
-
- **Given**: Initial state/preconditions
|
|
21
|
-
- **When**: User action or trigger
|
|
22
|
-
- **Then**: Expected outcome/result
|
|
23
|
-
|
|
24
|
-
Generates multiple AC items if needed (typically 2-5 per story).
|
|
25
|
-
|
|
26
|
-
Ensures each criterion is:
|
|
27
|
-
- Testable (not vague)
|
|
28
|
-
- Independent (doesn't depend on other AC)
|
|
29
|
-
- Clear (unambiguous language)
|
|
30
|
-
- Measurable (has a clear success/failure)
|
|
31
|
-
|
|
32
|
-
## Output
|
|
33
|
-
Well-formatted acceptance criteria ready to add to story.
|
|
34
|
-
|
|
35
|
-
## Example Activation
|
|
36
|
-
User: "User should be able to log in with email and password, and receive a JWT token"
|
|
37
|
-
Skill: Generates:
|
|
38
|
-
```
|
|
39
|
-
- **Given** a registered user with valid email and password
|
|
40
|
-
**When** user POSTs to /api/auth/login with credentials
|
|
41
|
-
**Then** they receive a 200 response with JWT token (24h expiration)
|
|
42
|
-
|
|
43
|
-
- **Given** a user enters wrong password
|
|
44
|
-
**When** they attempt login
|
|
45
|
-
**Then** they receive 401 Unauthorized and rate limit applied
|
|
46
|
-
```
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: adr-template
|
|
3
|
-
description: Generate Architecture Decision Record structure with context/decision/consequences
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# adr-template
|
|
7
|
-
|
|
8
|
-
Generate Architecture Decision Record structure with context/decision/consequences.
|
|
9
|
-
|
|
10
|
-
## Activation Keywords
|
|
11
|
-
- "ADR", "architecture decision", "decision record", "adr template"
|
|
12
|
-
|
|
13
|
-
## When to Use
|
|
14
|
-
- Major architectural decisions made during implementation
|
|
15
|
-
- Documenting why a technology/pattern was chosen
|
|
16
|
-
- Recording decision trade-offs and alternatives considered
|
|
17
|
-
|
|
18
|
-
## What This Does
|
|
19
|
-
Generates complete ADR structure with:
|
|
20
|
-
- **Title**: Clear, concise decision title
|
|
21
|
-
- **Status**: Proposed, Accepted, Deprecated, Superseded
|
|
22
|
-
- **Context**: Why this decision was needed, what problem does it solve
|
|
23
|
-
- **Decision**: What was decided and why
|
|
24
|
-
- **Consequences**: Positive outcomes and risks introduced
|
|
25
|
-
- **Alternatives**: Other options considered and why rejected
|
|
26
|
-
- **Related Stories**: Links to affected user stories
|
|
27
|
-
|
|
28
|
-
Includes proper YAML frontmatter for integration with status.json.
|
|
29
|
-
|
|
30
|
-
## Output
|
|
31
|
-
Ready-to-use ADR file in docs/03-decisions/adr-XXXX.md format
|
|
32
|
-
|
|
33
|
-
## Example Activation
|
|
34
|
-
User: "We decided to use JWT for authentication instead of sessions"
|
|
35
|
-
Skill: Generates:
|
|
36
|
-
```
|
|
37
|
-
---
|
|
38
|
-
adr_id: ADR-0042
|
|
39
|
-
title: Use JWT for stateless authentication
|
|
40
|
-
status: accepted
|
|
41
|
-
date: 2025-10-28
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Context
|
|
45
|
-
Application needs scalable authentication across microservices.
|
|
46
|
-
Session-based auth requires shared state/cache.
|
|
47
|
-
|
|
48
|
-
## Decision
|
|
49
|
-
Adopt JWT (JSON Web Tokens) for stateless authentication.
|
|
50
|
-
|
|
51
|
-
## Consequences
|
|
52
|
-
✅ Benefits: Scalable, distributed, microservice-friendly
|
|
53
|
-
❌ Risks: Token revocation requires blacklist, larger payload
|
|
54
|
-
|
|
55
|
-
## Alternatives Considered
|
|
56
|
-
1. Session + Redis: Requires shared state
|
|
57
|
-
2. OAuth2: Overkill for internal auth
|
|
58
|
-
|
|
59
|
-
## Related Stories
|
|
60
|
-
- US-0001: User Login API
|
|
61
|
-
- US-0002: Password Reset
|
|
62
|
-
```
|