@relipa/ai-flow-kit 0.2.0-beta.1 → 0.2.0

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.
@@ -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
  /**