ralphctl 0.1.0 → 0.1.2

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 (130) hide show
  1. package/README.md +58 -24
  2. package/dist/add-HGJCLWED.mjs +14 -0
  3. package/dist/add-MRGCS3US.mjs +14 -0
  4. package/dist/chunk-6PYTKGB5.mjs +316 -0
  5. package/dist/chunk-7TG3EAQ2.mjs +20 -0
  6. package/dist/chunk-EKMZZRWI.mjs +521 -0
  7. package/dist/chunk-JON4GCLR.mjs +59 -0
  8. package/dist/chunk-LOR7QBXX.mjs +3683 -0
  9. package/dist/chunk-MNMQC36F.mjs +556 -0
  10. package/dist/chunk-MRKOFVTM.mjs +537 -0
  11. package/dist/chunk-NTWO2LXB.mjs +52 -0
  12. package/dist/chunk-QBXHAXHI.mjs +562 -0
  13. package/dist/chunk-WGHJI3OI.mjs +214 -0
  14. package/dist/cli.mjs +4245 -0
  15. package/dist/create-MG7E7PLQ.mjs +10 -0
  16. package/dist/handle-UG5M2OON.mjs +22 -0
  17. package/dist/multiline-OHSNFCRG.mjs +40 -0
  18. package/dist/project-NT3L4FTB.mjs +28 -0
  19. package/dist/resolver-WSFWKACM.mjs +153 -0
  20. package/dist/sprint-4VHDLGFN.mjs +37 -0
  21. package/dist/wizard-LRELAN2J.mjs +196 -0
  22. package/package.json +19 -28
  23. package/CHANGELOG.md +0 -94
  24. package/bin/ralphctl +0 -13
  25. package/src/ai/executor.ts +0 -973
  26. package/src/ai/lifecycle.ts +0 -45
  27. package/src/ai/parser.ts +0 -40
  28. package/src/ai/permissions.ts +0 -207
  29. package/src/ai/process-manager.ts +0 -248
  30. package/src/ai/prompts/index.ts +0 -89
  31. package/src/ai/rate-limiter.ts +0 -89
  32. package/src/ai/runner.ts +0 -478
  33. package/src/ai/session.ts +0 -319
  34. package/src/ai/task-context.ts +0 -270
  35. package/src/cli-metadata.ts +0 -7
  36. package/src/cli.ts +0 -65
  37. package/src/commands/completion/index.ts +0 -33
  38. package/src/commands/config/config.ts +0 -58
  39. package/src/commands/config/index.ts +0 -33
  40. package/src/commands/dashboard/dashboard.ts +0 -5
  41. package/src/commands/dashboard/index.ts +0 -6
  42. package/src/commands/doctor/doctor.ts +0 -271
  43. package/src/commands/doctor/index.ts +0 -25
  44. package/src/commands/progress/index.ts +0 -25
  45. package/src/commands/progress/log.ts +0 -64
  46. package/src/commands/progress/show.ts +0 -14
  47. package/src/commands/project/add.ts +0 -336
  48. package/src/commands/project/index.ts +0 -104
  49. package/src/commands/project/list.ts +0 -31
  50. package/src/commands/project/remove.ts +0 -43
  51. package/src/commands/project/repo.ts +0 -118
  52. package/src/commands/project/show.ts +0 -49
  53. package/src/commands/sprint/close.ts +0 -180
  54. package/src/commands/sprint/context.ts +0 -109
  55. package/src/commands/sprint/create.ts +0 -60
  56. package/src/commands/sprint/current.ts +0 -75
  57. package/src/commands/sprint/delete.ts +0 -72
  58. package/src/commands/sprint/health.ts +0 -229
  59. package/src/commands/sprint/ideate.ts +0 -496
  60. package/src/commands/sprint/index.ts +0 -226
  61. package/src/commands/sprint/list.ts +0 -86
  62. package/src/commands/sprint/plan-utils.ts +0 -207
  63. package/src/commands/sprint/plan.ts +0 -549
  64. package/src/commands/sprint/refine.ts +0 -359
  65. package/src/commands/sprint/requirements.ts +0 -58
  66. package/src/commands/sprint/show.ts +0 -140
  67. package/src/commands/sprint/start.ts +0 -119
  68. package/src/commands/sprint/switch.ts +0 -20
  69. package/src/commands/task/add.ts +0 -316
  70. package/src/commands/task/import.ts +0 -150
  71. package/src/commands/task/index.ts +0 -123
  72. package/src/commands/task/list.ts +0 -145
  73. package/src/commands/task/next.ts +0 -45
  74. package/src/commands/task/remove.ts +0 -47
  75. package/src/commands/task/reorder.ts +0 -45
  76. package/src/commands/task/show.ts +0 -111
  77. package/src/commands/task/status.ts +0 -99
  78. package/src/commands/ticket/add.ts +0 -265
  79. package/src/commands/ticket/edit.ts +0 -166
  80. package/src/commands/ticket/index.ts +0 -114
  81. package/src/commands/ticket/list.ts +0 -128
  82. package/src/commands/ticket/refine-utils.ts +0 -89
  83. package/src/commands/ticket/refine.ts +0 -268
  84. package/src/commands/ticket/remove.ts +0 -48
  85. package/src/commands/ticket/show.ts +0 -74
  86. package/src/completion/handle.ts +0 -30
  87. package/src/completion/resolver.ts +0 -241
  88. package/src/interactive/dashboard.ts +0 -268
  89. package/src/interactive/escapable.ts +0 -81
  90. package/src/interactive/file-browser.ts +0 -153
  91. package/src/interactive/index.ts +0 -429
  92. package/src/interactive/menu.ts +0 -403
  93. package/src/interactive/selectors.ts +0 -273
  94. package/src/interactive/wizard.ts +0 -221
  95. package/src/providers/claude.ts +0 -53
  96. package/src/providers/copilot.ts +0 -86
  97. package/src/providers/index.ts +0 -43
  98. package/src/providers/types.ts +0 -85
  99. package/src/schemas/index.ts +0 -130
  100. package/src/store/config.ts +0 -74
  101. package/src/store/progress.ts +0 -230
  102. package/src/store/project.ts +0 -276
  103. package/src/store/sprint.ts +0 -229
  104. package/src/store/task.ts +0 -443
  105. package/src/store/ticket.ts +0 -178
  106. package/src/theme/index.ts +0 -215
  107. package/src/theme/ui.ts +0 -872
  108. package/src/utils/detect-scripts.ts +0 -247
  109. package/src/utils/editor-input.ts +0 -41
  110. package/src/utils/editor.ts +0 -37
  111. package/src/utils/exit-codes.ts +0 -27
  112. package/src/utils/file-lock.ts +0 -135
  113. package/src/utils/git.ts +0 -185
  114. package/src/utils/ids.ts +0 -37
  115. package/src/utils/issue-fetch.ts +0 -244
  116. package/src/utils/json-extract.ts +0 -62
  117. package/src/utils/multiline.ts +0 -61
  118. package/src/utils/path-selector.ts +0 -236
  119. package/src/utils/paths.ts +0 -108
  120. package/src/utils/provider.ts +0 -34
  121. package/src/utils/requirements-export.ts +0 -63
  122. package/src/utils/storage.ts +0 -107
  123. package/tsconfig.json +0 -25
  124. /package/{src/ai → dist}/prompts/ideate-auto.md +0 -0
  125. /package/{src/ai → dist}/prompts/ideate.md +0 -0
  126. /package/{src/ai → dist}/prompts/plan-auto.md +0 -0
  127. /package/{src/ai → dist}/prompts/plan-common.md +0 -0
  128. /package/{src/ai → dist}/prompts/plan-interactive.md +0 -0
  129. /package/{src/ai → dist}/prompts/task-execution.md +0 -0
  130. /package/{src/ai → dist}/prompts/ticket-refine.md +0 -0
