@relipa/ai-flow-kit 0.1.9 → 0.2.0-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.
Files changed (55) hide show
  1. package/README.md +65 -0
  2. package/bin/aiflow.js +74 -4
  3. package/custom/rules/output-language.md +36 -0
  4. package/custom/rules/project-conventions.md +16 -0
  5. package/custom/skills/automation-testing/SKILL.md +1 -1
  6. package/custom/skills/ba-skills/skill-ba-build-business-rules-v1.md +2 -0
  7. package/custom/skills/ba-skills/skill-ba-initial-analysis-v1.md +2 -0
  8. package/custom/skills/ba-skills/skill-ba-mermaid-flowchart-v1.md +2 -0
  9. package/custom/skills/ba-skills/skill-ba-qna-v1.md +2 -0
  10. package/custom/skills/ba-skills/skill-ba-ui-prototype-v1.md +2 -0
  11. package/custom/skills/ba-skills/skill-ba-write-uc-spec-v1.md +2 -0
  12. package/custom/skills/coverage-check/SKILL.md +2 -0
  13. package/custom/skills/create-system-requirement/SKILL.md +252 -0
  14. package/custom/skills/deploy-model/SKILL.md +2 -0
  15. package/custom/skills/design-experiment/SKILL.md +2 -0
  16. package/custom/skills/evaluate-model/SKILL.md +2 -0
  17. package/custom/skills/evidence-aggregation/SKILL.md +2 -0
  18. package/custom/skills/execute-flow/SKILL.md +2 -0
  19. package/custom/skills/explore-data/SKILL.md +2 -0
  20. package/custom/skills/frame-ml-problem/SKILL.md +2 -0
  21. package/custom/skills/gate-review/SKILL.md +2 -0
  22. package/custom/skills/generate-spec/SKILL.md +2 -0
  23. package/custom/skills/generate-test-report/SKILL.md +2 -0
  24. package/custom/skills/generate-testcase/SKILL.md +2 -0
  25. package/custom/skills/impact-analysis/SKILL.md +2 -0
  26. package/custom/skills/improve-algorithm/SKILL.md +2 -0
  27. package/custom/skills/ingest-data/SKILL.md +226 -0
  28. package/custom/skills/log-bug/SKILL.md +2 -0
  29. package/custom/skills/pr-impact-analysis/SKILL.md +2 -0
  30. package/custom/skills/read-study-requirement/SKILL.md +41 -2
  31. package/custom/skills/report-customer/SKILL.md +6 -2
  32. package/custom/skills/retest-orchestration/SKILL.md +2 -0
  33. package/custom/skills/review-plan/SKILL.md +2 -0
  34. package/custom/skills/script-sync/SKILL.md +1 -1
  35. package/custom/skills/test-analysis/SKILL.md +2 -0
  36. package/custom/skills/test-skills/rules/qa-writing-standards.md +2 -2
  37. package/custom/skills/train-model/SKILL.md +2 -0
  38. package/custom/templates/shared/create-testcase-workflow.md +32 -1
  39. package/custom/templates/shared/gate-workflow.md +64 -0
  40. package/docs/common/BA-Specs-Structure.md +3 -0
  41. package/docs/common/CHANGELOG.md +39 -0
  42. package/docs/common/Docs-Management-Flow.md +1 -1
  43. package/docs/common/INDEX.md +1 -0
  44. package/docs/common/Memory-Architecture-v1.0.md +1 -0
  45. package/docs/common/Project-Structure.md +5 -2
  46. package/package.json +1 -1
  47. package/scripts/create-score-excel.js +4 -111
  48. package/scripts/detect.js +22 -0
  49. package/scripts/hooks/session-start.js +1 -0
  50. package/scripts/init.js +24 -1
  51. package/scripts/link-resolver.js +194 -7
  52. package/scripts/prompt.js +144 -0
  53. package/scripts/task.js +22 -6
  54. package/scripts/ticket-writer.js +247 -0
  55. package/scripts/use.js +402 -135
