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.
- package/README.md +58 -24
- package/dist/add-HGJCLWED.mjs +14 -0
- package/dist/add-MRGCS3US.mjs +14 -0
- package/dist/chunk-6PYTKGB5.mjs +316 -0
- package/dist/chunk-7TG3EAQ2.mjs +20 -0
- package/dist/chunk-EKMZZRWI.mjs +521 -0
- package/dist/chunk-JON4GCLR.mjs +59 -0
- package/dist/chunk-LOR7QBXX.mjs +3683 -0
- package/dist/chunk-MNMQC36F.mjs +556 -0
- package/dist/chunk-MRKOFVTM.mjs +537 -0
- package/dist/chunk-NTWO2LXB.mjs +52 -0
- package/dist/chunk-QBXHAXHI.mjs +562 -0
- package/dist/chunk-WGHJI3OI.mjs +214 -0
- package/dist/cli.mjs +4245 -0
- package/dist/create-MG7E7PLQ.mjs +10 -0
- package/dist/handle-UG5M2OON.mjs +22 -0
- package/dist/multiline-OHSNFCRG.mjs +40 -0
- package/dist/project-NT3L4FTB.mjs +28 -0
- package/dist/resolver-WSFWKACM.mjs +153 -0
- package/dist/sprint-4VHDLGFN.mjs +37 -0
- package/dist/wizard-LRELAN2J.mjs +196 -0
- package/package.json +19 -28
- package/CHANGELOG.md +0 -94
- package/bin/ralphctl +0 -13
- package/src/ai/executor.ts +0 -973
- package/src/ai/lifecycle.ts +0 -45
- package/src/ai/parser.ts +0 -40
- package/src/ai/permissions.ts +0 -207
- package/src/ai/process-manager.ts +0 -248
- package/src/ai/prompts/index.ts +0 -89
- package/src/ai/rate-limiter.ts +0 -89
- package/src/ai/runner.ts +0 -478
- package/src/ai/session.ts +0 -319
- package/src/ai/task-context.ts +0 -270
- package/src/cli-metadata.ts +0 -7
- package/src/cli.ts +0 -65
- package/src/commands/completion/index.ts +0 -33
- package/src/commands/config/config.ts +0 -58
- package/src/commands/config/index.ts +0 -33
- package/src/commands/dashboard/dashboard.ts +0 -5
- package/src/commands/dashboard/index.ts +0 -6
- package/src/commands/doctor/doctor.ts +0 -271
- package/src/commands/doctor/index.ts +0 -25
- package/src/commands/progress/index.ts +0 -25
- package/src/commands/progress/log.ts +0 -64
- package/src/commands/progress/show.ts +0 -14
- package/src/commands/project/add.ts +0 -336
- package/src/commands/project/index.ts +0 -104
- package/src/commands/project/list.ts +0 -31
- package/src/commands/project/remove.ts +0 -43
- package/src/commands/project/repo.ts +0 -118
- package/src/commands/project/show.ts +0 -49
- package/src/commands/sprint/close.ts +0 -180
- package/src/commands/sprint/context.ts +0 -109
- package/src/commands/sprint/create.ts +0 -60
- package/src/commands/sprint/current.ts +0 -75
- package/src/commands/sprint/delete.ts +0 -72
- package/src/commands/sprint/health.ts +0 -229
- package/src/commands/sprint/ideate.ts +0 -496
- package/src/commands/sprint/index.ts +0 -226
- package/src/commands/sprint/list.ts +0 -86
- package/src/commands/sprint/plan-utils.ts +0 -207
- package/src/commands/sprint/plan.ts +0 -549
- package/src/commands/sprint/refine.ts +0 -359
- package/src/commands/sprint/requirements.ts +0 -58
- package/src/commands/sprint/show.ts +0 -140
- package/src/commands/sprint/start.ts +0 -119
- package/src/commands/sprint/switch.ts +0 -20
- package/src/commands/task/add.ts +0 -316
- package/src/commands/task/import.ts +0 -150
- package/src/commands/task/index.ts +0 -123
- package/src/commands/task/list.ts +0 -145
- package/src/commands/task/next.ts +0 -45
- package/src/commands/task/remove.ts +0 -47
- package/src/commands/task/reorder.ts +0 -45
- package/src/commands/task/show.ts +0 -111
- package/src/commands/task/status.ts +0 -99
- package/src/commands/ticket/add.ts +0 -265
- package/src/commands/ticket/edit.ts +0 -166
- package/src/commands/ticket/index.ts +0 -114
- package/src/commands/ticket/list.ts +0 -128
- package/src/commands/ticket/refine-utils.ts +0 -89
- package/src/commands/ticket/refine.ts +0 -268
- package/src/commands/ticket/remove.ts +0 -48
- package/src/commands/ticket/show.ts +0 -74
- package/src/completion/handle.ts +0 -30
- package/src/completion/resolver.ts +0 -241
- package/src/interactive/dashboard.ts +0 -268
- package/src/interactive/escapable.ts +0 -81
- package/src/interactive/file-browser.ts +0 -153
- package/src/interactive/index.ts +0 -429
- package/src/interactive/menu.ts +0 -403
- package/src/interactive/selectors.ts +0 -273
- package/src/interactive/wizard.ts +0 -221
- package/src/providers/claude.ts +0 -53
- package/src/providers/copilot.ts +0 -86
- package/src/providers/index.ts +0 -43
- package/src/providers/types.ts +0 -85
- package/src/schemas/index.ts +0 -130
- package/src/store/config.ts +0 -74
- package/src/store/progress.ts +0 -230
- package/src/store/project.ts +0 -276
- package/src/store/sprint.ts +0 -229
- package/src/store/task.ts +0 -443
- package/src/store/ticket.ts +0 -178
- package/src/theme/index.ts +0 -215
- package/src/theme/ui.ts +0 -872
- package/src/utils/detect-scripts.ts +0 -247
- package/src/utils/editor-input.ts +0 -41
- package/src/utils/editor.ts +0 -37
- package/src/utils/exit-codes.ts +0 -27
- package/src/utils/file-lock.ts +0 -135
- package/src/utils/git.ts +0 -185
- package/src/utils/ids.ts +0 -37
- package/src/utils/issue-fetch.ts +0 -244
- package/src/utils/json-extract.ts +0 -62
- package/src/utils/multiline.ts +0 -61
- package/src/utils/path-selector.ts +0 -236
- package/src/utils/paths.ts +0 -108
- package/src/utils/provider.ts +0 -34
- package/src/utils/requirements-export.ts +0 -63
- package/src/utils/storage.ts +0 -107
- package/tsconfig.json +0 -25
- /package/{src/ai → dist}/prompts/ideate-auto.md +0 -0
- /package/{src/ai → dist}/prompts/ideate.md +0 -0
- /package/{src/ai → dist}/prompts/plan-auto.md +0 -0
- /package/{src/ai → dist}/prompts/plan-common.md +0 -0
- /package/{src/ai → dist}/prompts/plan-interactive.md +0 -0
- /package/{src/ai → dist}/prompts/task-execution.md +0 -0
- /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
|
-
}
|