ralphctl 0.2.5 → 0.3.1
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/dist/add-CIM72NE3.mjs +18 -0
- package/dist/add-GX7P7XTT.mjs +16 -0
- package/dist/bootstrap-FMHG6DRY.mjs +11 -0
- package/dist/chunk-3QBEBKMZ.mjs +103 -0
- package/dist/{chunk-EDJX7TT6.mjs → chunk-57UWLHRH.mjs} +22 -2
- package/dist/chunk-747KW2RW.mjs +24 -0
- package/dist/chunk-7JLZQICD.mjs +228 -0
- package/dist/{chunk-7TG3EAQ2.mjs → chunk-CFUVE2BP.mjs} +1 -5
- package/dist/chunk-CSC4TBJB.mjs +5546 -0
- package/dist/{chunk-IB6OCKZW.mjs → chunk-CTP2A436.mjs} +60 -55
- package/dist/{chunk-UBPZHHCD.mjs → chunk-D2YGPLIV.mjs} +84 -41
- package/dist/chunk-EPDR6VO5.mjs +5109 -0
- package/dist/{chunk-QBXHAXHI.mjs → chunk-FKMKOWLA.mjs} +154 -208
- package/dist/{chunk-OEUJDSHY.mjs → chunk-IWXBJD2D.mjs} +1 -1
- package/dist/chunk-JOQO4HMM.mjs +269 -0
- package/dist/{chunk-EUNAUHC3.mjs → chunk-NUYQK5MN.mjs} +80 -29
- package/dist/{chunk-JRFOUFD3.mjs → chunk-YCDUVPRT.mjs} +32 -52
- package/dist/cli.mjs +171 -3996
- package/dist/create-7WFSCMP4.mjs +15 -0
- package/dist/{handle-TA4MYNQJ.mjs → handle-BBAZJ44Y.mjs} +2 -2
- package/dist/mount-U7QXVB5Q.mjs +6804 -0
- package/dist/{project-YONEJICR.mjs → project-2IE7VWDB.mjs} +9 -5
- package/dist/prompts/harness-context.md +3 -3
- package/dist/prompts/ideate-auto.md +8 -10
- package/dist/prompts/ideate.md +3 -2
- package/dist/prompts/plan-auto.md +12 -12
- package/dist/prompts/plan-common.md +47 -19
- package/dist/prompts/plan-interactive.md +8 -8
- package/dist/prompts/signals-evaluation.md +1 -1
- package/dist/prompts/sprint-feedback.md +48 -0
- package/dist/prompts/task-evaluation-resume.md +12 -5
- package/dist/prompts/task-evaluation.md +37 -33
- package/dist/prompts/task-execution.md +33 -24
- package/dist/prompts/ticket-refine.md +6 -5
- package/dist/prompts/validation-checklist.md +10 -10
- package/dist/{resolver-RXEY6EJE.mjs → resolver-EOE5WUMV.mjs} +5 -5
- package/dist/{sprint-FGLWYWKX.mjs → sprint-OGOFEJJH.mjs} +7 -9
- package/dist/start-WG7VMEB2.mjs +17 -0
- package/package.json +15 -13
- package/dist/add-3T225IX5.mjs +0 -16
- package/dist/add-6A5432U2.mjs +0 -16
- package/dist/chunk-742XQ7FL.mjs +0 -551
- package/dist/chunk-7LZ6GOGN.mjs +0 -53
- package/dist/chunk-CSICORGV.mjs +0 -4333
- package/dist/chunk-DUU5346E.mjs +0 -59
- package/dist/create-MYGOWO2F.mjs +0 -12
- package/dist/multiline-OHSNFCRG.mjs +0 -40
- package/dist/wizard-XZ7OGBCJ.mjs +0 -193
- package/schemas/config.schema.json +0 -30
- package/schemas/ideate-output.schema.json +0 -22
- package/schemas/projects.schema.json +0 -58
- package/schemas/requirements-output.schema.json +0 -24
- package/schemas/sprint.schema.json +0 -109
- package/schemas/task-import.schema.json +0 -56
- package/schemas/tasks.schema.json +0 -98
package/dist/chunk-742XQ7FL.mjs
DELETED
|
@@ -1,551 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
EXIT_ERROR,
|
|
4
|
-
exitWithCode
|
|
5
|
-
} from "./chunk-7TG3EAQ2.mjs";
|
|
6
|
-
import {
|
|
7
|
-
listProjects,
|
|
8
|
-
projectExists
|
|
9
|
-
} from "./chunk-EUNAUHC3.mjs";
|
|
10
|
-
import {
|
|
11
|
-
assertSprintStatus,
|
|
12
|
-
generateUuid8,
|
|
13
|
-
getEditor,
|
|
14
|
-
resolveSprintId,
|
|
15
|
-
setEditor
|
|
16
|
-
} from "./chunk-JRFOUFD3.mjs";
|
|
17
|
-
import {
|
|
18
|
-
ensureError,
|
|
19
|
-
unwrapOrThrow,
|
|
20
|
-
wrapAsync
|
|
21
|
-
} from "./chunk-OEUJDSHY.mjs";
|
|
22
|
-
import {
|
|
23
|
-
SprintSchema,
|
|
24
|
-
getSprintFilePath,
|
|
25
|
-
readValidatedJson,
|
|
26
|
-
writeValidatedJson
|
|
27
|
-
} from "./chunk-IB6OCKZW.mjs";
|
|
28
|
-
import {
|
|
29
|
-
IOError,
|
|
30
|
-
IssueFetchError,
|
|
31
|
-
ProjectNotFoundError,
|
|
32
|
-
SprintStatusError,
|
|
33
|
-
TicketNotFoundError
|
|
34
|
-
} from "./chunk-EDJX7TT6.mjs";
|
|
35
|
-
import {
|
|
36
|
-
createSpinner,
|
|
37
|
-
emoji,
|
|
38
|
-
error,
|
|
39
|
-
field,
|
|
40
|
-
fieldMultiline,
|
|
41
|
-
icons,
|
|
42
|
-
log,
|
|
43
|
-
muted,
|
|
44
|
-
renderCard,
|
|
45
|
-
showEmpty,
|
|
46
|
-
showError,
|
|
47
|
-
showSuccess,
|
|
48
|
-
showWarning
|
|
49
|
-
} from "./chunk-QBXHAXHI.mjs";
|
|
50
|
-
|
|
51
|
-
// src/commands/ticket/add.ts
|
|
52
|
-
import { confirm, input, select as select2 } from "@inquirer/prompts";
|
|
53
|
-
import { Result as Result3 } from "typescript-result";
|
|
54
|
-
|
|
55
|
-
// src/utils/editor-input.ts
|
|
56
|
-
import { editor } from "@inquirer/prompts";
|
|
57
|
-
import { Result } from "typescript-result";
|
|
58
|
-
|
|
59
|
-
// src/utils/editor.ts
|
|
60
|
-
import { select } from "@inquirer/prompts";
|
|
61
|
-
async function resolveEditor() {
|
|
62
|
-
const stored = await getEditor();
|
|
63
|
-
if (stored) return stored;
|
|
64
|
-
const choice = await select({
|
|
65
|
-
message: `${emoji.donut} Which editor should open for multiline input?`,
|
|
66
|
-
choices: [
|
|
67
|
-
{ name: "Sublime Text", value: "subl -w" },
|
|
68
|
-
{ name: "VS Code", value: "code --wait" },
|
|
69
|
-
{ name: "Vim", value: "vim" },
|
|
70
|
-
{ name: "Nano", value: "nano" },
|
|
71
|
-
{ name: "Use $EDITOR env var", value: "__env__" }
|
|
72
|
-
]
|
|
73
|
-
});
|
|
74
|
-
if (choice === "__env__") {
|
|
75
|
-
const envEditor = process.env["VISUAL"] ?? process.env["EDITOR"];
|
|
76
|
-
if (!envEditor) {
|
|
77
|
-
await setEditor("vim");
|
|
78
|
-
return "vim";
|
|
79
|
-
}
|
|
80
|
-
await setEditor(envEditor);
|
|
81
|
-
return envEditor;
|
|
82
|
-
}
|
|
83
|
-
await setEditor(choice);
|
|
84
|
-
return choice;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/utils/editor-input.ts
|
|
88
|
-
async function editorInput(options) {
|
|
89
|
-
if (!process.stdin.isTTY) {
|
|
90
|
-
const { multilineInput } = await import("./multiline-OHSNFCRG.mjs");
|
|
91
|
-
const value = await multilineInput({ message: options.message, default: options.default });
|
|
92
|
-
return Result.ok(value);
|
|
93
|
-
}
|
|
94
|
-
const editorCmd = await resolveEditor();
|
|
95
|
-
const prevVisual = process.env["VISUAL"];
|
|
96
|
-
process.env["VISUAL"] = editorCmd;
|
|
97
|
-
try {
|
|
98
|
-
const result = await editor({
|
|
99
|
-
message: options.message,
|
|
100
|
-
default: options.default,
|
|
101
|
-
postfix: ".md"
|
|
102
|
-
});
|
|
103
|
-
return Result.ok(result.trim());
|
|
104
|
-
} catch (err) {
|
|
105
|
-
return Result.error(
|
|
106
|
-
new IOError(
|
|
107
|
-
`Editor failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
108
|
-
err instanceof Error ? err : void 0
|
|
109
|
-
)
|
|
110
|
-
);
|
|
111
|
-
} finally {
|
|
112
|
-
if (prevVisual === void 0) delete process.env["VISUAL"];
|
|
113
|
-
else process.env["VISUAL"] = prevVisual;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/store/ticket.ts
|
|
118
|
-
async function getSprintData(sprintId) {
|
|
119
|
-
const id = await resolveSprintId(sprintId);
|
|
120
|
-
const result = await readValidatedJson(getSprintFilePath(id), SprintSchema);
|
|
121
|
-
if (!result.ok) throw result.error;
|
|
122
|
-
return result.value;
|
|
123
|
-
}
|
|
124
|
-
async function saveSprintData(sprint) {
|
|
125
|
-
const result = await writeValidatedJson(getSprintFilePath(sprint.id), sprint, SprintSchema);
|
|
126
|
-
if (!result.ok) throw result.error;
|
|
127
|
-
}
|
|
128
|
-
async function addTicket(input2, sprintId) {
|
|
129
|
-
const sprint = await getSprintData(sprintId);
|
|
130
|
-
assertSprintStatus(sprint, ["draft"], "add tickets");
|
|
131
|
-
if (!await projectExists(input2.projectName)) {
|
|
132
|
-
throw new ProjectNotFoundError(input2.projectName);
|
|
133
|
-
}
|
|
134
|
-
const ticket = {
|
|
135
|
-
id: generateUuid8(),
|
|
136
|
-
title: input2.title,
|
|
137
|
-
description: input2.description,
|
|
138
|
-
link: input2.link,
|
|
139
|
-
projectName: input2.projectName,
|
|
140
|
-
requirementStatus: "pending"
|
|
141
|
-
};
|
|
142
|
-
sprint.tickets.push(ticket);
|
|
143
|
-
await saveSprintData(sprint);
|
|
144
|
-
return ticket;
|
|
145
|
-
}
|
|
146
|
-
async function updateTicket(ticketId, updates, sprintId) {
|
|
147
|
-
const sprint = await getSprintData(sprintId);
|
|
148
|
-
assertSprintStatus(sprint, ["draft"], "update tickets");
|
|
149
|
-
const ticketIdx = sprint.tickets.findIndex((t) => t.id === ticketId);
|
|
150
|
-
if (ticketIdx === -1) {
|
|
151
|
-
throw new TicketNotFoundError(ticketId);
|
|
152
|
-
}
|
|
153
|
-
const ticket = sprint.tickets[ticketIdx];
|
|
154
|
-
if (!ticket) {
|
|
155
|
-
throw new TicketNotFoundError(ticketId);
|
|
156
|
-
}
|
|
157
|
-
if (updates.title !== void 0) {
|
|
158
|
-
ticket.title = updates.title;
|
|
159
|
-
}
|
|
160
|
-
if (updates.description !== void 0) {
|
|
161
|
-
ticket.description = updates.description || void 0;
|
|
162
|
-
}
|
|
163
|
-
if (updates.link !== void 0) {
|
|
164
|
-
ticket.link = updates.link || void 0;
|
|
165
|
-
}
|
|
166
|
-
await saveSprintData(sprint);
|
|
167
|
-
return ticket;
|
|
168
|
-
}
|
|
169
|
-
async function removeTicket(ticketId, sprintId) {
|
|
170
|
-
const sprint = await getSprintData(sprintId);
|
|
171
|
-
assertSprintStatus(sprint, ["draft"], "remove tickets");
|
|
172
|
-
const index = sprint.tickets.findIndex((t) => t.id === ticketId);
|
|
173
|
-
if (index === -1) {
|
|
174
|
-
throw new TicketNotFoundError(ticketId);
|
|
175
|
-
}
|
|
176
|
-
sprint.tickets.splice(index, 1);
|
|
177
|
-
await saveSprintData(sprint);
|
|
178
|
-
}
|
|
179
|
-
async function listTickets(sprintId) {
|
|
180
|
-
const sprint = await getSprintData(sprintId);
|
|
181
|
-
return sprint.tickets;
|
|
182
|
-
}
|
|
183
|
-
async function getTicket(ticketId, sprintId) {
|
|
184
|
-
const sprint = await getSprintData(sprintId);
|
|
185
|
-
const ticket = sprint.tickets.find((t) => t.id === ticketId);
|
|
186
|
-
if (!ticket) {
|
|
187
|
-
throw new TicketNotFoundError(ticketId);
|
|
188
|
-
}
|
|
189
|
-
return ticket;
|
|
190
|
-
}
|
|
191
|
-
function groupTicketsByProject(tickets) {
|
|
192
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
193
|
-
for (const ticket of tickets) {
|
|
194
|
-
const existing = grouped.get(ticket.projectName) ?? [];
|
|
195
|
-
existing.push(ticket);
|
|
196
|
-
grouped.set(ticket.projectName, existing);
|
|
197
|
-
}
|
|
198
|
-
return grouped;
|
|
199
|
-
}
|
|
200
|
-
function allRequirementsApproved(tickets) {
|
|
201
|
-
return tickets.length > 0 && tickets.every((t) => t.requirementStatus === "approved");
|
|
202
|
-
}
|
|
203
|
-
function getPendingRequirements(tickets) {
|
|
204
|
-
return tickets.filter((t) => t.requirementStatus === "pending");
|
|
205
|
-
}
|
|
206
|
-
function formatTicketDisplay(ticket) {
|
|
207
|
-
return `[${ticket.id}] ${ticket.title}`;
|
|
208
|
-
}
|
|
209
|
-
function formatTicketId(ticket) {
|
|
210
|
-
return ticket.id;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// src/utils/issue-fetch.ts
|
|
214
|
-
import { spawnSync } from "child_process";
|
|
215
|
-
import { Result as Result2 } from "typescript-result";
|
|
216
|
-
var MAX_COMMENTS = 20;
|
|
217
|
-
function parseIssueUrl(url) {
|
|
218
|
-
let parsed;
|
|
219
|
-
try {
|
|
220
|
-
parsed = new URL(url);
|
|
221
|
-
} catch {
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
228
|
-
if (parsed.hostname === "github.com") {
|
|
229
|
-
const owner = segments[0];
|
|
230
|
-
const repo = segments[1];
|
|
231
|
-
if (segments.length >= 4 && segments[2] === "issues" && owner && repo) {
|
|
232
|
-
const num = Number(segments[3]);
|
|
233
|
-
if (Number.isInteger(num) && num > 0) {
|
|
234
|
-
return { host: "github", hostname: parsed.hostname, owner, repo, number: num };
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return null;
|
|
238
|
-
}
|
|
239
|
-
const dashIdx = segments.indexOf("-");
|
|
240
|
-
if (dashIdx >= 2 && segments[dashIdx + 1] === "issues") {
|
|
241
|
-
const num = Number(segments[dashIdx + 2]);
|
|
242
|
-
if (Number.isInteger(num) && num > 0) {
|
|
243
|
-
const repo = segments[dashIdx - 1];
|
|
244
|
-
if (repo) {
|
|
245
|
-
const owner = segments.slice(0, dashIdx - 1).join("/");
|
|
246
|
-
return { host: "gitlab", hostname: parsed.hostname, owner, repo, number: num };
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
function fetchGitHubIssueResult(parsed) {
|
|
253
|
-
const result = spawnSync(
|
|
254
|
-
"gh",
|
|
255
|
-
[
|
|
256
|
-
"issue",
|
|
257
|
-
"view",
|
|
258
|
-
String(parsed.number),
|
|
259
|
-
"--repo",
|
|
260
|
-
`${parsed.owner}/${parsed.repo}`,
|
|
261
|
-
"--json",
|
|
262
|
-
"title,body,comments"
|
|
263
|
-
],
|
|
264
|
-
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
265
|
-
);
|
|
266
|
-
if (result.status !== 0) {
|
|
267
|
-
const stderr = result.stderr.trim();
|
|
268
|
-
return Result2.error(new IssueFetchError(`gh issue view failed: ${stderr || "unknown error"}`));
|
|
269
|
-
}
|
|
270
|
-
const data = JSON.parse(result.stdout);
|
|
271
|
-
const comments = (data.comments ?? []).slice(-MAX_COMMENTS).map((c) => ({
|
|
272
|
-
author: c.author?.login ?? "unknown",
|
|
273
|
-
createdAt: c.createdAt ?? "",
|
|
274
|
-
body: c.body ?? ""
|
|
275
|
-
}));
|
|
276
|
-
return Result2.ok({
|
|
277
|
-
title: data.title ?? "",
|
|
278
|
-
body: data.body ?? "",
|
|
279
|
-
comments,
|
|
280
|
-
url: `https://${parsed.hostname}/${parsed.owner}/${parsed.repo}/issues/${String(parsed.number)}`
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
function fetchGitLabIssueResult(parsed) {
|
|
284
|
-
const result = spawnSync(
|
|
285
|
-
"glab",
|
|
286
|
-
["issue", "view", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--output", "json"],
|
|
287
|
-
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
288
|
-
);
|
|
289
|
-
if (result.status !== 0) {
|
|
290
|
-
const stderr = result.stderr.trim();
|
|
291
|
-
return Result2.error(new IssueFetchError(`glab issue view failed: ${stderr || "unknown error"}`));
|
|
292
|
-
}
|
|
293
|
-
const data = JSON.parse(result.stdout);
|
|
294
|
-
const notesResult = spawnSync(
|
|
295
|
-
"glab",
|
|
296
|
-
["issue", "note", "list", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--output", "json"],
|
|
297
|
-
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
298
|
-
);
|
|
299
|
-
let comments = [];
|
|
300
|
-
if (notesResult.status === 0 && notesResult.stdout.trim()) {
|
|
301
|
-
try {
|
|
302
|
-
const notes = JSON.parse(notesResult.stdout);
|
|
303
|
-
comments = notes.slice(-MAX_COMMENTS).map((n) => ({
|
|
304
|
-
author: n.author?.username ?? "unknown",
|
|
305
|
-
createdAt: n.created_at ?? "",
|
|
306
|
-
body: n.body ?? ""
|
|
307
|
-
}));
|
|
308
|
-
} catch {
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return Result2.ok({
|
|
312
|
-
title: data.title ?? "",
|
|
313
|
-
body: data.description ?? "",
|
|
314
|
-
comments,
|
|
315
|
-
url: `https://${parsed.hostname}/${parsed.owner}/${parsed.repo}/-/issues/${String(parsed.number)}`
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
function fetchIssueResult(parsed) {
|
|
319
|
-
if (parsed.host === "github") {
|
|
320
|
-
return fetchGitHubIssueResult(parsed);
|
|
321
|
-
}
|
|
322
|
-
return fetchGitLabIssueResult(parsed);
|
|
323
|
-
}
|
|
324
|
-
function fetchIssue(parsed) {
|
|
325
|
-
return unwrapOrThrow(fetchIssueResult(parsed));
|
|
326
|
-
}
|
|
327
|
-
function fetchIssueFromUrl(url) {
|
|
328
|
-
const parsed = parseIssueUrl(url);
|
|
329
|
-
if (!parsed) return null;
|
|
330
|
-
return fetchIssue(parsed);
|
|
331
|
-
}
|
|
332
|
-
function formatIssueContext(data) {
|
|
333
|
-
const lines = [];
|
|
334
|
-
lines.push("## Source Issue Data");
|
|
335
|
-
lines.push("");
|
|
336
|
-
lines.push(`> Fetched live from ${data.url}`);
|
|
337
|
-
lines.push("");
|
|
338
|
-
lines.push(`**Title:** ${data.title}`);
|
|
339
|
-
lines.push("");
|
|
340
|
-
if (data.body) {
|
|
341
|
-
lines.push("**Body:**");
|
|
342
|
-
lines.push("");
|
|
343
|
-
lines.push(data.body);
|
|
344
|
-
lines.push("");
|
|
345
|
-
}
|
|
346
|
-
if (data.comments.length > 0) {
|
|
347
|
-
lines.push(`**Comments (${String(data.comments.length)}):**`);
|
|
348
|
-
lines.push("");
|
|
349
|
-
for (const comment of data.comments) {
|
|
350
|
-
const timestamp = comment.createdAt ? ` (${comment.createdAt})` : "";
|
|
351
|
-
lines.push(`---`);
|
|
352
|
-
lines.push(`**@${comment.author}**${timestamp}:`);
|
|
353
|
-
lines.push("");
|
|
354
|
-
lines.push(comment.body);
|
|
355
|
-
lines.push("");
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return lines.join("\n");
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// src/commands/ticket/add.ts
|
|
362
|
-
function tryFetchIssue(url) {
|
|
363
|
-
const spinner = createSpinner("Fetching issue data...");
|
|
364
|
-
spinner.start();
|
|
365
|
-
const fetchR = Result3.try(() => fetchIssueFromUrl(url));
|
|
366
|
-
if (!fetchR.ok) {
|
|
367
|
-
spinner.fail("Could not fetch issue data");
|
|
368
|
-
showWarning(fetchR.error.message);
|
|
369
|
-
log.newline();
|
|
370
|
-
return void 0;
|
|
371
|
-
}
|
|
372
|
-
const data = fetchR.value;
|
|
373
|
-
if (!data) {
|
|
374
|
-
spinner.stop();
|
|
375
|
-
return void 0;
|
|
376
|
-
}
|
|
377
|
-
spinner.succeed("Issue data fetched");
|
|
378
|
-
log.newline();
|
|
379
|
-
const bodyPreview = data.body.length > 200 ? data.body.slice(0, 200) + "..." : data.body;
|
|
380
|
-
const cardLines = [`Title: ${data.title}`, "", bodyPreview];
|
|
381
|
-
if (data.comments.length > 0) {
|
|
382
|
-
cardLines.push("", `${String(data.comments.length)} comment(s)`);
|
|
383
|
-
}
|
|
384
|
-
console.log(renderCard(`${icons.info} Fetched Issue`, cardLines));
|
|
385
|
-
log.newline();
|
|
386
|
-
return data;
|
|
387
|
-
}
|
|
388
|
-
function validateUrl(url) {
|
|
389
|
-
try {
|
|
390
|
-
new URL(url);
|
|
391
|
-
return true;
|
|
392
|
-
} catch {
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
async function addSingleTicketNonInteractive(options) {
|
|
397
|
-
const errors = [];
|
|
398
|
-
const trimmedTitle = options.title?.trim();
|
|
399
|
-
const trimmedProject = options.project?.trim();
|
|
400
|
-
if (!trimmedTitle) {
|
|
401
|
-
errors.push("--title is required");
|
|
402
|
-
}
|
|
403
|
-
if (!trimmedProject) {
|
|
404
|
-
errors.push("--project is required");
|
|
405
|
-
} else if (!await projectExists(trimmedProject)) {
|
|
406
|
-
errors.push(`Project '${trimmedProject}' does not exist. Add it first with 'ralphctl project add'.`);
|
|
407
|
-
}
|
|
408
|
-
if (options.link && !validateUrl(options.link)) {
|
|
409
|
-
errors.push("--link must be a valid URL");
|
|
410
|
-
}
|
|
411
|
-
if (errors.length > 0 || !trimmedTitle || !trimmedProject) {
|
|
412
|
-
showError("Validation failed");
|
|
413
|
-
for (const e of errors) {
|
|
414
|
-
log.item(error(e));
|
|
415
|
-
}
|
|
416
|
-
log.newline();
|
|
417
|
-
exitWithCode(EXIT_ERROR);
|
|
418
|
-
}
|
|
419
|
-
const title = trimmedTitle;
|
|
420
|
-
const trimmedDesc = options.description?.trim();
|
|
421
|
-
const description = trimmedDesc === "" ? void 0 : trimmedDesc;
|
|
422
|
-
const trimmedLink = options.link?.trim();
|
|
423
|
-
const link = trimmedLink === "" ? void 0 : trimmedLink;
|
|
424
|
-
const projectName = trimmedProject;
|
|
425
|
-
const addR = await wrapAsync(() => addTicket({ title, description, link, projectName }), ensureError);
|
|
426
|
-
if (!addR.ok) {
|
|
427
|
-
handleTicketError(addR.error);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
showTicketResult(addR.value);
|
|
431
|
-
}
|
|
432
|
-
async function addSingleTicketInteractive(options) {
|
|
433
|
-
const projects = await listProjects();
|
|
434
|
-
if (projects.length === 0) {
|
|
435
|
-
showEmpty("projects", "Add one first with: ralphctl project add");
|
|
436
|
-
return null;
|
|
437
|
-
}
|
|
438
|
-
const projectName = await select2({
|
|
439
|
-
message: `${icons.project} Project:`,
|
|
440
|
-
default: options.project ?? projects[0]?.name,
|
|
441
|
-
choices: projects.map((p) => ({
|
|
442
|
-
name: `${icons.project} ${p.name} ${muted(`- ${p.displayName}`)}`,
|
|
443
|
-
value: p.name
|
|
444
|
-
}))
|
|
445
|
-
});
|
|
446
|
-
const link = await input({
|
|
447
|
-
message: `${icons.info} Issue link (optional):`,
|
|
448
|
-
default: options.link?.trim(),
|
|
449
|
-
validate: (v) => {
|
|
450
|
-
if (!v) return true;
|
|
451
|
-
return validateUrl(v) ? true : "Invalid URL format";
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
const trimmedLink = link.trim();
|
|
455
|
-
const normalizedLink = trimmedLink === "" ? void 0 : trimmedLink;
|
|
456
|
-
let prefill;
|
|
457
|
-
if (normalizedLink) {
|
|
458
|
-
prefill = tryFetchIssue(normalizedLink);
|
|
459
|
-
}
|
|
460
|
-
let title = await input({
|
|
461
|
-
message: `${icons.ticket} Title:`,
|
|
462
|
-
default: prefill?.title ?? options.title?.trim(),
|
|
463
|
-
validate: (v) => v.trim().length > 0 ? true : "Title is required"
|
|
464
|
-
});
|
|
465
|
-
const descR = await editorInput({
|
|
466
|
-
message: "Description (recommended):",
|
|
467
|
-
default: prefill?.body ?? options.description?.trim()
|
|
468
|
-
});
|
|
469
|
-
if (!descR.ok) {
|
|
470
|
-
showError(`Editor input failed: ${descR.error.message}`);
|
|
471
|
-
return null;
|
|
472
|
-
}
|
|
473
|
-
const description = descR.value;
|
|
474
|
-
title = title.trim();
|
|
475
|
-
const trimmedDescription = description.trim();
|
|
476
|
-
const normalizedDescription = trimmedDescription === "" ? void 0 : trimmedDescription;
|
|
477
|
-
const addR = await wrapAsync(
|
|
478
|
-
() => addTicket({ title, description: normalizedDescription, link: normalizedLink, projectName }),
|
|
479
|
-
ensureError
|
|
480
|
-
);
|
|
481
|
-
if (!addR.ok) {
|
|
482
|
-
handleTicketError(addR.error);
|
|
483
|
-
return null;
|
|
484
|
-
}
|
|
485
|
-
showTicketResult(addR.value);
|
|
486
|
-
return addR.value;
|
|
487
|
-
}
|
|
488
|
-
function showTicketResult(ticket) {
|
|
489
|
-
showSuccess("Ticket added!", [
|
|
490
|
-
["ID", ticket.id],
|
|
491
|
-
["Title", ticket.title],
|
|
492
|
-
["Project", ticket.projectName]
|
|
493
|
-
]);
|
|
494
|
-
if (ticket.description) {
|
|
495
|
-
console.log(fieldMultiline("Description", ticket.description));
|
|
496
|
-
}
|
|
497
|
-
if (ticket.link) {
|
|
498
|
-
console.log(field("Link", ticket.link));
|
|
499
|
-
}
|
|
500
|
-
console.log("");
|
|
501
|
-
}
|
|
502
|
-
function handleTicketError(err) {
|
|
503
|
-
if (err instanceof SprintStatusError) {
|
|
504
|
-
showError(err.message);
|
|
505
|
-
} else if (err instanceof Error && err.message.includes("does not exist")) {
|
|
506
|
-
showError(err.message);
|
|
507
|
-
} else {
|
|
508
|
-
throw err;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
async function ticketAddCommand(options = {}) {
|
|
512
|
-
if (options.interactive === false) {
|
|
513
|
-
await addSingleTicketNonInteractive(options);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
let count = 0;
|
|
517
|
-
let lastProjectName = options.project;
|
|
518
|
-
while (true) {
|
|
519
|
-
const ticket = await addSingleTicketInteractive({ ...options, project: lastProjectName });
|
|
520
|
-
if (ticket) {
|
|
521
|
-
count++;
|
|
522
|
-
lastProjectName = ticket.projectName;
|
|
523
|
-
log.dim(`${String(count)} ticket(s) added in this session`);
|
|
524
|
-
} else {
|
|
525
|
-
break;
|
|
526
|
-
}
|
|
527
|
-
const another = await confirm({
|
|
528
|
-
message: `${emoji.donut} Add another ticket?`,
|
|
529
|
-
default: true
|
|
530
|
-
});
|
|
531
|
-
if (!another) break;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
export {
|
|
536
|
-
addTicket,
|
|
537
|
-
updateTicket,
|
|
538
|
-
removeTicket,
|
|
539
|
-
listTickets,
|
|
540
|
-
getTicket,
|
|
541
|
-
groupTicketsByProject,
|
|
542
|
-
allRequirementsApproved,
|
|
543
|
-
getPendingRequirements,
|
|
544
|
-
formatTicketDisplay,
|
|
545
|
-
formatTicketId,
|
|
546
|
-
editorInput,
|
|
547
|
-
fetchIssueFromUrl,
|
|
548
|
-
formatIssueContext,
|
|
549
|
-
addSingleTicketInteractive,
|
|
550
|
-
ticketAddCommand
|
|
551
|
-
};
|
package/dist/chunk-7LZ6GOGN.mjs
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
ensureError,
|
|
4
|
-
wrapAsync
|
|
5
|
-
} from "./chunk-OEUJDSHY.mjs";
|
|
6
|
-
|
|
7
|
-
// src/interactive/escapable.ts
|
|
8
|
-
import readline from "readline";
|
|
9
|
-
import { select } from "@inquirer/prompts";
|
|
10
|
-
import { bold, dim } from "colorette";
|
|
11
|
-
function defaultKeysHelpTip(keys) {
|
|
12
|
-
return keys.map(([key, action]) => `${bold(key)} ${dim(action)}`).join(dim(" \u2022 "));
|
|
13
|
-
}
|
|
14
|
-
function withEscapeHint(config, escLabel = "back") {
|
|
15
|
-
const originalTip = config.theme?.style?.keysHelpTip;
|
|
16
|
-
return {
|
|
17
|
-
...config,
|
|
18
|
-
theme: {
|
|
19
|
-
...config.theme,
|
|
20
|
-
style: {
|
|
21
|
-
...config.theme?.style,
|
|
22
|
-
keysHelpTip: (keys) => {
|
|
23
|
-
const allKeys = [...keys, ["esc", escLabel]];
|
|
24
|
-
return originalTip ? originalTip(allKeys) : defaultKeysHelpTip(allKeys);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
async function escapableSelect(config, options) {
|
|
31
|
-
const controller = new AbortController();
|
|
32
|
-
readline.emitKeypressEvents(process.stdin);
|
|
33
|
-
const onKeypress = (_ch, key) => {
|
|
34
|
-
if (key?.name === "escape") {
|
|
35
|
-
controller.abort();
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
process.stdin.on("keypress", onKeypress);
|
|
39
|
-
const r = await wrapAsync(
|
|
40
|
-
() => select(withEscapeHint(config, options?.escLabel ?? "back"), { signal: controller.signal }),
|
|
41
|
-
ensureError
|
|
42
|
-
);
|
|
43
|
-
process.stdin.removeListener("keypress", onKeypress);
|
|
44
|
-
if (!r.ok) {
|
|
45
|
-
if (r.error.name === "AbortPromptError") return null;
|
|
46
|
-
throw r.error;
|
|
47
|
-
}
|
|
48
|
-
return r.value;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export {
|
|
52
|
-
escapableSelect
|
|
53
|
-
};
|