@relipa/ai-flow-kit 0.1.5-beta.1 → 0.1.6

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 (42) hide show
  1. package/bin/aiflow.js +34 -0
  2. package/custom/rules/ml-conventions.md +11 -8
  3. package/custom/rules/project-conventions.md +30 -14
  4. package/custom/skills/design-experiment/SKILL.md +2 -2
  5. package/custom/skills/evaluate-model/SKILL.md +2 -2
  6. package/custom/skills/explore-data/SKILL.md +1 -1
  7. package/custom/skills/figma-to-component/SKILL.md +222 -20
  8. package/custom/skills/frame-ml-problem/SKILL.md +1 -1
  9. package/custom/skills/gate-review/SKILL.md +1 -1
  10. package/custom/skills/generate-spec/SKILL.md +22 -3
  11. package/custom/skills/read-study-requirement/SKILL.md +74 -6
  12. package/custom/skills/review-plan/SKILL.md +26 -3
  13. package/custom/templates/shared/coding-workflow.md +0 -0
  14. package/custom/templates/shared/create-spec-workflow.md +55 -0
  15. package/custom/templates/shared/create-testcase-workflow.md +55 -0
  16. package/custom/templates/shared/gate-workflow.md +131 -19
  17. package/custom/templates/shared/ml-gate-workflow.md +16 -9
  18. package/custom/templates/tools/claude.md +1 -1
  19. package/custom/templates/tools/copilot.md +1 -1
  20. package/custom/templates/tools/cursor.md +1 -1
  21. package/custom/templates/tools/gemini.md +1 -1
  22. package/custom/templates/tools/generic.md +1 -1
  23. package/docs/common/AIFLOW.md +21 -11
  24. package/docs/common/CHANGELOG.md +43 -0
  25. package/docs/common/ai-integration.md +2 -2
  26. package/docs/common/cli-reference.md +3 -1
  27. package/docs/common/workflows/bug-fix.md +2 -2
  28. package/docs/common/workflows/feature.md +2 -2
  29. package/docs/common/workflows/figma.md +176 -105
  30. package/package.json +2 -2
  31. package/scripts/create-score-excel.js +135 -14
  32. package/scripts/detect.js +11 -0
  33. package/scripts/docs-branch.js +264 -0
  34. package/scripts/docs-repo.js +49 -0
  35. package/scripts/hooks/figma-rate-limit.js +83 -0
  36. package/scripts/hooks/session-start.js +49 -14
  37. package/scripts/init.js +33 -2
  38. package/scripts/link-resolver.js +0 -0
  39. package/scripts/prompt.js +56 -11
  40. package/scripts/task.js +39 -23
  41. package/scripts/update.js +4 -0
  42. package/scripts/use.js +6 -5
@@ -20,7 +20,7 @@
20
20
  When you run `aiflow init`, it generates a `.cursorrules` file.
21
21
  - Cursor will automatically read this file to understand the **Gate Workflow**.
22
22
  - Use `@Codebase` when you need the AI to search across your whole project.
23
- - **Resumption**: If you start a task in Claude Code and switch to Cursor, Cursor will see the generated `04.Coding/` docs and resume from where you left off.
23
+ - **Resumption**: If you start a task in Claude Code and switch to Cursor, Cursor will see the generated `AK-Docs/04.Coding/` docs and resume from where you left off.
24
24
 
25
25
  ### 2. Gemini CLI
26
26
  Pass `GEMINI.md` as system instructions to your Gemini session.
@@ -37,7 +37,7 @@ Copilot uses `.github/copilot-instructions.md` to guide its suggestions.
37
37
  The "Secret Sauce" of `ai-flow-kit` is its file-based state management:
38
38
 
39
39
  1. **Context**: Saved in `.aiflow/context/current.json`.
40
- 2. **Progress**: Saved as Markdown docs in `04.Coding/<section>/[functionId]/[ticketId].md`.
40
+ 2. **Progress**: Saved as Markdown docs in `AK-Docs/04.Coding/<section>/[functionId]/[ticketId].md`.
41
41
  3. **Rules**: Saved in `.rules/`.
42
42
 
43
43
  Because these are standard files, any AI tool can read them. You can:
@@ -262,6 +262,7 @@ ak t n -t PROJ-33 # specify ticket (--ticket)
262
262
  | `plan/<id>/requirement.md` | Gate 2 |
263
263
  | `plan/<id>/plan.md` | Gate 3 |
264
264
  | `plan/<id>/summary.md` | Gate 5 |
