@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 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
- const first = args[0];
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
- cmd = (sec && !sec.startsWith('-')) ? `memory.${sec}` : 'memory';
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(() => { record('command.invoked', { command: 'task.status' }); taskCommand('status'); });
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(() => { record('command.invoked', { command: 'task.list' }); taskCommand('list'); });
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) => { record('command.invoked', { command: 'task.pause' }); taskCommand('pause', { note: opts.note }); });
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) => { record('command.invoked', { command: 'task.switch' }); taskCommand('switch', { taskId: 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) => { record('command.invoked', { command: 'task.resume' }); taskCommand('resume', { taskId: 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) => { record('command.invoked', { command: 'task.reset' }); taskCommand('reset', { taskId: 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) => { record('command.invoked', { command: 'task.remove' }); taskCommand('remove', { taskId: 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) => { record('command.invoked', { command: 'memory.save' }); memoryCommand('save', { 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) => { record('command.invoked', { command: 'memory.get' }); memoryCommand('get', { 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(() => { record('command.invoked', { command: 'memory.list' }); memoryCommand('list'); });
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) => { record('command.invoked', { command: 'memory.search' }); memoryCommand('search', { 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) => { record('command.invoked', { command: 'memory.delete' }); memoryCommand('delete', { 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(() => { record('command.invoked', { command: 'memory.clear' }); memoryCommand('clear'); });
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
- record(`gate.${action}`, {
378
+
379
+ const ticketId = options.ticket || '';
380
+
381
+ const telResult = record(`gate.${action}`, {
311
382
  gate: gateNum,
312
- ticket_id: options.ticket || '',
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${options.ticket ? ` --ticket ${options.ticket}` : ''} to save progress,`));
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
- .action((action) => {
397
- telemetryCommand(action || 'status');
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,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "figma": {
4
+ "command": "npx",
5
+ "args": ["-y", "@figma/mcp-server"],
6
+ "env": {}
7
+ }
8
+ }
9
+ }
@@ -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