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,180 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { confirm } from '@inquirer/prompts';
|
|
3
|
-
import { muted } from '@src/theme/index.ts';
|
|
4
|
-
import { closeSprint, getSprint, listSprints, SprintNotFoundError, SprintStatusError } from '@src/store/sprint.ts';
|
|
5
|
-
import { areAllTasksDone, listTasks } from '@src/store/task.ts';
|
|
6
|
-
import { selectSprint } from '@src/interactive/selectors.ts';
|
|
7
|
-
import { formatSprintStatus, log, showError, showRandomQuote, showSuccess, showWarning } from '@src/theme/ui.ts';
|
|
8
|
-
import { branchExists, getDefaultBranch, isGhAvailable } from '@src/utils/git.ts';
|
|
9
|
-
import { assertSafeCwd } from '@src/utils/paths.ts';
|
|
10
|
-
|
|
11
|
-
export async function sprintCloseCommand(args: string[]): Promise<void> {
|
|
12
|
-
let sprintId: string;
|
|
13
|
-
let createPr = false;
|
|
14
|
-
|
|
15
|
-
// Parse args
|
|
16
|
-
const positionalArgs: string[] = [];
|
|
17
|
-
for (const arg of args) {
|
|
18
|
-
if (arg === '--create-pr') {
|
|
19
|
-
createPr = true;
|
|
20
|
-
} else {
|
|
21
|
-
positionalArgs.push(arg);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// If explicit ID provided, use it
|
|
26
|
-
if (positionalArgs[0]) {
|
|
27
|
-
sprintId = positionalArgs[0];
|
|
28
|
-
} else {
|
|
29
|
-
// Check active sprints - show selector if multiple, auto-select if one
|
|
30
|
-
const sprints = await listSprints();
|
|
31
|
-
const activeSprints = sprints.filter((s) => s.status === 'active');
|
|
32
|
-
|
|
33
|
-
if (activeSprints.length === 0) {
|
|
34
|
-
showError('No active sprints to close.');
|
|
35
|
-
log.newline();
|
|
36
|
-
return;
|
|
37
|
-
} else if (activeSprints.length === 1 && activeSprints[0]) {
|
|
38
|
-
sprintId = activeSprints[0].id;
|
|
39
|
-
} else {
|
|
40
|
-
const selected = await selectSprint('Select sprint to close:', ['active']);
|
|
41
|
-
if (!selected) return;
|
|
42
|
-
sprintId = selected;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Check if all tasks are done
|
|
47
|
-
const allDone = await areAllTasksDone(sprintId);
|
|
48
|
-
if (!allDone) {
|
|
49
|
-
const tasks = await listTasks(sprintId);
|
|
50
|
-
const remaining = tasks.filter((t) => t.status !== 'done');
|
|
51
|
-
log.newline();
|
|
52
|
-
showWarning(`${String(remaining.length)} task(s) are not done:`);
|
|
53
|
-
for (const task of remaining) {
|
|
54
|
-
log.item(`${task.id}: ${task.name} (${task.status})`);
|
|
55
|
-
}
|
|
56
|
-
log.newline();
|
|
57
|
-
|
|
58
|
-
const proceed = await confirm({
|
|
59
|
-
message: 'Close sprint anyway?',
|
|
60
|
-
default: false,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (!proceed) {
|
|
64
|
-
console.log(muted('\nSprint close cancelled.\n'));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
// Load sprint before closing (need branch info for PR creation)
|
|
71
|
-
const sprintBeforeClose = await getSprint(sprintId);
|
|
72
|
-
const sprint = await closeSprint(sprintId);
|
|
73
|
-
showSuccess('Sprint closed!', [
|
|
74
|
-
['ID', sprint.id],
|
|
75
|
-
['Name', sprint.name],
|
|
76
|
-
['Status', formatSprintStatus(sprint.status)],
|
|
77
|
-
]);
|
|
78
|
-
showRandomQuote();
|
|
79
|
-
log.newline();
|
|
80
|
-
|
|
81
|
-
// Create PRs if requested and sprint had a branch
|
|
82
|
-
if (createPr && sprintBeforeClose.branch) {
|
|
83
|
-
await createPullRequests(sprintId, sprintBeforeClose.branch, sprint.name);
|
|
84
|
-
} else if (createPr && !sprintBeforeClose.branch) {
|
|
85
|
-
log.dim('No sprint branch set — skipping PR creation.');
|
|
86
|
-
log.newline();
|
|
87
|
-
}
|
|
88
|
-
} catch (err) {
|
|
89
|
-
if (err instanceof SprintNotFoundError) {
|
|
90
|
-
showError(`Sprint not found: ${sprintId}`);
|
|
91
|
-
log.newline();
|
|
92
|
-
} else if (err instanceof SprintStatusError) {
|
|
93
|
-
showError(err.message);
|
|
94
|
-
log.newline();
|
|
95
|
-
} else {
|
|
96
|
-
throw err;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Create pull requests for each repo that has tasks.
|
|
103
|
-
* Best-effort — prints manual command on failure.
|
|
104
|
-
*/
|
|
105
|
-
async function createPullRequests(sprintId: string, branchName: string, sprintName: string): Promise<void> {
|
|
106
|
-
if (!isGhAvailable()) {
|
|
107
|
-
showWarning('GitHub CLI (gh) not found. Install it to create PRs automatically.');
|
|
108
|
-
log.dim(` Manual: gh pr create --head ${branchName} --title "Sprint: ${sprintName}"`);
|
|
109
|
-
log.newline();
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const tasks = await listTasks(sprintId);
|
|
114
|
-
const uniquePaths = [...new Set(tasks.map((t) => t.projectPath))];
|
|
115
|
-
|
|
116
|
-
for (const projectPath of uniquePaths) {
|
|
117
|
-
try {
|
|
118
|
-
assertSafeCwd(projectPath);
|
|
119
|
-
|
|
120
|
-
if (!branchExists(projectPath, branchName)) {
|
|
121
|
-
log.dim(`Branch '${branchName}' not found in ${projectPath} — skipping`);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const baseBranch = getDefaultBranch(projectPath);
|
|
126
|
-
const title = `Sprint: ${sprintName}`;
|
|
127
|
-
|
|
128
|
-
log.info(`Creating PR in ${projectPath}...`);
|
|
129
|
-
|
|
130
|
-
// Push the branch first
|
|
131
|
-
const pushResult = spawnSync('git', ['push', '-u', 'origin', branchName], {
|
|
132
|
-
cwd: projectPath,
|
|
133
|
-
encoding: 'utf-8',
|
|
134
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
if (pushResult.status !== 0) {
|
|
138
|
-
showWarning(`Failed to push branch in ${projectPath}: ${pushResult.stderr.trim()}`);
|
|
139
|
-
log.dim(
|
|
140
|
-
` Manual: cd ${projectPath} && git push -u origin ${branchName} && gh pr create --base ${baseBranch} --head ${branchName} --title "${title}"`
|
|
141
|
-
);
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const result = spawnSync(
|
|
146
|
-
'gh',
|
|
147
|
-
[
|
|
148
|
-
'pr',
|
|
149
|
-
'create',
|
|
150
|
-
'--base',
|
|
151
|
-
baseBranch,
|
|
152
|
-
'--head',
|
|
153
|
-
branchName,
|
|
154
|
-
'--title',
|
|
155
|
-
title,
|
|
156
|
-
'--body',
|
|
157
|
-
`Sprint: ${sprintName}\nID: ${sprintId}`,
|
|
158
|
-
],
|
|
159
|
-
{
|
|
160
|
-
cwd: projectPath,
|
|
161
|
-
encoding: 'utf-8',
|
|
162
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
163
|
-
}
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
if (result.status === 0) {
|
|
167
|
-
const prUrl = result.stdout.trim();
|
|
168
|
-
showSuccess(`PR created: ${prUrl}`);
|
|
169
|
-
} else {
|
|
170
|
-
showWarning(`Failed to create PR in ${projectPath}: ${result.stderr.trim()}`);
|
|
171
|
-
log.dim(
|
|
172
|
-
` Manual: cd ${projectPath} && gh pr create --base ${baseBranch} --head ${branchName} --title "${title}"`
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
} catch (err) {
|
|
176
|
-
showWarning(`Error creating PR for ${projectPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
log.newline();
|
|
180
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { log, showNextStep, showWarning } from '@src/theme/ui.ts';
|
|
2
|
-
import { getSprint, resolveSprintId } from '@src/store/sprint.ts';
|
|
3
|
-
import { listTasks } from '@src/store/task.ts';
|
|
4
|
-
import { formatTicketDisplay, groupTicketsByProject } from '@src/store/ticket.ts';
|
|
5
|
-
import { getProject } from '@src/store/project.ts';
|
|
6
|
-
import { selectSprint } from '@src/interactive/selectors.ts';
|
|
7
|
-
|
|
8
|
-
export async function sprintContextCommand(args: string[]): Promise<void> {
|
|
9
|
-
const sprintId = args[0];
|
|
10
|
-
|
|
11
|
-
let id: string;
|
|
12
|
-
try {
|
|
13
|
-
id = await resolveSprintId(sprintId);
|
|
14
|
-
} catch {
|
|
15
|
-
// No current sprint set - offer selection
|
|
16
|
-
const selected = await selectSprint('Select sprint to show context for:');
|
|
17
|
-
if (!selected) {
|
|
18
|
-
showWarning('No sprints available.');
|
|
19
|
-
showNextStep('ralphctl sprint create', 'create a sprint first');
|
|
20
|
-
log.newline();
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
id = selected;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const sprint = await getSprint(id);
|
|
27
|
-
const tasks = await listTasks(id);
|
|
28
|
-
|
|
29
|
-
// Output in a format useful for the AI provider
|
|
30
|
-
console.log(`# Sprint: ${sprint.name}`);
|
|
31
|
-
console.log(`ID: ${sprint.id}`);
|
|
32
|
-
console.log(`Status: ${sprint.status}`);
|
|
33
|
-
console.log('');
|
|
34
|
-
|
|
35
|
-
// Tickets grouped by project
|
|
36
|
-
console.log('## Tickets');
|
|
37
|
-
console.log('');
|
|
38
|
-
if (sprint.tickets.length === 0) {
|
|
39
|
-
console.log('_No tickets defined_');
|
|
40
|
-
} else {
|
|
41
|
-
const ticketsByProject = groupTicketsByProject(sprint.tickets);
|
|
42
|
-
|
|
43
|
-
for (const [projectName, tickets] of ticketsByProject) {
|
|
44
|
-
console.log(`### Project: ${projectName}`);
|
|
45
|
-
|
|
46
|
-
// Get project repositories for context
|
|
47
|
-
try {
|
|
48
|
-
const project = await getProject(projectName);
|
|
49
|
-
const repoPaths = project.repositories.map((r) => `${r.name} (${r.path})`);
|
|
50
|
-
console.log(`Repositories: ${repoPaths.join(', ')}`);
|
|
51
|
-
} catch {
|
|
52
|
-
console.log('Repositories: (project not found)');
|
|
53
|
-
}
|
|
54
|
-
console.log('');
|
|
55
|
-
|
|
56
|
-
for (const ticket of tickets) {
|
|
57
|
-
const reqBadge = ticket.requirementStatus === 'approved' ? ' [approved]' : ' [pending]';
|
|
58
|
-
console.log(`#### ${formatTicketDisplay(ticket)}${reqBadge}`);
|
|
59
|
-
|
|
60
|
-
if (ticket.description) {
|
|
61
|
-
console.log('');
|
|
62
|
-
console.log(ticket.description);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (ticket.link) {
|
|
66
|
-
console.log('');
|
|
67
|
-
console.log(`Link: ${ticket.link}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Include refined requirements if available
|
|
71
|
-
if (ticket.requirements) {
|
|
72
|
-
console.log('');
|
|
73
|
-
console.log('**Refined Requirements:**');
|
|
74
|
-
console.log('');
|
|
75
|
-
console.log(ticket.requirements);
|
|
76
|
-
}
|
|
77
|
-
console.log('');
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Tasks section
|
|
83
|
-
console.log('## Tasks');
|
|
84
|
-
console.log('');
|
|
85
|
-
if (tasks.length === 0) {
|
|
86
|
-
console.log('_No tasks defined yet_');
|
|
87
|
-
} else {
|
|
88
|
-
for (const task of tasks) {
|
|
89
|
-
const ticketRef = task.ticketId ? ` [${task.ticketId}]` : '';
|
|
90
|
-
console.log(`### ${task.id}: ${task.name}${ticketRef}`);
|
|
91
|
-
console.log(`Status: ${task.status} | Order: ${String(task.order)} | Project: ${task.projectPath}`);
|
|
92
|
-
if (task.blockedBy.length > 0) {
|
|
93
|
-
console.log(`Blocked By: ${task.blockedBy.join(', ')}`);
|
|
94
|
-
}
|
|
95
|
-
if (task.description) {
|
|
96
|
-
console.log('');
|
|
97
|
-
console.log(task.description);
|
|
98
|
-
}
|
|
99
|
-
if (task.steps.length > 0) {
|
|
100
|
-
console.log('');
|
|
101
|
-
console.log('Steps:');
|
|
102
|
-
task.steps.forEach((step, i) => {
|
|
103
|
-
console.log(`${String(i + 1)}. ${step}`);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
console.log('');
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { confirm, input } from '@inquirer/prompts';
|
|
2
|
-
import { emoji, field, formatSprintStatus, icons, showNextStep, showRandomQuote, showSuccess } from '@src/theme/ui.ts';
|
|
3
|
-
import { setCurrentSprint } from '@src/store/config.ts';
|
|
4
|
-
import { createSprint } from '@src/store/sprint.ts';
|
|
5
|
-
|
|
6
|
-
export interface SprintCreateOptions {
|
|
7
|
-
name?: string;
|
|
8
|
-
interactive?: boolean; // Set by REPL or CLI (default true unless --no-interactive)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function sprintCreateCommand(options: SprintCreateOptions = {}): Promise<void> {
|
|
12
|
-
let name: string | undefined;
|
|
13
|
-
|
|
14
|
-
if (options.interactive === false) {
|
|
15
|
-
// Non-interactive: name is optional (will generate uuid8 if not provided)
|
|
16
|
-
const trimmed = options.name?.trim();
|
|
17
|
-
name = trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
18
|
-
} else {
|
|
19
|
-
// Interactive mode: prompt for name (can be left empty)
|
|
20
|
-
const inputName = await input({
|
|
21
|
-
message: `${icons.sprint} Sprint name (optional):`,
|
|
22
|
-
default: options.name?.trim(),
|
|
23
|
-
});
|
|
24
|
-
const trimmed = inputName.trim();
|
|
25
|
-
name = trimmed.length > 0 ? trimmed : undefined;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Create sprint (as draft) - name is optional, will generate uuid8 if empty
|
|
29
|
-
const sprint = await createSprint(name);
|
|
30
|
-
|
|
31
|
-
// Ask if user wants to set as current sprint
|
|
32
|
-
let setAsCurrent = true;
|
|
33
|
-
|
|
34
|
-
if (options.interactive) {
|
|
35
|
-
setAsCurrent = await confirm({
|
|
36
|
-
message: `${emoji.donut} Set as current sprint?`,
|
|
37
|
-
default: true,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
// In non-interactive mode, default to setting as current
|
|
41
|
-
|
|
42
|
-
if (setAsCurrent) {
|
|
43
|
-
await setCurrentSprint(sprint.id);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
showSuccess('Sprint created!', [
|
|
47
|
-
['ID', sprint.id],
|
|
48
|
-
['Name', sprint.name],
|
|
49
|
-
['Status', formatSprintStatus(sprint.status)],
|
|
50
|
-
]);
|
|
51
|
-
showRandomQuote();
|
|
52
|
-
|
|
53
|
-
if (setAsCurrent) {
|
|
54
|
-
console.log(field('Current', 'Yes (this sprint is now active target)'));
|
|
55
|
-
showNextStep('ralphctl ticket add --project <name>', 'add tickets to this sprint');
|
|
56
|
-
} else {
|
|
57
|
-
console.log(field('Current', 'No'));
|
|
58
|
-
showNextStep(`ralphctl sprint current ${sprint.id}`, 'set as current later');
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { getCurrentSprint, setCurrentSprint } from '@src/store/config.ts';
|
|
2
|
-
import { getSprint, SprintNotFoundError } from '@src/store/sprint.ts';
|
|
3
|
-
import { selectSprint } from '@src/interactive/selectors.ts';
|
|
4
|
-
import {
|
|
5
|
-
field,
|
|
6
|
-
formatSprintStatus,
|
|
7
|
-
log,
|
|
8
|
-
printHeader,
|
|
9
|
-
showError,
|
|
10
|
-
showNextStep,
|
|
11
|
-
showSuccess,
|
|
12
|
-
showWarning,
|
|
13
|
-
} from '@src/theme/ui.ts';
|
|
14
|
-
|
|
15
|
-
export async function sprintCurrentCommand(args: string[]): Promise<void> {
|
|
16
|
-
const sprintId = args[0];
|
|
17
|
-
|
|
18
|
-
if (!sprintId) {
|
|
19
|
-
// Show current sprint
|
|
20
|
-
const currentSprintId = await getCurrentSprint();
|
|
21
|
-
if (!currentSprintId) {
|
|
22
|
-
showWarning('No current sprint set.');
|
|
23
|
-
showNextStep('ralphctl sprint create', 'create a new sprint');
|
|
24
|
-
log.newline();
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const sprint = await getSprint(currentSprintId);
|
|
30
|
-
printHeader('Current Sprint');
|
|
31
|
-
console.log(field('ID', sprint.id));
|
|
32
|
-
console.log(field('Name', sprint.name));
|
|
33
|
-
console.log(field('Status', formatSprintStatus(sprint.status)));
|
|
34
|
-
log.newline();
|
|
35
|
-
} catch {
|
|
36
|
-
showWarning(`Current sprint "${currentSprintId}" no longer exists.`);
|
|
37
|
-
showNextStep('ralphctl sprint current -', 'select a different sprint');
|
|
38
|
-
log.newline();
|
|
39
|
-
}
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Set current sprint
|
|
44
|
-
if (sprintId === '-' || sprintId === '--select') {
|
|
45
|
-
const selectedId = await selectSprint('Select current sprint:', ['draft', 'active']);
|
|
46
|
-
if (!selectedId) return;
|
|
47
|
-
|
|
48
|
-
await setCurrentSprint(selectedId);
|
|
49
|
-
const sprint = await getSprint(selectedId);
|
|
50
|
-
showSuccess('Current sprint set!', [
|
|
51
|
-
['ID', sprint.id],
|
|
52
|
-
['Name', sprint.name],
|
|
53
|
-
]);
|
|
54
|
-
log.newline();
|
|
55
|
-
} else {
|
|
56
|
-
// Set by ID
|
|
57
|
-
try {
|
|
58
|
-
const sprint = await getSprint(sprintId);
|
|
59
|
-
await setCurrentSprint(sprintId);
|
|
60
|
-
showSuccess('Current sprint set!', [
|
|
61
|
-
['ID', sprint.id],
|
|
62
|
-
['Name', sprint.name],
|
|
63
|
-
]);
|
|
64
|
-
log.newline();
|
|
65
|
-
} catch (err) {
|
|
66
|
-
if (err instanceof SprintNotFoundError) {
|
|
67
|
-
showError(`Sprint not found: ${sprintId}`);
|
|
68
|
-
showNextStep('ralphctl sprint list', 'see available sprints');
|
|
69
|
-
log.newline();
|
|
70
|
-
} else {
|
|
71
|
-
throw err;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { confirm } from '@inquirer/prompts';
|
|
2
|
-
import { muted } from '@src/theme/index.ts';
|
|
3
|
-
import { deleteSprint, getSprint, SprintNotFoundError } from '@src/store/sprint.ts';
|
|
4
|
-
import { listTasks } from '@src/store/task.ts';
|
|
5
|
-
import { getCurrentSprint, setCurrentSprint } from '@src/store/config.ts';
|
|
6
|
-
import { selectSprint } from '@src/interactive/selectors.ts';
|
|
7
|
-
import { formatSprintStatus, log, showError, showRandomQuote, showSuccess, showTip } from '@src/theme/ui.ts';
|
|
8
|
-
|
|
9
|
-
export async function sprintDeleteCommand(args: string[]): Promise<void> {
|
|
10
|
-
const skipConfirm = args.includes('-y') || args.includes('--yes');
|
|
11
|
-
let sprintId = args.find((a) => !a.startsWith('-'));
|
|
12
|
-
|
|
13
|
-
if (!sprintId) {
|
|
14
|
-
const selected = await selectSprint('Select sprint to delete:');
|
|
15
|
-
if (!selected) return;
|
|
16
|
-
sprintId = selected;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const sprint = await getSprint(sprintId);
|
|
21
|
-
|
|
22
|
-
let taskCount = 0;
|
|
23
|
-
try {
|
|
24
|
-
const tasks = await listTasks(sprintId);
|
|
25
|
-
taskCount = tasks.length;
|
|
26
|
-
} catch {
|
|
27
|
-
// No tasks file
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (!skipConfirm) {
|
|
31
|
-
log.newline();
|
|
32
|
-
log.warn('This will permanently delete the sprint and all its data.');
|
|
33
|
-
log.item(`Name: ${sprint.name}`);
|
|
34
|
-
log.item(`Status: ${formatSprintStatus(sprint.status)}`);
|
|
35
|
-
log.item(`Tickets: ${String(sprint.tickets.length)}`);
|
|
36
|
-
log.item(`Tasks: ${String(taskCount)}`);
|
|
37
|
-
log.newline();
|
|
38
|
-
|
|
39
|
-
const confirmed = await confirm({
|
|
40
|
-
message: `Delete sprint "${sprint.name}"?`,
|
|
41
|
-
default: false,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
if (!confirmed) {
|
|
45
|
-
console.log(muted('\nSprint deletion cancelled.\n'));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const currentSprintId = await getCurrentSprint();
|
|
51
|
-
await deleteSprint(sprintId);
|
|
52
|
-
|
|
53
|
-
if (currentSprintId === sprintId) {
|
|
54
|
-
await setCurrentSprint(null);
|
|
55
|
-
showTip('Current sprint was cleared. Use "ralphctl sprint current" to set a new one.');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
showSuccess('Sprint deleted', [
|
|
59
|
-
['Name', sprint.name],
|
|
60
|
-
['ID', sprint.id],
|
|
61
|
-
]);
|
|
62
|
-
showRandomQuote();
|
|
63
|
-
log.newline();
|
|
64
|
-
} catch (err) {
|
|
65
|
-
if (err instanceof SprintNotFoundError) {
|
|
66
|
-
showError(`Sprint not found: ${sprintId}`);
|
|
67
|
-
log.newline();
|
|
68
|
-
} else {
|
|
69
|
-
throw err;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|