265
+ | `plan/<id>/output.md` _(gen-doc)_ | Done (Gate 2 complete) |
265
266
 
266
267
  **Example workflow:**
267
268
  ```bash
@@ -316,6 +317,7 @@ ak p feature # generate feature prompt
316
317
  ak p investigation # generate investigation prompt
317
318
  ak p refactor # generate refactor prompt
318
319
  ak p impact # generate impact analysis prompt
320
+ ak p gen-doc # generate document generation prompt (2-gate)
319
321
  ak p -l # list all prompt types (--list)
320
322
  ak p bug-fix -o p.md # save to file (--output)
321
323
  ak p bug-fix -L vietnamese # Vietnamese language (--lang)
@@ -323,7 +325,7 @@ ak p bug-fix -d comprehensive # comprehensive detail (--detail)
323
325
  ```
324
326
 
325
327
  **Options:**
326
- - `<type>` — Prompt type (bug-fix, feature, investigation, refactor, impact, docs)
328
+ - `<type>` — Prompt type (bug-fix, feature, investigation, refactor, impact, docs, gen-doc)
327
329
  - `-l, --list` — List available prompt types
328
330
  - `-o, --output <file>` — Save to file instead of printing
329
331
  - `-L, --lang <lang>` — Language (english, vietnamese)
@@ -17,7 +17,7 @@ AI will **automatically**:
17
17
  2. Read source code — trace data flow related to the bug
18
18
  3. Use `superpowers:systematic-debugging` to form root cause hypothesis
19
19
  4. Ask clarifying questions if reproduction steps are unclear
20
- 5. Output `04.Coding/01.Requirements/[functionId]/[ticketId].md` containing:
20
+ 5. Output `AK-Docs/04.Coding/01.Requirements/[functionId]/[ticketId].md` containing:
21
21
  - Bug analysis (current vs expected behavior)
22
22
  - **Root cause hypothesis** with evidence from source code
23
23
  - **Proposed fix approach** (with alternatives if applicable)
@@ -84,7 +84,7 @@ AI performs in the following order:
84
84
  AI runs:
85
85
  1. `superpowers:verification-before-completion` — all tests PASS (including old tests)
86
86
  2. `impact-analysis` — fix doesn't cause new bugs elsewhere
87
- 3. Create `04.Coding/04.Reviews/[functionId]/[ticketId].md`
87
+ 3. Create `AK-Docs/04.Coding/04.Reviews/[functionId]/[ticketId].md`
88
88
 
89
89
  **If OK, type:**
