@arvorco/relentless 0.1.27 → 0.3.0
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/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/agents/amp.ts +26 -13
- package/src/agents/codex.ts +35 -1
- package/src/agents/droid.ts +35 -1
- package/src/agents/opencode.ts +35 -1
- package/src/cli/queue.ts +406 -0
- package/src/execution/commands.ts +541 -0
- package/src/execution/runner.ts +236 -4
- package/src/init/scaffolder.ts +343 -117
- 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
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue Command Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles structured commands from the queue system.
|
|
5
|
+
* Implements PAUSE, ABORT, SKIP, and PRIORITY command execution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { QueueCommandType } from "../queue/types";
|
|
9
|
+
import { appendProgress } from "../prd/progress";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Command action types
|
|
13
|
+
*/
|
|
14
|
+
export type CommandActionType = "pause" | "abort" | "skip" | "priority" | "none";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pause action returned by handlePauseCommand
|
|
18
|
+
*/
|
|
19
|
+
export interface PauseAction {
|
|
20
|
+
type: "pause";
|
|
21
|
+
message: string;
|
|
22
|
+
reason?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Abort action returned by handleAbortCommand
|
|
27
|
+
*/
|
|
28
|
+
export interface AbortAction {
|
|
29
|
+
type: "abort";
|
|
30
|
+
reason: string;
|
|
31
|
+
exitCode: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Progress summary for abort message
|
|
36
|
+
*/
|
|
37
|
+
export interface AbortProgressSummary {
|
|
38
|
+
storiesCompleted: number;
|
|
39
|
+
storiesTotal: number;
|
|
40
|
+
iterations: number;
|
|
41
|
+
duration: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Result of executing a pause action
|
|
46
|
+
*/
|
|
47
|
+
export interface PauseResult {
|
|
48
|
+
resumed: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Input function type for pause action
|
|
53
|
+
* Used for dependency injection in tests
|
|
54
|
+
*/
|
|
55
|
+
export type InputFunction = () => Promise<string>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if any PAUSE command exists in the command list
|
|
59
|
+
*
|
|
60
|
+
* @param commands - List of commands from queue processing
|
|
61
|
+
* @returns true if PAUSE command is present
|
|
62
|
+
*/
|
|
63
|
+
export function shouldPause(
|
|
64
|
+
commands: Array<{ type: QueueCommandType; storyId?: string }>
|
|
65
|
+
): boolean {
|
|
66
|
+
return commands.some((cmd) => cmd.type === "PAUSE");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Handle PAUSE command - creates action object
|
|
71
|
+
*
|
|
72
|
+
* @param reason - Optional custom reason for the pause
|
|
73
|
+
* @returns PauseAction object
|
|
74
|
+
*/
|
|
75
|
+
export function handlePauseCommand(reason?: string): PauseAction {
|
|
76
|
+
const baseMessage = "Paused by user. Press Enter to continue...";
|
|
77
|
+
const message = reason ? `${reason}\n${baseMessage}` : baseMessage;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
type: "pause",
|
|
81
|
+
message,
|
|
82
|
+
reason,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Execute pause action - wait for user input
|
|
88
|
+
*
|
|
89
|
+
* In normal execution, this reads from stdin.
|
|
90
|
+
* In test mode, pass a mock input function.
|
|
91
|
+
*
|
|
92
|
+
* @param inputFn - Optional input function for testing
|
|
93
|
+
* @returns PauseResult with resumed status
|
|
94
|
+
*/
|
|
95
|
+
export async function executePauseAction(
|
|
96
|
+
inputFn?: InputFunction
|
|
97
|
+
): Promise<PauseResult> {
|
|
98
|
+
if (inputFn) {
|
|
99
|
+
// Test mode - use provided input function
|
|
100
|
+
await inputFn();
|
|
101
|
+
return { resumed: true };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Normal mode - wait for Enter key from stdin
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
const stdin = process.stdin;
|
|
107
|
+
stdin.setRawMode?.(false);
|
|
108
|
+
stdin.resume();
|
|
109
|
+
|
|
110
|
+
const onData = () => {
|
|
111
|
+
stdin.pause();
|
|
112
|
+
stdin.removeListener("data", onData);
|
|
113
|
+
resolve({ resumed: true });
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
stdin.once("data", onData);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Log pause event to progress.txt
|
|
122
|
+
*
|
|
123
|
+
* @param progressPath - Path to progress.txt file
|
|
124
|
+
*/
|
|
125
|
+
export async function logPauseToProgress(progressPath: string): Promise<void> {
|
|
126
|
+
const timestamp = new Date().toISOString().split("T")[0];
|
|
127
|
+
const entry = `
|
|
128
|
+
## Pause Event - ${timestamp}
|
|
129
|
+
|
|
130
|
+
User requested pause via [PAUSE] command.
|
|
131
|
+
Orchestrator paused and waited for user confirmation to continue.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
`;
|
|
135
|
+
|
|
136
|
+
await appendProgress(progressPath, entry);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Format pause message for display
|
|
141
|
+
*
|
|
142
|
+
* @param tuiMode - Whether to format for TUI display
|
|
143
|
+
* @returns Formatted pause message
|
|
144
|
+
*/
|
|
145
|
+
export function formatPauseMessage(tuiMode = false): string {
|
|
146
|
+
if (tuiMode) {
|
|
147
|
+
return "⏸️ Paused by user. Press any key to continue...";
|
|
148
|
+
}
|
|
149
|
+
return "⏸️ Paused by user. Press Enter to continue...";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// ABORT Command Functions
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check if any ABORT command exists in the command list
|
|
158
|
+
*
|
|
159
|
+
* @param commands - List of commands from queue processing
|
|
160
|
+
* @returns true if ABORT command is present
|
|
161
|
+
*/
|
|
162
|
+
export function shouldAbort(
|
|
163
|
+
commands: Array<{ type: QueueCommandType; storyId?: string }>
|
|
164
|
+
): boolean {
|
|
165
|
+
return commands.some((cmd) => cmd.type === "ABORT");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Handle ABORT command - creates action object
|
|
170
|
+
*
|
|
171
|
+
* @param reason - Optional custom reason for the abort
|
|
172
|
+
* @returns AbortAction object
|
|
173
|
+
*/
|
|
174
|
+
export function handleAbortCommand(reason?: string): AbortAction {
|
|
175
|
+
return {
|
|
176
|
+
type: "abort",
|
|
177
|
+
reason: reason ?? "User requested abort via [ABORT] command",
|
|
178
|
+
exitCode: 0, // Clean exit, not an error
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Log abort event to progress.txt
|
|
184
|
+
*
|
|
185
|
+
* @param progressPath - Path to progress.txt file
|
|
186
|
+
* @param reason - Optional custom reason for the abort
|
|
187
|
+
*/
|
|
188
|
+
export async function logAbortToProgress(
|
|
189
|
+
progressPath: string,
|
|
190
|
+
reason?: string
|
|
191
|
+
): Promise<void> {
|
|
192
|
+
const timestamp = new Date().toISOString().split("T")[0];
|
|
193
|
+
const reasonText = reason ?? "User requested abort via [ABORT] command";
|
|
194
|
+
const entry = `
|
|
195
|
+
## Abort Event - ${timestamp}
|
|
196
|
+
|
|
197
|
+
${reasonText}
|
|
198
|
+
Orchestrator stopped cleanly with exit code 0.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
`;
|
|
202
|
+
|
|
203
|
+
await appendProgress(progressPath, entry);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Format abort message for display
|
|
208
|
+
*
|
|
209
|
+
* @param tuiMode - Whether to format for TUI display
|
|
210
|
+
* @returns Formatted abort message
|
|
211
|
+
*/
|
|
212
|
+
export function formatAbortMessage(tuiMode = false): string {
|
|
213
|
+
if (tuiMode) {
|
|
214
|
+
return "🛑 Aborted by user.";
|
|
215
|
+
}
|
|
216
|
+
return "🛑 Aborted by user.";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Generate progress summary for abort
|
|
221
|
+
*
|
|
222
|
+
* @param summary - Progress summary data
|
|
223
|
+
* @returns Formatted summary string
|
|
224
|
+
*/
|
|
225
|
+
export function generateAbortSummary(summary: AbortProgressSummary): string {
|
|
226
|
+
const { storiesCompleted, storiesTotal, iterations, duration } = summary;
|
|
227
|
+
|
|
228
|
+
// Format duration
|
|
229
|
+
const seconds = Math.floor(duration / 1000);
|
|
230
|
+
const minutes = Math.floor(seconds / 60);
|
|
231
|
+
const remainingSeconds = seconds % 60;
|
|
232
|
+
const durationStr =
|
|
233
|
+
minutes > 0 ? `${minutes}m ${remainingSeconds}s` : `${seconds}s`;
|
|
234
|
+
|
|
235
|
+
return `
|
|
236
|
+
Progress Summary:
|
|
237
|
+
Stories: ${storiesCompleted}/${storiesTotal} complete
|
|
238
|
+
Iterations: ${iterations}
|
|
239
|
+
Duration: ${durationStr}
|
|
240
|
+
`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ============================================================================
|
|
244
|
+
// SKIP Command Functions
|
|
245
|
+
// ============================================================================
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Skip action returned by handleSkipCommand
|
|
249
|
+
*/
|
|
250
|
+
export interface SkipAction {
|
|
251
|
+
type: "skip";
|
|
252
|
+
storyId: string;
|
|
253
|
+
rejected: boolean;
|
|
254
|
+
reason?: string;
|
|
255
|
+
customReason?: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if any SKIP command exists in the command list
|
|
260
|
+
*
|
|
261
|
+
* @param commands - List of commands from queue processing
|
|
262
|
+
* @returns true if SKIP command is present
|
|
263
|
+
*/
|
|
264
|
+
export function shouldSkip(
|
|
265
|
+
commands: Array<{ type: QueueCommandType; storyId?: string }>
|
|
266
|
+
): boolean {
|
|
267
|
+
return commands.some((cmd) => cmd.type === "SKIP");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Get all SKIP commands from the command list
|
|
272
|
+
*
|
|
273
|
+
* @param commands - List of commands from queue processing
|
|
274
|
+
* @returns Array of SKIP commands with story IDs
|
|
275
|
+
*/
|
|
276
|
+
export function getSkipCommands(
|
|
277
|
+
commands: Array<{ type: QueueCommandType; storyId?: string }>
|
|
278
|
+
): Array<{ type: "SKIP"; storyId: string }> {
|
|
279
|
+
return commands
|
|
280
|
+
.filter((cmd): cmd is { type: "SKIP"; storyId: string } =>
|
|
281
|
+
cmd.type === "SKIP" && cmd.storyId !== undefined
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Handle SKIP command - creates action object
|
|
287
|
+
*
|
|
288
|
+
* Checks if the story is currently in progress. If so, rejects the skip.
|
|
289
|
+
*
|
|
290
|
+
* @param storyId - The story ID to skip
|
|
291
|
+
* @param currentStoryId - The story currently in progress (or null if none)
|
|
292
|
+
* @param customReason - Optional custom reason for the skip
|
|
293
|
+
* @returns SkipAction object
|
|
294
|
+
*/
|
|
295
|
+
export function handleSkipCommand(
|
|
296
|
+
storyId: string,
|
|
297
|
+
currentStoryId: string | null,
|
|
298
|
+
customReason?: string
|
|
299
|
+
): SkipAction {
|
|
300
|
+
// Check if trying to skip the story currently in progress
|
|
301
|
+
if (currentStoryId && storyId === currentStoryId) {
|
|
302
|
+
return {
|
|
303
|
+
type: "skip",
|
|
304
|
+
storyId,
|
|
305
|
+
rejected: true,
|
|
306
|
+
reason: `Cannot skip ${storyId}: story is currently in progress. Wait for iteration to complete.`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
type: "skip",
|
|
312
|
+
storyId,
|
|
313
|
+
rejected: false,
|
|
314
|
+
customReason,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Log skip event to progress.txt
|
|
320
|
+
*
|
|
321
|
+
* @param progressPath - Path to progress.txt file
|
|
322
|
+
* @param storyId - The story ID that was skipped
|
|
323
|
+
* @param reason - Optional custom reason for the skip
|
|
324
|
+
*/
|
|
325
|
+
export async function logSkipToProgress(
|
|
326
|
+
progressPath: string,
|
|
327
|
+
storyId: string,
|
|
328
|
+
reason?: string
|
|
329
|
+
): Promise<void> {
|
|
330
|
+
const timestamp = new Date().toISOString().split("T")[0];
|
|
331
|
+
const reasonText = reason ?? "User requested skip via [SKIP] command";
|
|
332
|
+
const entry = `
|
|
333
|
+
## Skip Event - ${timestamp}
|
|
334
|
+
|
|
335
|
+
Story ${storyId} was skipped.
|
|
336
|
+
${reasonText}
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
`;
|
|
340
|
+
|
|
341
|
+
await appendProgress(progressPath, entry);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Log rejected skip event to progress.txt
|
|
346
|
+
*
|
|
347
|
+
* @param progressPath - Path to progress.txt file
|
|
348
|
+
* @param storyId - The story ID that was attempted to skip
|
|
349
|
+
*/
|
|
350
|
+
export async function logSkipRejectedToProgress(
|
|
351
|
+
progressPath: string,
|
|
352
|
+
storyId: string
|
|
353
|
+
): Promise<void> {
|
|
354
|
+
const timestamp = new Date().toISOString().split("T")[0];
|
|
355
|
+
const entry = `
|
|
356
|
+
## Skip Rejected - ${timestamp}
|
|
357
|
+
|
|
358
|
+
Attempted to skip ${storyId} but story is currently in progress.
|
|
359
|
+
Skip command was ignored. Wait for the current iteration to complete.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
`;
|
|
363
|
+
|
|
364
|
+
await appendProgress(progressPath, entry);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Format skip message for display
|
|
369
|
+
*
|
|
370
|
+
* @param storyId - The story ID being skipped
|
|
371
|
+
* @param rejected - Whether the skip was rejected
|
|
372
|
+
* @param tuiMode - Whether to format for TUI display
|
|
373
|
+
* @returns Formatted skip message
|
|
374
|
+
*/
|
|
375
|
+
export function formatSkipMessage(
|
|
376
|
+
storyId: string,
|
|
377
|
+
rejected: boolean,
|
|
378
|
+
tuiMode = false
|
|
379
|
+
): string {
|
|
380
|
+
if (rejected) {
|
|
381
|
+
if (tuiMode) {
|
|
382
|
+
return `⚠️ Cannot skip ${storyId}: story is currently in progress`;
|
|
383
|
+
}
|
|
384
|
+
return `⚠️ Cannot skip ${storyId}: story is currently in progress. Wait for iteration to complete.`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (tuiMode) {
|
|
388
|
+
return `⏭️ Skipped ${storyId}`;
|
|
389
|
+
}
|
|
390
|
+
return `⏭️ Skipped ${storyId}`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ============================================================================
|
|
394
|
+
// PRIORITY Command Functions
|
|
395
|
+
// ============================================================================
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Priority action returned by handlePriorityCommand
|
|
399
|
+
*/
|
|
400
|
+
export interface PriorityAction {
|
|
401
|
+
type: "priority";
|
|
402
|
+
storyId: string;
|
|
403
|
+
isCurrentStory: boolean;
|
|
404
|
+
message?: string;
|
|
405
|
+
customReason?: string;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Check if any PRIORITY command exists in the command list
|
|
410
|
+
*
|
|
411
|
+
* @param commands - List of commands from queue processing
|
|
412
|
+
* @returns true if PRIORITY command is present
|
|
413
|
+
*/
|
|
414
|
+
export function shouldPrioritize(
|
|
415
|
+
commands: Array<{ type: QueueCommandType; storyId?: string }>
|
|
416
|
+
): boolean {
|
|
417
|
+
return commands.some((cmd) => cmd.type === "PRIORITY");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Get all PRIORITY commands from the command list
|
|
422
|
+
*
|
|
423
|
+
* @param commands - List of commands from queue processing
|
|
424
|
+
* @returns Array of PRIORITY commands with story IDs
|
|
425
|
+
*/
|
|
426
|
+
export function getPriorityCommands(
|
|
427
|
+
commands: Array<{ type: QueueCommandType; storyId?: string }>
|
|
428
|
+
): Array<{ type: "PRIORITY"; storyId: string }> {
|
|
429
|
+
return commands.filter(
|
|
430
|
+
(cmd): cmd is { type: "PRIORITY"; storyId: string } =>
|
|
431
|
+
cmd.type === "PRIORITY" && cmd.storyId !== undefined
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Handle PRIORITY command - creates action object
|
|
437
|
+
*
|
|
438
|
+
* Checks if the story is the currently executing story. If so, shows an info message.
|
|
439
|
+
*
|
|
440
|
+
* @param storyId - The story ID to prioritize
|
|
441
|
+
* @param currentStoryId - The story currently in progress (or null if none)
|
|
442
|
+
* @param customReason - Optional custom reason for the priority change
|
|
443
|
+
* @returns PriorityAction object
|
|
444
|
+
*/
|
|
445
|
+
export function handlePriorityCommand(
|
|
446
|
+
storyId: string,
|
|
447
|
+
currentStoryId: string | null,
|
|
448
|
+
customReason?: string
|
|
449
|
+
): PriorityAction {
|
|
450
|
+
// Check if trying to prioritize the story currently in progress
|
|
451
|
+
if (currentStoryId && storyId === currentStoryId) {
|
|
452
|
+
return {
|
|
453
|
+
type: "priority",
|
|
454
|
+
storyId,
|
|
455
|
+
isCurrentStory: true,
|
|
456
|
+
message: `Story ${storyId} is already in progress`,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
type: "priority",
|
|
462
|
+
storyId,
|
|
463
|
+
isCurrentStory: false,
|
|
464
|
+
customReason,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Log priority event to progress.txt
|
|
470
|
+
*
|
|
471
|
+
* @param progressPath - Path to progress.txt file
|
|
472
|
+
* @param storyId - The story ID that was prioritized
|
|
473
|
+
* @param reason - Optional custom reason for the priority change
|
|
474
|
+
*/
|
|
475
|
+
export async function logPriorityToProgress(
|
|
476
|
+
progressPath: string,
|
|
477
|
+
storyId: string,
|
|
478
|
+
reason?: string
|
|
479
|
+
): Promise<void> {
|
|
480
|
+
const timestamp = new Date().toISOString().split("T")[0];
|
|
481
|
+
const reasonText = reason ?? "User requested priority via [PRIORITY] command";
|
|
482
|
+
const entry = `
|
|
483
|
+
## Priority Change - ${timestamp}
|
|
484
|
+
|
|
485
|
+
Story ${storyId} was prioritized to be next.
|
|
486
|
+
${reasonText}
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
`;
|
|
490
|
+
|
|
491
|
+
await appendProgress(progressPath, entry);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Log priority info event to progress.txt (when story is already current)
|
|
496
|
+
*
|
|
497
|
+
* @param progressPath - Path to progress.txt file
|
|
498
|
+
* @param storyId - The story ID that was attempted to prioritize
|
|
499
|
+
*/
|
|
500
|
+
export async function logPriorityInfoToProgress(
|
|
501
|
+
progressPath: string,
|
|
502
|
+
storyId: string
|
|
503
|
+
): Promise<void> {
|
|
504
|
+
const timestamp = new Date().toISOString().split("T")[0];
|
|
505
|
+
const entry = `
|
|
506
|
+
## Priority Info - ${timestamp}
|
|
507
|
+
|
|
508
|
+
Attempted to prioritize ${storyId} but story is already in progress.
|
|
509
|
+
Execution continues normally.
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
`;
|
|
513
|
+
|
|
514
|
+
await appendProgress(progressPath, entry);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Format priority message for display
|
|
519
|
+
*
|
|
520
|
+
* @param storyId - The story ID being prioritized
|
|
521
|
+
* @param isCurrentStory - Whether the story is the current one
|
|
522
|
+
* @param tuiMode - Whether to format for TUI display
|
|
523
|
+
* @returns Formatted priority message
|
|
524
|
+
*/
|
|
525
|
+
export function formatPriorityMessage(
|
|
526
|
+
storyId: string,
|
|
527
|
+
isCurrentStory: boolean,
|
|
528
|
+
tuiMode = false
|
|
529
|
+
): string {
|
|
530
|
+
if (isCurrentStory) {
|
|
531
|
+
if (tuiMode) {
|
|
532
|
+
return `ℹ️ ${storyId} is already in progress`;
|
|
533
|
+
}
|
|
534
|
+
return `ℹ️ Story ${storyId} is already in progress`;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (tuiMode) {
|
|
538
|
+
return `⬆️ Prioritized ${storyId}`;
|
|
539
|
+
}
|
|
540
|
+
return `⬆️ Prioritized ${storyId}`;
|
|
541
|
+
}
|