@relipa/ai-flow-kit 0.1.2-beta.0 → 0.1.3

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.
Files changed (41) hide show
  1. package/README.md +26 -0
  2. package/bin/aiflow.js +40 -1
  3. package/custom/harness/playwright/.env.example +10 -0
  4. package/custom/harness/playwright/playwright.config.ts +34 -0
  5. package/custom/harness/playwright/tests/e2e/auth.setup.ts +25 -0
  6. package/custom/harness/playwright/tests/e2e/fixtures/test.ts +6 -0
  7. package/custom/harness/playwright/tests/e2e/pages/BasePage.ts +9 -0
  8. package/custom/harness/playwright/tests/e2e/support/auth.ts +5 -0
  9. package/custom/mcp-presets/playwright.json +8 -0
  10. package/custom/rules/test-patterns.md +70 -0
  11. package/custom/skills/automation-testing/SKILL.md +239 -0
  12. package/custom/skills/automation-testing/templates/BasePage.ts +29 -0
  13. package/custom/skills/automation-testing/templates/PageObject.example.ts +29 -0
  14. package/custom/skills/automation-testing/templates/playwright.config.ts +39 -0
  15. package/custom/skills/automation-testing/templates/spec.example.ts +29 -0
  16. package/custom/skills/coverage-check/SKILL.md +202 -0
  17. package/custom/skills/evidence-aggregation/SKILL.md +246 -0
  18. package/custom/skills/execute-flow/SKILL.md +479 -0
  19. package/custom/skills/execute-flow/templates/playwright.config.ts +39 -0
  20. package/custom/skills/generate-test-report/SKILL.md +99 -0
  21. package/custom/skills/generate-test-report/templates/test-report.md +58 -0
  22. package/custom/skills/generate-testcase/SKILL.md +303 -0
  23. package/custom/skills/generate-testcase/templates/testcase.md +88 -0
  24. package/custom/skills/log-bug/SKILL.md +131 -0
  25. package/custom/skills/pr-impact-analysis/SKILL.md +180 -0
  26. package/custom/skills/retest-orchestration/SKILL.md +191 -0
  27. package/custom/skills/script-sync/SKILL.md +208 -0
  28. package/custom/skills/test-analysis/SKILL.md +262 -0
  29. package/custom/templates/shared/gate-workflow.md +306 -2
  30. package/docs/common/CHANGELOG.md +84 -0
  31. package/docs/common/QUICK_START.md +30 -0
  32. package/docs/common/cli-reference.md +48 -7
  33. package/package.json +1 -1
  34. package/scripts/create-score-excel.js +20 -7
  35. package/scripts/detect.js +10 -0
  36. package/scripts/guide.js +9 -0
  37. package/scripts/prompt.js +36 -0
  38. package/scripts/scaffold-playwright.js +106 -0
  39. package/scripts/task.js +21 -7
  40. package/scripts/update.js +124 -124
  41. package/scripts/use.js +30 -12
@@ -358,6 +358,31 @@ aiflow use PROJ-88
358
358
  claude # AI auto-starts: map dependencies → assess risk → output report