90
90
  ```
@@ -16,7 +16,7 @@ AI will **automatically**:
16
16
  1. Load ticket info from Backlog/Jira
17
17
  2. Read source code — understand architecture, related files, patterns
18
18
  3. Ask clarifying questions if anything is unclear (one at a time)
19
- 4. Output `04.Coding/01.Requirements/[functionId]/[ticketId].md` containing:
19
+ 4. Output `AK-Docs/04.Coding/01.Requirements/[functionId]/[ticketId].md` containing:
20
20
  - Requirements summary (AI's understanding)
21
21
  - Source code analysis (related files, data flow)
22
22
  - **Proposed solution** with options and recommendation
@@ -83,7 +83,7 @@ AI runs:
83
83
  1. `superpowers:verification-before-completion` — all tests PASS
84
84
  2. `impact-analysis` — no breaking changes outside of scope
85
85
  3. Review checklist — all items ticked
86
- 4. Create `04.Coding/04.Reviews/[functionId]/[ticketId].md`
86
+ 4. Create `AK-Docs/04.Coding/04.Reviews/[functionId]/[ticketId].md`
87
87
 
88
88
  AI displays summary and waits for developer review.
89
89
 
@@ -1,105 +1,176 @@
1
- # Figma → Component Workflow
2
-
3
- Generate UI components directly from Figma designs using the `figma-to-component` skill.
4
-
5
- ---
6
-
7
- ## Prerequisites
8
-
9
- Choose one adapter based on your setup:
10
-
11
- ### Option A REST API (recommended, no Desktop required)
12
-
13
- ```bash
14
- ak init -a figma
15
- # Prompts for FIGMA_API_TOKEN
16
- ```
17
-
18
- Get your token: [figma.com/settings](https://www.figma.com/settings) → Security → Personal access tokens → Create new token (File content: read).
19
-
20
- ### Option B — Official Figma MCP (requires Figma Desktop app)
21
-
22
- ```bash
23
- ak init -a figma-desktop
24
- # No token neededuses Desktop app session
25
- ```
26
-
27
- Requirements: Figma Desktop app installed and the target file open before invoking the skill.
28
-
29
- ---
30
-
31
- ## Getting a Figma Node URL
32
-
33
- 1. Open your file in Figma (browser or Desktop)
34
- 2. Right-click on a frame or component → **Copy link to selection**
35
- 3. The URL looks like:
36
- ```
37
- https://www.figma.com/design/FILEKEY/FileName?node-id=123-456
38
- ```
39
- - `FILEKEY` the file identifier (between `/design/` and the next `/`)
40
- - `node-id=123-456` the specific frame or component to generate
41
-
42
- You can also link to the entire file (no `node-id`) to let the skill list available frames.
43
-
44
- ---
45
-
46
- ## Trigger the Skill
47
-
48
- Paste the Figma URL into Claude Code with a generation request:
49
-
50
- ```
51
- Generate component from this Figma frame:
52
- https://www.figma.com/design/XXXXX/App?node-id=123-456
53
- ```
54
-
55
- ```
56
- Implement the UserCard UI from Figma:
57
- https://www.figma.com/design/XXXXX/App?node-id=78-910
58
- ```
59
-
60
- ```
61
- Read Figma and generate component following my project conventions:
62
- https://www.figma.com/design/XXXXX/App?node-id=42-0
63
- ```
64
-
65
- ---
66
-
67
- ## What to Expect
68
-
69
- The skill outputs in this order:
70
-
71
- 1. **Design Summary** — layout structure, color palette, typography scale used in the design
72
- 2. **Component file** — complete code for your detected framework (React, Next.js App Router, Vue 3, or Angular)
73
- 3. **Usage example** — how to import and use the component
74
- 4. **Notes** — anything that couldn't be mapped 1:1 from Figma (custom fonts, unsupported effects, etc.)
75
-
76
- **Framework auto-detection order:**
77
- 1. Read `CLAUDE.md` for framework identifier (`nextjs-app-router`, `reactjs`, `vue-nuxt`, `angular`)
78
- 2. Scan project files (`app/` dir App Router, `angular.json` Angular, `nuxt.config.*` Vue)
79
- 3. Fallback Next.js / React
80
-
81
- ---
82
-
83
- ## Review Checklist
84
-
85
- After the component is generated, verify:
86
-
87
- - [ ] Layout matches Figma (flex direction, alignment, gap)
88
- - [ ] Colors are correct (Tailwind class or arbitrary value)
89
- - [ ] Typography matches (size, weight, line-height)
90
- - [ ] Spacing correct (padding, margin, gap)
91
- - [ ] Interactive states handled (`'use client'` in App Router, variants in React/Vue/Angular)
92
- - [ ] Images use correct component (`next/image` for Next.js, `<img>` for others)
93
- - [ ] `className` / `class` prop exposed for external overrides
94
-
95
- ---
96
-
97
- ## Troubleshooting
98
-
99
- | Problem | Fix |
100
- |---|---|
101
- | `get_figma_data` tool not found | Run `ak init -a figma` to configure the MCP |
102
- | "Invalid API token" | Token expired — create a new one at figma.com/settings |
103
- | Wrong node generated | Check the `node-id` in the URL matches your selection in Figma |
104
- | Figma Desktop MCP not responding | Ensure Figma Desktop app is open with the file loaded |
105
- | Component uses wrong framework | Add framework to `CLAUDE.md` (e.g. `nextjs-app-router`) |
1
+ # Figma → Component Workflow
2
+
3
+ Generate UI components directly from Figma designs using the `figma-to-component` skill.
4
+
5
+ ---
6
+
7
+ ## Prerequisites
8
+
9
+ Choose one adapter based on your setup:
10
+
11
+ > **Which adapter?** The REST API is rate-limited **per seat** (see [Rate limits](#rate-limits-429)
12
+ > below): a View/Collab seat gets only ~6 heavy calls per MONTH — effectively unusable.
13
+ > - Token from a **Dev/Full seat** → Option A (REST) works fine with the batched strategy.
14
+ > - Only a View/Collab seat, or design-heavy work (many frames, art-heavy exports) →
15
+ > prefer **Option B (Figma Desktop)** — it uses the local app session and consumes no REST quota.
16
+
17
+ ### Option A — REST API (no Desktop required; needs a Dev/Full-seat token)
18
+
19
+ ```bash
20
+ ak init -a figma
21
+ # Prompts for FIGMA_API_TOKEN
22
+ ```
23
+
24
+ Get your token: [figma.com/settings](https://www.figma.com/settings) → Security → Personal access tokens → Create new token (File content: read). Create it from an account with a **Dev or Full seat** a View/Collab seat's token is capped at ~6 Tier 1 calls/month.
25
+
26
+ ### Option B — Official Figma MCP (requires Figma Desktop app)
27
+
28
+ ```bash
29
+ ak init -a figma-desktop
30
+ # No token needed — uses Desktop app session
31
+ ```
32
+
33
+ Requirements: Figma Desktop app installed and the target file open before invoking the skill.
34
+
35
+ ---
36
+
37
+ ## Getting a Figma Node URL
38
+
39
+ 1. Open your file in Figma (browser or Desktop)
40
+ 2. Right-click on a frame or component → **Copy link to selection**
41
+ 3. The URL looks like:
42
+ ```
43
+ https://www.figma.com/design/FILEKEY/FileName?node-id=123-456
44
+ ```
45
+ - `FILEKEY` — the file identifier (between `/design/` and the next `/`)
46
+ - `node-id=123-456` the specific frame or component to generate
47
+
48
+ You can also link to the entire file (no `node-id`) to let the skill list available frames.
49
+
50
+ ---
51
+
52
+ ## Trigger the Skill
53
+
54
+ Paste the Figma URL into Claude Code with a generation request:
55
+
56
+ ```
57
+ Generate component from this Figma frame:
58
+ https://www.figma.com/design/XXXXX/App?node-id=123-456
59
+ ```
60
+
61
+ ```
62
+ Implement the UserCard UI from Figma:
63
+ https://www.figma.com/design/XXXXX/App?node-id=78-910
64
+ ```
65
+
66
+ ```
67
+ Read Figma and generate component following my project conventions:
68
+ https://www.figma.com/design/XXXXX/App?node-id=42-0
69
+ ```
70
+
71
+ ---
72
+
73
+ ## What to Expect
74
+
75
+ The skill outputs in this order:
76
+
77
+ 1. **Design Summary** layout structure, color palette, typography scale used in the design
78
+ 2. **Component file** complete code for your detected framework (React, Next.js App Router, Vue 3, or Angular)
79
+ 3. **Usage example** how to import and use the component
80
+ 4. **Notes** — anything that couldn't be mapped 1:1 from Figma (custom fonts, unsupported effects, etc.)
81
+
82
+ **Framework auto-detection order:**
83
+ 1. Read `CLAUDE.md` for framework identifier (`nextjs-app-router`, `reactjs`, `vue-nuxt`, `angular`)
84
+ 2. Scan project files (`app/` dir → App Router, `angular.json` → Angular, `nuxt.config.*` → Vue)
85
+ 3. Fallback Next.js / React
86
+
87
+ ---
88
+
89
+ ## Figma trong Gate workflow
90
+
91
+ Khi làm việc theo quy trình 5 Gate (xem `CLAUDE.md`) với ticket liên quan UI, Figma được
92
+ nối tự động không cần gọi skill thủ công:
93
+
94
+ | Gate | Vai trò Figma |
95
+ |------|----------------|
96
+ | **1** | `read-study-requirement` dò Figma URL trong ticket (hoặc hỏi DEV một lần), đọc design **một lần** qua MCP, lưu artifact `plan/[ticket-id]/design/` (design-context.md + images/ + nodes.json). |
97
+ | **2** | `generate-spec` đọc `design-context.md` → đưa component tree, design tokens, danh sách ảnh vào plan TDD. |
98
+ | **3** | `figma-to-component` chạy ở **Gate mode**: dùng lại cache `nodes.json`, copy ảnh sang `public/assets/figma/`, sinh component (không gọi lại MCP). |
99
+ | **4** | `review-plan` đối chiếu UI đã code với `design-context.md`. |
100
+
101
+ **Artifact dùng chung** nguồn sự thật cho mọi Gate sau Gate 1:
102
+
103
+ ```
104
+ plan/[ticket-id]/design/
105
+ ├── design-context.md # layout, tokens, component list, image map
106
+ ├── images/ # ảnh export từ Figma
107
+ └── nodes.json # raw get_figma_data (cache)
108
+ ```
109
+
110
+ **Refresh khi design đổi:** gõ "reload figma" → Gate 1 fetch lại, ghi đè `design/`, cập nhật `Fetched`.
111
+
112
+ **Ticket không liên quan UI** hoặc không có Figma URL → toàn bộ nhánh Figma được bỏ qua.
113
+
114
+ ---
115
+
116
+ ## Review Checklist
117
+
118
+ After the component is generated, verify:
119
+
120
+ - [ ] Layout matches Figma (flex direction, alignment, gap)
121
+ - [ ] Colors are correct (Tailwind class or arbitrary value)
122
+ - [ ] Typography matches (size, weight, line-height)
123
+ - [ ] Spacing correct (padding, margin, gap)
124
+ - [ ] Interactive states handled (`'use client'` in App Router, variants in React/Vue/Angular)
125
+ - [ ] Images use correct component (`next/image` for Next.js, `<img>` for others)
126
+ - [ ] `className` / `class` prop exposed for external overrides
127
+
128
+ ---
129
+
130
+ ## Rate limits (429)
131
+
132
+ Figma rate-limits the REST API by **endpoint tier × plan × seat** (since 2025-11-17).
133
+ `get_figma_data` (file/nodes) and image renders share the same **Tier 1** bucket:
134
+
135
+ | Seat of the token owner | Tier 1 budget (file / nodes / render images) |
136
+ |---|---|
137
+ | View / Collab | **~6 calls / MONTH** (any plan) |
138
+ | Dev / Full — Starter | 10 / min |
139
+ | Dev / Full — Professional | 15 / min |
140
+ | Dev / Full — Organization+ | 20 / min |
141
+
142
+ The fills-map endpoint (`/v1/files/:key/images`) is Tier 2 (25-100/min) — the skill prefers it.
143
+ Downloading image bytes from the returned S3 URLs costs nothing.
144
+
145
+ > **Measured on a free personal (Starter) account, 2026-07:** the API returned
146
+ > `x-figma-rate-limit-type: low` with `retry-after ≈ 4 days` after only ~4 Tier 1 calls —
147
+ > i.e. free accounts behave like the View/Collab monthly bucket, NOT 10/min. On a free
148
+ > account, budget ONE batched fetch per design and cache everything; for anything heavier
149
+ > use `figma-desktop` or a paid Dev/Full seat.
150
+
151
+ **Client-side guard:** `ak init` installs a PreToolUse hook
152
+ (`.claude/hooks/figma-rate-limit.js`) that throttles every `mcp__figma__*` call to
153
+ 6 REST-call units per 60s (safe under the 10/min Starter budget; `download_figma_images`
154
+ counts as 3 units — it makes up to 3 REST calls internally). It sleeps instead of
155
+ failing, so calls are delayed — never dropped. Tune with `FIGMA_RATE_LIMIT_UNITS`
156
+ (e.g. `15` on a Professional plan).
157
+
158
+ **When you hit a 429, read the `Retry-After` header:**
159
+
160
+ - **≤ 60s** → per-minute bucket. Wait and retry once — this is normal on art-heavy frames.
161
+ - **Hours/days, or `X-Figma-Rate-Limit-Type: low`** → per-seat quota. A new token on the same
162
+ account will NOT help. Fix: use a token from a Dev/Full seat, or switch to
163
+ `ak init -a figma-desktop`.
164
+
165
+ ---
166
+
167
+ ## Troubleshooting
168
+
169
+ | Problem | Fix |
170
+ |---|---|
171
+ | `get_figma_data` tool not found | Run `ak init -a figma` to configure the MCP |
172
+ | "Invalid API token" | Token expired — create a new one at figma.com/settings |
173
+ | `429` immediately / every session | Token from a View/Collab seat (~6 calls/month) — see [Rate limits](#rate-limits-429) |
174
+ | Wrong node generated | Check the `node-id` in the URL matches your selection in Figma |
175
+ | Figma Desktop MCP not responding | Ensure Figma Desktop app is open with the file loaded |
176
+ | Component uses wrong framework | Add framework to `CLAUDE.md` (e.g. `nextjs-app-router`) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relipa/ai-flow-kit",
3
- "version": "0.1.5-beta.1",
3
+ "version": "0.1.6",
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": {
@@ -65,4 +65,4 @@
65
65
  "<rootDir>/tests/**/*.test.js"
66
66
  ]
67
67
  }
68
- }
68
+ }
@@ -24,6 +24,25 @@ function norm(cmd) {
24
24
  return ALIAS_MAP[c] || c;
25
25
  }
26
26
 
27
+ // ── Version helpers ─────────────────────────────────────────────────────────
28
+ const CURRENT_VERSION = '0.1.4';
29
+
30
+ function parseVersion(v) {
31
+ if (!v || v === '-' || v === '' || v.toLowerCase() === 'unknown') return null;
32
+ const core = v.split('-')[0]; // strip prerelease suffix, e.g. 0.1.5-beta.1 -> 0.1.5
33
+ const parts = core.split('.').map(n => parseInt(n, 10));
34
+ if (parts.some(n => Number.isNaN(n))) return null;
35
+ return parts;
36
+ }
37
+
38
+ function compareVersion(a, b) {
39
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
40
+ const diff = (a[i] || 0) - (b[i] || 0);
41
+ if (diff !== 0) return diff;
42
+ }
43
+ return 0;
44
+ }
45
+
27
46
  // ── Parse log file ─────────────────────────────────────────────────────────
28
47
  function parseLog(filePath) {
29
48
  const lines = fs.readFileSync(filePath, 'utf-8').split(/\r?\n/);
@@ -39,17 +58,19 @@ function parseLog(filePath) {
39
58
  if (!user || user === '-' || user === '') continue;
40
59
 
41
60
  const timestamp = cols[0] || '';
61
+ const version = cols[5] || '';
42
62
  const command = cols[6] || '';
43
63
  const ticket = cols[9] || '';
44
64
 
45
65
  const date = timestamp.split(' ')[0]; // dd/MM/yyyy
46
66
 
47
67
  if (!users[user]) {
48
- users[user] = { email: user, days: new Set(), events: 0, rawCmds: new Set(), gateCmds: [] };
68
+ users[user] = { email: user, days: new Set(), events: 0, rawCmds: new Set(), gateCmds: [], versions: new Set() };
49
69
  }
50
70
  const u = users[user];
51
71
  u.events++;
52
72
  if (date) u.days.add(date);
73
+ if (version.trim()) u.versions.add(version.trim());
53
74
 
54
75
  const cmd = norm(command);
55
76
  if (cmd) u.rawCmds.add(cmd);
@@ -130,6 +151,19 @@ const SCORE_LABELS = [
130
151
  'Sử dụng thường xuyên workflow',
131
152
  ];
132
153
 
154
+ // ── Version status for a user ──────────────────────────────────────────────
155
+ function getVersionStatus(u) {
156
+ const all = [...u.versions];
157
+ const parsed = all.map(v => ({ raw: v, parts: parseVersion(v) })).filter(v => v.parts);
158
+ if (parsed.length === 0) {
159
+ return { status: all.length ? 'unknown' : 'none', latest: null, all };
160
+ }
161
+ parsed.sort((a, b) => compareVersion(a.parts, b.parts));
162
+ const latest = parsed[parsed.length - 1];
163
+ const isOutdated = compareVersion(latest.parts, parseVersion(CURRENT_VERSION)) < 0;
164
+ return { status: isOutdated ? 'outdated' : 'ok', latest: latest.raw, all };
165
+ }
166
+
133
167
  // ── Build summary row for each user ───────────────────────────────────────
134
168
  function buildRow(email, u) {
135
169
  const { events, days, rawCmds, gateCmds } = u;
@@ -198,12 +232,12 @@ function buildRow(email, u) {
198
232
  impactLabel: '',
199
233
  totalScore: score,
200
234
  notes,
235
+ versionStatus: getVersionStatus(u),
201
236
  };
202
237
  }
203
238
 
204
239
  // ── Excel writer ───────────────────────────────────────────────────────────
205
- async function writeExcel(rows, outputPath, sheetLabel, fromLabel, toLabel) {
206
- const workbook = new ExcelJS.Workbook();
240
+ function writeExcel(rows, workbook, sheetLabel, fromLabel, toLabel) {
207
241
  const sheet = workbook.addWorksheet(`Score ${sheetLabel}`);
208
242
 
209
243
  sheet.columns = [
@@ -247,20 +281,23 @@ async function writeExcel(rows, outputPath, sheetLabel, fromLabel, toLabel) {
247
281
  const hRow = sheet.addRow(HEADERS);
248
282
  hRow.height = 36;
249
283
  hRow.eachCell(cell => {
250
- cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4472C4' } };
284
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4F81BD' } };
251
285
  cell.font = { bold: true, color: { argb: 'FFFFFFFF' }, size: 11 };
252
286
  cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
253
287
  cell.border = border;
254
288
  });
255
289
 
256
- // Score → row color
290
+ // Score → row color (matches requirements/GetPoints-format.png)
257
291
  const COLORS = {
258
- 0: 'FFFFC7CE',
259
- 1: 'FFFFEB9C',
260
- 2: 'FFFFFFCC',
261
- 3: 'FFD9EAD3',
262
- 4: 'FF93C47D',
263
- 5: 'FF6AA84F',
292
+ 0: 'FFFFFFFF',
293
+ 1: 'FFF5F8EE',
294
+ 2: 'FFEBF1DE',
295
+ 3: 'FFD8E4BC',
296
+ 4: 'FFC4D79B',
297
+ 5: 'FF76933C',
298
+ };
299
+ const FONT_COLORS = {
300
+ 0: 'FFFF0000',
264
301
  };
265
302
 
266
303
  rows.forEach((row, idx) => {
@@ -274,9 +311,11 @@ async function writeExcel(rows, outputPath, sheetLabel, fromLabel, toLabel) {
274
311
  r.height = 20;
275
312
 
276
313
  const fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: COLORS[row.depthScore] || 'FFFFFFFF' } };
314
+ const fontColor = FONT_COLORS[row.depthScore] || 'FF000000';
277
315
  r.eachCell({ includeEmpty: true }, (cell, col) => {
278
316
  cell.border = border;
279
317
  cell.fill = fill;
318
+ cell.font = { color: { argb: fontColor } };
280
319
  cell.alignment = (col <= 4 || (col >= 9 && col <= 13))
281
320
  ? { vertical: 'middle', horizontal: 'center' }
282
321
  : { vertical: 'middle', horizontal: 'left', wrapText: false };
@@ -289,8 +328,81 @@ async function writeExcel(rows, outputPath, sheetLabel, fromLabel, toLabel) {
289
328
  sheet.views = [{ state: 'frozen', ySplit: 2 }];
290
329
  sheet.autoFilter = { from: 'A2', to: 'N2' };
291
330
 
331
+ return sheet;
332
+ }
333
+
334
+ // ── Version warning sheet ───────────────────────────────────────────────────
335
+ function writeVersionSheet(workbook, rows) {
336
+ const sheet = workbook.addWorksheet('Version Warning');
337
+ sheet.columns = [
338
+ { key: 'no', width: 5 },
339
+ { key: 'email', width: 30 },
340
+ { key: 'status', width: 16 },
341
+ { key: 'latest', width: 16 },
342
+ { key: 'allVersions', width: 30 },
343
+ { key: 'note', width: 50 },
344
+ ];
345
+
346
+ const border = {
347
+ top: { style: 'thin' }, left: { style: 'thin' },
348
+ bottom: { style: 'thin' }, right: { style: 'thin' },
349
+ };
350
+
351
+ sheet.addRow([`Cảnh báo phiên bản — Yêu cầu phiên bản hiện tại: ${CURRENT_VERSION}`]);
352
+ sheet.mergeCells('A1:F1');
353
+ const title = sheet.getCell('A1');
354
+ title.font = { bold: true, size: 13, color: { argb: 'FF1F3864' } };
355
+ title.alignment = { vertical: 'middle', horizontal: 'center' };
356
+ title.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFD6DCE4' } };
357
+ sheet.getRow(1).height = 28;
358
+
359
+ const HEADERS = ['#', 'Email', 'Trạng thái', 'Phiên bản mới nhất', 'Các phiên bản dùng trong tuần', 'Ghi chú'];
360
+ const hRow = sheet.addRow(HEADERS);
361
+ hRow.height = 20;
362
+ hRow.eachCell(cell => {
363
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4F81BD' } };
364
+ cell.font = { bold: true, color: { argb: 'FFFFFFFF' }, size: 11 };
365
+ cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
366
+ cell.border = border;
367
+ });
368
+
369
+ const warnings = rows.filter(row => row.versionStatus.status === 'outdated' || row.versionStatus.status === 'unknown');
370
+
371
+ const STATUS_LABEL = { outdated: 'CŨ HƠN', unknown: 'KHÔNG XÁC ĐỊNH', ok: 'OK', none: 'Không có dữ liệu' };
372
+ const STATUS_COLOR = { outdated: 'FFFFC7CE', unknown: 'FFFFEB9C' };
373
+
374
+ warnings.forEach((row, idx) => {
375
+ const vs = row.versionStatus;
376
+ const note = vs.status === 'outdated'
377
+ ? `Đang dùng ${vs.latest}, chưa cập nhật lên ${CURRENT_VERSION}`
378
+ : 'Không đọc được phiên bản (giá trị "-" hoặc "unknown") — cần kiểm tra lại';
379
+ const r = sheet.addRow([
380
+ idx + 1, row.email, STATUS_LABEL[vs.status], vs.latest || '-', vs.all.join(', ') || '-', note,
381
+ ]);
382
+ r.height = 20;
383
+ const fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: STATUS_COLOR[vs.status] || 'FFFFFFFF' } };
384
+ r.eachCell({ includeEmpty: true }, (cell, col) => {
385
+ cell.border = border;
386
+ cell.fill = fill;
387
+ cell.alignment = (col <= 4)
388
+ ? { vertical: 'middle', horizontal: 'center' }
389
+ : { vertical: 'middle', horizontal: 'left', wrapText: false };
390
+ });
391
+ });
392
+
393
+ sheet.views = [{ state: 'frozen', ySplit: 2 }];
394
+ sheet.autoFilter = { from: 'A2', to: 'F2' };
395
+
396
+ return warnings;
397
+ }
398
+
399
+ async function writeExcelFile(rows, outputPath, sheetLabel, fromLabel, toLabel) {
400
+ const workbook = new ExcelJS.Workbook();
401
+ writeExcel(rows, workbook, sheetLabel, fromLabel, toLabel);
402
+ const warnings = writeVersionSheet(workbook, rows);
292
403
  await workbook.xlsx.writeFile(outputPath);
293
- console.log(`Written: ${outputPath} (${rows.length} users)`);
404
+ console.log(`Written: ${outputPath} (${rows.length} users, ${warnings.length} version warning(s))`);
405
+ return warnings;
294
406
  }
295
407
 
296
408
  // ── Canonical user list (fixed order) ─────────────────────────────────────
@@ -315,6 +427,10 @@ const CANONICAL_USERS = [
315
427
  'anhnt@relipasoft.com',
316
428
  'khanhdd@relipasoft.com',
317
429
  'hieunv@relipasoft.com',
430
+ 'cucdt@relipasoft.com',
431
+ 'khanhnv@relipasoft.com',
432
+ 'namhh@relipasoft.com',
433
+ 'tannt@relipasoft.com',
318
434
  ];
319
435
 
320
436
  // Log email → canonical email (for slight mismatches)
@@ -323,7 +439,7 @@ const LOG_EMAIL_ALIAS = {
323
439
  };
324
440
 
325
441
  const EMPTY_USER = (email) => ({
326
- email, events: 0, days: new Set(), rawCmds: new Set(), gateCmds: [],
442
+ email, events: 0, days: new Set(), rawCmds: new Set(), gateCmds: [], versions: new Set(),
327
443
  });
328
444
 
329
445
  // ── Main ───────────────────────────────────────────────────────────────────
@@ -357,6 +473,7 @@ const EMPTY_USER = (email) => ({
357
473
  u.events += data.events;
358
474
  data.days.forEach(d => u.days.add(d));
359
475
  data.rawCmds.forEach(c => u.rawCmds.add(c));
476
+ data.versions.forEach(v => u.versions.add(v));
360
477
  u.gateCmds.push(...data.gateCmds);
361
478
  }
362
479
  }
@@ -367,5 +484,9 @@ const EMPTY_USER = (email) => ({
367
484
  return buildRow(email, u);
368
485
  });
369
486
 
370
- await writeExcel(rows, outputFile, sheetLabel, fromLabel, toLabel);
487
+ const warnings = await writeExcelFile(rows, outputFile, sheetLabel, fromLabel, toLabel);
488
+ if (warnings.length) {
489
+ console.log(`\n⚠️ Version warnings (< ${CURRENT_VERSION} or unknown):`);
490
+ warnings.forEach(w => console.log(` - ${w.email}: ${w.versionStatus.latest || 'unknown'} (${w.versionStatus.status})`));
491
+ }
371
492
  })();
package/scripts/detect.js CHANGED
@@ -74,6 +74,17 @@ class TaskDetector {
74
74
  antiKeywords: [],
75
75
  icon: '🧪',
76
76
  description: 'Writing tests and QA'
77
+ },
78
+ 'gen-doc': {
79
+ keywords: [
80
+ 'gen-doc', 'generate doc', 'generate document', 'tạo tài liệu',
81
+ 'tài liệu flow', 'tài liệu logic', 'tài liệu điều tra',
82
+ 'flow document', 'logic document', 'analysis document',
83
+ 'source analysis', 'feature document', 'system document'
84
+ ],
85
+ antiKeywords: ['code', 'fix', 'bug'],
86
+ icon: '📝',
87
+ description: 'Generate document from source code / feature analysis'
77
88
  }
78
89
  };
79
90
  }