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,145 +0,0 @@
1
- import { colors } from '@src/theme/index.ts';
2
- import { listTasks } from '@src/store/task.ts';
3
- import { TaskStatusSchema } from '@src/schemas/index.ts';
4
- import { badge, formatTaskStatus, icons, log, printHeader, renderTable, showEmpty, showError } from '@src/theme/ui.ts';
5
-
6
- interface TaskListFilters {
7
- brief: boolean;
8
- statusFilter?: string;
9
- projectFilter?: string;
10
- ticketFilter?: string;
11
- blockedOnly: boolean;
12
- }
13
-
14
- function parseListArgs(args: string[]): TaskListFilters {
15
- const result: TaskListFilters = {
16
- brief: false,
17
- blockedOnly: false,
18
- };
19
-
20
- for (let i = 0; i < args.length; i++) {
21
- const arg = args[i];
22
- const next = args[i + 1];
23
- if (arg === '-b' || arg === '--brief') result.brief = true;
24
- else if (arg === '--status' && next) {
25
- result.statusFilter = next;
26
- i++;
27
- } else if (arg === '--project' && next) {
28
- result.projectFilter = next;
29
- i++;
30
- } else if (arg === '--ticket' && next) {
31
- result.ticketFilter = next;
32
- i++;
33
- } else if (arg === '--blocked') result.blockedOnly = true;
34
- }
35
- return result;
36
- }
37
-
38
- function buildFilterSummary(filters: TaskListFilters): string {
39
- const parts: string[] = [];
40
- if (filters.statusFilter) parts.push(`status=${filters.statusFilter}`);
41
- if (filters.projectFilter) parts.push(`project=${filters.projectFilter}`);
42
- if (filters.ticketFilter) parts.push(`ticket=${filters.ticketFilter}`);
43
- if (filters.blockedOnly) parts.push('blocked');
44
- return parts.length > 0 ? ` (filtered: ${parts.join(', ')})` : '';
45
- }
46
-
47
- export async function taskListCommand(args: string[] = []): Promise<void> {
48
- const { brief, statusFilter, projectFilter, ticketFilter, blockedOnly } = parseListArgs(args);
49
-
50
- // Validate status filter
51
- if (statusFilter) {
52
- const result = TaskStatusSchema.safeParse(statusFilter);
53
- if (!result.success) {
54
- showError(`Invalid status: "${statusFilter}". Valid values: todo, in_progress, done`);
55
- return;
56
- }
57
- }
58
-
59
- const tasks = await listTasks();
60
-
61
- if (tasks.length === 0) {
62
- showEmpty('tasks', 'Add one with: ralphctl task add');
63
- return;
64
- }
65
-
66
- // Apply filters
67
- let filtered = tasks;
68
- if (statusFilter) filtered = filtered.filter((t) => t.status === statusFilter);
69
- if (projectFilter) filtered = filtered.filter((t) => t.projectPath.includes(projectFilter));
70
- if (ticketFilter) filtered = filtered.filter((t) => t.ticketId === ticketFilter);
71
- if (blockedOnly) filtered = filtered.filter((t) => t.blockedBy.length > 0);
72
-
73
- const filterStr = buildFilterSummary({ brief, statusFilter, projectFilter, ticketFilter, blockedOnly });
74
- const isFiltered = filtered.length !== tasks.length;
75
-
76
- if (filtered.length === 0) {
77
- showEmpty('matching tasks', 'Try adjusting your filters');
78
- return;
79
- }
80
-
81
- if (brief) {
82
- // Brief mode: one line per task
83
- const countLabel = isFiltered ? `${String(filtered.length)} of ${String(tasks.length)}` : String(tasks.length);
84
- console.log(`\n# Tasks (${countLabel})${filterStr}\n`);
85
- for (const task of filtered) {
86
- const ticketRef = task.ticketId ? ` [${task.ticketId}]` : '';
87
- const blockedRef = task.blockedBy.length > 0 ? ` (blocked by: ${task.blockedBy.join(', ')})` : '';
88
- console.log(
89
- `- ${String(task.order)}. **[${task.status}]** ${task.id}: ${task.name} (${task.projectPath})${ticketRef}${blockedRef}`
90
- );
91
- }
92
- console.log('');
93
- return;
94
- }
95
-
96
- // Interactive list with table
97
- const tasksByStatus = {
98
- todo: filtered.filter((t) => t.status === 'todo').length,
99
- in_progress: filtered.filter((t) => t.status === 'in_progress').length,
100
- done: filtered.filter((t) => t.status === 'done').length,
101
- };
102
-
103
- printHeader(`Tasks (${String(filtered.length)})`, icons.task);
104
-
105
- // Status summary
106
- log.raw(
107
- `${formatTaskStatus('todo')} ${String(tasksByStatus.todo)} ` +
108
- `${formatTaskStatus('in_progress')} ${String(tasksByStatus.in_progress)} ` +
109
- `${formatTaskStatus('done')} ${String(tasksByStatus.done)}`
110
- );
111
- log.newline();
112
-
113
- const rows: string[][] = filtered.map((task) => {
114
- const statusIcon =
115
- task.status === 'done' ? icons.success : task.status === 'in_progress' ? icons.active : icons.inactive;
116
- const statusColor = task.status === 'done' ? 'success' : task.status === 'in_progress' ? 'warning' : 'muted';
117
- const blocked = task.blockedBy.length > 0 ? colors.warning('(blocked)') : '';
118
- return [badge(statusIcon, statusColor), String(task.order), task.name, task.id, blocked];
119
- });
120
-
121
- console.log(
122
- renderTable(
123
- [
124
- { header: '', minWidth: 0 },
125
- { header: '#', align: 'right' },
126
- { header: 'Name' },
127
- { header: 'ID' },
128
- { header: '' },
129
- ],
130
- rows
131
- )
132
- );
133
-
134
- // Progress summary
135
- const percent = filtered.length > 0 ? Math.round((tasksByStatus.done / filtered.length) * 100) : 0;
136
- const progressColor = percent === 100 ? colors.success : percent > 50 ? colors.warning : colors.muted;
137
- const showingLabel = isFiltered
138
- ? `Showing ${String(filtered.length)} of ${String(tasks.length)} task(s)${filterStr}`
139
- : `Showing ${String(tasks.length)} task(s)`;
140
- log.newline();
141
- log.dim(
142
- `Progress: ${progressColor(`${String(tasksByStatus.done)}/${String(filtered.length)} (${String(percent)}%)`)} | ${showingLabel}`
143
- );
144
- log.newline();
145
- }
@@ -1,45 +0,0 @@
1
- import { getNextTask } from '@src/store/task.ts';
2
- import { field, formatTaskStatus, log, printHeader, showEmpty, showNextStep } from '@src/theme/ui.ts';
3
-
4
- export async function taskNextCommand(): Promise<void> {
5
- const task = await getNextTask();
6
-
7
- if (!task) {
8
- showEmpty('pending tasks', 'All tasks are done, or add more with: ralphctl task add');
9
- return;
10
- }
11
-
12
- printHeader('Next Task');
13
- console.log(field('ID', task.id));
14
- console.log(field('Name', task.name));
15
- console.log(field('Status', formatTaskStatus(task.status)));
16
- console.log(field('Order', String(task.order)));
17
-
18
- if (task.ticketId) {
19
- console.log(field('Ticket', task.ticketId));
20
- }
21
-
22
- if (task.description) {
23
- log.newline();
24
- console.log(field('Description', ''));
25
- log.raw(task.description, 2);
26
- }
27
-
28
- if (task.steps.length > 0) {
29
- log.newline();
30
- console.log(field('Steps', ''));
31
- task.steps.forEach((step, i) => {
32
- log.raw(`${String(i + 1)}. ${step}`, 2);
33
- });
34
- }
35
-
36
- if (task.blockedBy.length > 0) {
37
- log.newline();
38
- console.log(field('Blocked By', ''));
39
- task.blockedBy.forEach((dep) => {
40
- log.item(dep);
41
- });
42
- }
43
-
44
- showNextStep(`ralphctl task status ${task.id} in_progress`, 'Start working on this task');
45
- }
@@ -1,47 +0,0 @@
1
- import { confirm } from '@inquirer/prompts';
2
- import { muted } from '@src/theme/index.ts';
3
- import { getTask, removeTask, TaskNotFoundError } from '@src/store/task.ts';
4
- import { SprintStatusError } from '@src/store/sprint.ts';
5
- import { selectTask } from '@src/interactive/selectors.ts';
6
- import { log, showError, showSuccess } from '@src/theme/ui.ts';
7
-
8
- export async function taskRemoveCommand(args: string[]): Promise<void> {
9
- const skipConfirm = args.includes('-y') || args.includes('--yes');
10
- let taskId = args.find((a) => !a.startsWith('-'));
11
-
12
- if (!taskId) {
13
- const selected = await selectTask('Select task to remove:');
14
- if (!selected) return;
15
- taskId = selected;
16
- }
17
-
18
- try {
19
- const task = await getTask(taskId);
20
-
21
- if (!skipConfirm) {
22
- const confirmed = await confirm({
23
- message: `Remove task "${task.name}" (${task.id})?`,
24
- default: false,
25
- });
26
-
27
- if (!confirmed) {
28
- console.log(muted('\nTask removal cancelled.\n'));
29
- return;
30
- }
31
- }
32
-
33
- await removeTask(taskId);
34
- showSuccess('Task removed', [['ID', taskId]]);
35
- log.newline();
36
- } catch (err) {
37
- if (err instanceof TaskNotFoundError) {
38
- showError(`Task not found: ${taskId}`);
39
- log.newline();
40
- } else if (err instanceof SprintStatusError) {
41
- showError(err.message);
42
- log.newline();
43
- } else {
44
- throw err;
45
- }
46
- }
47
- }
@@ -1,45 +0,0 @@
1
- import { reorderTask, TaskNotFoundError } from '@src/store/task.ts';
2
- import { SprintStatusError } from '@src/store/sprint.ts';
3
- import { inputPositiveInt, selectTask } from '@src/interactive/selectors.ts';
4
- import { log, showError, showSuccess } from '@src/theme/ui.ts';
5
-
6
- export async function taskReorderCommand(args: string[]): Promise<void> {
7
- let taskId = args[0];
8
- let newOrder: number | undefined;
9
-
10
- if (args[1]) {
11
- newOrder = parseInt(args[1], 10);
12
- }
13
-
14
- // Interactive: select task if not provided
15
- if (!taskId) {
16
- const selected = await selectTask('Select task to reorder:');
17
- if (!selected) return;
18
- taskId = selected;
19
- }
20
-
21
- // Interactive: ask for new position if not provided
22
- if (newOrder === undefined || isNaN(newOrder) || newOrder < 1) {
23
- newOrder = await inputPositiveInt('New position (1 = highest priority):');
24
- }
25
-
26
- try {
27
- const task = await reorderTask(taskId, newOrder);
28
- showSuccess('Task reordered!', [
29
- ['ID', task.id],
30
- ['Name', task.name],
31
- ['New Order', String(task.order)],
32
- ]);
33
- log.newline();
34
- } catch (err) {
35
- if (err instanceof TaskNotFoundError) {
36
- showError(`Task not found: ${taskId}`);
37
- log.newline();
38
- } else if (err instanceof SprintStatusError) {
39
- showError(err.message);
40
- log.newline();
41
- } else {
42
- throw err;
43
- }
44
- }
45
- }
@@ -1,111 +0,0 @@
1
- import { colors, muted } from '@src/theme/index.ts';
2
- import { getTask, TaskNotFoundError } from '@src/store/task.ts';
3
- import { getTicket } from '@src/store/ticket.ts';
4
- import {
5
- DETAIL_LABEL_WIDTH,
6
- formatTaskStatus,
7
- horizontalLine,
8
- icons,
9
- labelValue,
10
- log,
11
- renderCard,
12
- showError,
13
- showNextStep,
14
- } from '@src/theme/ui.ts';
15
- import { selectTask } from '@src/interactive/selectors.ts';
16
-
17
- export async function taskShowCommand(args: string[]): Promise<void> {
18
- let taskId = args[0];
19
-
20
- if (!taskId) {
21
- const selected = await selectTask('Select task to show:');
22
- if (!selected) return;
23
- taskId = selected;
24
- }
25
-
26
- try {
27
- const task = await getTask(taskId);
28
-
29
- // Task info card
30
- const infoLines: string[] = [
31
- labelValue('ID', task.id),
32
- labelValue('Status', formatTaskStatus(task.status)),
33
- labelValue('Order', String(task.order)),
34
- labelValue('Project', task.projectPath),
35
- ];
36
-
37
- if (task.ticketId) {
38
- infoLines.push(labelValue('Ticket', task.ticketId));
39
- }
40
-
41
- if (task.description) {
42
- infoLines.push('');
43
- infoLines.push(labelValue('Description', ''));
44
- for (const line of task.description.split('\n')) {
45
- infoLines.push(`${' '.repeat(DETAIL_LABEL_WIDTH + 1)}${line}`);
46
- }
47
- }
48
-
49
- log.newline();
50
- console.log(renderCard(`${icons.task} ${task.name}`, infoLines));
51
-
52
- // Steps card (if any)
53
- if (task.steps.length > 0) {
54
- log.newline();
55
- const stepLines: string[] = [];
56
- for (let i = 0; i < task.steps.length; i++) {
57
- const step = task.steps[i] ?? '';
58
- const checkbox = task.status === 'done' ? colors.success('[x]') : muted('[ ]');
59
- stepLines.push(`${checkbox} ${muted(String(i + 1) + '.')} ${step}`);
60
- }
61
- console.log(renderCard(`${icons.bullet} Steps (${String(task.steps.length)})`, stepLines));
62
- }
63
-
64
- // Dependencies card (if any)
65
- if (task.blockedBy.length > 0) {
66
- log.newline();
67
- const depLines: string[] = [];
68
- for (const dep of task.blockedBy) {
69
- depLines.push(`${icons.bullet} ${dep}`);
70
- }
71
- console.log(renderCard(`${icons.warning} Blocked By`, depLines));
72
- }
73
-
74
- // Requirements card (from linked ticket, if refined)
75
- if (task.ticketId) {
76
- try {
77
- const ticket = await getTicket(task.ticketId);
78
- if (ticket.requirements) {
79
- log.newline();
80
- const reqLines = ticket.requirements.split('\n');
81
- console.log(renderCard(`${icons.ticket} Requirements`, reqLines));
82
- }
83
- } catch {
84
- // Ticket may not exist anymore - silently skip
85
- }
86
- }
87
-
88
- // Verification card (if verified)
89
- if (task.verified) {
90
- log.newline();
91
- const verifyLines: string[] = [`${colors.success(icons.success)} Verified`];
92
- if (task.verificationOutput) {
93
- verifyLines.push(colors.muted(horizontalLine(30, 'rounded')));
94
- for (const line of task.verificationOutput.split('\n').slice(0, 10)) {
95
- verifyLines.push(muted(line));
96
- }
97
- }
98
- console.log(renderCard(`${icons.success} Verification`, verifyLines));
99
- }
100
-
101
- log.newline();
102
- } catch (err) {
103
- if (err instanceof TaskNotFoundError) {
104
- showError(`Task not found: ${taskId}`);
105
- showNextStep('ralphctl task list', 'see available tasks');
106
- log.newline();
107
- } else {
108
- throw err;
109
- }
110
- }
111
- }
@@ -1,99 +0,0 @@
1
- import { TaskNotFoundError, updateTaskStatus } from '@src/store/task.ts';
2
- import { formatTaskStatus, log, showError, showNextStep, showSuccess } from '@src/theme/ui.ts';
3
- import { type TaskStatus, TaskStatusSchema } from '@src/schemas/index.ts';
4
- import { SprintStatusError } from '@src/store/sprint.ts';
5
- import { selectTask, selectTaskStatus } from '@src/interactive/selectors.ts';
6
- import { EXIT_ERROR, exitWithCode } from '@src/utils/exit-codes.ts';
7
-
8
- const VALID_STATUSES: TaskStatus[] = ['todo', 'in_progress', 'done'];
9
-
10
- export interface TaskStatusOptions {
11
- taskId?: string;
12
- status?: string;
13
- noInteractive?: boolean;
14
- }
15
-
16
- export async function taskStatusCommand(args: string[], options: TaskStatusOptions = {}): Promise<void> {
17
- let taskId = args[0] ?? options.taskId;
18
- let newStatus = args[1] ?? options.status;
19
-
20
- // Non-interactive mode: validate required params, fail fast
21
- if (options.noInteractive) {
22
- const errors: string[] = [];
23
-
24
- if (!taskId?.trim()) {
25
- errors.push('Task ID is required');
26
- }
27
-
28
- if (!newStatus?.trim()) {
29
- errors.push('Status is required');
30
- } else {
31
- const result = TaskStatusSchema.safeParse(newStatus);
32
- if (!result.success) {
33
- errors.push(`Invalid status: ${newStatus} (valid: ${VALID_STATUSES.join(', ')})`);
34
- }
35
- }
36
-
37
- if (errors.length > 0) {
38
- showError('Validation failed');
39
- for (const e of errors) {
40
- log.error(e);
41
- }
42
- log.newline();
43
- exitWithCode(EXIT_ERROR);
44
- }
45
- }
46
-
47
- // Interactive: select task if not provided
48
- if (!taskId) {
49
- const selected = await selectTask('Select task to update:');
50
- if (!selected) return;
51
- taskId = selected;
52
- }
53
-
54
- // Interactive: select status if not provided
55
- if (!newStatus) {
56
- const selected = await selectTaskStatus('Select new status:');
57
- if (!selected) return;
58
- newStatus = selected;
59
- }
60
-
61
- const result = TaskStatusSchema.safeParse(newStatus);
62
- if (!result.success) {
63
- showError(`Invalid status: ${newStatus}`);
64
- log.dim(`Valid statuses: ${VALID_STATUSES.join(', ')}`);
65
- log.newline();
66
-
67
- if (options.noInteractive) {
68
- exitWithCode(EXIT_ERROR);
69
- }
70
- return;
71
- }
72
-
73
- try {
74
- const task = await updateTaskStatus(taskId, result.data);
75
- showSuccess('Task status updated!', [
76
- ['ID', task.id],
77
- ['Name', task.name],
78
- ['Status', formatTaskStatus(task.status)],
79
- ]);
80
- log.newline();
81
- } catch (err) {
82
- if (err instanceof TaskNotFoundError) {
83
- showError(`Task not found: ${taskId}`);
84
- showNextStep('ralphctl task list', 'see available tasks');
85
- log.newline();
86
- if (options.noInteractive) {
87
- exitWithCode(EXIT_ERROR);
88
- }
89
- } else if (err instanceof SprintStatusError) {
90
- showError(err.message);
91
- log.newline();
92
- if (options.noInteractive) {
93
- exitWithCode(EXIT_ERROR);
94
- }
95
- } else {
96
- throw err;
97
- }
98
- }
99
- }