359
359
  ```
360
360
 
361
+ ### Execute Tests (QA Tester)
362
+
363
+ Run existing test cases with Playwright — sync scripts, execute, collect evidence, and generate report.
364
+
365
+ **Entry point 1: from a ticket** (ticket `type` must be `"execute"`)
366
+
367
+ ```bash
368
+ aiflow use PROJ-44 # loads ticket with taskType "execute"
369
+ claude # AI auto-starts execute-flow Gate 1 (pre-flight check)
370
+ ```
371
+
372
+ **Entry point 2: from a TC file**
373
+
374
+ ```bash
375
+ ak execute ./testcases/AD10.md # AI starts execute-flow with this TC file
376
+ ```
377
+
378
+ **Entry point 3: manual**
379
+
380
+ ```bash
381
+ ak execute # AI asks: "TC file ở đâu?"
382
+ ```
383
+
384
+ Output lands in `ak-test/{repo}/results/{screenId}/run-{N}/` — never deleted between runs.
385
+
361
386
  ### Figma Design → Code
362
387
 
363
388
  Turn a Figma design into a React / Next.js / Vue / Angular component automatically.
@@ -414,6 +439,11 @@ ak g -f # architecture diagram (ak guide
414
439
  ak g -c # command reference (ak guide --commands)
415
440
  ak sync # sync aiflow instruction files (ak sync-skills)
416
441
 
442
+ # Execute flow (QA Tester)
443
+ ak execute PROJ-44 # execute từ ticket (taskType phải là "execute")
444
+ ak execute ./testcases/AD10.md # execute từ TC file
445
+ ak execute # execute manual (AI hỏi TC file)
446
+
417
447
  # Per task
418
448
  ak use PROJ-33 # load context (Fast Mode, default) — alias: ak u PROJ-33
419
449
  ak use PROJ-33 PROJ-10 docs/arch.md # multi-target: primary + supplementary (v0.1.0+)
@@ -17,13 +17,13 @@ ak <alias> --help # Also works with aliases
17
17
  | Full command | Short | Full command | Short |
18
18
  |---|---|---|---|
19
19
  | `ak init` | `ak i` | `ak use` | `ak u` |
20
- | `ak prompt` | `ak p` | `ak detect` | `ak d` |
21
- | `ak task` | `ak t` | `ak context` | `ak ctx` |
22
- | `ak checkpoint` | `ak cp` | `ak validate` | `ak vl` |
23
- | `ak memory` | `ak mem` | `ak guide` | `ak g` |
24
- | `ak remove` | `ak rm` | `ak update` | `ak up` |
25
- | `ak sync-skills` | `ak sync` | `ak doctor` | `ak dr` |
26
- | `ak telemetry` | `ak tel` | | |
20
+ | `ak execute` | `ak ex` | `ak prompt` | `ak p` |
21
+ | `ak detect` | `ak d` | `ak task` | `ak t` |
22
+ | `ak context` | `ak ctx` | `ak checkpoint` | `ak cp` |
23
+ | `ak validate` | `ak vl` | `ak memory` | `ak mem` |
24
+ | `ak guide` | `ak g` | `ak remove` | `ak rm` |
25
+ | `ak update` | `ak up` | `ak sync-skills` | `ak sync` |
26
+ | `ak doctor` | `ak dr` | `ak telemetry` | `ak tel` |
27
27
 
28
28
  | `task` sub-command | Short | `memory` sub-command | Short |
29
29
  |---|---|---|---|
@@ -156,6 +156,47 @@ $ aiflow use PROJ-33 PROJ-10 docs/arch.md
156
156
  ✓ Auto-resolved 2 link(s) from description
157
157
  ```
158
158
 
