@relipa/ai-flow-kit 0.0.8 → 0.0.9-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/aiflow.js +109 -34
- package/bin/ak.js +3 -3
- package/custom/mcp-presets/README.md +66 -0
- package/custom/mcp-presets/figma-desktop.json +9 -0
- package/custom/rules/project-conventions.md +51 -0
- package/custom/templates/python.md +79 -79
- package/custom/templates/shared/gate-workflow.md +10 -8
- package/custom/templates/tools/gemini.md +22 -12
- package/docs/common/CHANGELOG.md +18 -0
- package/docs/common/workflows/figma.md +105 -0
- package/package.json +1 -1
- package/scripts/hooks/block-git-write.js +52 -52
- package/scripts/hooks/session-start.js +295 -274
- package/scripts/hooks/session-stop.js +122 -55
- package/scripts/init.js +13 -5
- package/scripts/prompt.js +21 -0
- package/scripts/task.js +41 -1
- package/scripts/telemetry/cli.js +19 -8
- package/scripts/telemetry/record.js +11 -4
package/README.md
CHANGED
|
@@ -126,7 +126,7 @@ aiflow init --framework spring-boot,reactjs --adapter backlog,jira
|
|
|
126
126
|
|
|
127
127
|
**Supported Frameworks:** `spring-boot`, `laravel`, `php`, `nestjs`, `reactjs`, `nextjs`, `vue-nuxt`, `nodejs-express`, `python`, `python-django`, `python-fastapi`
|
|
128
128
|
|
|
129
|
-
**Supported Adapters:** `backlog`, `jira`, `google-sheets`, `figma`
|
|
129
|
+
**Supported Adapters:** `backlog`, `jira`, `google-sheets`, `figma`, `figma-desktop`
|
|
130
130
|
|
|
131
131
|
---
|
|
132
132
|
|
|
@@ -281,7 +281,7 @@ aiflow memory list
|
|
|
281
281
|
| `impact-analysis` | "impact scope", "breaking change" | Analyze the impact when changing code |
|
|
282
282
|
| `generate-spec` | "new feature", "spec", "design" | Write technical specs, integrates `brainstorming` |
|
|
283
283
|
| `report-customer` | "customer report", "incident" | Write incident reports for Customer Service |
|
|
284
|
-
| `figma-to-component` | "figma", "design", "generate component" | Read Figma design → generate React/Next.js/Vue component |
|
|
284
|
+
| `figma-to-component` | "figma", "design", "generate component" | Read Figma design → generate React/Next.js App Router/Vue/Angular component. [Workflow guide](docs/common/workflows/figma.md) |
|
|
285
285
|
|
|
286
286
|
### Superpowers skills (upstream, inherited from obra/superpowers)
|
|
287
287
|
|
package/bin/aiflow.js
CHANGED
|
@@ -18,6 +18,7 @@ const telemetryCommand= require('../scripts/telemetry/cli');
|
|
|
18
18
|
const taskCommand = require('../scripts/task');
|
|
19
19
|
const checkpointCommand = require('../scripts/checkpoint');
|
|
20
20
|
const { record } = require('../scripts/telemetry/record');
|
|
21
|
+
const { updateTaskGateState } = require('../scripts/task');
|
|
21
22
|
|
|
22
23
|
// ── DEPRECATION WARNING ───────────────────────────────────────
|
|
23
24
|
if (!process.env.AIFLOW_IS_AK) {
|
|
@@ -42,7 +43,30 @@ program
|
|
|
42
43
|
const args = process.argv.slice(2);
|
|
43
44
|
let cmd = 'help';
|
|
44
45
|
if (args.length > 0) {
|
|
45
|
-
|
|
46
|
+
let first = args[0];
|
|
47
|
+
|
|
48
|
+
// 1. Phân giải alias cho root commands
|
|
49
|
+
const aliases = {
|
|
50
|
+
'i': 'init',
|
|
51
|
+
'u': 'use',
|
|
52
|
+
'p': 'prompt',
|
|
53
|
+
'd': 'detect',
|
|
54
|
+
't': 'task',
|
|
55
|
+
'ctx': 'context',
|
|
56
|
+
'cp': 'checkpoint',
|
|
57
|
+
'vl': 'validate',
|
|
58
|
+
'mem': 'memory',
|
|
59
|
+
'g': 'guide',
|
|
60
|
+
'rm': 'remove',
|
|
61
|
+
'up': 'update',
|
|
62
|
+
'sync': 'sync-skills',
|
|
63
|
+
'dr': 'doctor',
|
|
64
|
+
'tel': 'telemetry'
|
|
65
|
+
};
|
|
66
|
+
if (aliases[first]) {
|
|
67
|
+
first = aliases[first];
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
if (['-V', '-v', '--version'].includes(first)) {
|
|
47
71
|
cmd = 'version';
|
|
48
72
|
} else if (['-h', '--help'].includes(first)) {
|
|
@@ -52,7 +76,42 @@ program
|
|
|
52
76
|
if (args.includes('--help') || args.includes('-h')) cmd = 'help';
|
|
53
77
|
} else if (first === 'memory') {
|
|
54
78
|
const sec = args[1];
|
|
55
|
-
|
|
79
|
+
// Phân giải alias cho memory subcommands
|
|
80
|
+
let sub = sec;
|
|
81
|
+
if (sec && !sec.startsWith('-')) {
|
|
82
|
+
const memAliases = {
|
|
83
|
+
's': 'save',
|
|
84
|
+
'g': 'get',
|
|
85
|
+
'ls': 'list',
|
|
86
|
+
'sr': 'search',
|
|
87
|
+
'd': 'delete',
|
|
88
|
+
'cl': 'clear'
|
|
89
|
+
};
|
|
90
|
+
if (memAliases[sec]) sub = memAliases[sec];
|
|
91
|
+
cmd = `memory.${sub}`;
|
|
92
|
+
} else {
|
|
93
|
+
cmd = 'memory';
|
|
94
|
+
}
|
|
95
|
+
} else if (first === 'task') {
|
|
96
|
+
const sec = args[1];
|
|
97
|
+
// Phân giải alias subcommand của task
|
|
98
|
+
let sub = sec;
|
|
99
|
+
if (sec && !sec.startsWith('-')) {
|
|
100
|
+
const subAliases = {
|
|
101
|
+
'st': 'status',
|
|
102
|
+
'ls': 'list',
|
|
103
|
+
'p': 'pause',
|
|
104
|
+
'sw': 'switch',
|
|
105
|
+
'r': 'resume',
|
|
106
|
+
'rst': 'reset',
|
|
107
|
+
'rm': 'remove',
|
|
108
|
+
'n': 'next'
|
|
109
|
+
};
|
|
110
|
+
if (subAliases[sec]) sub = subAliases[sec];
|
|
111
|
+
cmd = `task.${sub}`;
|
|
112
|
+
} else {
|
|
113
|
+
cmd = 'task';
|
|
114
|
+
}
|
|
56
115
|
} else {
|
|
57
116
|
cmd = first;
|
|
58
117
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -88,7 +147,6 @@ program
|
|
|
88
147
|
? options.env.split(',').map(s => s.trim()).filter(Boolean)
|
|
89
148
|
: Object.keys(require('../scripts/init').AI_TOOL_FILES || {}).filter(t => t !== 'generic');
|
|
90
149
|
|
|
91
|
-
record('command.invoked', { command: 'init' });
|
|
92
150
|
initCommand(options);
|
|
93
151
|
});
|
|
94
152
|
|
|
@@ -109,7 +167,6 @@ program
|
|
|
109
167
|
.option('-F, --fast', 'fast mode: minimal Q&A, concise requirement doc (Default)')
|
|
110
168
|
.option('-U, --full', 'full mode: force complete analysis with Q&A (default)')
|
|
111
169
|
.action((target, options) => {
|
|
112
|
-
record('command.invoked', { command: 'use' });
|
|
113
170
|
useCommand(target, options);
|
|
114
171
|
});
|
|
115
172
|
|
|
@@ -123,7 +180,6 @@ program
|
|
|
123
180
|
.option('-L, --lang <lang>', 'language: english (default) or vietnamese')
|
|
124
181
|
.option('-d, --detail <level>', 'detail level: minimal | standard (default) | comprehensive')
|
|
125
182
|
.action((type, options) => {
|
|
126
|
-
record('command.invoked', { command: 'prompt' });
|
|
127
183
|
promptCommand(type, options);
|
|
128
184
|
});
|
|
129
185
|
|
|
@@ -135,7 +191,6 @@ program
|
|
|
135
191
|
.option('-v, --verbose', 'show detection details')
|
|
136
192
|
.option('-t, --threshold <number>', 'confidence threshold (0-100)')
|
|
137
193
|
.action((description, options) => {
|
|
138
|
-
record('command.invoked', { command: 'detect' });
|
|
139
194
|
const detector = new TaskDetector();
|
|
140
195
|
const threshold = options.threshold ? parseInt(options.threshold) / 100 : 0.8;
|
|
141
196
|
const result = detector.detect(description, threshold);
|
|
@@ -150,44 +205,44 @@ taskCmd
|
|
|
150
205
|
.command('status')
|
|
151
206
|
.alias('st')
|
|
152
207
|
.description('Show active task and pending tasks')
|
|
153
|
-
.action(() => {
|
|
208
|
+
.action(() => { taskCommand('status'); });
|
|
154
209
|
|
|
155
210
|
taskCmd
|
|
156
211
|
.command('list')
|
|
157
212
|
.alias('ls')
|
|
158
213
|
.description('List all saved tasks')
|
|
159
|
-
.action(() => {
|
|
214
|
+
.action(() => { taskCommand('list'); });
|
|
160
215
|
|
|
161
216
|
taskCmd
|
|
162
217
|
.command('pause')
|
|
163
218
|
.alias('p')
|
|
164
219
|
.description('Pause current task and save progress')
|
|
165
220
|
.option('-n, --note <text>', 'note about why you are pausing')
|
|
166
|
-
.action((opts) => {
|
|
221
|
+
.action((opts) => { taskCommand('pause', { note: opts.note }); });
|
|
167
222
|
|
|
168
223
|
taskCmd
|
|
169
224
|
.command('switch <ticket-id>')
|
|
170
225
|
.alias('sw')
|
|
171
226
|
.description('Pause current task and switch to another')
|
|
172
|
-
.action((ticketId) => {
|
|
227
|
+
.action((ticketId) => { taskCommand('switch', { taskId: ticketId }); });
|
|
173
228
|
|
|
174
229
|
taskCmd
|
|
175
230
|
.command('resume <ticket-id>')
|
|
176
231
|
.alias('r')
|
|
177
232
|
.description('Resume a paused task')
|
|
178
|
-
.action((ticketId) => {
|
|
233
|
+
.action((ticketId) => { taskCommand('resume', { taskId: ticketId }); });
|
|
179
234
|
|
|
180
235
|
taskCmd
|
|
181
236
|
.command('reset <ticket-id>')
|
|
182
237
|
.alias('rst')
|
|
183
238
|
.description('Reset task to Gate 1 (keeps ticket context, clears gate progress)')
|
|
184
|
-
.action((ticketId) => {
|
|
239
|
+
.action((ticketId) => { taskCommand('reset', { taskId: ticketId }); });
|
|
185
240
|
|
|
186
241
|
taskCmd
|
|
187
242
|
.command('remove <ticket-id>')
|
|
188
243
|
.alias('rm')
|
|
189
244
|
.description('Permanently delete all saved data for a task')
|
|
190
|
-
.action((ticketId) => {
|
|
245
|
+
.action((ticketId) => { taskCommand('remove', { taskId: ticketId }); });
|
|
191
246
|
|
|
192
247
|
taskCmd
|
|
193
248
|
.command('next')
|
|
@@ -195,7 +250,6 @@ taskCmd
|
|
|
195
250
|
.description('Approve current gate and prepare next session (saves state for resume)')
|
|
196
251
|
.option('-t, --ticket <id>', 'ticket ID (defaults to active task)')
|
|
197
252
|
.action((opts) => {
|
|
198
|
-
record('command.invoked', { command: 'task.next' });
|
|
199
253
|
taskCommand('next', { taskId: opts.ticket });
|
|
200
254
|
});
|
|
201
255
|
|
|
@@ -210,7 +264,6 @@ program
|
|
|
210
264
|
.option('--clear', 'clear all saved contexts')
|
|
211
265
|
.option('--show', 'show current context')
|
|
212
266
|
.action((action, options) => {
|
|
213
|
-
record('command.invoked', { command: 'context' });
|
|
214
267
|
contextCommand(action, options);
|
|
215
268
|
});
|
|
216
269
|
|
|
@@ -224,7 +277,6 @@ program
|
|
|
224
277
|
.requiredOption('-n, --tokens <n>', 'estimated token count', parseInt)
|
|
225
278
|
.option('-t, --ticket <id>', 'ticket ID (defaults to active task)')
|
|
226
279
|
.action((opts) => {
|
|
227
|
-
record('command.invoked', { command: 'checkpoint' });
|
|
228
280
|
checkpointCommand({ gate: opts.gate, step: opts.step, tokens: opts.tokens, ticket: opts.ticket });
|
|
229
281
|
});
|
|
230
282
|
|
|
@@ -237,7 +289,6 @@ program
|
|
|
237
289
|
.option('-v, --verbose', 'detailed report')
|
|
238
290
|
.option('-x, --fix', 'auto-fix trailing whitespace')
|
|
239
291
|
.action((file, options) => {
|
|
240
|
-
record('command.invoked', { command: 'validate' });
|
|
241
292
|
validateCommand(file, {
|
|
242
293
|
ruleset: options.ruleset,
|
|
243
294
|
verbose: options.verbose,
|
|
@@ -253,37 +304,37 @@ memCmd
|
|
|
253
304
|
.command('save <key> <value>')
|
|
254
305
|
.alias('s')
|
|
255
306
|
.description('Save a memory entry')
|
|
256
|
-
.action((key, value) => {
|
|
307
|
+
.action((key, value) => { memoryCommand('save', { key, value }); });
|
|
257
308
|
|
|
258
309
|
memCmd
|
|
259
310
|
.command('get <key>')
|
|
260
311
|
.alias('g')
|
|
261
312
|
.description('Retrieve a memory entry')
|
|
262
|
-
.action((key) => {
|
|
313
|
+
.action((key) => { memoryCommand('get', { key }); });
|
|
263
314
|
|
|
264
315
|
memCmd
|
|
265
316
|
.command('list')
|
|
266
317
|
.alias('ls')
|
|
267
318
|
.description('List all memories')
|
|
268
|
-
.action(() => {
|
|
319
|
+
.action(() => { memoryCommand('list'); });
|
|
269
320
|
|
|
270
321
|
memCmd
|
|
271
322
|
.command('search <query>')
|
|
272
323
|
.alias('sr')
|
|
273
324
|
.description('Search memories by keyword')
|
|
274
|
-
.action((query) => {
|
|
325
|
+
.action((query) => { memoryCommand('search', { query }); });
|
|
275
326
|
|
|
276
327
|
memCmd
|
|
277
328
|
.command('delete <key>')
|
|
278
329
|
.alias('d')
|
|
279
330
|
.description('Delete a memory entry')
|
|
280
|
-
.action((key) => {
|
|
331
|
+
.action((key) => { memoryCommand('delete', { key }); });
|
|
281
332
|
|
|
282
333
|
memCmd
|
|
283
334
|
.command('clear')
|
|
284
335
|
.alias('cl')
|
|
285
336
|
.description('Clear all memories')
|
|
286
|
-
.action(() => {
|
|
337
|
+
.action(() => { memoryCommand('clear'); });
|
|
287
338
|
|
|
288
339
|
// ── guide ─────────────────────────────────────────────────────
|
|
289
340
|
program
|
|
@@ -293,10 +344,27 @@ program
|
|
|
293
344
|
.option('-f, --flow', 'show architecture & flow diagram only')
|
|
294
345
|
.option('-c, --commands', 'show command reference only')
|
|
295
346
|
.action((options) => {
|
|
296
|
-
record('command.invoked', { command: 'guide' });
|
|
297
347
|
guideCommand(options);
|
|
298
348
|
});
|
|
299
349
|
|
|
350
|
+
// ── telemetry feedback helper ─────────────────────────────────
|
|
351
|
+
function showTelResult(result, label) {
|
|
352
|
+
try {
|
|
353
|
+
if (!result) return;
|
|
354
|
+
if (result.ok) {
|
|
355
|
+
console.log(chalk.green(` [tel] ✓ ${label} logged`));
|
|
356
|
+
} else if (result.reason === 'disabled') {
|
|
357
|
+
console.log(chalk.yellow(` [tel] ⚠ skipped — telemetry is disabled (run 'aiflow telemetry enable')`));
|
|
358
|
+
} else if (result.reason === 'opted-out') {
|
|
359
|
+
console.log(chalk.yellow(` [tel] ⚠ skipped — repo has .aiflow/no-telemetry opt-out`));
|
|
360
|
+
} else if (result.reason === 'no-url') {
|
|
361
|
+
console.log(chalk.yellow(` [tel] ⚠ skipped — no server URL configured (run 'aiflow telemetry enable')`));
|
|
362
|
+
} else if (result.error) {
|
|
363
|
+
console.log(chalk.yellow(` [tel] ⚠ error logging '${label}': ${result.error}`));
|
|
364
|
+
}
|
|
365
|
+
} catch (e) { /* never block main flow */ }
|
|
366
|
+
}
|
|
367
|
+
|
|
300
368
|
// ── gate (telemetry for gate workflow events) ─────────────────
|
|
301
369
|
program
|
|
302
370
|
.command('gate <number> <action>')
|
|
@@ -307,17 +375,26 @@ program
|
|
|
307
375
|
const gateNum = parseInt(number);
|
|
308
376
|
if (isNaN(gateNum) || gateNum < 1 || gateNum > 5) return;
|
|
309
377
|
if (!['start', 'approved'].includes(action)) return;
|
|
310
|
-
|
|
378
|
+
|
|
379
|
+
const ticketId = options.ticket || '';
|
|
380
|
+
|
|
381
|
+
const telResult = record(`gate.${action}`, {
|
|
311
382
|
gate: gateNum,
|
|
312
|
-
ticket_id:
|
|
383
|
+
ticket_id: ticketId,
|
|
313
384
|
command: `gate${gateNum}.${action}`,
|
|
314
385
|
ai_tool: options.aiTool || ''
|
|
315
386
|
});
|
|
387
|
+
showTelResult(telResult, `gate${gateNum}.${action}`);
|
|
388
|
+
|
|
389
|
+
// Keep task-state.json currentGate in sync (silent — never blocks the AI)
|
|
390
|
+
if (ticketId) {
|
|
391
|
+
updateTaskGateState(ticketId, gateNum, action).catch(() => {});
|
|
392
|
+
}
|
|
316
393
|
|
|
317
394
|
if (action === 'approved') {
|
|
318
395
|
console.log(chalk.cyan(`\n Gate ${gateNum} approved!`));
|
|
319
396
|
console.log(chalk.yellow(` Pro-Tip: To avoid context pollution, it's recommended to start a fresh chat session.`));
|
|
320
|
-
console.log(chalk.gray(` Run: aiflow task next${
|
|
397
|
+
console.log(chalk.gray(` Run: aiflow task next${ticketId ? ` --ticket ${ticketId}` : ''} to save progress,`));
|
|
321
398
|
console.log(chalk.gray(` then open a NEW chatbox and type "continue".\n`));
|
|
322
399
|
}
|
|
323
400
|
});
|
|
@@ -330,7 +407,6 @@ program
|
|
|
330
407
|
.option('--version <ver>', 'remove a specific cached version from .aiflow/versions/')
|
|
331
408
|
.option('-g, --global', 'uninstall the global npm package (removes `aiflow` command)')
|
|
332
409
|
.action((options) => {
|
|
333
|
-
record('command.invoked', { command: 'remove' });
|
|
334
410
|
removeCommand(options);
|
|
335
411
|
});
|
|
336
412
|
|
|
@@ -341,7 +417,6 @@ program
|
|
|
341
417
|
.description('Update to latest version')
|
|
342
418
|
.option('-f, --force', 'force update even if already on latest')
|
|
343
419
|
.action((options) => {
|
|
344
|
-
record('command.invoked', { command: 'update' });
|
|
345
420
|
updateCommand(options);
|
|
346
421
|
});
|
|
347
422
|
|
|
@@ -351,7 +426,6 @@ program
|
|
|
351
426
|
.alias('sync')
|
|
352
427
|
.description('Manually synchronize AI Instruction files with local custom skills')
|
|
353
428
|
.action(async () => {
|
|
354
|
-
record('command.invoked', { command: 'sync-skills' });
|
|
355
429
|
const projectDir = process.cwd();
|
|
356
430
|
const { setupFramework, AI_TOOL_FILES, ensureAiflowGitignored } = require('../scripts/init');
|
|
357
431
|
const fs = require('fs-extra');
|
|
@@ -384,7 +458,6 @@ program
|
|
|
384
458
|
.option('-t, --ticket <id>', 'show token breakdown for specific ticket')
|
|
385
459
|
.option('-v, --verbose', 'detailed output')
|
|
386
460
|
.action((opts) => {
|
|
387
|
-
record('command.invoked', { command: 'doctor' });
|
|
388
461
|
doctorCommand({ ticket: opts.ticket, verbose: opts.verbose });
|
|
389
462
|
});
|
|
390
463
|
|
|
@@ -392,9 +465,11 @@ program
|
|
|
392
465
|
program
|
|
393
466
|
.command('telemetry [action]')
|
|
394
467
|
.alias('tel')
|
|
395
|
-
.description('Manage telemetry logging (enable|disable|status)')
|
|
396
|
-
.
|
|
397
|
-
|
|
468
|
+
.description('Manage telemetry logging (enable|disable|status|log)')
|
|
469
|
+
.option('--event <event>', 'event name (for log action)')
|
|
470
|
+
.option('--detail <detail>', 'detail string (for log action)')
|
|
471
|
+
.action((action, options) => {
|
|
472
|
+
telemetryCommand(action || 'status', options);
|
|
398
473
|
});
|
|
399
474
|
|
|
400
475
|
program.parse(process.argv);
|
package/bin/ak.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
process.env.AIFLOW_IS_AK = '1';
|
|
4
|
-
require('./aiflow.js');
|
|
2
|
+
|
|
3
|
+
process.env.AIFLOW_IS_AK = '1';
|
|
4
|
+
require('./aiflow.js');
|
|
@@ -53,6 +53,72 @@ aiflow use BACKLOG-456
|
|
|
53
53
|
# Loads task from your Backlog instance
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
### Figma (`figma.json`)
|
|
57
|
+
|
|
58
|
+
Connect to Figma via REST API to read design files and generate components.
|
|
59
|
+
|
|
60
|
+
**When to use:** Default choice. Works anywhere — no Desktop app required.
|
|
61
|
+
|
|
62
|
+
**Setup:**
|
|
63
|
+
```bash
|
|
64
|
+
aiflow init --adapter figma
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Required Environment Variables:**
|
|
68
|
+
- `FIGMA_API_TOKEN` — Your Figma Personal Access Token
|
|
69
|
+
|
|
70
|
+
**Get API Token:**
|
|
71
|
+
1. Go to https://www.figma.com/settings → Security → Personal access tokens
|
|
72
|
+
2. Create a new token with File content (read) scope
|
|
73
|
+
3. Copy the token
|
|
74
|
+
|
|
75
|
+
**Tools provided by `figma-developer-mcp`:**
|
|
76
|
+
- `get_figma_data(fileKey, nodeId, depth)` — fetch node layout, styles, components
|
|
77
|
+
- `download_figma_images(fileKey, nodes, localPath)` — export images
|
|
78
|
+
|
|
79
|
+
**Usage:**
|
|
80
|
+
```
|
|
81
|
+
Generate component from Figma:
|
|
82
|
+
https://www.figma.com/design/XXXXX/App?node-id=123-456
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
See: [Figma workflow guide](../../docs/common/workflows/figma.md)
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### Figma Desktop (`figma-desktop.json`)
|
|
90
|
+
|
|
91
|
+
Connect via the official Figma MCP server. Requires Figma Desktop app open.
|
|
92
|
+
|
|
93
|
+
**When to use:** When your team prefers the official Figma tooling and has Desktop app licenses.
|
|
94
|
+
**When NOT to use:** CI environments, headless servers, team members without Desktop licenses.
|
|
95
|
+
|
|
96
|
+
**Setup:**
|
|
97
|
+
```bash
|
|
98
|
+
aiflow init --adapter figma-desktop
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Requirements:**
|
|
102
|
+
- Figma Desktop app installed
|
|
103
|
+
- A Figma file open in Desktop when invoking the skill
|
|
104
|
+
- No API token needed — uses Desktop session
|
|
105
|
+
|
|
106
|
+
**Tools provided by `@figma/mcp-server`:**
|
|
107
|
+
- `get_figma_data(fileKey, nodeId)` — fetch design data for open file
|
|
108
|
+
- `get_node_children(nodeId)` — traverse component tree
|
|
109
|
+
- `get_local_components()` — list components in the open file
|
|
110
|
+
|
|
111
|
+
| | `figma` (REST API) | `figma-desktop` (Official) |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| Needs Desktop app | No | Yes (must be open) |
|
|
114
|
+
| Auth | API Token | Desktop session |
|
|
115
|
+
| Works in CI | Yes | No |
|
|
116
|
+
| Access scope | Full REST API | Currently open file |
|
|
117
|
+
|
|
118
|
+
See: [Figma workflow guide](../../docs/common/workflows/figma.md)
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
56
122
|
### Google Sheets (`google-sheets.json`)
|
|
57
123
|
|
|
58
124
|
Connect to Google Sheets for loading task context.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Project Conventions (Override Skill Defaults)
|
|
2
|
+
|
|
3
|
+
> **MANDATORY:** These rules override ANY upstream skill default (including `writing-plans`, `generate-spec`, and all superpowers skills).
|
|
4
|
+
> Read this file before writing any output file during the Gate Workflow.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Plan Output Paths
|
|
9
|
+
|
|
10
|
+
| Output | Path | Note |
|
|
11
|
+
|--------|------|------|
|
|
12
|
+
| Requirement doc | `plan/[ticket-id]/requirement.md` | Gate 1 output |
|
|
13
|
+
| Implementation plan | `plan/[ticket-id]/plan.md` | Gate 2 output — **NOT** `docs/superpowers/plans/` |
|
|
14
|
+
| Summary | `plan/[ticket-id]/summary.md` | Gate 4 output |
|
|
15
|
+
|
|
16
|
+
> **Override:** The `writing-plans` skill defaults to `docs/superpowers/plans/<filename>.md`.
|
|
17
|
+
> In this project, **always save plans to `plan/[ticket-id]/plan.md` instead.**
|
|
18
|
+
>
|
|
19
|
+
> The `[ticket-id]` comes from `.aiflow/context/current.json`.
|
|
20
|
+
> If no ticket context exists, use a descriptive slug: `plan/<feature-name>/plan.md`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Execution Handoff Message
|
|
25
|
+
|
|
26
|
+
When `writing-plans` skill says to announce the saved path, use the **actual path**:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Plan complete and saved to `plan/[ticket-id]/plan.md`. Two execution options: ...
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Do NOT copy the hardcoded path from the skill template (`docs/superpowers/plans/`).
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Checklist Before Writing Any File
|
|
37
|
+
|
|
38
|
+
Before saving any plan, requirement, or summary file, verify:
|
|
39
|
+
|
|
40
|
+
- [ ] Output path follows the table above — NOT the skill's default path
|
|
41
|
+
- [ ] `[ticket-id]` is read from `.aiflow/context/current.json`
|
|
42
|
+
- [ ] Directory `plan/[ticket-id]/` exists or will be created
|
|
43
|
+
- [ ] Announced path in the handoff message matches the actual saved path
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Other Conventions
|
|
48
|
+
|
|
49
|
+
- **Review checklist:** `custom/rules/review-checklist.md`
|
|
50
|
+
- **Code style:** `custom/rules/code-style.md`
|
|
51
|
+
- **Naming:** `custom/rules/naming.md`
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
# Python AI System Prompt
|
|
2
|
-
|
|
3
|
-
You are an expert Python developer. Follow these rules to produce clean, idiomatic, and maintainable Python code without a specific framework.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Project Structure
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
project/
|
|
11
|
-
├── main.py # Entry point
|
|
12
|
-
├── src/
|
|
13
|
-
│ ├── service/ # Business logic
|
|
14
|
-
│ ├── repository/ # Data access layer
|
|
15
|
-
│ ├── model/ # Data classes / domain models
|
|
16
|
-
│ └── util/ # Pure helper functions
|
|
17
|
-
├── tests/ # Pytest suite
|
|
18
|
-
├── requirements.txt
|
|
19
|
-
└── pyproject.toml
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Python Rules
|
|
25
|
-
|
|
26
|
-
- Use **type hints** on all function signatures and return types.
|
|
27
|
-
- Follow **PEP 8** and keep functions small and single-purpose.
|
|
28
|
-
- Use **dataclasses** or **NamedTuple** for value objects; avoid plain dicts for structured data.
|
|
29
|
-
- Prefer **pathlib** over `os.path`; prefer `with` statements for file/resource handling.
|
|
30
|
-
- Raise specific exceptions — never `raise Exception("message")`.
|
|
31
|
-
|
|
32
|
-
```python
|
|
33
|
-
# ✅ Good
|
|
34
|
-
from dataclasses import dataclass
|
|
35
|
-
|
|
36
|
-
@dataclass
|
|
37
|
-
class UserRequest:
|
|
38
|
-
email: str
|
|
39
|
-
full_name: str
|
|
40
|
-
|
|
41
|
-
def create_user(request: UserRequest) -> UserResponse:
|
|
42
|
-
if not request.email:
|
|
43
|
-
raise ValueError("email is required")
|
|
44
|
-
...
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## Testing Rules
|
|
50
|
-
|
|
51
|
-
- Use **Pytest** for all tests.
|
|
52
|
-
- Name test functions `test_<what>_<expected_outcome>`.
|
|
53
|
-
- Use `pytest.raises` to assert exceptions.
|
|
54
|
-
|
|
55
|
-
```python
|
|
56
|
-
def test_create_user_raises_when_email_is_empty():
|
|
57
|
-
with pytest.raises(ValueError, match="email is required"):
|
|
58
|
-
create_user(UserRequest(email="", full_name="Test"))
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## Naming Conventions
|
|
64
|
-
|
|
65
|
-
| Element | Convention | Example |
|
|
66
|
-
|---------|-----------|---------|
|
|
67
|
-
| Module/package | snake_case | `user_service.py` |
|
|
68
|
-
| Class | PascalCase | `UserService` |
|
|
69
|
-
| Function/variable | snake_case | `find_by_id`, `user_id` |
|
|
70
|
-
| Constant | UPPER_SNAKE_CASE | `MAX_RETRY` |
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Common Anti-Patterns to Avoid
|
|
75
|
-
|
|
76
|
-
- ❌ Bare `except:` — always catch a specific exception type
|
|
77
|
-
- ❌ Mutable default arguments (`def f(x=[])`) — use `None` sentinel instead
|
|
78
|
-
- ❌ Global state — pass dependencies explicitly
|
|
79
|
-
- ❌ Returning `None` implicitly on error paths — raise or return a typed result
|
|
1
|
+
# Python AI System Prompt
|
|
2
|
+
|
|
3
|
+
You are an expert Python developer. Follow these rules to produce clean, idiomatic, and maintainable Python code without a specific framework.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Project Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
project/
|
|
11
|
+
├── main.py # Entry point
|
|
12
|
+
├── src/
|
|
13
|
+
│ ├── service/ # Business logic
|
|
14
|
+
│ ├── repository/ # Data access layer
|
|
15
|
+
│ ├── model/ # Data classes / domain models
|
|
16
|
+
│ └── util/ # Pure helper functions
|
|
17
|
+
├── tests/ # Pytest suite
|
|
18
|
+
├── requirements.txt
|
|
19
|
+
└── pyproject.toml
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Python Rules
|
|
25
|
+
|
|
26
|
+
- Use **type hints** on all function signatures and return types.
|
|
27
|
+
- Follow **PEP 8** and keep functions small and single-purpose.
|
|
28
|
+
- Use **dataclasses** or **NamedTuple** for value objects; avoid plain dicts for structured data.
|
|
29
|
+
- Prefer **pathlib** over `os.path`; prefer `with` statements for file/resource handling.
|
|
30
|
+
- Raise specific exceptions — never `raise Exception("message")`.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
# ✅ Good
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class UserRequest:
|
|
38
|
+
email: str
|
|
39
|
+
full_name: str
|
|
40
|
+
|
|
41
|
+
def create_user(request: UserRequest) -> UserResponse:
|
|
42
|
+
if not request.email:
|
|
43
|
+
raise ValueError("email is required")
|
|
44
|
+
...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Testing Rules
|
|
50
|
+
|
|
51
|
+
- Use **Pytest** for all tests.
|
|
52
|
+
- Name test functions `test_<what>_<expected_outcome>`.
|
|
53
|
+
- Use `pytest.raises` to assert exceptions.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
def test_create_user_raises_when_email_is_empty():
|
|
57
|
+
with pytest.raises(ValueError, match="email is required"):
|
|
58
|
+
create_user(UserRequest(email="", full_name="Test"))
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Naming Conventions
|
|
64
|
+
|
|
65
|
+
| Element | Convention | Example |
|
|
66
|
+
|---------|-----------|---------|
|
|
67
|
+
| Module/package | snake_case | `user_service.py` |
|
|
68
|
+
| Class | PascalCase | `UserService` |
|
|
69
|
+
| Function/variable | snake_case | `find_by_id`, `user_id` |
|
|
70
|
+
| Constant | UPPER_SNAKE_CASE | `MAX_RETRY` |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Common Anti-Patterns to Avoid
|
|
75
|
+
|
|
76
|
+
- ❌ Bare `except:` — always catch a specific exception type
|
|
77
|
+
- ❌ Mutable default arguments (`def f(x=[])`) — use `None` sentinel instead
|
|
78
|
+
- ❌ Global state — pass dependencies explicitly
|
|
79
|
+
- ❌ Returning `None` implicitly on error paths — raise or return a typed result
|