qwen-superpowers 1.0.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/AGENTS.md +106 -0
- package/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +257 -0
- package/cli/index.js +335 -0
- package/hooks/post-execute.js +103 -0
- package/hooks/pre-execute.js +106 -0
- package/package.json +65 -0
- package/scripts/install.js +71 -0
- package/scripts/release.js +97 -0
- package/scripts/verify-skills.js +81 -0
- package/scripts/version.js +43 -0
- package/skills/brainstorming/SKILL.md +94 -0
- package/skills/code-review/SKILL.md +101 -0
- package/skills/executing-plans/SKILL.md +71 -0
- package/skills/git-worktrees/SKILL.md +105 -0
- package/skills/subagent-driven-development/SKILL.md +109 -0
- package/skills/systematic-debugging/SKILL.md +74 -0
- package/skills/test-driven-development/SKILL.md +88 -0
- package/skills/verification-before-completion/SKILL.md +109 -0
- package/skills/writing-plans/SKILL.md +101 -0
- package/skills/writing-skills/SKILL.md +112 -0
package/cli/index.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* qwen-superpowers CLI
|
|
5
|
+
* Interface en ligne de commande pour gérer les skills et le workflow Qwen Superpowers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
const ROOT_DIR = path.join(__dirname, '..');
|
|
12
|
+
const SKILLS_DIR = path.join(ROOT_DIR, 'skills');
|
|
13
|
+
const CONFIG_FILE = path.join(ROOT_DIR, '.qwen-powers.json');
|
|
14
|
+
|
|
15
|
+
// ─── Couleurs CLI ────────────────────────────────────────────────
|
|
16
|
+
const colors = {
|
|
17
|
+
reset: '\x1b[0m',
|
|
18
|
+
bold: '\x1b[1m',
|
|
19
|
+
green: '\x1b[32m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
blue: '\x1b[34m',
|
|
22
|
+
red: '\x1b[31m',
|
|
23
|
+
cyan: '\x1b[36m',
|
|
24
|
+
gray: '\x1b[90m',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function colorize(color, text) {
|
|
28
|
+
return `${colors[color]}${text}${colors.reset}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── Commandes ───────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const commands = {
|
|
34
|
+
help() {
|
|
35
|
+
const lines = [
|
|
36
|
+
'',
|
|
37
|
+
colorize('bold', colorize('cyan', '+---------------------------------------------------------+')),
|
|
38
|
+
colorize('bold', colorize('cyan', '|') + ' Qwen Superpowers CLI v1.0.0 ' + colorize('bold', colorize('cyan', '|'))),
|
|
39
|
+
colorize('bold', colorize('cyan', '+---------------------------------------------------------+')),
|
|
40
|
+
'',
|
|
41
|
+
colorize('bold', 'USAGE:'),
|
|
42
|
+
' ' + colorize('green', 'qwen-powers') + ' <commande> [options]',
|
|
43
|
+
'',
|
|
44
|
+
colorize('bold', 'COMMANDES:'),
|
|
45
|
+
' ' + colorize('yellow', 'list') + ' Liste tous les skills disponibles',
|
|
46
|
+
' ' + colorize('yellow', 'show') + ' <skill> Affiche les details d\'un skill',
|
|
47
|
+
' ' + colorize('yellow', 'init') + ' [target-dir] Initialise le plugin dans un projet cible',
|
|
48
|
+
' ' + colorize('yellow', 'enable') + ' <skill> Active un skill dans la config locale',
|
|
49
|
+
' ' + colorize('yellow', 'disable') + ' <skill> Desactive un skill',
|
|
50
|
+
' ' + colorize('yellow', 'workflow') + ' Affiche le workflow en 7 etapes',
|
|
51
|
+
' ' + colorize('yellow', 'hook') + ' <name> Execute un hook (pre-execute, post-execute)',
|
|
52
|
+
' ' + colorize('yellow', 'update') + ' Met a jour les skills depuis le depot',
|
|
53
|
+
' ' + colorize('yellow', 'create') + ' <nom> Cree un nouveau skill (squelette)',
|
|
54
|
+
' ' + colorize('yellow', 'help') + ' Affiche cette aide',
|
|
55
|
+
'',
|
|
56
|
+
colorize('bold', 'EXEMPLES:'),
|
|
57
|
+
' ' + colorize('gray', 'qwen-powers list'),
|
|
58
|
+
' ' + colorize('gray', 'qwen-powers show test-driven-development'),
|
|
59
|
+
' ' + colorize('gray', 'qwen-powers init .'),
|
|
60
|
+
' ' + colorize('gray', 'qwen-powers enable tdd'),
|
|
61
|
+
'',
|
|
62
|
+
];
|
|
63
|
+
console.log(lines.join('\n'));
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
list() {
|
|
67
|
+
const skills = fs.readdirSync(SKILLS_DIR).filter(d =>
|
|
68
|
+
fs.statSync(path.join(SKILLS_DIR, d)).isDirectory()
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
console.log(`\n${colorize('bold', colorize('cyan', '📚 Skills disponibles:'))}\n`);
|
|
72
|
+
|
|
73
|
+
skills.forEach((skill, i) => {
|
|
74
|
+
const skillFile = path.join(SKILLS_DIR, skill, 'SKILL.md');
|
|
75
|
+
let description = 'Pas de description.';
|
|
76
|
+
if (fs.existsSync(skillFile)) {
|
|
77
|
+
const content = fs.readFileSync(skillFile, 'utf-8');
|
|
78
|
+
const match = content.match(/^#\s*(.+)$/m);
|
|
79
|
+
const descMatch = content.match(/^[>-]\s+(.+)$/m);
|
|
80
|
+
description = descMatch ? descMatch[1] : (match ? match[1] : 'Pas de description.');
|
|
81
|
+
}
|
|
82
|
+
const enabled = isSkillEnabled(skill);
|
|
83
|
+
const status = enabled
|
|
84
|
+
? colorize('green', '✓ activé')
|
|
85
|
+
: colorize('gray', '○ désactivé');
|
|
86
|
+
console.log(` ${colorize('bold', colorize('yellow', `${i + 1}. ${skill}`))} ${status}`);
|
|
87
|
+
console.log(` ${colorize('gray', description)}\n`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(` ${colorize('gray', `Total: ${skills.length} skills`)}\n`);
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
show(skillName) {
|
|
94
|
+
if (!skillName) {
|
|
95
|
+
console.error(colorize('red', '❌ Usage: qwen-powers show <nom-du-skill>'));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
const skillDir = path.join(SKILLS_DIR, skillName);
|
|
99
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
100
|
+
|
|
101
|
+
if (!fs.existsSync(skillFile)) {
|
|
102
|
+
console.error(colorize('red', `❌ Skill '${skillName}' introuvable.`));
|
|
103
|
+
console.log(` Répertoire: ${skillDir}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const content = fs.readFileSync(skillFile, 'utf-8');
|
|
108
|
+
console.log(`\n${colorize('bold', colorize('cyan', '━'.repeat(60)))}`);
|
|
109
|
+
console.log(content);
|
|
110
|
+
console.log(colorize('bold', colorize('cyan', '━'.repeat(60))) + '\n');
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
init(targetDir) {
|
|
114
|
+
const dest = path.resolve(targetDir || '.');
|
|
115
|
+
console.log(`\n${colorize('bold', colorize('green', `🚀 Initialisation de Qwen Superpowers dans: ${dest}`))}\n`);
|
|
116
|
+
|
|
117
|
+
// Copier le fichier de config racine
|
|
118
|
+
const configTemplate = path.join(ROOT_DIR, '.qwen-powers.example.json');
|
|
119
|
+
const configDest = path.join(dest, '.qwen-powers.json');
|
|
120
|
+
|
|
121
|
+
if (!fs.existsSync(configDest)) {
|
|
122
|
+
if (fs.existsSync(configTemplate)) {
|
|
123
|
+
fs.copyFileSync(configTemplate, configDest);
|
|
124
|
+
console.log(colorize('green', ' ✓ .qwen-powers.json créé'));
|
|
125
|
+
} else {
|
|
126
|
+
const defaultConfig = {
|
|
127
|
+
version: "1.0.0",
|
|
128
|
+
enabledSkills: [],
|
|
129
|
+
workflow: {
|
|
130
|
+
enforceStages: true,
|
|
131
|
+
stages: [
|
|
132
|
+
"brainstorming",
|
|
133
|
+
"git-worktrees",
|
|
134
|
+
"writing-plans",
|
|
135
|
+
"subagent-driven-development",
|
|
136
|
+
"test-driven-development",
|
|
137
|
+
"code-review",
|
|
138
|
+
"finishing-branch"
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
hooks: {
|
|
142
|
+
preExecute: [],
|
|
143
|
+
postExecute: []
|
|
144
|
+
},
|
|
145
|
+
settings: {
|
|
146
|
+
tddEnforced: true,
|
|
147
|
+
codeReviewRequired: true,
|
|
148
|
+
autoRunTests: true,
|
|
149
|
+
autoLint: true
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
fs.writeFileSync(configDest, JSON.stringify(defaultConfig, null, 2));
|
|
153
|
+
console.log(colorize('green', ' ✓ .qwen-powers.json créé avec la config par défaut'));
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
console.log(colorize('yellow', ' ⚠ .qwen-powers.json existe déjà'));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Créer le dossier .qwen-skills symlink ou copie
|
|
160
|
+
const skillsDest = path.join(dest, '.qwen-skills');
|
|
161
|
+
if (!fs.existsSync(skillsDest)) {
|
|
162
|
+
try {
|
|
163
|
+
// Sur Windows, on copie au lieu de symlink pour éviter les problèmes de permissions
|
|
164
|
+
copyDirRecursive(SKILLS_DIR, skillsDest);
|
|
165
|
+
console.log(colorize('green', ' ✓ Skills copiés dans .qwen-skills/'));
|
|
166
|
+
} catch (e) {
|
|
167
|
+
console.error(colorize('red', ` ❌ Erreur copie skills: ${e.message}`));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(`\n${colorize('bold', colorize('green', '✅ Initialisation terminée !'))}`);
|
|
172
|
+
console.log(colorize('gray', ' Exécutez `qwen-powers list` pour voir les skills disponibles.'));
|
|
173
|
+
console.log(colorize('gray', ' Modifiez .qwen-powers.json pour activer les skills.\n'));
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
enable(skillName) {
|
|
177
|
+
if (!skillName) {
|
|
178
|
+
console.error(colorize('red', '❌ Usage: qwen-powers enable <nom-du-skill>'));
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
const config = loadConfig();
|
|
182
|
+
if (!config.enabledSkills.includes(skillName)) {
|
|
183
|
+
config.enabledSkills.push(skillName);
|
|
184
|
+
saveConfig(config);
|
|
185
|
+
console.log(colorize('green', `✓ Skill '${skillName}' activé.`));
|
|
186
|
+
} else {
|
|
187
|
+
console.log(colorize('yellow', `⚠ Skill '${skillName}' déjà activé.`));
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
disable(skillName) {
|
|
192
|
+
if (!skillName) {
|
|
193
|
+
console.error(colorize('red', '❌ Usage: qwen-powers disable <nom-du-skill>'));
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
const config = loadConfig();
|
|
197
|
+
config.enabledSkills = config.enabledSkills.filter(s => s !== skillName);
|
|
198
|
+
saveConfig(config);
|
|
199
|
+
console.log(colorize('green', `✓ Skill '${skillName}' désactivé.`));
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
workflow() {
|
|
203
|
+
const stages = [
|
|
204
|
+
{ num: 1, name: 'Brainstorming', desc: 'Questionnement socratique pour affiner l\'idee -> sauvegarde du design doc' },
|
|
205
|
+
{ num: 2, name: 'Git Worktrees', desc: 'Creation d\'un workspace isole -> setup -> verification tests propres' },
|
|
206
|
+
{ num: 3, name: 'Writing Plans', desc: 'Decoupage en taches de 2-5 min avec chemins, snippets et etapes de verification' },
|
|
207
|
+
{ num: 4, name: 'Subagent-Driven Dev', desc: 'Dispatch d\'un sous-agent par tache -> revue 2 etapes -> continue autonome' },
|
|
208
|
+
{ num: 5, name: 'Test-Driven Dev', desc: 'Enforce RED -> GREEN -> REFACTOR -> supprime code ecrit avant tests -> commit si passe' },
|
|
209
|
+
{ num: 6, name: 'Code Review', desc: 'Compare le resultat au plan -> bloque si issues critiques -> rapport par severite' },
|
|
210
|
+
{ num: 7, name: 'Branch Finalization', desc: 'Verifie tous les tests -> presente options merge/PR/garder/supprimer -> cleanup' },
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
console.log('');
|
|
214
|
+
console.log(colorize('bold', colorize('cyan', '+---------------------------------------------------------+')));
|
|
215
|
+
console.log(colorize('bold', colorize('cyan', '|') + ' Workflow en 7 Etapes ' + colorize('bold', colorize('cyan', '|'))));
|
|
216
|
+
console.log(colorize('bold', colorize('cyan', '+---------------------------------------------------------+')));
|
|
217
|
+
console.log('');
|
|
218
|
+
|
|
219
|
+
stages.forEach(s => {
|
|
220
|
+
console.log(' ' + colorize('bold', colorize('yellow', 'Etape ' + s.num + ':')) + ' ' + colorize('bold', s.name));
|
|
221
|
+
console.log(' ' + colorize('gray', s.desc));
|
|
222
|
+
console.log('');
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
hook(hookName) {
|
|
227
|
+
if (!hookName) {
|
|
228
|
+
console.error(colorize('red', '❌ Usage: qwen-powers hook <nom-du-hook>'));
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
const hookFile = path.join(ROOT_DIR, 'hooks', `${hookName}.js`);
|
|
232
|
+
if (!fs.existsSync(hookFile)) {
|
|
233
|
+
console.error(colorize('red', `❌ Hook '${hookName}' introuvable.`));
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
require(hookFile);
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
update() {
|
|
240
|
+
console.log(colorize('yellow', '⚠ La mise à jour automatique sera implémentée prochainement.'));
|
|
241
|
+
console.log(colorize('gray', ' Pour l\'instant, mettez à jour via: npm update qwen-superpowers'));
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
create(skillName) {
|
|
245
|
+
if (!skillName) {
|
|
246
|
+
console.error(colorize('red', '❌ Usage: qwen-powers create <nom-du-skill>'));
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
const slug = skillName.toLowerCase().replace(/\s+/g, '-');
|
|
250
|
+
const skillDir = path.join(SKILLS_DIR, slug);
|
|
251
|
+
|
|
252
|
+
if (fs.existsSync(skillDir)) {
|
|
253
|
+
console.error(colorize('red', `❌ Le skill '${slug}' existe déjà.`));
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
258
|
+
|
|
259
|
+
const template = `# ${slug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())}
|
|
260
|
+
|
|
261
|
+
> Décrivez brièvement le but de ce skill.
|
|
262
|
+
|
|
263
|
+
## Triggers
|
|
264
|
+
- Quand utiliser ce skill ?
|
|
265
|
+
- Quels motifs ou contextes déclenchent ce skill ?
|
|
266
|
+
|
|
267
|
+
## Instructions
|
|
268
|
+
1. Étape 1
|
|
269
|
+
2. Étape 2
|
|
270
|
+
3. Étape 3
|
|
271
|
+
|
|
272
|
+
## Règles
|
|
273
|
+
- Règle 1
|
|
274
|
+
- Règle 2
|
|
275
|
+
|
|
276
|
+
## Sortie attendue
|
|
277
|
+
- Que doit produire l'agent après exécution ?
|
|
278
|
+
|
|
279
|
+
## Exemple d'utilisation
|
|
280
|
+
\`\`\`
|
|
281
|
+
Exemple concret d'invocation et de résultat attendu.
|
|
282
|
+
\`\`\`
|
|
283
|
+
`;
|
|
284
|
+
|
|
285
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), template);
|
|
286
|
+
console.log(colorize('green', `✓ Skill '${slug}' créé dans: ${skillDir}`));
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// ─── Helpers ─────────────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
function loadConfig() {
|
|
293
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
294
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
295
|
+
}
|
|
296
|
+
return { enabledSkills: [], workflow: { enforceStages: true }, settings: {} };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function saveConfig(config) {
|
|
300
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isSkillEnabled(skillName) {
|
|
304
|
+
const config = loadConfig();
|
|
305
|
+
return config.enabledSkills && config.enabledSkills.includes(skillName);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function copyDirRecursive(src, dest) {
|
|
309
|
+
if (!fs.existsSync(dest)) {
|
|
310
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
311
|
+
}
|
|
312
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
313
|
+
for (const entry of entries) {
|
|
314
|
+
const srcPath = path.join(src, entry.name);
|
|
315
|
+
const destPath = path.join(dest, entry.name);
|
|
316
|
+
if (entry.isDirectory()) {
|
|
317
|
+
copyDirRecursive(srcPath, destPath);
|
|
318
|
+
} else {
|
|
319
|
+
fs.copyFileSync(srcPath, destPath);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ─── Point d'entrée ──────────────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
const args = process.argv.slice(2);
|
|
327
|
+
const command = args[0] || 'help';
|
|
328
|
+
|
|
329
|
+
if (commands[command]) {
|
|
330
|
+
commands[command](...args.slice(1));
|
|
331
|
+
} else {
|
|
332
|
+
console.error(colorize('red', `❌ Commande '${command}' inconnue.`));
|
|
333
|
+
commands.help();
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook: post-execute
|
|
3
|
+
*
|
|
4
|
+
* Exécuté APRÈS qu'une tâche ou une commande est terminée.
|
|
5
|
+
* Utilisé pour vérifier le résultat, nettoyer l'environnement,
|
|
6
|
+
* et préparer l'étape suivante du workflow.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const ROOT = process.cwd();
|
|
13
|
+
const CONFIG_FILE = path.join(ROOT, '.qwen-powers.json');
|
|
14
|
+
|
|
15
|
+
function postExecute(context) {
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
const results = {
|
|
18
|
+
success: true,
|
|
19
|
+
nextSteps: [],
|
|
20
|
+
cleanup: [],
|
|
21
|
+
warnings: []
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Vérification post-TDD
|
|
25
|
+
if (config.settings && config.settings.tddEnforced && context.type === 'code') {
|
|
26
|
+
const testsPassed = context.testResults && context.testResults.passed;
|
|
27
|
+
if (!testsPassed) {
|
|
28
|
+
results.success = false;
|
|
29
|
+
results.warnings.push({
|
|
30
|
+
type: 'TDD_VIOLATION',
|
|
31
|
+
message: 'Les tests ne passent pas. Impossible de continuer ou de commit.'
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Vérification code review
|
|
37
|
+
if (config.settings && config.settings.codeReviewRequired) {
|
|
38
|
+
if (!context.reviewDone) {
|
|
39
|
+
results.nextSteps.push({
|
|
40
|
+
action: 'code-review',
|
|
41
|
+
message: 'Une revue de code est requise avant de continuer. Utilisez le skill `code-review`.'
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Auto-lint check
|
|
47
|
+
if (config.settings && config.settings.autoLint) {
|
|
48
|
+
results.nextSteps.push({
|
|
49
|
+
action: 'lint',
|
|
50
|
+
message: 'Vérifiez le linting: `npm run lint` (ou équivalent)'
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Prochaine étape du workflow
|
|
55
|
+
if (config.workflow && config.workflow.enforceStages) {
|
|
56
|
+
const currentStage = context.currentStage;
|
|
57
|
+
const stages = config.workflow.stages;
|
|
58
|
+
if (currentStage && stages) {
|
|
59
|
+
const currentIndex = stages.indexOf(currentStage);
|
|
60
|
+
if (currentIndex >= 0 && currentIndex < stages.length - 1) {
|
|
61
|
+
const nextStage = stages[currentIndex + 1];
|
|
62
|
+
results.nextSteps.push({
|
|
63
|
+
action: `workflow-next`,
|
|
64
|
+
message: `Prochaine étape du workflow: ${nextStage}`
|
|
65
|
+
});
|
|
66
|
+
} else if (currentIndex === stages.length - 1) {
|
|
67
|
+
results.nextSteps.push({
|
|
68
|
+
action: 'workflow-complete',
|
|
69
|
+
message: 'Workflow en 7 étapes terminé ! Prêt pour merge/PR.'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Suggestions de cleanup
|
|
76
|
+
results.cleanup.push({
|
|
77
|
+
action: 'remove-debug',
|
|
78
|
+
message: 'Supprimer les logs/console.log de debug ajoutés pendant le dev'
|
|
79
|
+
});
|
|
80
|
+
results.cleanup.push({
|
|
81
|
+
action: 'format',
|
|
82
|
+
message: 'Formater le code: `npm run format` (ou équivalent)'
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return results;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function loadConfig() {
|
|
89
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
90
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
91
|
+
}
|
|
92
|
+
return { settings: {}, workflow: {} };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Export pour utilisation par le système de hooks
|
|
96
|
+
module.exports = { postExecute };
|
|
97
|
+
|
|
98
|
+
// Si exécuté directement via CLI
|
|
99
|
+
if (require.main === module) {
|
|
100
|
+
const context = JSON.parse(process.argv[2] || '{}');
|
|
101
|
+
const result = postExecute(context);
|
|
102
|
+
console.log(JSON.stringify(result, null, 2));
|
|
103
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook: pre-execute
|
|
3
|
+
*
|
|
4
|
+
* Exécuté AVANT qu'une tâche ou une commande ne soit lancée.
|
|
5
|
+
* Utilisé pour valider l'environnement, sauvegarder l'état, ou blocker
|
|
6
|
+
* si des conditions ne sont pas remplies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const ROOT = process.cwd();
|
|
13
|
+
const CONFIG_FILE = path.join(ROOT, '.qwen-powers.json');
|
|
14
|
+
|
|
15
|
+
function preExecute(context) {
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
const results = {
|
|
18
|
+
blocked: false,
|
|
19
|
+
warnings: [],
|
|
20
|
+
checks: []
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Check 1: TDD enforcement
|
|
24
|
+
if (config.settings && config.settings.tddEnforced && context.type === 'code') {
|
|
25
|
+
const hasTest = context.files && context.files.some(f => isTestFile(f));
|
|
26
|
+
if (!hasTest) {
|
|
27
|
+
results.blocked = true;
|
|
28
|
+
results.checks.push({
|
|
29
|
+
name: 'TDD Check',
|
|
30
|
+
status: '❌ FAIL',
|
|
31
|
+
message: 'Aucun fichier de test détecté. Le TDD est activé — écrivez le test d\'abord.'
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
results.checks.push({
|
|
35
|
+
name: 'TDD Check',
|
|
36
|
+
status: '✅ PASS',
|
|
37
|
+
message: 'Fichier de test détecté.'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check 2: Branch isolation
|
|
43
|
+
if (config.workflow && config.workflow.enforceStages) {
|
|
44
|
+
const currentBranch = getCurrentBranch();
|
|
45
|
+
if (currentBranch === 'main' || currentBranch === 'master') {
|
|
46
|
+
results.blocked = true;
|
|
47
|
+
results.checks.push({
|
|
48
|
+
name: 'Branch Check',
|
|
49
|
+
status: '❌ FAIL',
|
|
50
|
+
message: `Vous êtes sur la branche '${currentBranch}'. Créez une branche de feature ou utilisez un worktree.`
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
results.checks.push({
|
|
54
|
+
name: 'Branch Check',
|
|
55
|
+
status: '✅ PASS',
|
|
56
|
+
message: `Branche de travail: ${currentBranch}`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check 3: Tests passing baseline
|
|
62
|
+
if (config.settings && config.settings.autoRunTests) {
|
|
63
|
+
results.checks.push({
|
|
64
|
+
name: 'Baseline Check',
|
|
65
|
+
status: '⚠️ SKIP',
|
|
66
|
+
message: 'Vérification des tests existants — assurez-vous que les tests passent avant de commencer.'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function loadConfig() {
|
|
74
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
75
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
76
|
+
}
|
|
77
|
+
return { settings: {}, workflow: {} };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isTestFile(filePath) {
|
|
81
|
+
const testPatterns = ['.test.', '.spec.', '__tests__', '/test/', '_test.py', 'test_'];
|
|
82
|
+
return testPatterns.some(p => filePath.includes(p));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getCurrentBranch() {
|
|
86
|
+
try {
|
|
87
|
+
const { execSync } = require('child_process');
|
|
88
|
+
return execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
|
|
89
|
+
} catch {
|
|
90
|
+
return 'unknown';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Export pour utilisation par le système de hooks
|
|
95
|
+
module.exports = { preExecute };
|
|
96
|
+
|
|
97
|
+
// Si exécuté directement via CLI
|
|
98
|
+
if (require.main === module) {
|
|
99
|
+
const context = JSON.parse(process.argv[2] || '{}');
|
|
100
|
+
const result = preExecute(context);
|
|
101
|
+
console.log(JSON.stringify(result, null, 2));
|
|
102
|
+
|
|
103
|
+
if (result.blocked) {
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qwen-superpowers",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Plugin de super-pouvoirs pour Qwen Code — Transforme l'agent IA en ingenieur logiciel discipline avec workflow structure, TDD, revue de code et developpement par sous-agents.",
|
|
5
|
+
"main": "cli/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"qwen-powers": "./cli/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node cli/index.js help",
|
|
11
|
+
"list": "node cli/index.js list",
|
|
12
|
+
"install-plugin": "node scripts/install.js",
|
|
13
|
+
"verify": "node scripts/verify-skills.js",
|
|
14
|
+
"test": "npm run verify && npm run test:cli",
|
|
15
|
+
"test:cli": "node cli/index.js help && node cli/index.js list && node cli/index.js workflow",
|
|
16
|
+
"prepublishOnly": "npm test",
|
|
17
|
+
"preversion": "npm test",
|
|
18
|
+
"version": "node scripts/version.js && git add CHANGELOG.md",
|
|
19
|
+
"postversion": "echo \"Nouvelle version: $(node -p \"require('./package.json').version\")\""
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"qwen",
|
|
23
|
+
"qwen-code",
|
|
24
|
+
"cli",
|
|
25
|
+
"skills",
|
|
26
|
+
"plugin",
|
|
27
|
+
"tdd",
|
|
28
|
+
"code-review",
|
|
29
|
+
"workflow",
|
|
30
|
+
"ai-agent",
|
|
31
|
+
"superpowers",
|
|
32
|
+
"agent",
|
|
33
|
+
"coding-assistant"
|
|
34
|
+
],
|
|
35
|
+
"author": "",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/stelio-fondation/qwen-superpowers"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/stelio-fondation/qwen-superpowers/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/stelio-fondation/qwen-superpowers#readme",
|
|
45
|
+
"files": [
|
|
46
|
+
"cli/",
|
|
47
|
+
"skills/",
|
|
48
|
+
"hooks/",
|
|
49
|
+
"scripts/",
|
|
50
|
+
"AGENTS.md",
|
|
51
|
+
"README.md",
|
|
52
|
+
"CHANGELOG.md",
|
|
53
|
+
"LICENSE",
|
|
54
|
+
"package.json"
|
|
55
|
+
],
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18.0.0"
|
|
58
|
+
},
|
|
59
|
+
"os": [
|
|
60
|
+
"win32",
|
|
61
|
+
"darwin",
|
|
62
|
+
"linux"
|
|
63
|
+
],
|
|
64
|
+
"preferGlobal": true
|
|
65
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script d'installation pour qwen-superpowers
|
|
3
|
+
* Copie les fichiers nécessaires dans le projet cible.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const ROOT = path.join(__dirname, '..');
|
|
10
|
+
|
|
11
|
+
function install(targetDir) {
|
|
12
|
+
const dest = path.resolve(targetDir || process.cwd());
|
|
13
|
+
console.log(`\n🚀 Installation de Qwen Superpowers dans: ${dest}\n`);
|
|
14
|
+
|
|
15
|
+
// 1. Copier AGENTS.md
|
|
16
|
+
const agentsSrc = path.join(ROOT, 'AGENTS.md');
|
|
17
|
+
const agentsDest = path.join(dest, 'AGENTS.md');
|
|
18
|
+
if (!fs.existsSync(agentsDest)) {
|
|
19
|
+
fs.copyFileSync(agentsSrc, agentsDest);
|
|
20
|
+
console.log(' ✓ AGENTS.md copié');
|
|
21
|
+
} else {
|
|
22
|
+
console.log(' ⚠ AGENTS.md existe déjà');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 2. Copier .qwen-powers.json
|
|
26
|
+
const configSrc = path.join(ROOT, '.qwen-powers.example.json');
|
|
27
|
+
const configDest = path.join(dest, '.qwen-powers.json');
|
|
28
|
+
if (!fs.existsSync(configDest)) {
|
|
29
|
+
fs.copyFileSync(configSrc, configDest);
|
|
30
|
+
console.log(' ✓ .qwen-powers.json créé');
|
|
31
|
+
} else {
|
|
32
|
+
console.log(' ⚠ .qwen-powers.json existe déjà');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 3. Copier les skills
|
|
36
|
+
const skillsSrc = path.join(ROOT, 'skills');
|
|
37
|
+
const skillsDest = path.join(dest, '.qwen-skills');
|
|
38
|
+
if (!fs.existsSync(skillsDest)) {
|
|
39
|
+
copyDirRecursive(skillsSrc, skillsDest);
|
|
40
|
+
console.log(' ✓ Skills copiés dans .qwen-skills/');
|
|
41
|
+
} else {
|
|
42
|
+
console.log(' ⚠ .qwen-skills/ existe déjà');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log('\n✅ Installation terminée !');
|
|
46
|
+
console.log(' Modifiez .qwen-powers.json pour configurer les skills.\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function copyDirRecursive(src, dest) {
|
|
50
|
+
if (!fs.existsSync(dest)) {
|
|
51
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const srcPath = path.join(src, entry.name);
|
|
56
|
+
const destPath = path.join(dest, entry.name);
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
copyDirRecursive(srcPath, destPath);
|
|
59
|
+
} else {
|
|
60
|
+
fs.copyFileSync(srcPath, destPath);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Exécuter si appelé directement
|
|
66
|
+
if (require.main === module) {
|
|
67
|
+
const target = process.argv[2] || '.';
|
|
68
|
+
install(target);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { install };
|