159
+ ## execute (alias: `ex`)
160
+
161
+ Execute test cases via the 4-gate Executing Flow. Syncs Playwright scripts, runs them, collects evidence, and generates a test report.
162
+
163
+ ```bash
164
+ ak execute PROJ-44 # from ticket (taskType must be "execute")
165
+ ak execute ./testcases/AD10.md # from TC file
166
+ ak execute # manual — AI asks for TC file
167
+ ```
168
+
169
+ **Arguments:**
170
+ - `[target]` — Optional. A ticket ID (`PROJ-44`), a path to a TC file (`./testcases/AD10.md`), or omitted for manual entry.
171
+
172
+ **What it does (4 gates):**
173
+ 1. **Gate 1 — Pre-flight:** Parse TC file, verify `ak-test/{repo}/` and `playwright.config.ts` exist, check `BASE_URL`
174
+ 2. **Gate 2 — Script Sync:** Hash-based sync — gen new TCs, update changed, skip unchanged. Uses Playwright MCP for real selectors (never fabricates).
175
+ 3. **Gate 3 — Execute & Evidence:** Run Playwright, organize evidence into `run-{N}/`, update TC file R1/R2 columns (`✅ Pass` · `❌ Fail` · `⏭️ Untest` · `⏳ Pending`)
176
+ 4. **Gate 4 — Report & Bug Logging:** Generate `testreport.md`, confirm each bug with TESTER before logging to Jira
177
+
178
+ **Output structure:**
179
+ ```
180
+ ak-test/{repo}/
181
+ ├── scripts/{screenId}/{ScreenID}.spec.ts
182
+ └── results/{screenId}/run-{N}/
183
+ ├── {TC_ID}-{scenario}/ (screenshots, trace.zip, result.md)
184
+ ├── bugs/BUG-NNN-{slug}.md
185
+ └── testreport.md
186
+ ```
187
+
188
+ **Example:**
189
+ ```bash
190
+ $ ak execute ./testcases/AD10.md
191
+ ⏸️ GATE 1: EXECUTE FLOW READY
192
+ TC file: testcases/AD10.md
193
+ Repo: repo-fe | Screen: AD10 — Create Product
194
+ BASE_URL: http://localhost:3000
195
+ → Type APPROVED to sync scripts (Gate 2)
196
+ ```
197
+
198
+ ---
199
+
159
200
  ## fetch-links
160
201
 
161
202
  Fetch a single Backlog/Jira URL and print a `SupplementaryContext` JSON object to stdout. Intended for AI runtime use inside the `read-study-requirement` skill — when AI encounters a link in the ticket description that wasn't already resolved by `ak use`, it can call this command and integrate the JSON output.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relipa/ai-flow-kit",
3
- "version": "0.1.2-beta.0",
3
+ "version": "0.1.3",
4
4
  "description": "All-in-one AI Flow Kit for team development with Claude AI - skills, templates, and MCP adapters",
5
5
  "author": "Example Team",
