@relipa/ai-flow-kit 0.2.0-beta.2 → 0.2.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/custom/rules/java/spring-boot-rules.md +209 -0
- package/custom/rules/javascript/nestjs-examples.md +41 -0
- package/custom/rules/javascript/nestjs-rules.md +42 -0
- package/custom/rules/javascript/nodejs-express-examples.md +35 -0
- package/custom/rules/javascript/nodejs-express-rules.md +49 -0
- package/custom/rules/javascript/reactjs-examples.md +380 -0
- package/custom/rules/javascript/reactjs-rules.md +173 -0
- package/custom/rules/php/php-examples.md +161 -0
- package/custom/rules/php/php-rules.md +127 -0
- package/custom/rules/python/python-django-examples.md +34 -0
- package/custom/rules/python/python-django-rules.md +48 -0
- package/custom/rules/python/python-examples.md +32 -0
- package/custom/rules/python/python-fastapi-examples.md +30 -0
- package/custom/rules/python/python-fastapi-rules.md +35 -0
- package/custom/rules/python/python-ml-examples.md +187 -0
- package/custom/rules/python/python-ml-rules.md +121 -0
- package/custom/rules/python/python-rules.md +58 -0
- package/custom/skills/ba-skills/skill-ba-qna-template-v1.md +4 -4
- package/custom/skills/ba-skills/skill-ba-qna-v1.md +6 -0
- package/custom/skills/create-system-requirement/SKILL.md +52 -16
- package/custom/skills/create-system-requirement/system-requirement-template-v1.md +128 -0
- package/custom/skills/impact-analysis/SKILL.md +106 -106
- package/custom/skills/ingest-data/SKILL.md +53 -5
- package/custom/skills/report-customer/SKILL.md +99 -99
- package/custom/templates/nestjs.md +5 -72
- package/custom/templates/nodejs-express.md +5 -73
- package/custom/templates/php-plain.md +5 -261
- package/custom/templates/php.md +5 -261
- package/custom/templates/python-django.md +5 -71
- package/custom/templates/python-fastapi.md +5 -54
- package/custom/templates/python-ml.md +1 -269
- package/custom/templates/python.md +5 -79
- package/custom/templates/reactjs.md +5 -492
- package/custom/templates/shared/create-testcase-workflow.md +30 -2
- package/custom/templates/shared/gate-workflow.md +5 -3
- package/custom/templates/shared/ml-gate-workflow.md +1 -0
- package/custom/templates/spring-boot.md +5 -224
- package/docs/common/CHANGELOG.md +20 -10
- package/package.json +1 -1
- package/scripts/init.js +143 -40
- package/scripts/link-resolver.js +60 -24
- package/scripts/ticket-writer.js +72 -3
package/scripts/link-resolver.js
CHANGED
|
@@ -197,24 +197,41 @@ async function fetchBacklogPriorities(domain, apiKey) {
|
|
|
197
197
|
return httpsGet(`https://${domain}/api/v2/priorities?apiKey=${apiKey}`);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Fetches a single Backlog issue — used to validate a PM-supplied "ticket cha có sẵn" before
|
|
202
|
+
* using it as `parentIssueId` for a batch of child issues (PM Workflow_v1.0.md Vấn đề 5, ticket cha).
|
|
203
|
+
* Returns the internal numeric `id` (needed for `parentIssueId`, which is NOT the issueKey).
|
|
204
|
+
*/
|
|
205
|
+
async function getBacklogIssue(domain, apiKey, issueIdOrKey) {
|
|
206
|
+
const issue = await httpsGet(`https://${domain}/api/v2/issues/${issueIdOrKey}?apiKey=${apiKey}`);
|
|
207
|
+
return { id: issue.id, issueKey: issue.issueKey, projectId: issue.projectId, summary: issue.summary || '' };
|
|
208
|
+
}
|
|
209
|
+
|
|
200
210
|
/**
|
|
201
211
|
* Creates a Backlog issue. Requires an apiKey with WRITE permission — distinct from the
|
|
202
212
|
* read-only key used elsewhere in this file for `ak fetch-links`/`ak use`.
|
|
203
213
|
* `projectId`, `summary`, `issueTypeId`, `priorityId` are mandatory per Backlog's API.
|
|
214
|
+
*
|
|
215
|
+
* `parentIssueId` (optional) — Backlog's internal numeric issue `id` (not the `issueKey`) to
|
|
216
|
+
* link this as a child issue. ⚠️ NOT verified against a live space: requires the target project
|
|
217
|
+
* to have issue hierarchy ("Subtasking") enabled — if it isn't, Backlog rejects the request and
|
|
218
|
+
* the caller should surface the raw error rather than assume the link succeeded.
|
|
204
219
|
*/
|
|
205
|
-
async function createBacklogIssue(domain, apiKeyWrite, { projectId, summary, description, issueTypeId, priorityId }) {
|
|
220
|
+
async function createBacklogIssue(domain, apiKeyWrite, { projectId, summary, description, issueTypeId, priorityId, parentIssueId }) {
|
|
221
|
+
const payload = {
|
|
222
|
+
projectId: String(projectId),
|
|
223
|
+
summary,
|
|
224
|
+
issueTypeId: String(issueTypeId),
|
|
225
|
+
priorityId: String(priorityId),
|
|
226
|
+
description: description || '',
|
|
227
|
+
};
|
|
228
|
+
if (parentIssueId) payload.parentIssueId = String(parentIssueId);
|
|
206
229
|
const issue = await httpsPost(
|
|
207
230
|
`https://${domain}/api/v2/issues?apiKey=${apiKeyWrite}`,
|
|
208
|
-
|
|
209
|
-
projectId: String(projectId),
|
|
210
|
-
summary,
|
|
211
|
-
issueTypeId: String(issueTypeId),
|
|
212
|
-
priorityId: String(priorityId),
|
|
213
|
-
description: description || '',
|
|
214
|
-
},
|
|
231
|
+
payload,
|
|
215
232
|
{ bodyType: 'form' }
|
|
216
233
|
);
|
|
217
|
-
return { ticketId: issue.issueKey, url: `https://${domain}/view/${issue.issueKey}`, raw: issue };
|
|
234
|
+
return { ticketId: issue.issueKey, internalId: issue.id, url: `https://${domain}/view/${issue.issueKey}`, raw: issue };
|
|
218
235
|
}
|
|
219
236
|
|
|
220
237
|
function jiraAuth(email, token) {
|
|
@@ -262,25 +279,44 @@ async function fetchJiraComment(domain, email, token, ticketId, commentId) {
|
|
|
262
279
|
|
|
263
280
|
// ── Jira WRITE — issue creation (PM Workflow_v1.0.md "Vấn đề 5") ────────────
|
|
264
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Fetches a single Jira issue — used to validate a PM-supplied "ticket cha có sẵn" before
|
|
284
|
+
* using its key as `parent` for a batch of child issues.
|
|
285
|
+
*/
|
|
286
|
+
async function getJiraIssue(domain, email, token, ticketId) {
|
|
287
|
+
const issue = await httpsGet(
|
|
288
|
+
`https://${domain}/rest/api/3/issue/${ticketId}`,
|
|
289
|
+
{ Authorization: jiraAuth(email, token), Accept: 'application/json' }
|
|
290
|
+
);
|
|
291
|
+
const fields = issue.fields || {};
|
|
292
|
+
return { key: issue.key, projectKey: fields.project ? fields.project.key : null, summary: fields.summary || '' };
|
|
293
|
+
}
|
|
294
|
+
|
|
265
295
|
/**
|
|
266
296
|
* Creates a Jira issue. `apiTokenWrite`/`emailWrite` are distinct from the read-only
|
|
267
297
|
* credentials used elsewhere in this file — same "separate WRITE key" policy as Backlog.
|
|
298
|
+
*
|
|
299
|
+
* `parentKey` (optional) — Jira issue key to link this as a child via `fields.parent`.
|
|
300
|
+
* ⚠️ NOT verified against a live instance: this field works directly for team-managed
|
|
301
|
+
* ("next-gen") projects; company-managed ("classic") projects may require the child's
|
|
302
|
+
* `issuetype` to be a dedicated `Subtask` type instead — if Jira rejects the request,
|
|
303
|
+
* surface the raw error rather than assume the link succeeded.
|
|
268
304
|
*/
|
|
269
|
-
async function createJiraIssue(domain, emailWrite, apiTokenWrite, { projectKey, summary, description, issueTypeName }) {
|
|
305
|
+
async function createJiraIssue(domain, emailWrite, apiTokenWrite, { projectKey, summary, description, issueTypeName, parentKey }) {
|
|
306
|
+
const fields = {
|
|
307
|
+
project: { key: projectKey },
|
|
308
|
+
summary,
|
|
309
|
+
issuetype: { name: issueTypeName || 'Task' },
|
|
310
|
+
description: {
|
|
311
|
+
type: 'doc',
|
|
312
|
+
version: 1,
|
|
313
|
+
content: [{ type: 'paragraph', content: [{ type: 'text', text: description || '' }] }],
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
if (parentKey) fields.parent = { key: parentKey };
|
|
270
317
|
const issue = await httpsPost(
|
|
271
318
|
`https://${domain}/rest/api/3/issue`,
|
|
272
|
-
{
|
|
273
|
-
fields: {
|
|
274
|
-
project: { key: projectKey },
|
|
275
|
-
summary,
|
|
276
|
-
issuetype: { name: issueTypeName || 'Task' },
|
|
277
|
-
description: {
|
|
278
|
-
type: 'doc',
|
|
279
|
-
version: 1,
|
|
280
|
-
content: [{ type: 'paragraph', content: [{ type: 'text', text: description || '' }] }],
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
},
|
|
319
|
+
{ fields },
|
|
284
320
|
{ headers: { Authorization: jiraAuth(emailWrite, apiTokenWrite), Accept: 'application/json' }, bodyType: 'json' }
|
|
285
321
|
);
|
|
286
322
|
return { ticketId: issue.key, url: `https://${domain}/browse/${issue.key}`, raw: issue };
|
|
@@ -365,6 +401,6 @@ module.exports = {
|
|
|
365
401
|
fetchBacklogTicket, fetchBacklogComment,
|
|
366
402
|
fetchBacklogDocument, fetchBacklogDocumentComment, fetchBacklogWiki,
|
|
367
403
|
fetchJiraTicket, fetchJiraComment,
|
|
368
|
-
fetchBacklogIssueTypes, fetchBacklogPriorities, createBacklogIssue,
|
|
369
|
-
createJiraIssue,
|
|
404
|
+
fetchBacklogIssueTypes, fetchBacklogPriorities, createBacklogIssue, getBacklogIssue,
|
|
405
|
+
createJiraIssue, getJiraIssue,
|
|
370
406
|
};
|
package/scripts/ticket-writer.js
CHANGED
|
@@ -24,7 +24,9 @@ const {
|
|
|
24
24
|
fetchBacklogIssueTypes,
|
|
25
25
|
fetchBacklogPriorities,
|
|
26
26
|
createBacklogIssue,
|
|
27
|
+
getBacklogIssue,
|
|
27
28
|
createJiraIssue,
|
|
29
|
+
getJiraIssue,
|
|
28
30
|
} = linkResolver;
|
|
29
31
|
|
|
30
32
|
// Only these keys can be written via `ak credentials set` — dedicated adapter credentials
|
|
@@ -104,13 +106,23 @@ async function pickBacklogDefaults(domain, apiKeyRead, projectId) {
|
|
|
104
106
|
* "target": "backlog" | "jira",
|
|
105
107
|
* "projectId": "12345", // optional — Backlog only, overrides BACKLOG_DEFAULT_PROJECT_ID
|
|
106
108
|
* "projectKey": "PROJ", // optional — Jira only, overrides JIRA_DEFAULT_PROJECT_KEY
|
|
109
|
+
* "parentTicket": { // optional — ingest-data SKILL.md § 5.6b "Ticket cha"
|
|
110
|
+
* "mode": "create" | "existing" | "none",
|
|
111
|
+
* "title": "...", // required when mode === "create"
|
|
112
|
+
* "description": "...", // optional when mode === "create"
|
|
113
|
+
* "existingId": "..." // required when mode === "existing" (Backlog key/id or Jira key)
|
|
114
|
+
* },
|
|
107
115
|
* "tasks": [
|
|
108
|
-
* { "type": "spec|coding|test|other", "title": "...", "description": "..." }
|
|
116
|
+
* { "type": "spec|system-requirement|impact-analysis|coding|test|execute-test|other", "title": "...", "description": "..." }
|
|
109
117
|
* ]
|
|
110
118
|
* }
|
|
111
119
|
*
|
|
112
120
|
* `description` is expected to already be fully composed by the AI per the ticket template in
|
|
113
121
|
* PM Workflow_v1.0.md §6.1 (Nội dung task + Nguồn tham chiếu) — this command posts it as-is.
|
|
122
|
+
*
|
|
123
|
+
* When `parentTicket.mode` is "create" or "existing", every task in `tasks` is created as a
|
|
124
|
+
* child of that parent (Backlog `parentIssueId` / Jira `parent`) — see link-resolver.js for the
|
|
125
|
+
* caveat that this depends on the target project supporting issue hierarchy/subtasks.
|
|
114
126
|
*/
|
|
115
127
|
async function createTicketsCommand(inputFile, options = {}) {
|
|
116
128
|
const emit = (obj) => {
|
|
@@ -119,6 +131,7 @@ async function createTicketsCommand(inputFile, options = {}) {
|
|
|
119
131
|
console.log(chalk.red(`✗ ${obj.message || obj.error}`));
|
|
120
132
|
} else {
|
|
121
133
|
console.log(chalk.green(`✓ ${obj.results.filter(r => r.ok).length}/${obj.results.length} ticket(s) created on ${obj.target}.`));
|
|
134
|
+
if (obj.parent) console.log(` ${chalk.cyan('↳ parent:')} ${obj.parent.ticketId} ${chalk.gray(obj.parent.url)}`);
|
|
122
135
|
for (const r of obj.results) {
|
|
123
136
|
console.log(r.ok ? ` ${chalk.green('✓')} ${r.ticketId} ${chalk.gray(r.title)}` : ` ${chalk.red('✗')} ${chalk.gray(r.title)} — ${r.error}`);
|
|
124
137
|
}
|
|
@@ -173,6 +186,34 @@ async function createTicketsCommand(inputFile, options = {}) {
|
|
|
173
186
|
return;
|
|
174
187
|
}
|
|
175
188
|
|
|
189
|
+
const parentTicket = input.parentTicket || { mode: 'none' };
|
|
190
|
+
let parentRef = null;
|
|
191
|
+
if (parentTicket.mode === 'create') {
|
|
192
|
+
try {
|
|
193
|
+
const createdParent = await createBacklogIssue(write.domain, write.apiKeyWrite, {
|
|
194
|
+
projectId,
|
|
195
|
+
summary: parentTicket.title,
|
|
196
|
+
description: parentTicket.description || '',
|
|
197
|
+
issueTypeId: defaults.defaultIssueTypeId,
|
|
198
|
+
priorityId: defaults.defaultPriorityId,
|
|
199
|
+
});
|
|
200
|
+
parentRef = { ticketId: createdParent.ticketId, internalId: createdParent.internalId, url: createdParent.url };
|
|
201
|
+
} catch (err) {
|
|
202
|
+
emit({ ok: false, error: 'parent-create-failed', message: `Không tạo được ticket cha trên Backlog: ${err.message}` });
|
|
203
|
+
process.exitCode = 1;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
} else if (parentTicket.mode === 'existing') {
|
|
207
|
+
try {
|
|
208
|
+
const existing = await getBacklogIssue(write.domain, write.apiKeyRead, parentTicket.existingId);
|
|
209
|
+
parentRef = { ticketId: existing.issueKey, internalId: existing.id, url: `https://${write.domain}/view/${existing.issueKey}` };
|
|
210
|
+
} catch (err) {
|
|
211
|
+
emit({ ok: false, error: 'parent-not-found', message: `Không tìm thấy ticket cha "${parentTicket.existingId}" trên Backlog: ${err.message}` });
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
176
217
|
const results = [];
|
|
177
218
|
for (const task of tasks) {
|
|
178
219
|
try {
|
|
@@ -182,13 +223,14 @@ async function createTicketsCommand(inputFile, options = {}) {
|
|
|
182
223
|
description: task.description,
|
|
183
224
|
issueTypeId: task.issueTypeId || defaults.defaultIssueTypeId,
|
|
184
225
|
priorityId: task.priorityId || defaults.defaultPriorityId,
|
|
226
|
+
parentIssueId: parentRef ? parentRef.internalId : undefined,
|
|
185
227
|
});
|
|
186
228
|
results.push({ type: task.type, title: task.title, ok: true, ticketId: created.ticketId, url: created.url });
|
|
187
229
|
} catch (err) {
|
|
188
230
|
results.push({ type: task.type, title: task.title, ok: false, error: err.message });
|
|
189
231
|
}
|
|
190
232
|
}
|
|
191
|
-
emit({ ok: true, target, projectId, results });
|
|
233
|
+
emit({ ok: true, target, projectId, parent: parentRef, results });
|
|
192
234
|
return;
|
|
193
235
|
}
|
|
194
236
|
|
|
@@ -206,6 +248,32 @@ async function createTicketsCommand(inputFile, options = {}) {
|
|
|
206
248
|
return;
|
|
207
249
|
}
|
|
208
250
|
|
|
251
|
+
const parentTicket = input.parentTicket || { mode: 'none' };
|
|
252
|
+
let parentRef = null;
|
|
253
|
+
if (parentTicket.mode === 'create') {
|
|
254
|
+
try {
|
|
255
|
+
const createdParent = await createJiraIssue(write.domain, write.emailWrite, write.apiTokenWrite, {
|
|
256
|
+
projectKey,
|
|
257
|
+
summary: parentTicket.title,
|
|
258
|
+
description: parentTicket.description || '',
|
|
259
|
+
});
|
|
260
|
+
parentRef = { ticketId: createdParent.ticketId, url: createdParent.url };
|
|
261
|
+
} catch (err) {
|
|
262
|
+
emit({ ok: false, error: 'parent-create-failed', message: `Không tạo được ticket cha trên Jira: ${err.message}` });
|
|
263
|
+
process.exitCode = 1;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
} else if (parentTicket.mode === 'existing') {
|
|
267
|
+
try {
|
|
268
|
+
const existing = await getJiraIssue(write.domain, write.emailWrite, write.apiTokenWrite, parentTicket.existingId);
|
|
269
|
+
parentRef = { ticketId: existing.key, url: `https://${write.domain}/browse/${existing.key}` };
|
|
270
|
+
} catch (err) {
|
|
271
|
+
emit({ ok: false, error: 'parent-not-found', message: `Không tìm thấy ticket cha "${parentTicket.existingId}" trên Jira: ${err.message}` });
|
|
272
|
+
process.exitCode = 1;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
209
277
|
const results = [];
|
|
210
278
|
for (const task of tasks) {
|
|
211
279
|
try {
|
|
@@ -214,13 +282,14 @@ async function createTicketsCommand(inputFile, options = {}) {
|
|
|
214
282
|
summary: task.title,
|
|
215
283
|
description: task.description,
|
|
216
284
|
issueTypeName: task.issueType || 'Task',
|
|
285
|
+
parentKey: parentRef ? parentRef.ticketId : undefined,
|
|
217
286
|
});
|
|
218
287
|
results.push({ type: task.type, title: task.title, ok: true, ticketId: created.ticketId, url: created.url });
|
|
219
288
|
} catch (err) {
|
|
220
289
|
results.push({ type: task.type, title: task.title, ok: false, error: err.message });
|
|
221
290
|
}
|
|
222
291
|
}
|
|
223
|
-
emit({ ok: true, target, projectKey, results });
|
|
292
|
+
emit({ ok: true, target, projectKey, parent: parentRef, results });
|
|
224
293
|
}
|
|
225
294
|
|
|
226
295
|
/**
|