@@ -1,496 +0,0 @@
1
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { input, select } from '@inquirer/prompts';
4
- import { editorInput } from '@src/utils/editor-input.ts';
5
- import { error, muted } from '@src/theme/index.ts';
6
- import {
7
- createSpinner,
8
- field,
9
- icons,
10
- log,
11
- printHeader,
12
- showError,
13
- showInfo,
14
- showNextStep,
15
- showSuccess,
16
- showTip,
17
- showWarning,
18
- terminalBell,
19
- } from '@src/theme/ui.ts';
20
- import { assertSprintStatus, getSprint, resolveSprintId, saveSprint } from '@src/store/sprint.ts';
21
- import { addTicket } from '@src/store/ticket.ts';
22
- import { getTasks, validateImportTasks } from '@src/store/task.ts';
23
- import { getProject, listProjects } from '@src/store/project.ts';
24
- import { fileExists } from '@src/utils/storage.ts';
25
- import { getIdeateDir } from '@src/utils/paths.ts';
26
- import { buildIdeateAutoPrompt, buildIdeatePrompt } from '@src/ai/prompts/index.ts';
27
- import { spawnHeadless, spawnInteractive } from '@src/ai/session.ts';
28
- import { IdeateOutputSchema, type Repository } from '@src/schemas/index.ts';
29
- import { selectProjectPaths } from '@src/interactive/selectors.ts';
30
- import { extractJsonObject } from '@src/utils/json-extract.ts';
31
- import { resolveProvider, providerDisplayName } from '@src/utils/provider.ts';
32
- import { getActiveProvider } from '@src/providers/index.ts';
33
- import {
34
- getTaskImportSchema,
35
- importTasks,
36
- parsePlanningBlocked,
37
- parseTasksJson,
38
- renderParsedTasksTable,
39
- } from './plan-utils.ts';
40
-
41
- interface IdeateOptions {
42
- auto: boolean;
43
- allPaths: boolean;
44
- project?: string;
45
- }
46
-
47
- function parseArgs(args: string[]): { sprintId?: string; options: IdeateOptions } {
48
- const options: IdeateOptions = {
49
- auto: false,
50
- allPaths: false,
51
- };
52
- let sprintId: string | undefined;
53
-
54
- for (let i = 0; i < args.length; i++) {
55
- const arg = args[i];
56
- const nextArg = args[i + 1];
57
-
58
- if (arg === '--auto') {
59
- options.auto = true;
60
- } else if (arg === '--all-paths') {
61
- options.allPaths = true;
62
- } else if (arg === '--project') {
63
- options.project = nextArg;
64
- i++;
65
- } else if (!arg?.startsWith('-')) {
66
- sprintId = arg;
67
- }
68
- }
69
-
70
- return { sprintId, options };
71
- }
72
-
73
- async function invokeAiInteractive(prompt: string, repoPaths: string[], ideateDir: string): Promise<void> {
74
- // Write full context to the ideation directory for reference
75
- const contextFile = join(ideateDir, 'ideate-context.md');
76
- await writeFile(contextFile, prompt, 'utf-8');
77
-
78
- const provider = await getActiveProvider();
79
-
80
- // Build initial prompt that tells the AI to start the two-phase process
81
- const startPrompt = `I have a quick idea I want to implement. The full context is in ideate-context.md. Please read that file and help me refine the idea into requirements and then plan implementation tasks.`;
82
-
83
- // Build args - pass all repo paths in a single --add-dir
84
- const args: string[] = ['--add-dir', ...repoPaths];
85
-
86
- const result = spawnInteractive(
87
- startPrompt,
88
- {
89
- cwd: ideateDir,
90
- args,
91
- env: provider.getSpawnEnv(),
92
- },
93
- provider
94
- );
95
-
96
- if (result.error) {
97
- throw new Error(result.error);
98
- }
99
- }
100
-
101
- async function invokeAiAuto(prompt: string, repoPaths: string[], ideateDir: string): Promise<string> {
102
- const provider = await getActiveProvider();
103
-
104
- // Build args - all repo paths via --add-dir (neutral CWD in ideation dir)
105
- const args: string[] = ['--permission-mode', 'plan', '--print'];
106
- for (const path of repoPaths) {
107
- args.push('--add-dir', path);
108
- }
109
- args.push('-p', prompt);
110
-
111
- return spawnHeadless(
112
- {
113
- cwd: ideateDir,
114
- args,
115
- env: provider.getSpawnEnv(),
116
- },
117
- provider
118
- );
119
- }
120
-
121
- function parseIdeateOutput(output: string): { requirements: string; tasks: unknown[] } {
122
- // Try to extract a balanced JSON object from the output
123
- const jsonStr = extractJsonObject(output);
124
-
125
- let parsed: unknown;
126
- try {
127
- parsed = JSON.parse(jsonStr);
128
- } catch (err) {
129
- throw new Error(`Invalid JSON: ${err instanceof Error ? err.message : 'parse error'}`, { cause: err });
130
- }
131
-
132
- // Validate against schema
133
- const result = IdeateOutputSchema.safeParse(parsed);
134
- if (!result.success) {
135
- const issues = result.error.issues
136
- .map((issue) => {
137
- const path = issue.path.length > 0 ? `[${issue.path.join('.')}]` : '';
138
- return ` ${path}: ${issue.message}`;
139
- })
140
- .join('\n');
141
- throw new Error(`Invalid ideate output format:\n${issues}`);
142
- }
143
-
144
- return result.data;
145
- }
146
-
147
- export async function sprintIdeateCommand(args: string[]): Promise<void> {
148
- const { sprintId, options } = parseArgs(args);
149
-
150
- let id: string;
151
- try {
152
- id = await resolveSprintId(sprintId);
153
- } catch {
154
- showWarning('No sprint specified and no current sprint set.');
155
- showNextStep('ralphctl sprint create', 'create a new sprint');
156
- log.newline();
157
- return;
158
- }
159
-
160
- const sprint = await getSprint(id);
161
-
162
- // Check sprint status - must be draft to ideate
163
- try {
164
- assertSprintStatus(sprint, ['draft'], 'ideate');
165
- } catch (err) {
166
- if (err instanceof Error) {
167
- showError(err.message);
168
- log.newline();
169
- }
170
- return;
171
- }
172
-
173
- // Check if projects exist
174
- const projects = await listProjects();
175
- if (projects.length === 0) {
176
- showWarning('No projects configured.');
177
- showNextStep('ralphctl project add', 'add a project first');
178
- log.newline();
179
- return;
180
- }
181
-
182
- printHeader('Quick Ideation', icons.ticket);
183
- console.log(field('Sprint', sprint.name));
184
- console.log(field('ID', sprint.id));
185
- console.log(field('Mode', options.auto ? 'Auto (headless)' : 'Interactive'));
186
- log.newline();
187
-
188
- // Prompt for project if not specified
189
- let projectName = options.project;
190
- if (!projectName) {
191
- if (projects.length === 1) {
192
- projectName = projects[0]?.name;
193
- console.log(field('Project', projectName ?? '(unknown)'));
194
- } else {
195
- // Interactive project selection
196
- projectName = await select({
197
- message: 'Select project:',
198
- choices: projects.map((p) => ({ name: p.displayName, value: p.name })),
199
- });
200
- }
201
- }
202
-
203
- if (!projectName) {
204
- showError('No project selected.');
205
- log.newline();
206
- return;
207
- }
208
-
209
- // Validate project exists
210
- let project: Awaited<ReturnType<typeof getProject>>;
211
- try {
212
- project = await getProject(projectName);
213
- } catch {
214
- showError(`Project '${projectName}' not found.`);
215
- log.newline();
216
- return;
217
- }
218
-
219
- // Prompt for idea title and description
220
- const ideaTitle = await input({
221
- message: 'Idea title (short summary):',
222
- validate: (value) => (value.trim().length > 0 ? true : 'Title is required'),
223
- });
224
-
225
- const ideaDescription = await editorInput({
226
- message: 'Idea description (what you want to build):',
227
- });
228
-
229
- if (!ideaDescription.trim()) {
230
- showError('Description is required.');
231
- log.newline();
232
- return;
233
- }
234
-
235
- log.newline();
236
- showInfo('Creating ticket...');
237
-
238
- // Auto-create ticket with pending status
239
- const ticket = await addTicket(
240
- {
241
- title: ideaTitle,
242
- description: ideaDescription,
243
- projectName,
244
- },
245
- id
246
- );
247
-
248
- console.log(field('Ticket ID', ticket.id));
249
- log.newline();
250
-
251
- // Resolve AI provider early for display names
252
- const providerName = providerDisplayName(await resolveProvider());
253
-
254
- // Select which paths the AI should explore (same as plan.ts)
255
- let selectedPaths: string[];
256
- const totalRepos = project.repositories.length;
257
-
258
- if (options.allPaths) {
259
- // --all-paths: use all repos
260
- selectedPaths = project.repositories.map((r) => r.path);
261
- } else if (options.auto) {
262
- // --auto: use first repo per project
263
- selectedPaths = project.repositories.slice(0, 1).map((r) => r.path);
264
- } else if (totalRepos === 1) {
265
- // Only one repo - no selection needed
266
- selectedPaths = [project.repositories[0]?.path ?? ''];
267
- } else {
268
- // Multiple repos available - show checkbox
269
- const reposByProject = new Map<string, Repository[]>();
270
- reposByProject.set(projectName, project.repositories);
271
-
272
- selectedPaths = await selectProjectPaths(reposByProject, 'Select paths to explore:');
273
- }
274
-
275
- // Save selected paths to ticket.affectedRepositories
276
- ticket.affectedRepositories = selectedPaths;
277
- await saveSprint(sprint);
278
-
279
- if (selectedPaths.length > 1) {
280
- console.log(muted(`Paths: ${selectedPaths.join(', ')}`));
281
- } else {
282
- console.log(muted(`Path: ${selectedPaths[0] ?? process.cwd()}`));
283
- }
284
-
285
- // Format repositories for prompt
286
- const repositoriesText = selectedPaths.map((path) => `- ${path}`).join('\n');
287
-
288
- // Load schema
289
- const schema = await getTaskImportSchema();
290
-
291
- // Create ideation directory
292
- const ideateDir = getIdeateDir(id, ticket.id);
293
- await mkdir(ideateDir, { recursive: true });
294
-
295
- if (options.auto) {
296
- // Headless mode - AI generates autonomously
297
- const prompt = buildIdeateAutoPrompt(ideaTitle, ideaDescription, projectName, repositoriesText, schema);
298
- const spinner = createSpinner(`${providerName} is refining idea and planning tasks...`);
299
- spinner.start();
300
-
301
- let output: string;
302
- try {
303
- output = await invokeAiAuto(prompt, selectedPaths, ideateDir);
304
- spinner.succeed(`${providerName} finished`);
305
- } catch (err) {
306
- spinner.fail(`${providerName} session failed`);
307
- if (err instanceof Error) {
308
- showError(`Failed to invoke ${providerName}: ${err.message}`);
309
- showTip(`Make sure the ${providerName.toLowerCase()} CLI is installed and configured.`);
310
- log.newline();
311
- }
312
- return;
313
- }
314
-
315
- // Check for planning-blocked signal before parsing JSON
316
- const blockedReason = parsePlanningBlocked(output);
317
- if (blockedReason) {
318
- showWarning(`Planning blocked: ${blockedReason}`);
319
- log.newline();
320
- return;
321
- }
322
-
323
- log.dim('Parsing response...');
324
- let ideateOutput: { requirements: string; tasks: unknown[] };
325
- try {
326
- ideateOutput = parseIdeateOutput(output);
327
- } catch (err) {
328
- if (err instanceof Error) {
329
- showError(`Failed to parse ${providerName} output: ${err.message}`);
330
- log.dim('Raw output:');
331
- console.log(output);
332
- log.newline();
333
- }
334
- return;
335
- }
336
-
337
- // Update ticket with requirements
338
- const ticketIdx = sprint.tickets.findIndex((t) => t.id === ticket.id);
339
- const ticketToUpdate = sprint.tickets[ticketIdx];
340
- if (ticketIdx !== -1 && ticketToUpdate) {
341
- ticketToUpdate.requirements = ideateOutput.requirements;
342
- ticketToUpdate.requirementStatus = 'approved';
343
- }
344
- await saveSprint(sprint);
345
-
346
- showSuccess('Requirements approved and saved!');
347
- log.newline();
348
-
349
- // Parse and validate tasks
350
- let parsedTasks: ReturnType<typeof parseTasksJson>;
351
- try {
352
- parsedTasks = parseTasksJson(JSON.stringify(ideateOutput.tasks));
353
- } catch (err) {
354
- if (err instanceof Error) {
355
- showError(`Failed to parse tasks: ${err.message}`);
356
- log.newline();
357
- }
358
- return;
359
- }
360
-
361
- if (parsedTasks.length === 0) {
362
- showWarning('No tasks generated.');
363
- log.newline();
364
- return;
365
- }
366
-
367
- showSuccess(`Generated ${String(parsedTasks.length)} task(s):`);
368
- log.newline();
369
- console.log(renderParsedTasksTable(parsedTasks));
370
- console.log('');
371
-
372
- // Validate before import
373
- const existingTasks = await getTasks(id);
374
- const ticketIds = new Set(sprint.tickets.map((t) => t.id));
375
- const validationErrors = validateImportTasks(parsedTasks, existingTasks, ticketIds);
376
- if (validationErrors.length > 0) {
377
- showError('Validation failed');
378
- for (const err of validationErrors) {
379
- log.item(error(err));
380
- }
381
- log.newline();
382
- return;
383
- }
384
-
385
- showInfo('Importing tasks...');
386
- const imported = await importTasks(parsedTasks, id);
387
- terminalBell();
388
- showSuccess(`Imported ${String(imported)}/${String(parsedTasks.length)} tasks.`);
389
- log.newline();
390
- } else {
391
- // Interactive mode - user iterates with AI
392
- const outputFile = join(ideateDir, 'output.json');
393
- const prompt = buildIdeatePrompt(ideaTitle, ideaDescription, projectName, repositoriesText, outputFile, schema);
394
-
395
- showInfo(`Starting interactive ${providerName} session...`);
396
- console.log(muted(` Exploring: ${selectedPaths.join(', ')}`));
397
- console.log(muted(`\n ${providerName} will guide you through requirements refinement and task planning.`));
398
- console.log(muted(` When done, ask ${providerName} to write the output to: ${outputFile}\n`));
399
-
400
- try {
401
- await invokeAiInteractive(prompt, selectedPaths, ideateDir);
402
- } catch (err) {
403
- if (err instanceof Error) {
404
- showError(`Failed to invoke ${providerName}: ${err.message}`);
405
- showTip(`Make sure the ${providerName.toLowerCase()} CLI is installed and configured.`);
406
- log.newline();
407
- }
408
- return;
409
- }
410
-
411
- // Check if output file was created
412
- console.log('');
413
- if (await fileExists(outputFile)) {
414
- showInfo('Output file found. Processing...');
415
-
416
- let content: string;
417
- try {
418
- content = await readFile(outputFile, 'utf-8');
419
- } catch {
420
- showError(`Failed to read output file: ${outputFile}`);
421
- log.newline();
422
- return;
423
- }
424
-
425
- let ideateOutput: { requirements: string; tasks: unknown[] };
426
- try {
427
- ideateOutput = parseIdeateOutput(content);
428
- } catch (err) {
429
- if (err instanceof Error) {
430
- showError(`Failed to parse output file: ${err.message}`);
431
- log.newline();
432
- }
433
- return;
434
- }
435
-
436
- // Update ticket with requirements
437
- const ticketIdx = sprint.tickets.findIndex((t) => t.id === ticket.id);
438
- const ticketToUpdate = sprint.tickets[ticketIdx];
439
- if (ticketIdx !== -1 && ticketToUpdate) {
440
- ticketToUpdate.requirements = ideateOutput.requirements;
441
- ticketToUpdate.requirementStatus = 'approved';
442
- }
443
- await saveSprint(sprint);
444
-
445
- showSuccess('Requirements approved and saved!');
446
- log.newline();
447
-
448
- // Parse and validate tasks
449
- let parsedTasks: ReturnType<typeof parseTasksJson>;
450
- try {
451
- parsedTasks = parseTasksJson(JSON.stringify(ideateOutput.tasks));
452
- } catch (err) {
453
- if (err instanceof Error) {
454
- showError(`Failed to parse tasks: ${err.message}`);
455
- log.newline();
456
- }
457
- return;
458
- }
459
-
460
- if (parsedTasks.length === 0) {
461
- showWarning('No tasks in file.');
462
- log.newline();
463
- return;
464
- }
465
-
466
- showSuccess(`Found ${String(parsedTasks.length)} task(s):`);
467
- log.newline();
468
- console.log(renderParsedTasksTable(parsedTasks));
469
- console.log('');
470
-
471
- // Validate before import
472
- const existingTasks = await getTasks(id);
473
- const ticketIds = new Set(sprint.tickets.map((t) => t.id));
474
- const validationErrors = validateImportTasks(parsedTasks, existingTasks, ticketIds);
475
- if (validationErrors.length > 0) {
476
- showError('Validation failed');
477
- for (const err of validationErrors) {
478
- log.item(error(err));
479
- }
480
- log.newline();
481
- return;
482
- }
483
-
484
- showInfo('Importing tasks...');
485
- const imported = await importTasks(parsedTasks, id);
486
- terminalBell();
487
- showSuccess(`Imported ${String(imported)}/${String(parsedTasks.length)} tasks.`);
488
- log.newline();
489
- } else {
490
- showWarning('No output file found.');
491
- showTip(`Expected: ${outputFile}`);
492
- showNextStep('ralphctl sprint ideate', 'run ideation again');
493
- log.newline();
494
- }
495
- }
496
- }