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,89 +0,0 @@
|
|
|
1
|
-
import { writeFile } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { formatTicketDisplay } from '@src/store/ticket.ts';
|
|
4
|
-
import { spawnInteractive } from '@src/ai/session.ts';
|
|
5
|
-
import { getActiveProvider } from '@src/providers/index.ts';
|
|
6
|
-
import { extractJsonArray } from '@src/utils/json-extract.ts';
|
|
7
|
-
import { type RefinedRequirement, RefinedRequirementsSchema, type Ticket } from '@src/schemas/index.ts';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Format a single ticket for the AI prompt.
|
|
11
|
-
*/
|
|
12
|
-
export function formatTicketForPrompt(ticket: Ticket): string {
|
|
13
|
-
const lines: string[] = [];
|
|
14
|
-
|
|
15
|
-
lines.push(`### ${formatTicketDisplay(ticket)}`);
|
|
16
|
-
lines.push(`Project: ${ticket.projectName}`);
|
|
17
|
-
|
|
18
|
-
if (ticket.description) {
|
|
19
|
-
lines.push('');
|
|
20
|
-
lines.push('**Description:**');
|
|
21
|
-
lines.push(ticket.description);
|
|
22
|
-
}
|
|
23
|
-
if (ticket.link) {
|
|
24
|
-
lines.push('');
|
|
25
|
-
lines.push(`**Link:** ${ticket.link}`);
|
|
26
|
-
}
|
|
27
|
-
lines.push('');
|
|
28
|
-
|
|
29
|
-
return lines.join('\n');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Parse a requirements JSON file into validated RefinedRequirement array.
|
|
34
|
-
*/
|
|
35
|
-
export function parseRequirementsFile(content: string): RefinedRequirement[] {
|
|
36
|
-
// Try to extract a balanced JSON array from the content (handles surrounding text)
|
|
37
|
-
const jsonStr = extractJsonArray(content);
|
|
38
|
-
|
|
39
|
-
let parsed: unknown;
|
|
40
|
-
try {
|
|
41
|
-
parsed = JSON.parse(jsonStr);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
throw new Error(`Invalid JSON: ${err instanceof Error ? err.message : 'parse error'}`, { cause: err });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!Array.isArray(parsed)) {
|
|
47
|
-
throw new Error('Expected JSON array');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Validate against schema
|
|
51
|
-
const result = RefinedRequirementsSchema.safeParse(parsed);
|
|
52
|
-
if (!result.success) {
|
|
53
|
-
const issues = result.error.issues
|
|
54
|
-
.map((issue) => {
|
|
55
|
-
const path = issue.path.length > 0 ? `[${issue.path.join('.')}]` : '';
|
|
56
|
-
return ` ${path}: ${issue.message}`;
|
|
57
|
-
})
|
|
58
|
-
.join('\n');
|
|
59
|
-
throw new Error(`Invalid requirements format:\n${issues}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return result.data;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Run an interactive AI session for refinement in the given working directory.
|
|
67
|
-
*/
|
|
68
|
-
export async function runAiSession(workingDir: string, prompt: string, ticketTitle: string): Promise<void> {
|
|
69
|
-
// Write full context to a file for reference
|
|
70
|
-
const contextFile = join(workingDir, 'refine-context.md');
|
|
71
|
-
await writeFile(contextFile, prompt, 'utf-8');
|
|
72
|
-
|
|
73
|
-
const provider = await getActiveProvider();
|
|
74
|
-
|
|
75
|
-
// Build initial prompt that tells the AI to read the context file
|
|
76
|
-
const startPrompt = `I need help refining the requirements for "${ticketTitle}". The full context is in refine-context.md. Please read that file now and follow the instructions to help refine the ticket requirements.`;
|
|
77
|
-
|
|
78
|
-
const result = spawnInteractive(
|
|
79
|
-
startPrompt,
|
|
80
|
-
{
|
|
81
|
-
cwd: workingDir,
|
|
82
|
-
},
|
|
83
|
-
provider
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
if (result.error) {
|
|
87
|
-
throw new Error(result.error);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { confirm } from '@inquirer/prompts';
|
|
4
|
-
import {
|
|
5
|
-
createSpinner,
|
|
6
|
-
emoji,
|
|
7
|
-
field,
|
|
8
|
-
fieldMultiline,
|
|
9
|
-
icons,
|
|
10
|
-
log,
|
|
11
|
-
printHeader,
|
|
12
|
-
renderCard,
|
|
13
|
-
showError,
|
|
14
|
-
showSuccess,
|
|
15
|
-
showTip,
|
|
16
|
-
showWarning,
|
|
17
|
-
} from '@src/theme/ui.ts';
|
|
18
|
-
import { assertSprintStatus, getSprint, resolveSprintId, saveSprint } from '@src/store/sprint.ts';
|
|
19
|
-
import { formatTicketDisplay } from '@src/store/ticket.ts';
|
|
20
|
-
import { buildTicketRefinePrompt } from '@src/ai/prompts/index.ts';
|
|
21
|
-
import { fileExists } from '@src/utils/storage.ts';
|
|
22
|
-
import { getRefinementDir, getSchemaPath } from '@src/utils/paths.ts';
|
|
23
|
-
import { IssueFetchError, fetchIssueFromUrl, formatIssueContext } from '@src/utils/issue-fetch.ts';
|
|
24
|
-
import { type RefinedRequirement } from '@src/schemas/index.ts';
|
|
25
|
-
import { resolveProvider, providerDisplayName } from '@src/utils/provider.ts';
|
|
26
|
-
import { formatTicketForPrompt, parseRequirementsFile, runAiSession } from './refine-utils.ts';
|
|
27
|
-
import { selectTicket } from '@src/interactive/selectors.ts';
|
|
28
|
-
import { EXIT_ERROR, exitWithCode } from '@src/utils/exit-codes.ts';
|
|
29
|
-
|
|
30
|
-
export interface TicketRefineOptions {
|
|
31
|
-
interactive?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function ticketRefineCommand(ticketId?: string, options: TicketRefineOptions = {}): Promise<void> {
|
|
35
|
-
const isInteractive = options.interactive !== false;
|
|
36
|
-
|
|
37
|
-
// Resolve sprint
|
|
38
|
-
let sprintId: string;
|
|
39
|
-
try {
|
|
40
|
-
sprintId = await resolveSprintId();
|
|
41
|
-
} catch {
|
|
42
|
-
showWarning('No current sprint set.');
|
|
43
|
-
showTip('Create a sprint first or set one with: ralphctl sprint current');
|
|
44
|
-
log.newline();
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const sprint = await getSprint(sprintId);
|
|
49
|
-
|
|
50
|
-
// Must be draft
|
|
51
|
-
try {
|
|
52
|
-
assertSprintStatus(sprint, ['draft'], 'refine ticket');
|
|
53
|
-
} catch (err) {
|
|
54
|
-
if (err instanceof Error) {
|
|
55
|
-
showError(err.message);
|
|
56
|
-
log.newline();
|
|
57
|
-
}
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Find approved tickets
|
|
62
|
-
const approvedTickets = sprint.tickets.filter((t) => t.requirementStatus === 'approved');
|
|
63
|
-
if (approvedTickets.length === 0) {
|
|
64
|
-
showWarning('No approved tickets to re-refine.');
|
|
65
|
-
showTip('Run "ralphctl sprint refine" to refine pending tickets first.');
|
|
66
|
-
log.newline();
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Resolve ticket ID
|
|
71
|
-
let resolvedId = ticketId;
|
|
72
|
-
if (!resolvedId) {
|
|
73
|
-
if (!isInteractive) {
|
|
74
|
-
showError('Ticket ID is required in non-interactive mode');
|
|
75
|
-
exitWithCode(EXIT_ERROR);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const selected = await selectTicket('Select ticket to re-refine:', (t) => t.requirementStatus === 'approved');
|
|
79
|
-
if (!selected) return;
|
|
80
|
-
resolvedId = selected;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Find the ticket
|
|
84
|
-
const ticket = sprint.tickets.find((t) => t.id === resolvedId);
|
|
85
|
-
if (!ticket) {
|
|
86
|
-
showError(`Ticket not found: ${resolvedId}`);
|
|
87
|
-
if (!isInteractive) exitWithCode(EXIT_ERROR);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (ticket.requirementStatus !== 'approved') {
|
|
92
|
-
showError('Only approved tickets can be re-refined. Run "ralphctl sprint refine" for pending tickets.');
|
|
93
|
-
if (!isInteractive) exitWithCode(EXIT_ERROR);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Show ticket info
|
|
98
|
-
printHeader('Re-Refine Ticket', icons.ticket);
|
|
99
|
-
console.log(field('Sprint', sprint.name));
|
|
100
|
-
console.log(field('Ticket', formatTicketDisplay(ticket)));
|
|
101
|
-
console.log(field('Project', ticket.projectName));
|
|
102
|
-
if (ticket.link) {
|
|
103
|
-
console.log(field('Link', ticket.link));
|
|
104
|
-
}
|
|
105
|
-
if (ticket.description) {
|
|
106
|
-
console.log(fieldMultiline('Description', ticket.description));
|
|
107
|
-
}
|
|
108
|
-
log.newline();
|
|
109
|
-
|
|
110
|
-
// Load schema
|
|
111
|
-
const schemaPath = getSchemaPath('requirements-output.schema.json');
|
|
112
|
-
const schema = await readFile(schemaPath, 'utf-8');
|
|
113
|
-
|
|
114
|
-
const providerName = providerDisplayName(await resolveProvider());
|
|
115
|
-
|
|
116
|
-
// Confirm before starting AI session
|
|
117
|
-
const proceed = await confirm({
|
|
118
|
-
message: `${emoji.donut} Start ${providerName} re-refinement session?`,
|
|
119
|
-
default: true,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
if (!proceed) {
|
|
123
|
-
log.dim('Cancelled.');
|
|
124
|
-
log.newline();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Fetch live issue data if ticket has a link
|
|
129
|
-
let issueContext = '';
|
|
130
|
-
if (ticket.link) {
|
|
131
|
-
const fetchSpinner = createSpinner('Fetching issue data...');
|
|
132
|
-
fetchSpinner.start();
|
|
133
|
-
try {
|
|
134
|
-
const issueData = fetchIssueFromUrl(ticket.link);
|
|
135
|
-
if (issueData) {
|
|
136
|
-
issueContext = formatIssueContext(issueData);
|
|
137
|
-
fetchSpinner.succeed(`Issue data fetched (${String(issueData.comments.length)} comment(s))`);
|
|
138
|
-
} else {
|
|
139
|
-
fetchSpinner.stop();
|
|
140
|
-
}
|
|
141
|
-
} catch (err) {
|
|
142
|
-
fetchSpinner.fail('Could not fetch issue data');
|
|
143
|
-
if (err instanceof IssueFetchError || err instanceof Error) {
|
|
144
|
-
showWarning(`${err.message} — continuing without issue context`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Build prompt with existing requirements as context
|
|
150
|
-
const refineDir = getRefinementDir(sprintId, ticket.id);
|
|
151
|
-
await mkdir(refineDir, { recursive: true });
|
|
152
|
-
const outputFile = join(refineDir, 'requirements.json');
|
|
153
|
-
|
|
154
|
-
let ticketContent = formatTicketForPrompt(ticket);
|
|
155
|
-
if (ticket.requirements) {
|
|
156
|
-
ticketContent += '\n### Previously Approved Requirements\n\n';
|
|
157
|
-
ticketContent += ticket.requirements;
|
|
158
|
-
ticketContent += '\n';
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const prompt = buildTicketRefinePrompt(ticketContent, outputFile, schema, issueContext);
|
|
162
|
-
|
|
163
|
-
log.dim(`Working directory: ${refineDir}`);
|
|
164
|
-
log.dim(`Requirements output: ${outputFile}`);
|
|
165
|
-
log.newline();
|
|
166
|
-
|
|
167
|
-
const spinner = createSpinner(`Starting ${providerName} session...`);
|
|
168
|
-
spinner.start();
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
await runAiSession(refineDir, prompt, ticket.title);
|
|
172
|
-
spinner.succeed(`${providerName} session completed`);
|
|
173
|
-
} catch (err) {
|
|
174
|
-
spinner.fail(`${providerName} session failed`);
|
|
175
|
-
if (err instanceof Error) {
|
|
176
|
-
showError(err.message);
|
|
177
|
-
}
|
|
178
|
-
log.newline();
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
log.newline();
|
|
183
|
-
|
|
184
|
-
// Process the requirements file
|
|
185
|
-
if (!(await fileExists(outputFile))) {
|
|
186
|
-
showWarning('No requirements file found from AI session.');
|
|
187
|
-
log.newline();
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
let content: string;
|
|
192
|
-
try {
|
|
193
|
-
content = await readFile(outputFile, 'utf-8');
|
|
194
|
-
} catch {
|
|
195
|
-
showError(`Failed to read requirements file: ${outputFile}`);
|
|
196
|
-
log.newline();
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
let refinedRequirements: RefinedRequirement[];
|
|
201
|
-
try {
|
|
202
|
-
refinedRequirements = parseRequirementsFile(content);
|
|
203
|
-
} catch (err) {
|
|
204
|
-
if (err instanceof Error) {
|
|
205
|
-
showError(`Failed to parse requirements file: ${err.message}`);
|
|
206
|
-
}
|
|
207
|
-
log.newline();
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (refinedRequirements.length === 0) {
|
|
212
|
-
showWarning('No requirements found in output file.');
|
|
213
|
-
log.newline();
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Find matching requirements
|
|
218
|
-
const matchingRequirements = refinedRequirements.filter((r) => r.ref === ticket.id || r.ref === ticket.title);
|
|
219
|
-
|
|
220
|
-
if (matchingRequirements.length === 0) {
|
|
221
|
-
showWarning('Requirement reference does not match this ticket.');
|
|
222
|
-
log.newline();
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Combine multiple requirements into one (safety net for split outputs)
|
|
227
|
-
const requirement: RefinedRequirement =
|
|
228
|
-
matchingRequirements.length === 1
|
|
229
|
-
? {
|
|
230
|
-
ref: matchingRequirements[0]?.ref ?? '',
|
|
231
|
-
requirements: matchingRequirements[0]?.requirements ?? '',
|
|
232
|
-
}
|
|
233
|
-
: {
|
|
234
|
-
ref: matchingRequirements[0]?.ref ?? '',
|
|
235
|
-
requirements: matchingRequirements
|
|
236
|
-
.map((r, idx) => {
|
|
237
|
-
const text = r.requirements.trim();
|
|
238
|
-
if (/^#\s/.test(text)) return text;
|
|
239
|
-
return `# ${String(idx + 1)}. Section ${String(idx + 1)}\n\n${text}`;
|
|
240
|
-
})
|
|
241
|
-
.join('\n\n---\n\n'),
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// Show requirement for review
|
|
245
|
-
const reqLines = requirement.requirements.split('\n');
|
|
246
|
-
console.log(renderCard(`${icons.ticket} Re-Refined Requirements`, reqLines));
|
|
247
|
-
log.newline();
|
|
248
|
-
|
|
249
|
-
const approveRequirement = await confirm({
|
|
250
|
-
message: `${emoji.donut} Approve these requirements?`,
|
|
251
|
-
default: true,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
if (approveRequirement) {
|
|
255
|
-
const ticketIdx = sprint.tickets.findIndex((t) => t.id === ticket.id);
|
|
256
|
-
const ticketToSave = sprint.tickets[ticketIdx];
|
|
257
|
-
if (ticketIdx !== -1 && ticketToSave) {
|
|
258
|
-
ticketToSave.requirements = requirement.requirements;
|
|
259
|
-
// Keep requirementStatus as 'approved'
|
|
260
|
-
}
|
|
261
|
-
await saveSprint(sprint);
|
|
262
|
-
showSuccess('Requirements updated and saved!');
|
|
263
|
-
} else {
|
|
264
|
-
log.dim('Requirements not approved. Previous requirements unchanged.');
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
log.newline();
|
|
268
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { confirm } from '@inquirer/prompts';
|
|
2
|
-
import { muted } from '@src/theme/index.ts';
|
|
3
|
-
import { formatTicketDisplay, getTicket, removeTicket, TicketNotFoundError } from '@src/store/ticket.ts';
|
|
4
|
-
import { SprintStatusError } from '@src/store/sprint.ts';
|
|
5
|
-
import { selectTicket } from '@src/interactive/selectors.ts';
|
|
6
|
-
import { log, showError, showNextStep, showSuccess } from '@src/theme/ui.ts';
|
|
7
|
-
|
|
8
|
-
export async function ticketRemoveCommand(args: string[]): Promise<void> {
|
|
9
|
-
const skipConfirm = args.includes('-y') || args.includes('--yes');
|
|
10
|
-
let ticketId = args.find((a) => !a.startsWith('-'));
|
|
11
|
-
|
|
12
|
-
if (!ticketId) {
|
|
13
|
-
const selected = await selectTicket('Select ticket to remove:');
|
|
14
|
-
if (!selected) return;
|
|
15
|
-
ticketId = selected;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const ticket = await getTicket(ticketId);
|
|
20
|
-
|
|
21
|
-
if (!skipConfirm) {
|
|
22
|
-
const confirmed = await confirm({
|
|
23
|
-
message: `Remove ticket ${formatTicketDisplay(ticket)}?`,
|
|
24
|
-
default: false,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
if (!confirmed) {
|
|
28
|
-
console.log(muted('\nTicket removal cancelled.\n'));
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
await removeTicket(ticketId);
|
|
34
|
-
showSuccess('Ticket removed', [['ID', ticketId]]);
|
|
35
|
-
log.newline();
|
|
36
|
-
} catch (err) {
|
|
37
|
-
if (err instanceof TicketNotFoundError) {
|
|
38
|
-
showError(`Ticket not found: ${ticketId}`);
|
|
39
|
-
showNextStep('ralphctl ticket list', 'see available tickets');
|
|
40
|
-
log.newline();
|
|
41
|
-
} else if (err instanceof SprintStatusError) {
|
|
42
|
-
showError(err.message);
|
|
43
|
-
log.newline();
|
|
44
|
-
} else {
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { muted } from '@src/theme/index.ts';
|
|
2
|
-
import { getTicket, TicketNotFoundError } from '@src/store/ticket.ts';
|
|
3
|
-
import { getProject } from '@src/store/project.ts';
|
|
4
|
-
import { selectTicket } from '@src/interactive/selectors.ts';
|
|
5
|
-
import { badge, icons, labelValue, log, renderCard, showError, showNextStep } from '@src/theme/ui.ts';
|
|
6
|
-
|
|
7
|
-
export async function ticketShowCommand(args: string[]): Promise<void> {
|
|
8
|
-
let ticketId = args[0];
|
|
9
|
-
|
|
10
|
-
if (!ticketId) {
|
|
11
|
-
const selected = await selectTicket('Select ticket to show:');
|
|
12
|
-
if (!selected) return;
|
|
13
|
-
ticketId = selected;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const ticket = await getTicket(ticketId);
|
|
18
|
-
|
|
19
|
-
const reqBadge = ticket.requirementStatus === 'approved' ? badge('approved', 'success') : badge('pending', 'muted');
|
|
20
|
-
|
|
21
|
-
// Ticket info card
|
|
22
|
-
const infoLines: string[] = [labelValue('ID', ticket.id)];
|
|
23
|
-
infoLines.push(labelValue('Project', ticket.projectName));
|
|
24
|
-
infoLines.push(labelValue('Requirements', reqBadge));
|
|
25
|
-
|
|
26
|
-
if (ticket.link) {
|
|
27
|
-
infoLines.push(labelValue('Link', ticket.link));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Repositories
|
|
31
|
-
try {
|
|
32
|
-
const project = await getProject(ticket.projectName);
|
|
33
|
-
infoLines.push('');
|
|
34
|
-
for (const repo of project.repositories) {
|
|
35
|
-
infoLines.push(` ${icons.bullet} ${repo.name} ${muted('→')} ${muted(repo.path)}`);
|
|
36
|
-
}
|
|
37
|
-
} catch {
|
|
38
|
-
infoLines.push(labelValue('Repositories', muted('(project not found)')));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
log.newline();
|
|
42
|
-
console.log(renderCard(`${icons.ticket} ${ticket.title}`, infoLines));
|
|
43
|
-
|
|
44
|
-
// Description card (if present)
|
|
45
|
-
if (ticket.description) {
|
|
46
|
-
log.newline();
|
|
47
|
-
const descLines: string[] = [];
|
|
48
|
-
for (const line of ticket.description.split('\n')) {
|
|
49
|
-
descLines.push(line);
|
|
50
|
-
}
|
|
51
|
-
console.log(renderCard(`${icons.edit} Description`, descLines));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Affected repositories card (if set from planning)
|
|
55
|
-
if (ticket.affectedRepositories && ticket.affectedRepositories.length > 0) {
|
|
56
|
-
log.newline();
|
|
57
|
-
const affectedLines: string[] = [];
|
|
58
|
-
for (const repoPath of ticket.affectedRepositories) {
|
|
59
|
-
affectedLines.push(`${icons.bullet} ${repoPath}`);
|
|
60
|
-
}
|
|
61
|
-
console.log(renderCard(`${icons.project} Affected Repositories`, affectedLines));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
log.newline();
|
|
65
|
-
} catch (err) {
|
|
66
|
-
if (err instanceof TicketNotFoundError) {
|
|
67
|
-
showError(`Ticket not found: ${ticketId}`);
|
|
68
|
-
showNextStep('ralphctl ticket list', 'see available tickets');
|
|
69
|
-
log.newline();
|
|
70
|
-
} else {
|
|
71
|
-
throw err;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
package/src/completion/handle.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Check for shell completion environment variables and handle the completion request.
|
|
5
|
-
* Returns `true` if a completion was handled (caller should exit), `false` otherwise.
|
|
6
|
-
*
|
|
7
|
-
* This runs BEFORE banner/interactive mode — must not produce any extra output.
|
|
8
|
-
*/
|
|
9
|
-
export async function handleCompletionRequest(program: Command): Promise<boolean> {
|
|
10
|
-
const env = process.env;
|
|
11
|
-
|
|
12
|
-
// tabtab sets these env vars when the shell triggers completion
|
|
13
|
-
if (!env['COMP_CWORD'] || !env['COMP_POINT'] || !env['COMP_LINE']) {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const tabtab = (await import('tabtab')).default;
|
|
18
|
-
const { resolveCompletions } = await import('@src/completion/resolver.ts');
|
|
19
|
-
|
|
20
|
-
const tabEnv = tabtab.parseEnv(env);
|
|
21
|
-
|
|
22
|
-
const completions = await resolveCompletions(program, {
|
|
23
|
-
line: tabEnv.line,
|
|
24
|
-
last: tabEnv.last,
|
|
25
|
-
prev: tabEnv.prev,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
tabtab.log(completions);
|
|
29
|
-
return true;
|
|
30
|
-
}
|