@@ -0,0 +1,247 @@
1
+ 'use strict';
2
+ // Vấn đề 5 (docs/internal/PM Workflow_v1.0.md) — turns an AI-proposed, PM-approved task list
3
+ // (born out of `ingest-data` Gate 3) into real tickets on Backlog/Jira.
4
+ //
5
+ // Read access (fetch-links, listing projects/issueTypes/priorities) reuses the existing
6
+ // read-only credentials already configured for `ak use`/`ak fetch-links`. Creating a ticket is a
7
+ // WRITE action and requires a SEPARATE credential (`BACKLOG_API_KEY_WRITE` / `JIRA_API_TOKEN_WRITE`)
8
+ // — same "distinct WRITE key" policy the PM Workflow doc calls for, kept apart from the read key so
9
+ // a compromised/misconfigured read key can never create tickets.
10
+
11
+ const fs = require('fs-extra');
12
+ const chalk = require('chalk');
13
+ const useCommand = require('./use');
14
+ const linkResolver = require('./link-resolver');
15
+
16
+ const {
17
+ loadCredentials,
18
+ resolveBacklogCredentials,
19
+ resolveJiraCredentials,
20
+ saveMcpCredentials,
21
+ } = useCommand;
22
+
23
+ const {
24
+ fetchBacklogIssueTypes,
25
+ fetchBacklogPriorities,
26
+ createBacklogIssue,
27
+ createJiraIssue,
28
+ } = linkResolver;
29
+
30
+ // Only these keys can be written via `ak credentials set` — dedicated adapter credentials
31
+ // (BACKLOG_API_KEY, JIRA_API_TOKEN, ...) still go through `ak init --adapter` on purpose.
32
+ const SETTABLE_CREDENTIAL_KEYS = [
33
+ 'BACKLOG_API_KEY_WRITE',
34
+ 'JIRA_API_TOKEN_WRITE',
35
+ 'JIRA_EMAIL_WRITE',
36
+ 'BACKLOG_DEFAULT_PROJECT_ID',
37
+ 'BACKLOG_DEFAULT_PROJECT_KEY',
38
+ 'JIRA_DEFAULT_PROJECT_KEY',
39
+ ];
40
+
41
+ function jiraFullDomain(domain) {
42
+ return domain.includes('.') ? domain : `${domain}.atlassian.net`;
43
+ }
44
+
45
+ function missingCredResult(target, field, message) {
46
+ return { ok: false, error: 'missing-write-credentials', target, field, message };
47
+ }
48
+
49
+ async function resolveBacklogWrite(creds) {
50
+ const read = resolveBacklogCredentials(creds);
51
+ if (!read) {
52
+ return missingCredResult(
53
+ 'backlog', 'BACKLOG_API_KEY / BACKLOG_SPACE_KEY',
54
+ 'Chưa cấu hình credentials đọc Backlog. Chạy `ak init --adapter backlog` trước.'
55
+ );
56
+ }
57
+ const apiKeyWrite = process.env.BACKLOG_API_KEY_WRITE || creds.BACKLOG_API_KEY_WRITE;
58
+ if (!apiKeyWrite) {
59
+ return missingCredResult(
60
+ 'backlog', 'BACKLOG_API_KEY_WRITE',
61
+ 'Cần API key Backlog có quyền tạo issue (khác key đọc hiện tại). Cung cấp API key này, mình sẽ lưu lại và tạo ticket ngay.'
62
+ );
63
+ }
64
+ return { ok: true, domain: read.domain, apiKeyRead: read.apiKey, apiKeyWrite };
65
+ }
66
+
67
+ async function resolveJiraWrite(creds) {
68
+ const read = resolveJiraCredentials(creds);
69
+ if (!read) {
70
+ return missingCredResult(
71
+ 'jira', 'JIRA_API_TOKEN / JIRA_EMAIL / JIRA_DOMAIN',
72
+ 'Chưa cấu hình credentials đọc Jira. Chạy `ak init --adapter jira` trước.'
73
+ );
74
+ }
75
+ const apiTokenWrite = process.env.JIRA_API_TOKEN_WRITE || creds.JIRA_API_TOKEN_WRITE;
76
+ if (!apiTokenWrite) {
77
+ return missingCredResult(
78
+ 'jira', 'JIRA_API_TOKEN_WRITE',
79
+ 'Cần Jira API token có quyền tạo issue (khác token đọc hiện tại). Cung cấp token này, mình sẽ lưu lại và tạo ticket ngay.'
80
+ );
81
+ }
82
+ const emailWrite = process.env.JIRA_EMAIL_WRITE || creds.JIRA_EMAIL_WRITE || read.email;
83
+ return { ok: true, domain: jiraFullDomain(read.domain), emailWrite, apiTokenWrite };
84
+ }
85
+
86
+ async function pickBacklogDefaults(domain, apiKeyRead, projectId) {
87
+ const [issueTypes, priorities] = await Promise.all([
88
+ fetchBacklogIssueTypes(domain, apiKeyRead, projectId),
89
+ fetchBacklogPriorities(domain, apiKeyRead),
90
+ ]);
91
+ if (!issueTypes || !issueTypes.length) throw new Error(`Project ${projectId} không có issueType nào — không thể tạo issue.`);
92
+ const defaultIssueTypeId = issueTypes[0].id;
93
+ const normal = (priorities || []).find((p) => /normal|trung bình/i.test(p.name));
94
+ const defaultPriorityId = (normal || (priorities || [])[0] || {}).id;
95
+ if (!defaultPriorityId) throw new Error('Không lấy được danh sách priority từ Backlog.');
96
+ return { defaultIssueTypeId, defaultPriorityId };
97
+ }
98
+
99
+ /**
100
+ * `ak tasks create-tickets <file.json> [--json]`
101
+ *
102
+ * Input file shape:
103
+ * {
104
+ * "target": "backlog" | "jira",
105
+ * "projectId": "12345", // optional — Backlog only, overrides BACKLOG_DEFAULT_PROJECT_ID
106
+ * "projectKey": "PROJ", // optional — Jira only, overrides JIRA_DEFAULT_PROJECT_KEY
107
+ * "tasks": [
108
+ * { "type": "spec|coding|test|other", "title": "...", "description": "..." }
109
+ * ]
110
+ * }
111
+ *
112
+ * `description` is expected to already be fully composed by the AI per the ticket template in
113
+ * PM Workflow_v1.0.md §6.1 (Nội dung task + Nguồn tham chiếu) — this command posts it as-is.
114
+ */
115
+ async function createTicketsCommand(inputFile, options = {}) {
116
+ const emit = (obj) => {
117
+ if (options.json) { process.stdout.write(JSON.stringify(obj)); return; }
118
+ if (obj.ok === false) {
119
+ console.log(chalk.red(`✗ ${obj.message || obj.error}`));
120
+ } else {
121
+ console.log(chalk.green(`✓ ${obj.results.filter(r => r.ok).length}/${obj.results.length} ticket(s) created on ${obj.target}.`));
122
+ for (const r of obj.results) {
123
+ console.log(r.ok ? ` ${chalk.green('✓')} ${r.ticketId} ${chalk.gray(r.title)}` : ` ${chalk.red('✗')} ${chalk.gray(r.title)} — ${r.error}`);
124
+ }
125
+ }
126
+ console.log(JSON.stringify(obj));
127
+ };
128
+
129
+ let input;
130
+ try {
131
+ input = await fs.readJson(inputFile);
132
+ } catch (err) {
133
+ emit({ ok: false, error: 'invalid-input-file', message: `Không đọc được ${inputFile}: ${err.message}` });
134
+ process.exitCode = 1;
135
+ return;
136
+ }
137
+
138
+ const target = input.target;
139
+ const tasks = input.tasks || [];
140
+ if (!['backlog', 'jira'].includes(target)) {
141
+ emit({ ok: false, error: 'invalid-target', message: `target phải là "backlog" hoặc "jira", nhận được: ${target}` });
142
+ process.exitCode = 1;
143
+ return;
144
+ }
145
+ if (!tasks.length) {
146
+ emit({ ok: false, error: 'empty-tasks', message: 'File input không có task nào trong "tasks".' });
147
+ process.exitCode = 1;
148
+ return;
149
+ }
150
+
151
+ const creds = await loadCredentials();
152
+
153
+ if (target === 'backlog') {
154
+ const write = await resolveBacklogWrite(creds);
155
+ if (!write.ok) { emit(write); process.exitCode = 2; return; }
156
+
157
+ const projectId = input.projectId || creds.BACKLOG_DEFAULT_PROJECT_ID;
158
+ if (!projectId) {
159
+ emit({
160
+ ok: false, error: 'missing-project',
161
+ message: 'Chưa xác định projectId đích trên Backlog. Chạy `ak backlog-projects` để xem danh sách, rồi `ak backlog-set-default-project <id> <key>`.',
162
+ });
163
+ process.exitCode = 2;
164
+ return;
165
+ }
166
+
167
+ let defaults;
168
+ try {
169
+ defaults = await pickBacklogDefaults(write.domain, write.apiKeyRead, projectId);
170
+ } catch (err) {
171
+ emit({ ok: false, error: 'project-lookup-failed', message: err.message });
172
+ process.exitCode = 1;
173
+ return;
174
+ }
175
+
176
+ const results = [];
177
+ for (const task of tasks) {
178
+ try {
179
+ const created = await createBacklogIssue(write.domain, write.apiKeyWrite, {
180
+ projectId,
181
+ summary: task.title,
182
+ description: task.description,
183
+ issueTypeId: task.issueTypeId || defaults.defaultIssueTypeId,
184
+ priorityId: task.priorityId || defaults.defaultPriorityId,
185
+ });
186
+ results.push({ type: task.type, title: task.title, ok: true, ticketId: created.ticketId, url: created.url });
187
+ } catch (err) {
188
+ results.push({ type: task.type, title: task.title, ok: false, error: err.message });
189
+ }
190
+ }
191
+ emit({ ok: true, target, projectId, results });
192
+ return;
193
+ }
194
+
195
+ // target === 'jira'
196
+ const write = await resolveJiraWrite(creds);
197
+ if (!write.ok) { emit(write); process.exitCode = 2; return; }
198
+
199
+ const projectKey = input.projectKey || creds.JIRA_DEFAULT_PROJECT_KEY;
200
+ if (!projectKey) {
201
+ emit({
202
+ ok: false, error: 'missing-project',
203
+ message: 'Chưa xác định project đích trên Jira. Chạy `ak jira-projects` để xem danh sách, rồi `ak jira-set-default-project <key>`.',
204
+ });
205
+ process.exitCode = 2;
206
+ return;
207
+ }
208
+
209
+ const results = [];
210
+ for (const task of tasks) {
211
+ try {
212
+ const created = await createJiraIssue(write.domain, write.emailWrite, write.apiTokenWrite, {
213
+ projectKey,
214
+ summary: task.title,
215
+ description: task.description,
216
+ issueTypeName: task.issueType || 'Task',
217
+ });
218
+ results.push({ type: task.type, title: task.title, ok: true, ticketId: created.ticketId, url: created.url });
219
+ } catch (err) {
220
+ results.push({ type: task.type, title: task.title, ok: false, error: err.message });
221
+ }
222
+ }
223
+ emit({ ok: true, target, projectKey, results });
224
+ }
225
+
226
+ /**
227
+ * `ak credentials set <KEY> <VALUE>` — lets the AI persist a WRITE key the user pasted in chat
228
+ * (after being asked, per the missing-write-credentials error above) without a bespoke command
229
+ * per key. Restricted to an allowlist — dedicated adapter read-credentials still go through
230
+ * `ak init --adapter`, this is only for the pieces that flow introduced.
231
+ */
232
+ async function setCredentialCommand(key, value) {
233
+ if (!SETTABLE_CREDENTIAL_KEYS.includes(key)) {
234
+ console.log(chalk.red(`✗ Unknown credential key: ${key}.`));
235
+ console.log(chalk.gray(` Allowed: ${SETTABLE_CREDENTIAL_KEYS.join(', ')}`));
236
+ process.exitCode = 1;
237
+ return;
238
+ }
239
+ await saveMcpCredentials({ [key]: value });
240
+ console.log(chalk.green(`✓ Saved ${key} to ~/.aiflow/credentials.json`));
241
+ }
242
+
243
+ module.exports = {
244
+ createTicketsCommand,
245
+ setCredentialCommand,
246
+ SETTABLE_CREDENTIAL_KEYS,
247
+ };