6
6
  "publishConfig": {
@@ -193,9 +193,9 @@ function buildRow(email, u) {
193
193
  }
194
194
 
195
195
  // ── Excel writer ───────────────────────────────────────────────────────────
196
- async function writeExcel(rows, outputPath) {
196
+ async function writeExcel(rows, outputPath, sheetLabel, fromLabel, toLabel) {
197
197
  const workbook = new ExcelJS.Workbook();
198
- const sheet = workbook.addWorksheet('Score 18-24.05.2026');
198
+ const sheet = workbook.addWorksheet(`Score ${sheetLabel}`);
199
199
 
200
200
  sheet.columns = [
201
201
  { key: 'no', width: 5 },
@@ -220,7 +220,7 @@ async function writeExcel(rows, outputPath) {
220
220
  };
221
221
 
222
222
  // Title row
223
- sheet.addRow(['AI Flow Kit — Weekly Score Report: 18/05/202624/05/2026']);
223
+ sheet.addRow([`AI Flow Kit — Weekly Score Report: ${fromLabel}${toLabel}`]);
224
224
  sheet.mergeCells('A1:N1');
225
225
  const title = sheet.getCell('A1');
226
226
  title.font = { bold: true, size: 13, color: { argb: 'FF1F3864' } };
@@ -296,7 +296,7 @@ const CANONICAL_USERS = [
296
296
  'nguyenlt@relipasoft.com',
297
297
  'thuongvv@relipasoft.com',
298
298
  'yenvtb@relipasoft.com',
299
- 'tructth@relipasoft.com',
299
+ 'tructh@relipasoft.com',
300
300
  'thainq@relipasoft.com',
301
301
  'tuoittx@relipasoft.com',
302
302
  'hungdv@relipasoft.com',
@@ -323,8 +323,21 @@ const EMPTY_USER = (email) => ({
323
323
 
324
324
  // ── Main ───────────────────────────────────────────────────────────────────
325
325
  (async () => {
326
- const logFile = path.join(__dirname, '..', 'tellog', 'log_18-05-2026_24-05-2026');
327
- const outputFile = path.join(__dirname, '..', 'tellog', 'log_18-05-2026_24-05-2026_new.xlsx');
326
+ // Usage: node create-score-excel.js [log-file-stem]
327
+ // e.g.: node create-score-excel.js log_08-06-2026_14-06-2026
328
+ const stem = process.argv[2] || 'log_18-05-2026_24-05-2026';
329
+
330
+ // Derive display dates from stem: log_DD-MM-YYYY_DD-MM-YYYY → DD/MM/YYYY – DD/MM/YYYY
331
+ const dateMatch = stem.match(/log_(\d{2}-\d{2}-\d{4})_(\d{2}-\d{2}-\d{4})/);
332
+ const [fromLabel, toLabel] = dateMatch
333
+ ? [dateMatch[1].replace(/-/g, '/'), dateMatch[2].replace(/-/g, '/')]
334
+ : ['?', '?'];
335
+ const sheetLabel = dateMatch
336
+ ? `${dateMatch[1].slice(0,5).replace(/-/g,'.')}-${dateMatch[2].slice(0,5).replace(/-/g,'.')}.${dateMatch[1].slice(6,10)}`
337
+ : stem;
338
+
339
+ const logFile = path.join(__dirname, '..', 'tellog', stem);
340
+ const outputFile = path.join(__dirname, '..', 'tellog', `${stem}.xlsx`);
328
341
 
329
342
  const rawUsers = parseLog(logFile);
330
343
 
@@ -350,5 +363,5 @@ const EMPTY_USER = (email) => ({
350
363
  return buildRow(email, u);
351
364
  });
352
365
 
353
- await writeExcel(rows, outputFile);
366
+ await writeExcel(rows, outputFile, sheetLabel, fromLabel, toLabel);
354
367
  })();
package/scripts/detect.js CHANGED
@@ -64,6 +64,16 @@ class TaskDetector {
64
64
  antiKeywords: ['code'],
65
65
  icon: '📖',
66
66
  description: 'Writing documentation'
67
+ },
68
+ 'testing': {
69
+ keywords: [
70
+ 'test', 'testing', 'unit test', 'integration test', 'e2e',
71
+ 'qa', 'quality assurance', 'coverage', 'spec', 'assert',
72
+ 'mock', 'stub', 'test case', 'test suite', 'automated test'
73
+ ],
74
+ antiKeywords: [],
75
+ icon: '🧪',
76
+ description: 'Writing tests and QA'
67
77
  }
68
78
  };
69
79
  }
package/scripts/guide.js CHANGED
@@ -181,6 +181,14 @@ function showCommands() {
181
181
  ['aiflow detect "<description>"', '', 'Auto-detect task type'],
182
182
  ]
183
183
  },
184
+ {
185
+ group: 'QA Execute Flow',
186
+ items: [
187
+ ['aiflow execute <TICKET-ID>', '', 'Execute tests from ticket (taskType: execute)'],
188
+ ['aiflow execute <TC-FILE>', '', 'Execute tests from TC file path'],
189
+ ['aiflow execute', '', 'Execute tests — manual (AI asks for TC file)'],
190
+ ]
191
+ },
184
192
  {
185
193
  group: 'Task management',
186
194
  items: [
@@ -279,6 +287,7 @@ function showPromptGuide() {
279
287
  ['refactor', 'Improving code without changing behavior'],
280
288
  ['impact-analysis', 'Analyzing the impact of changes'],
281
289
  ['documentation', 'Writing docs, README, API reference'],
290
+ ['testing', 'Writing tests, test coverage, QA'],
282
291
  ];
283
292
 
284
293
  for (const [type, when] of types) {
package/scripts/prompt.js CHANGED
@@ -215,6 +215,42 @@ AI must:
215
215
  5. Display "GATE 1: Impact report complete" → wait for **APPROVED**
216
216
 
217
217
  **Do NOT make any code changes.** Implementation needs a separate ticket.
218
+ `,
219
+ },
220
+ 'testing': {
221
+ header: 'Testing',
222
+ instruction: 'Write tests for the feature or scenario described below.',
223
+ skillWorkflow: `
224
+ ## STRICT GATE WORKFLOW — Testing
225
+
226
+ ### GATE 1 — AI Analyze Requirement
227
+ **INVOKE:** \`read-study-requirement\` skill
228
+ AI must:
229
+ 1. Load ticket context + read source code thoroughly
230
+ 2. Understand what needs to be tested (unit, integration, e2e)
231
+ 3. If unclear → ask ONE question at a time
232
+ 4. Output \`plan/[ticket-id]/requirement.md\` with:
233
+ - Test scope and strategy
234
+ - Test cases to cover (happy path + edge cases + failure cases)
235
+ - Source code references
236
+ - Effort estimate
237
+ 5. Display "GATE 1: Test plan ready" → wait for **APPROVED**
238
+
239
+ ### GATE 2 — Implementation Plan
240
+ **INVOKE:** \`generate-spec\` skill, then \`superpowers:writing-plans\`
241
+ - Create detailed TDD test implementation plan.
242
+ - Output \`plan/[ticket-id]/plan.md\`.
243
+ - Display: "GATE 2 PAUSED: type APPROVED to start coding".
244
+
245
+ ### GATE 3 — Write Tests (TDD)
246
+ Only runs after Gate 2 APPROVED.
247
+ **INVOKE:** \`superpowers:test-driven-development\`
248
+ - Write failing tests first → confirm FAIL → implement → confirm PASS.
249
+
250
+ ### GATE 4 — AI Self-Review (wait for APPROVED)
251
+ **INVOKE:** \`superpowers:verification-before-completion\`
252
+ All tests must PASS. Check coverage meets requirements.
253
+ Display: "GATE 4 PAUSED: type APPROVED or BUG: [description]"
218
254
  `,
219
255
  },
220
256
  'documentation': {
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+ const chalk = require('chalk');
6
+
7
+ const PKG_DIR = path.join(__dirname, '..');
8
+ const HARNESS_SRC = path.join(PKG_DIR, 'custom', 'harness', 'playwright');
9
+
10
+ const HARNESS_FILES = [
11
+ 'playwright.config.ts',
12
+ '.env.example',
13
+ 'tests/e2e/pages/BasePage.ts',
14
+ 'tests/e2e/fixtures/test.ts',
15
+ 'tests/e2e/auth.setup.ts',
16
+ 'tests/e2e/support/auth.ts',
17
+ ];
18
+
19
+ const GITIGNORE_LINES = [
20
+ '# Playwright',
21
+ 'playwright-report/',
22
+ 'test-results/',
23
+ 'tests/e2e/.auth/',
24
+ '.env',
25
+ ];
26
+
27
+ const MCP_CONFIG = {
28
+ playwright: {
29
+ command: 'npx',
30
+ args: ['-y', '@playwright/mcp@latest', '--isolated', '--browser', 'chromium', '--headless'],
31
+ },
32
+ };
33
+
34
+ /**
35
+ * Scaffold Playwright E2E harness into a target project directory.
36
+ * @param {string} [targetDir] Destination directory (defaults to process.cwd())
37
+ * @param {{ force?: boolean, silent?: boolean }} [opts]
38
+ * @returns {Promise<{ copied: string[], skipped: string[], mcpUpdated: boolean }>}
39
+ */
40
+ async function scaffoldPlaywright(targetDir, opts) {
41
+ if (targetDir === undefined) targetDir = process.cwd();
42
+ var force = opts && opts.force ? true : false;
43
+ var silent = opts && opts.silent ? true : false;
44
+ var log = silent ? function () {} : console.log;
45
+
46
+ var result = { copied: [], skipped: [], mcpUpdated: false };
47
+
48
+ // ── Harness files ─────────────────────────────────────────────
49
+ for (var i = 0; i < HARNESS_FILES.length; i++) {
50
+ var relPath = HARNESS_FILES[i];
51
+ var src = path.join(HARNESS_SRC, relPath);
52
+ var dest = path.join(targetDir, relPath);
53
+ var exists = await fs.pathExists(dest);
54
+
55
+ if (!force && exists) {
56
+ log(chalk.gray(' ↷ Skipped (already exists): ' + relPath));
57
+ result.skipped.push(relPath);
58
+ continue;
59
+ }
60
+
61
+ await fs.ensureDir(path.dirname(dest));
62
+ await fs.copy(src, dest, { overwrite: true });
63
+ log(chalk.green(' ✓ ' + (exists ? 'Overwrote' : 'Created') + ': ' + relPath));
64
+ result.copied.push(relPath);
65
+ }
66
+
67
+ // ── .mcp.json ─────────────────────────────────────────────────
68
+ var mcpFile = path.join(targetDir, '.mcp.json');
69
+ var mcpConfig = { mcpServers: {} };
70
+ if (await fs.pathExists(mcpFile)) {
71
+ mcpConfig = await fs.readJson(mcpFile).catch(function () { return { mcpServers: {} }; });
72
+ }
73
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
74
+
75
+ if (!force && mcpConfig.mcpServers.playwright) {
76
+ log(chalk.gray(' ↷ Skipped .mcp.json playwright entry (already exists)'));
77
+ } else {
78
+ mcpConfig.mcpServers.playwright = MCP_CONFIG.playwright;
79
+ await fs.writeJson(mcpFile, mcpConfig, { spaces: 2 });
80
+ log(chalk.green(' ✓ Updated .mcp.json with playwright MCP server'));
81
+ result.mcpUpdated = true;
82
+ }
83
+
84
+ // ── .gitignore ────────────────────────────────────────────────
85
+ var gitignoreFile = path.join(targetDir, '.gitignore');
86
+ var gitignoreContent = '';
87
+ if (await fs.pathExists(gitignoreFile)) {
88
+ gitignoreContent = await fs.readFile(gitignoreFile, 'utf-8');
89
+ }
90
+ var missingLines = GITIGNORE_LINES.filter(function (line) {
91
+ return !gitignoreContent.includes(line);
92
+ });
93
+ if (missingLines.length > 0) {
94
+ var addition = '\n' + missingLines.join('\n') + '\n';
95
+ await fs.appendFile(gitignoreFile, addition);
96
+ log(chalk.green(' ✓ Updated .gitignore'));
97
+ }
98
+
99
+ return result;
100
+ }
101
+
102
+ module.exports = {
103
+ scaffoldPlaywright: scaffoldPlaywright,
104
+ HARNESS_FILES: HARNESS_FILES,
105
+ MCP_CONFIG: MCP_CONFIG,
106
+ };
package/scripts/task.js CHANGED
@@ -189,7 +189,7 @@ async function resumeTask(taskId) {
189
189
  await fs.writeJson(statePath, taskState, { spaces: 2 });
190
190
 
191
191
  console.log(chalk.green(`✓ Resumed task: ${taskId}`));
192
- console.log(` ${chalk.white('Gate:')} ${taskState.currentGate} (${gateLabel(taskState.currentGate)})`);
192
+ console.log(` ${chalk.white('Gate:')} ${taskState.currentGate} (${gateLabel(taskState.currentGate, taskState.taskType)})`);
193
193
  console.log(` ${chalk.white('Title:')} ${ctx.title.substring(0, 70)}`);
194
194
  if (taskState.notes) console.log(` ${chalk.white('Note:')} ${taskState.notes}`);
195
195
  console.log(chalk.gray('\n Open Claude to resume: claude'));
@@ -355,10 +355,11 @@ async function nextGate(taskId) {
355
355
  const summaryContent = await generateMarkdownSummary(taskState);
356
356
  await fs.writeFile(summaryPath, summaryContent, 'utf-8');
357
357
 
358
- const gateLabels = { 1: 'Analyze', 2: 'Plan', 3: 'Code', 4: 'Review', 5: 'PR' };
358
+ const taskType = existing.taskType || 'feature';
359
359
  console.log(chalk.green(`✓ Gate ${currentGate} approved for ${resolvedId}.`));
360
360
  console.log(chalk.gray(` Summary saved to: plan/${resolvedId}/task-summary.md`));
361
- console.log(chalk.white(`\n Next: Gate ${nextGateNum} — ${gateLabels[nextGateNum] || 'Done'}`));
361
+ const nextLabel = gateLabel(nextGateNum, taskType);
362
+ console.log(chalk.white(`\n Next: Gate ${nextGateNum} — ${nextLabel}`));
362
363
  console.log(chalk.cyan(`\n To continue in a fresh session (Recommended to avoid context pollution):`));
363
364
  console.log(chalk.gray(` 1. Open a NEW chatbox or terminal session.`));
364
365
  console.log(chalk.gray(` 2. Run: aiflow task resume ${resolvedId} (to load context).`));
@@ -418,21 +419,24 @@ async function detectCurrentGate(taskId) {
418
419
  }
419
420
 
420
421
  async function generateMarkdownSummary(taskState) {
422
+ const taskType = taskState.taskType || 'feature';
423
+ const maxGate = taskType === 'testing' ? 4 : 5;
421
424
  const lines = [];
422
425
  lines.push(`# Task Summary: ${taskState.taskId}`);
423
426
  lines.push(`**Title:** ${taskState.title}`);
427
+ lines.push(`**Type:** ${taskType}`);
424
428
  lines.push(`**Status:** ${taskState.status}`);
425
- lines.push(`**Current Gate:** ${taskState.currentGate} (${gateLabel(taskState.currentGate)})`);
429
+ lines.push(`**Current Gate:** ${taskState.currentGate} (${gateLabel(taskState.currentGate, taskType)})`);
426
430
  lines.push(`**Updated At:** ${new Date().toLocaleString()}`);
427
431
  lines.push(``);
428
432
  lines.push(`## Gate History`);
429
433
  lines.push(`| Gate | Status | Approved At |`);
430
434
  lines.push(`|------|--------|-------------|`);
431
- for (let i = 1; i <= 5; i++) {
435
+ for (let i = 1; i <= maxGate; i++) {
432
436
  const approvedAt = taskState.gateApprovals && taskState.gateApprovals[String(i)];
433
437
  const status = approvedAt ? '✅ Approved' : (i === taskState.currentGate ? '⏳ In Progress' : '⚪ Pending');
434
438
  const timeStr = approvedAt ? new Date(approvedAt).toLocaleString() : '-';
435
- lines.push(`| Gate ${i} (${gateLabel(i)}) | ${status} | ${timeStr} |`);
439
+ lines.push(`| Gate ${i} (${gateLabel(i, taskType)}) | ${status} | ${timeStr} |`);
436
440
  }
437
441
  lines.push(``);
438
442
  lines.push(`---`);
@@ -440,7 +444,16 @@ async function generateMarkdownSummary(taskState) {
440
444
  return lines.join('\n');
441
445
  }
442
446
 
443
- function gateLabel(n) {
447
+ function gateLabel(n, taskType) {
448
+ if (taskType === 'testing') {
449
+ const labels = {
450
+ 1: 'Phân tích & Confirm',
451
+ 2: 'Lập kế hoạch Test',
452
+ 3: 'Thực thi & Evidence',
453
+ 4: 'Báo cáo & Sign-off',
454
+ };
455
+ return labels[n] || `Gate ${n}`;
456
+ }
444
457
  const labels = {
445
458
  1: 'AI Analyze Requirement',
446
459
  2: 'Implementation Plan',
@@ -494,6 +507,7 @@ module.exports.createOrActivateTaskState = async function createOrActivateTaskSt
494
507
  ...existing,
495
508
  taskId,
496
509
  title: ctx.title || '',
510
+ taskType: ctx.taskType || existing.taskType || 'feature',
497
511
  status: 'active',
498
512
  createdAt: existing.createdAt || now,
499
513
  updatedAt: now,