@arvorco/relentless 0.2.0 → 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/.claude/commands/relentless.constitution.md +1 -1
- package/.claude/commands/relentless.specify.md +1 -1
- package/.claude/skills/specify/scripts/bash/create-new-feature.sh +2 -2
- package/.claude/skills/specify/scripts/bash/setup-plan.sh +1 -1
- package/CHANGELOG.md +95 -1
- package/README.md +205 -983
- package/Relentless.png +0 -0
- package/Relentless.svg +1 -0
- package/bin/relentless.ts +121 -0
- package/package.json +1 -1
- package/src/cli/queue.ts +406 -0
- package/src/execution/commands.ts +541 -0
- package/src/execution/runner.ts +236 -4
- package/src/prd/parser.ts +140 -0
- package/src/prd/types.ts +8 -6
- package/src/queue/index.ts +45 -0
- package/src/queue/loader.ts +97 -0
- package/src/queue/lock.ts +137 -0
- package/src/queue/parser.ts +142 -0
- package/src/queue/processor.ts +141 -0
- package/src/queue/types.ts +81 -0
- package/src/queue/writer.ts +210 -0
- package/src/tui/App.tsx +23 -0
- package/src/tui/TUIRunner.tsx +183 -2
- package/src/tui/components/QueueInput.tsx +160 -0
- package/src/tui/components/QueuePanel.tsx +169 -0
- package/src/tui/components/QueueRemoval.tsx +306 -0
- package/src/tui/hooks/useTUI.ts +6 -0
- package/src/tui/types.ts +13 -0
- package/.claude/skills/specify/scripts/bash/update-agent-context.sh +0 -799
package/bin/relentless.ts
CHANGED
|
@@ -427,6 +427,127 @@ agents
|
|
|
427
427
|
}
|
|
428
428
|
});
|
|
429
429
|
|
|
430
|
+
// Queue commands
|
|
431
|
+
const queue = program.command("queue").description("Manage queue for mid-run guidance");
|
|
432
|
+
|
|
433
|
+
queue
|
|
434
|
+
.command("add <message>")
|
|
435
|
+
.description("Add a message or command to the queue")
|
|
436
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
437
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
438
|
+
.action(async (message, options) => {
|
|
439
|
+
const { queueAdd, resolveFeaturePath } = await import("../src/cli/queue");
|
|
440
|
+
|
|
441
|
+
const resolved = await resolveFeaturePath(options.dir, options.feature);
|
|
442
|
+
if (resolved.error) {
|
|
443
|
+
console.error(chalk.red(resolved.error));
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const result = await queueAdd({
|
|
448
|
+
message,
|
|
449
|
+
featurePath: resolved.path!,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
if (result.success) {
|
|
453
|
+
console.log(chalk.green(`✓ ${result.message}`));
|
|
454
|
+
} else {
|
|
455
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
queue
|
|
461
|
+
.command("list")
|
|
462
|
+
.description("List queue contents for a feature")
|
|
463
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
464
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
465
|
+
.option("-a, --all", "Show all items including processed", false)
|
|
466
|
+
.action(async (options) => {
|
|
467
|
+
const { queueList, formatQueueList, resolveFeaturePath } = await import("../src/cli/queue");
|
|
468
|
+
|
|
469
|
+
const resolved = await resolveFeaturePath(options.dir, options.feature);
|
|
470
|
+
if (resolved.error) {
|
|
471
|
+
console.error(chalk.red(resolved.error));
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const result = await queueList({
|
|
476
|
+
featurePath: resolved.path!,
|
|
477
|
+
showAll: options.all,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
if (result.success) {
|
|
481
|
+
const output = formatQueueList({
|
|
482
|
+
...result,
|
|
483
|
+
featureName: options.feature,
|
|
484
|
+
});
|
|
485
|
+
console.log(output);
|
|
486
|
+
} else {
|
|
487
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
queue
|
|
493
|
+
.command("remove <index>")
|
|
494
|
+
.description("Remove an item from the queue by index (1-based)")
|
|
495
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
496
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
497
|
+
.action(async (index, options) => {
|
|
498
|
+
const { queueRemove, resolveFeaturePath } = await import("../src/cli/queue");
|
|
499
|
+
|
|
500
|
+
const resolved = await resolveFeaturePath(options.dir, options.feature);
|
|
501
|
+
if (resolved.error) {
|
|
502
|
+
console.error(chalk.red(resolved.error));
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const parsedIndex = parseInt(index, 10);
|
|
507
|
+
if (isNaN(parsedIndex)) {
|
|
508
|
+
console.error(chalk.red(`Invalid index: ${index}. Must be a number`));
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const result = await queueRemove({
|
|
513
|
+
index: parsedIndex,
|
|
514
|
+
featurePath: resolved.path!,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
if (result.success) {
|
|
518
|
+
console.log(chalk.green(`✓ ${result.message}`));
|
|
519
|
+
} else {
|
|
520
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
queue
|
|
526
|
+
.command("clear")
|
|
527
|
+
.description("Clear all items from the queue")
|
|
528
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
529
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
530
|
+
.action(async (options) => {
|
|
531
|
+
const { queueClear, resolveFeaturePath } = await import("../src/cli/queue");
|
|
532
|
+
|
|
533
|
+
const resolved = await resolveFeaturePath(options.dir, options.feature);
|
|
534
|
+
if (resolved.error) {
|
|
535
|
+
console.error(chalk.red(resolved.error));
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const result = await queueClear({
|
|
540
|
+
featurePath: resolved.path!,
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
if (result.success) {
|
|
544
|
+
console.log(chalk.green(`✓ ${result.message}`));
|
|
545
|
+
} else {
|
|
546
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
|
|
430
551
|
// Analyze command
|
|
431
552
|
program
|
|
432
553
|
.command("analyze")
|
package/package.json
CHANGED
package/src/cli/queue.ts
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Queue Functions
|
|
3
|
+
*
|
|
4
|
+
* Functions for queue CLI commands.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { findRelentlessDir } from "../config";
|
|
10
|
+
import {
|
|
11
|
+
addToQueue,
|
|
12
|
+
loadQueue,
|
|
13
|
+
removeFromQueue,
|
|
14
|
+
clearQueue,
|
|
15
|
+
type QueueItem,
|
|
16
|
+
} from "../queue";
|
|
17
|
+
|
|
18
|
+
/** Result of a queue add operation */
|
|
19
|
+
export interface QueueAddResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
message?: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Options for queueAdd function */
|
|
26
|
+
export interface QueueAddOptions {
|
|
27
|
+
message: string;
|
|
28
|
+
featurePath: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Result of resolving a feature path */
|
|
32
|
+
export interface ResolveFeaturePathResult {
|
|
33
|
+
path?: string;
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the feature path from a working directory and feature name.
|
|
39
|
+
*
|
|
40
|
+
* @param workingDir - The working directory (project root)
|
|
41
|
+
* @param featureName - The feature name
|
|
42
|
+
* @returns The resolved feature path or an error
|
|
43
|
+
*/
|
|
44
|
+
export async function resolveFeaturePath(
|
|
45
|
+
workingDir: string,
|
|
46
|
+
featureName: string
|
|
47
|
+
): Promise<ResolveFeaturePathResult> {
|
|
48
|
+
const relentlessDir = findRelentlessDir(workingDir);
|
|
49
|
+
|
|
50
|
+
if (!relentlessDir) {
|
|
51
|
+
return {
|
|
52
|
+
error: "Relentless not initialized. Run: relentless init",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const featurePath = join(relentlessDir, "features", featureName);
|
|
57
|
+
|
|
58
|
+
if (!existsSync(featurePath)) {
|
|
59
|
+
return {
|
|
60
|
+
error: `Feature '${featureName}' not found`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { path: featurePath };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Adds a message to the queue for a feature.
|
|
69
|
+
*
|
|
70
|
+
* @param options - The queue add options
|
|
71
|
+
* @returns The result of the operation
|
|
72
|
+
*/
|
|
73
|
+
export async function queueAdd(options: QueueAddOptions): Promise<QueueAddResult> {
|
|
74
|
+
const { message, featurePath } = options;
|
|
75
|
+
|
|
76
|
+
// Validate feature path exists
|
|
77
|
+
if (!existsSync(featurePath)) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: `Feature path not found: ${featurePath}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await addToQueue(featurePath, message);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
message: `Added to queue: ${message}`,
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: `Failed to add to queue: ${(error as Error).message}`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Item in queue list output */
|
|
100
|
+
export interface QueueListItem {
|
|
101
|
+
index: number;
|
|
102
|
+
timestamp: string;
|
|
103
|
+
content: string;
|
|
104
|
+
type: "prompt" | "command";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Result of a queue list operation */
|
|
108
|
+
export interface QueueListResult {
|
|
109
|
+
success: boolean;
|
|
110
|
+
isEmpty: boolean;
|
|
111
|
+
pendingItems: QueueListItem[];
|
|
112
|
+
processedItems: QueueListItem[];
|
|
113
|
+
featureName?: string;
|
|
114
|
+
error?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Options for queueList function */
|
|
118
|
+
export interface QueueListOptions {
|
|
119
|
+
featurePath: string;
|
|
120
|
+
showAll: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Converts a QueueItem to a QueueListItem.
|
|
125
|
+
*
|
|
126
|
+
* @param item - The QueueItem to convert
|
|
127
|
+
* @param index - The 1-based index
|
|
128
|
+
* @returns The QueueListItem
|
|
129
|
+
*/
|
|
130
|
+
function toQueueListItem(item: QueueItem, index: number): QueueListItem {
|
|
131
|
+
return {
|
|
132
|
+
index,
|
|
133
|
+
timestamp: item.addedAt,
|
|
134
|
+
content: item.content,
|
|
135
|
+
type: item.type,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Lists queue items for a feature.
|
|
141
|
+
*
|
|
142
|
+
* @param options - The queue list options
|
|
143
|
+
* @returns The result of the operation
|
|
144
|
+
*/
|
|
145
|
+
export async function queueList(options: QueueListOptions): Promise<QueueListResult> {
|
|
146
|
+
const { featurePath, showAll } = options;
|
|
147
|
+
|
|
148
|
+
// Validate feature path exists
|
|
149
|
+
if (!existsSync(featurePath)) {
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
isEmpty: true,
|
|
153
|
+
pendingItems: [],
|
|
154
|
+
processedItems: [],
|
|
155
|
+
error: `Feature path not found: ${featurePath}`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const state = await loadQueue(featurePath);
|
|
161
|
+
|
|
162
|
+
const pendingItems = state.pending.map((item, index) =>
|
|
163
|
+
toQueueListItem(item, index + 1)
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const processedItems = showAll
|
|
167
|
+
? state.processed.map((item, index) => toQueueListItem(item, index + 1))
|
|
168
|
+
: [];
|
|
169
|
+
|
|
170
|
+
const isEmpty = pendingItems.length === 0 && processedItems.length === 0;
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
isEmpty,
|
|
175
|
+
pendingItems,
|
|
176
|
+
processedItems,
|
|
177
|
+
featureName: featurePath.split("/").pop(),
|
|
178
|
+
};
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
isEmpty: true,
|
|
183
|
+
pendingItems: [],
|
|
184
|
+
processedItems: [],
|
|
185
|
+
error: `Failed to list queue: ${(error as Error).message}`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Options for formatting queue list output */
|
|
191
|
+
export interface FormatQueueListOptions {
|
|
192
|
+
success: boolean;
|
|
193
|
+
isEmpty: boolean;
|
|
194
|
+
pendingItems: QueueListItem[];
|
|
195
|
+
processedItems: QueueListItem[];
|
|
196
|
+
featureName: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Formats the time portion of an ISO timestamp.
|
|
201
|
+
*
|
|
202
|
+
* @param timestamp - ISO timestamp string
|
|
203
|
+
* @returns Time in HH:MM format
|
|
204
|
+
*/
|
|
205
|
+
function formatTime(timestamp: string): string {
|
|
206
|
+
try {
|
|
207
|
+
const date = new Date(timestamp);
|
|
208
|
+
const hours = date.getUTCHours().toString().padStart(2, "0");
|
|
209
|
+
const minutes = date.getUTCMinutes().toString().padStart(2, "0");
|
|
210
|
+
return `${hours}:${minutes}`;
|
|
211
|
+
} catch {
|
|
212
|
+
return "--:--";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Formats the date portion of an ISO timestamp.
|
|
218
|
+
*
|
|
219
|
+
* @param timestamp - ISO timestamp string
|
|
220
|
+
* @returns Date in YYYY-MM-DD format
|
|
221
|
+
*/
|
|
222
|
+
function formatDate(timestamp: string): string {
|
|
223
|
+
try {
|
|
224
|
+
return timestamp.split("T")[0];
|
|
225
|
+
} catch {
|
|
226
|
+
return "----:--:--";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Formats queue list output for display.
|
|
232
|
+
*
|
|
233
|
+
* @param options - The format options
|
|
234
|
+
* @returns Formatted string for display
|
|
235
|
+
*/
|
|
236
|
+
export function formatQueueList(options: FormatQueueListOptions): string {
|
|
237
|
+
const { isEmpty, pendingItems, processedItems, featureName } = options;
|
|
238
|
+
|
|
239
|
+
const lines: string[] = [];
|
|
240
|
+
|
|
241
|
+
// Header with feature name
|
|
242
|
+
lines.push(`\nQueue: ${featureName}`);
|
|
243
|
+
lines.push("");
|
|
244
|
+
|
|
245
|
+
if (isEmpty) {
|
|
246
|
+
lines.push("Queue is empty");
|
|
247
|
+
lines.push("");
|
|
248
|
+
return lines.join("\n");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Pending items
|
|
252
|
+
if (pendingItems.length > 0) {
|
|
253
|
+
lines.push(`Pending (${pendingItems.length} items):`);
|
|
254
|
+
for (const item of pendingItems) {
|
|
255
|
+
const time = formatTime(item.timestamp);
|
|
256
|
+
const date = formatDate(item.timestamp);
|
|
257
|
+
const typeIndicator = item.type === "command" ? " [cmd]" : "";
|
|
258
|
+
lines.push(` ${item.index}. [${date} ${time}] ${item.content}${typeIndicator}`);
|
|
259
|
+
}
|
|
260
|
+
lines.push("");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Processed items
|
|
264
|
+
if (processedItems.length > 0) {
|
|
265
|
+
lines.push(`Processed (${processedItems.length} items):`);
|
|
266
|
+
for (const item of processedItems) {
|
|
267
|
+
const time = formatTime(item.timestamp);
|
|
268
|
+
const date = formatDate(item.timestamp);
|
|
269
|
+
lines.push(` ${item.index}. [${date} ${time}] ${item.content} ✓`);
|
|
270
|
+
}
|
|
271
|
+
lines.push("");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return lines.join("\n");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Result of a queue remove operation */
|
|
278
|
+
export interface QueueRemoveResult {
|
|
279
|
+
success: boolean;
|
|
280
|
+
message?: string;
|
|
281
|
+
removedContent?: string;
|
|
282
|
+
error?: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Options for queueRemove function */
|
|
286
|
+
export interface QueueRemoveOptions {
|
|
287
|
+
index: number;
|
|
288
|
+
featurePath: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Removes an item from the queue by 1-based index.
|
|
293
|
+
*
|
|
294
|
+
* @param options - The queue remove options
|
|
295
|
+
* @returns The result of the operation
|
|
296
|
+
*/
|
|
297
|
+
export async function queueRemove(
|
|
298
|
+
options: QueueRemoveOptions
|
|
299
|
+
): Promise<QueueRemoveResult> {
|
|
300
|
+
const { index, featurePath } = options;
|
|
301
|
+
|
|
302
|
+
// Validate feature path exists
|
|
303
|
+
if (!existsSync(featurePath)) {
|
|
304
|
+
return {
|
|
305
|
+
success: false,
|
|
306
|
+
error: `Feature path not found: ${featurePath}`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Validate index is positive
|
|
311
|
+
if (index < 1) {
|
|
312
|
+
return {
|
|
313
|
+
success: false,
|
|
314
|
+
error: `Invalid index: ${index}. Index must be 1 or greater`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Load queue to check state
|
|
319
|
+
const state = await loadQueue(featurePath);
|
|
320
|
+
const queueLength = state.pending.length;
|
|
321
|
+
|
|
322
|
+
// Handle empty queue
|
|
323
|
+
if (queueLength === 0) {
|
|
324
|
+
return {
|
|
325
|
+
success: false,
|
|
326
|
+
error: "Queue is empty",
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Validate index against queue length
|
|
331
|
+
if (index > queueLength) {
|
|
332
|
+
const itemWord = queueLength === 1 ? "item" : "items";
|
|
333
|
+
return {
|
|
334
|
+
success: false,
|
|
335
|
+
error: `Invalid index: ${index}. Queue has ${queueLength} ${itemWord}`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Remove the item
|
|
340
|
+
const removedItem = await removeFromQueue(featurePath, index);
|
|
341
|
+
|
|
342
|
+
if (!removedItem) {
|
|
343
|
+
return {
|
|
344
|
+
success: false,
|
|
345
|
+
error: `Failed to remove item at index ${index}`,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
success: true,
|
|
351
|
+
message: `Removed: ${removedItem.content}`,
|
|
352
|
+
removedContent: removedItem.content,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Result of a queue clear operation */
|
|
357
|
+
export interface QueueClearResult {
|
|
358
|
+
success: boolean;
|
|
359
|
+
message?: string;
|
|
360
|
+
clearedCount: number;
|
|
361
|
+
error?: string;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Options for queueClear function */
|
|
365
|
+
export interface QueueClearOptions {
|
|
366
|
+
featurePath: string;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Clears all items from the queue.
|
|
371
|
+
*
|
|
372
|
+
* @param options - The queue clear options
|
|
373
|
+
* @returns The result of the operation
|
|
374
|
+
*/
|
|
375
|
+
export async function queueClear(
|
|
376
|
+
options: QueueClearOptions
|
|
377
|
+
): Promise<QueueClearResult> {
|
|
378
|
+
const { featurePath } = options;
|
|
379
|
+
|
|
380
|
+
// Validate feature path exists
|
|
381
|
+
if (!existsSync(featurePath)) {
|
|
382
|
+
return {
|
|
383
|
+
success: false,
|
|
384
|
+
clearedCount: 0,
|
|
385
|
+
error: `Feature path not found: ${featurePath}`,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Clear the queue
|
|
390
|
+
const count = await clearQueue(featurePath);
|
|
391
|
+
|
|
392
|
+
if (count === 0) {
|
|
393
|
+
return {
|
|
394
|
+
success: true,
|
|
395
|
+
clearedCount: 0,
|
|
396
|
+
message: "Queue is already empty",
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const itemWord = count === 1 ? "item" : "items";
|
|
401
|
+
return {
|
|
402
|
+
success: true,
|
|
403
|
+
clearedCount: count,
|
|
404
|
+
message: `Cleared ${count} ${itemWord} from queue`,
|
|
405
|
+
};
|
|
406
|
+
}
|