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,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
- }
@@ -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
- }