ralphctl 0.1.2 → 0.1.4

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.
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  escapableSelect
4
- } from "./chunk-NTWO2LXB.mjs";
4
+ } from "./chunk-7LZ6GOGN.mjs";
5
5
  import {
6
- IssueFetchError,
7
6
  allRequirementsApproved,
8
7
  fetchIssueFromUrl,
9
8
  formatIssueContext,
@@ -12,7 +11,7 @@ import {
12
11
  getPendingRequirements,
13
12
  groupTicketsByProject,
14
13
  listTickets
15
- } from "./chunk-MNMQC36F.mjs";
14
+ } from "./chunk-F2MMCTB5.mjs";
16
15
  import {
17
16
  EXIT_ALL_BLOCKED,
18
17
  EXIT_ERROR,
@@ -22,13 +21,10 @@ import {
22
21
  exitWithCode
23
22
  } from "./chunk-7TG3EAQ2.mjs";
24
23
  import {
25
- ProjectNotFoundError,
26
24
  getProject,
27
25
  listProjects
28
- } from "./chunk-WGHJI3OI.mjs";
26
+ } from "./chunk-PDI6HBZ7.mjs";
29
27
  import {
30
- SprintNotFoundError,
31
- SprintStatusError,
32
28
  activateSprint,
33
29
  assertSprintStatus,
34
30
  closeSprint,
@@ -43,7 +39,12 @@ import {
43
39
  setAiProvider,
44
40
  summarizeProgressForContext,
45
41
  withFileLock
46
- } from "./chunk-EKMZZRWI.mjs";
42
+ } from "./chunk-LFDW6MWF.mjs";
43
+ import {
44
+ ensureError,
45
+ unwrapOrThrow,
46
+ wrapAsync
47
+ } from "./chunk-OEUJDSHY.mjs";
47
48
  import {
48
49
  ImportTasksSchema,
49
50
  RefinedRequirementsSchema,
@@ -59,7 +60,17 @@ import {
59
60
  getTasksFilePath,
60
61
  readValidatedJson,
61
62
  writeValidatedJson
62
- } from "./chunk-6PYTKGB5.mjs";
63
+ } from "./chunk-W3TY22IS.mjs";
64
+ import {
65
+ DependencyCycleError,
66
+ IOError,
67
+ IssueFetchError,
68
+ ProjectNotFoundError,
69
+ SpawnError,
70
+ SprintNotFoundError,
71
+ SprintStatusError,
72
+ TaskNotFoundError
73
+ } from "./chunk-EDJX7TT6.mjs";
63
74
  import {
64
75
  colors,
65
76
  createSpinner,
@@ -95,6 +106,7 @@ import {
95
106
  import { mkdir, readFile } from "fs/promises";
96
107
  import { join as join4 } from "path";
97
108
  import { confirm } from "@inquirer/prompts";
109
+ import { Result as Result3 } from "typescript-result";
98
110
 
99
111
  // src/ai/prompts/index.ts
100
112
  import { existsSync, readFileSync } from "fs";
@@ -211,6 +223,7 @@ function providerDisplayName(provider) {
211
223
  // src/commands/ticket/refine-utils.ts
212
224
  import { writeFile as writeFile2 } from "fs/promises";
213
225
  import { join as join3 } from "path";
226
+ import { Result as Result2 } from "typescript-result";
214
227
 
215
228
  // src/ai/session.ts
216
229
  import { spawn, spawnSync } from "child_process";
@@ -357,8 +370,7 @@ var ProcessManager = class _ProcessManager {
357
370
  try {
358
371
  callback();
359
372
  } catch (err) {
360
- const error2 = err;
361
- console.error("Error in cleanup callback:", error2.message);
373
+ console.error("Error in cleanup callback:", err instanceof Error ? err.message : String(err));
362
374
  }
363
375
  }
364
376
  this.cleanupCallbacks.clear();
@@ -399,6 +411,7 @@ var ProcessManager = class _ProcessManager {
399
411
  };
400
412
 
401
413
  // src/providers/claude.ts
414
+ import { Result } from "typescript-result";
402
415
  var claudeAdapter = {
403
416
  name: "claude",
404
417
  displayName: "Claude",
@@ -412,15 +425,15 @@ var claudeAdapter = {
412
425
  return ["-p", "--output-format", "json", ...this.baseArgs, ...extraArgs];
413
426
  },
414
427
  parseJsonOutput(stdout) {
415
- try {
416
- const parsed = JSON.parse(stdout);
417
- return {
418
- result: parsed.result ?? stdout,
419
- sessionId: parsed.session_id ?? null
420
- };
421
- } catch {
428
+ const jsonResult = Result.try(() => JSON.parse(stdout));
429
+ if (!jsonResult.ok) {
422
430
  return { result: stdout, sessionId: null };
423
431
  }
432
+ const parsed = jsonResult.value;
433
+ return {
434
+ result: parsed.result ?? stdout,
435
+ sessionId: parsed.session_id ?? null
436
+ };
424
437
  },
425
438
  detectRateLimit(stderr) {
426
439
  const patterns = [/rate.?limit/i, /\b429\b/, /too many requests/i, /overloaded/i, /\b529\b/];
@@ -456,22 +469,23 @@ var copilotAdapter = {
456
469
  return { result: stdout.trim(), sessionId: null };
457
470
  },
458
471
  async extractSessionId(cwd) {
459
- try {
460
- const files = await readdir(cwd);
461
- const shareFile = files.find((f) => /^copilot-session-[a-zA-Z0-9_][a-zA-Z0-9_-]*\.md$/.test(f));
462
- if (!shareFile) return null;
463
- const match = /^copilot-session-([a-zA-Z0-9_][a-zA-Z0-9_-]{0,127})\.md$/.exec(shareFile);
464
- if (!match?.[1]) return null;
465
- const filePath = join2(cwd, shareFile);
466
- const stat = await lstat(filePath).catch(() => null);
467
- if (stat?.isFile()) {
468
- await unlink(filePath).catch(() => {
469
- });
470
- }
471
- return match[1];
472
- } catch {
473
- return null;
472
+ const filesResult = await wrapAsync(
473
+ () => readdir(cwd),
474
+ (err) => new IOError(`Failed to read directory: ${cwd}`, err instanceof Error ? err : void 0)
475
+ );
476
+ if (!filesResult.ok) return null;
477
+ const files = filesResult.value;
478
+ const shareFile = files.find((f) => /^copilot-session-[a-zA-Z0-9_][a-zA-Z0-9_-]*\.md$/.test(f));
479
+ if (!shareFile) return null;
480
+ const match = /^copilot-session-([a-zA-Z0-9_][a-zA-Z0-9_-]{0,127})\.md$/.exec(shareFile);
481
+ if (!match?.[1]) return null;
482
+ const filePath = join2(cwd, shareFile);
483
+ const stat = await lstat(filePath).catch(() => null);
484
+ if (stat?.isFile()) {
485
+ await unlink(filePath).catch(() => {
486
+ });
474
487
  }
488
+ return match[1];
475
489
  },
476
490
  detectRateLimit(stderr) {
477
491
  const patterns = [/rate.?limit/i, /\b429\b/, /too many requests/i, /overloaded/i, /\b529\b/];
@@ -507,34 +521,6 @@ async function getActiveProvider() {
507
521
  }
508
522
 
509
523
  // src/ai/session.ts
510
- var SpawnError = class extends Error {
511
- stderr;
512
- exitCode;
513
- rateLimited;
514
- retryAfterMs;
515
- /** Session ID if available (for resume after rate limit) */
516
- sessionId;
517
- constructor(message, stderr, exitCode, sessionId, provider) {
518
- super(message);
519
- this.name = "SpawnError";
520
- this.stderr = stderr;
521
- this.exitCode = exitCode;
522
- this.sessionId = sessionId ?? null;
523
- const rl = provider ? provider.detectRateLimit(stderr) : detectRateLimitFallback(stderr);
524
- this.rateLimited = rl.rateLimited;
525
- this.retryAfterMs = rl.retryAfterMs;
526
- }
527
- };
528
- function detectRateLimitFallback(stderr) {
529
- const patterns = [/rate.?limit/i, /\b429\b/, /too many requests/i, /overloaded/i, /\b529\b/];
530
- const isRateLimited = patterns.some((p) => p.test(stderr));
531
- if (!isRateLimited) {
532
- return { rateLimited: false, retryAfterMs: null };
533
- }
534
- const retryMatch = /retry.?after:?\s*(\d+)/i.exec(stderr);
535
- const retryAfterMs = retryMatch?.[1] ? parseInt(retryMatch[1], 10) * 1e3 : null;
536
- return { rateLimited: true, retryAfterMs };
537
- }
538
524
  function spawnInteractive(prompt, options, provider) {
539
525
  assertSafeCwd(options.cwd);
540
526
  const p = provider ?? {
@@ -570,7 +556,7 @@ async function spawnHeadlessRaw(options, provider) {
570
556
  const allArgs = p.buildHeadlessArgs(options.args ?? []);
571
557
  if (options.resumeSessionId) {
572
558
  if (!/^[a-zA-Z0-9_][a-zA-Z0-9_-]{0,127}$/.test(options.resumeSessionId)) {
573
- reject(new SpawnError("Invalid session ID format", "", 1, null, p));
559
+ reject(new SpawnError("Invalid session ID format", "", 1));
574
560
  return;
575
561
  }
576
562
  allArgs.push("--resume", options.resumeSessionId);
@@ -584,13 +570,13 @@ async function spawnHeadlessRaw(options, provider) {
584
570
  try {
585
571
  manager.registerChild(child);
586
572
  } catch {
587
- reject(new SpawnError("Cannot spawn during shutdown", "", 1, null, p));
573
+ reject(new SpawnError("Cannot spawn during shutdown", "", 1));
588
574
  return;
589
575
  }
590
576
  const MAX_PROMPT_SIZE = 1e6;
591
577
  if (options.prompt) {
592
578
  if (options.prompt.length > MAX_PROMPT_SIZE) {
593
- reject(new SpawnError("Prompt exceeds maximum size (1MB)", "", 1, null, p));
579
+ reject(new SpawnError("Prompt exceeds maximum size (1MB)", "", 1));
594
580
  return;
595
581
  }
596
582
  child.stdin.write(options.prompt);
@@ -615,19 +601,18 @@ async function spawnHeadlessRaw(options, provider) {
615
601
  `${p.displayName} CLI exited with code ${String(exitCode)}: ${stderr}`,
616
602
  stderr,
617
603
  exitCode,
618
- sessionId,
619
- p
604
+ sessionId
620
605
  )
621
606
  );
622
607
  } else {
623
608
  resolve({ stdout: result, stderr, exitCode: 0, sessionId });
624
609
  }
625
610
  })().catch((err) => {
626
- reject(new SpawnError(`Unexpected error in close handler: ${String(err)}`, "", 1, null, p));
611
+ reject(new SpawnError(`Unexpected error in close handler: ${String(err)}`, "", 1));
627
612
  });
628
613
  });
629
614
  child.on("error", (err) => {
630
- reject(new SpawnError(`Failed to spawn ${p.binary} CLI: ${err.message}`, "", 1, null, p));
615
+ reject(new SpawnError(`Failed to spawn ${p.binary} CLI: ${err.message}`, "", 1));
631
616
  });
632
617
  });
633
618
  }
@@ -650,24 +635,23 @@ async function spawnWithRetry(options, retryOptions, provider) {
650
635
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
651
636
  const elapsed = Date.now() - startTime;
652
637
  if (attempt > 0 && elapsed >= totalTimeoutMs) {
653
- throw new SpawnError(`Total retry timeout exceeded (${String(totalTimeoutMs)}ms)`, "", 1, resumeSessionId, p);
638
+ throw new SpawnError(`Total retry timeout exceeded (${String(totalTimeoutMs)}ms)`, "", 1, resumeSessionId);
654
639
  }
655
- try {
656
- return await spawnHeadlessRaw({ ...options, resumeSessionId }, p);
657
- } catch (err) {
658
- if (!(err instanceof SpawnError) || !err.rateLimited) {
659
- throw err;
660
- }
661
- if (err.sessionId) {
662
- resumeSessionId = err.sessionId;
663
- }
664
- if (attempt >= maxRetries) {
665
- throw err;
666
- }
667
- const delay = Math.min(err.retryAfterMs ?? BASE_DELAY_MS * Math.pow(2, attempt), MAX_DELAY_MS) + jitter();
668
- retryOptions?.onRetry?.(attempt + 1, delay, err);
669
- await sleep(delay);
640
+ const r = await wrapAsync(async () => spawnHeadlessRaw({ ...options, resumeSessionId }, p), ensureError);
641
+ if (r.ok) return r.value;
642
+ const err = r.error;
643
+ if (!(err instanceof SpawnError) || !err.rateLimited) {
644
+ throw err;
645
+ }
646
+ if (err.sessionId) {
647
+ resumeSessionId = err.sessionId;
670
648
  }
649
+ if (attempt >= maxRetries) {
650
+ throw err;
651
+ }
652
+ const delay = Math.min(err.retryAfterMs ?? BASE_DELAY_MS * Math.pow(2, attempt), MAX_DELAY_MS) + jitter();
653
+ retryOptions?.onRetry?.(attempt + 1, delay, err);
654
+ await sleep(delay);
671
655
  }
672
656
  throw new Error("Max retries exceeded");
673
657
  }
@@ -732,12 +716,11 @@ function formatTicketForPrompt(ticket) {
732
716
  }
733
717
  function parseRequirementsFile(content) {
734
718
  const jsonStr = extractJsonArray(content);
735
- let parsed;
736
- try {
737
- parsed = JSON.parse(jsonStr);
738
- } catch (err) {
739
- throw new Error(`Invalid JSON: ${err instanceof Error ? err.message : "parse error"}`, { cause: err });
719
+ const parseR = Result2.try(() => JSON.parse(jsonStr));
720
+ if (!parseR.ok) {
721
+ throw new Error(`Invalid JSON: ${parseR.error.message}`, { cause: parseR.error });
740
722
  }
723
+ const parsed = parseR.value;
741
724
  if (!Array.isArray(parsed)) {
742
725
  throw new Error("Expected JSON array");
743
726
  }
@@ -787,23 +770,20 @@ function parseArgs(args) {
787
770
  }
788
771
  async function sprintRefineCommand(args) {
789
772
  const { sprintId, options } = parseArgs(args);
790
- let id;
791
- try {
792
- id = await resolveSprintId(sprintId);
793
- } catch {
773
+ const idR = await wrapAsync(() => resolveSprintId(sprintId), ensureError);
774
+ if (!idR.ok) {
794
775
  showWarning("No sprint specified and no current sprint set.");
795
776
  showTip("Specify a sprint ID or create one first.");
796
777
  log.newline();
797
778
  return;
798
779
  }
780
+ const id = idR.value;
799
781
  const sprint = await getSprint(id);
800
782
  try {
801
783
  assertSprintStatus(sprint, ["draft"], "refine");
802
784
  } catch (err) {
803
- if (err instanceof Error) {
804
- showError(err.message);
805
- log.newline();
806
- }
785
+ showError(err instanceof Error ? err.message : String(err));
786
+ log.newline();
807
787
  return;
808
788
  }
809
789
  if (sprint.tickets.length === 0) {
@@ -861,9 +841,8 @@ async function sprintRefineCommand(args) {
861
841
  console.log(fieldMultiline("Description", ticket.description, 14));
862
842
  }
863
843
  log.newline();
864
- try {
865
- await getProject(ticket.projectName);
866
- } catch {
844
+ const projectR = await wrapAsync(() => getProject(ticket.projectName), ensureError);
845
+ if (!projectR.ok) {
867
846
  showWarning(`Project '${ticket.projectName}' not found.`);
868
847
  log.dim("Skipping this ticket.");
869
848
  log.newline();
@@ -884,21 +863,20 @@ async function sprintRefineCommand(args) {
884
863
  if (ticket.link) {
885
864
  const fetchSpinner = createSpinner("Fetching issue data...");
886
865
  fetchSpinner.start();
887
- try {
888
- const issueData = fetchIssueFromUrl(ticket.link);
889
- if (issueData) {
890
- issueContext = formatIssueContext(issueData);
891
- fetchSpinner.succeed(`Issue data fetched (${String(issueData.comments.length)} comment(s))`);
892
- } else {
893
- fetchSpinner.stop();
894
- }
895
- } catch (err) {
866
+ const link = ticket.link;
867
+ const issueR = Result3.try(() => fetchIssueFromUrl(link));
868
+ if (issueR.ok && issueR.value) {
869
+ issueContext = formatIssueContext(issueR.value);
870
+ fetchSpinner.succeed(`Issue data fetched (${String(issueR.value.comments.length)} comment(s))`);
871
+ } else if (!issueR.ok) {
896
872
  fetchSpinner.fail("Could not fetch issue data");
897
- if (err instanceof IssueFetchError) {
898
- showWarning(`${err.message} \u2014 continuing without issue context`);
899
- } else if (err instanceof Error) {
900
- showWarning(`${err.message} \u2014 continuing without issue context`);
873
+ if (issueR.error instanceof IssueFetchError) {
874
+ showWarning(`${issueR.error.message} \u2014 continuing without issue context`);
875
+ } else {
876
+ showWarning(`${issueR.error.message} \u2014 continuing without issue context`);
901
877
  }
878
+ } else {
879
+ fetchSpinner.stop();
902
880
  }
903
881
  }
904
882
  const refineDir = getRefinementDir(id, ticket.id);
@@ -911,40 +889,32 @@ async function sprintRefineCommand(args) {
911
889
  log.newline();
912
890
  const spinner = createSpinner(`Starting ${providerName} session...`);
913
891
  spinner.start();
914
- try {
915
- await runAiSession(refineDir, prompt, ticket.title);
916
- spinner.succeed(`${providerName} session completed`);
917
- } catch (err) {
892
+ const sessionR = await wrapAsync(() => runAiSession(refineDir, prompt, ticket.title), ensureError);
893
+ if (!sessionR.ok) {
918
894
  spinner.fail(`${providerName} session failed`);
919
- if (err instanceof Error) {
920
- showError(err.message);
921
- }
895
+ showError(sessionR.error.message);
922
896
  log.newline();
923
897
  skipped++;
924
898
  continue;
925
899
  }
900
+ spinner.succeed(`${providerName} session completed`);
926
901
  log.newline();
927
902
  if (await fileExists(outputFile)) {
928
- let content;
929
- try {
930
- content = await readFile(outputFile, "utf-8");
931
- } catch {
903
+ const contentR = await wrapAsync(() => readFile(outputFile, "utf-8"), ensureError);
904
+ if (!contentR.ok) {
932
905
  showError(`Failed to read requirements file: ${outputFile}`);
933
906
  log.newline();
934
907
  skipped++;
935
908
  continue;
936
909
  }
937
- let refinedRequirements;
938
- try {
939
- refinedRequirements = parseRequirementsFile(content);
940
- } catch (err) {
941
- if (err instanceof Error) {
942
- showError(`Failed to parse requirements file: ${err.message}`);
943
- }
910
+ const parseR = Result3.try(() => parseRequirementsFile(contentR.value));
911
+ if (!parseR.ok) {
912
+ showError(`Failed to parse requirements file: ${parseR.error.message}`);
944
913
  log.newline();
945
914
  skipped++;
946
915
  continue;
947
916
  }
917
+ const refinedRequirements = parseR.value;
948
918
  if (refinedRequirements.length === 0) {
949
919
  showWarning("No requirements found in output file.");
950
920
  log.newline();
@@ -1012,15 +982,11 @@ ${text}`;
1012
982
  showSuccess("All requirements approved!");
1013
983
  const sprintDir = getSprintDir(id);
1014
984
  const outputPath = join4(sprintDir, "requirements.md");
1015
- try {
1016
- await exportRequirementsToMarkdown(updatedSprint, outputPath);
985
+ const exportR = await wrapAsync(() => exportRequirementsToMarkdown(updatedSprint, outputPath), ensureError);
986
+ if (exportR.ok) {
1017
987
  log.dim(`Requirements saved to: ${outputPath}`);
1018
- } catch (err) {
1019
- if (err instanceof Error) {
1020
- showError(`Failed to write requirements: ${err.message}`);
1021
- } else {
1022
- showError("Failed to write requirements: Unknown error");
1023
- }
988
+ } else {
989
+ showError(`Failed to write requirements: ${exportR.error.message}`);
1024
990
  }
1025
991
  showTip('Run "ralphctl sprint plan" to generate tasks.');
1026
992
  } else {
@@ -1034,31 +1000,19 @@ ${text}`;
1034
1000
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
1035
1001
  import { join as join5 } from "path";
1036
1002
  import { confirm as confirm3 } from "@inquirer/prompts";
1003
+ import { Result as Result4 } from "typescript-result";
1037
1004
 
1038
1005
  // src/store/task.ts
1039
- var TaskNotFoundError = class extends Error {
1040
- taskId;
1041
- constructor(taskId) {
1042
- super(`Task not found: ${taskId}`);
1043
- this.name = "TaskNotFoundError";
1044
- this.taskId = taskId;
1045
- }
1046
- };
1047
- var DependencyCycleError = class extends Error {
1048
- cycle;
1049
- constructor(cycle) {
1050
- super(`Dependency cycle detected: ${cycle.join(" \u2192 ")}`);
1051
- this.name = "DependencyCycleError";
1052
- this.cycle = cycle;
1053
- }
1054
- };
1055
1006
  async function getTasks(sprintId) {
1056
1007
  const id = await resolveSprintId(sprintId);
1057
- return readValidatedJson(getTasksFilePath(id), TasksSchema);
1008
+ const result = await readValidatedJson(getTasksFilePath(id), TasksSchema);
1009
+ if (!result.ok) throw result.error;
1010
+ return result.value;
1058
1011
  }
1059
1012
  async function saveTasks(tasks, sprintId) {
1060
1013
  const id = await resolveSprintId(sprintId);
1061
- await writeValidatedJson(getTasksFilePath(id), tasks, TasksSchema);
1014
+ const result = await writeValidatedJson(getTasksFilePath(id), tasks, TasksSchema);
1015
+ if (!result.ok) throw result.error;
1062
1016
  }
1063
1017
  async function getTask(taskId, sprintId) {
1064
1018
  const tasks = await getTasks(sprintId);
@@ -1073,7 +1027,7 @@ async function addTask(input3, sprintId) {
1073
1027
  const sprint = await getSprint(id);
1074
1028
  assertSprintStatus(sprint, ["draft"], "add tasks");
1075
1029
  const tasksFilePath = getTasksFilePath(id);
1076
- return withFileLock(tasksFilePath, async () => {
1030
+ const lockResult = await withFileLock(tasksFilePath, async () => {
1077
1031
  const tasks = await getTasks(id);
1078
1032
  const maxOrder = tasks.reduce((max, t) => Math.max(max, t.order), 0);
1079
1033
  const task = {
@@ -1092,13 +1046,15 @@ async function addTask(input3, sprintId) {
1092
1046
  await saveTasks(tasks, id);
1093
1047
  return task;
1094
1048
  });
1049
+ if (!lockResult.ok) throw lockResult.error;
1050
+ return lockResult.value;
1095
1051
  }
1096
1052
  async function removeTask(taskId, sprintId) {
1097
1053
  const id = await resolveSprintId(sprintId);
1098
1054
  const sprint = await getSprint(id);
1099
1055
  assertSprintStatus(sprint, ["draft"], "remove tasks");
1100
1056
  const tasksFilePath = getTasksFilePath(id);
1101
- await withFileLock(tasksFilePath, async () => {
1057
+ const lockResult = await withFileLock(tasksFilePath, async () => {
1102
1058
  const tasks = await getTasks(id);
1103
1059
  const index = tasks.findIndex((t) => t.id === taskId);
1104
1060
  if (index === -1) {
@@ -1107,13 +1063,14 @@ async function removeTask(taskId, sprintId) {
1107
1063
  tasks.splice(index, 1);
1108
1064
  await saveTasks(tasks, id);
1109
1065
  });
1066
+ if (!lockResult.ok) throw lockResult.error;
1110
1067
  }
1111
1068
  async function updateTaskStatus(taskId, status, sprintId) {
1112
1069
  const id = await resolveSprintId(sprintId);
1113
1070
  const sprint = await getSprint(id);
1114
1071
  assertSprintStatus(sprint, ["active"], "update task status");
1115
1072
  const tasksFilePath = getTasksFilePath(id);
1116
- return withFileLock(tasksFilePath, async () => {
1073
+ const lockResult = await withFileLock(tasksFilePath, async () => {
1117
1074
  const tasks = await getTasks(id);
1118
1075
  const task = tasks.find((t) => t.id === taskId);
1119
1076
  if (!task) {
@@ -1123,13 +1080,15 @@ async function updateTaskStatus(taskId, status, sprintId) {
1123
1080
  await saveTasks(tasks, id);
1124
1081
  return task;
1125
1082
  });
1083
+ if (!lockResult.ok) throw lockResult.error;
1084
+ return lockResult.value;
1126
1085
  }
1127
1086
  async function updateTask(taskId, updates, sprintId) {
1128
1087
  const id = await resolveSprintId(sprintId);
1129
1088
  const sprint = await getSprint(id);
1130
1089
  assertSprintStatus(sprint, ["active"], "update task");
1131
1090
  const tasksFilePath = getTasksFilePath(id);
1132
- return withFileLock(tasksFilePath, async () => {
1091
+ const lockResult = await withFileLock(tasksFilePath, async () => {
1133
1092
  const tasks = await getTasks(id);
1134
1093
  const task = tasks.find((t) => t.id === taskId);
1135
1094
  if (!task) {
@@ -1144,6 +1103,8 @@ async function updateTask(taskId, updates, sprintId) {
1144
1103
  await saveTasks(tasks, id);
1145
1104
  return task;
1146
1105
  });
1106
+ if (!lockResult.ok) throw lockResult.error;
1107
+ return lockResult.value;
1147
1108
  }
1148
1109
  async function isTaskBlocked(taskId, sprintId) {
1149
1110
  const tasks = await getTasks(sprintId);
@@ -1175,7 +1136,7 @@ async function reorderTask(taskId, newOrder, sprintId) {
1175
1136
  const sprint = await getSprint(id);
1176
1137
  assertSprintStatus(sprint, ["draft"], "reorder tasks");
1177
1138
  const tasksFilePath = getTasksFilePath(id);
1178
- return withFileLock(tasksFilePath, async () => {
1139
+ const lockResult = await withFileLock(tasksFilePath, async () => {
1179
1140
  const tasks = await getTasks(id);
1180
1141
  const task = tasks.find((t) => t.id === taskId);
1181
1142
  if (!task) {
@@ -1198,6 +1159,8 @@ async function reorderTask(taskId, newOrder, sprintId) {
1198
1159
  await saveTasks(tasks, id);
1199
1160
  return task;
1200
1161
  });
1162
+ if (!lockResult.ok) throw lockResult.error;
1163
+ return lockResult.value;
1201
1164
  }
1202
1165
  async function listTasks(sprintId) {
1203
1166
  const tasks = await getTasks(sprintId);
@@ -1240,7 +1203,7 @@ function topologicalSort(tasks) {
1240
1203
  async function reorderByDependencies(sprintId) {
1241
1204
  const id = await resolveSprintId(sprintId);
1242
1205
  const tasksFilePath = getTasksFilePath(id);
1243
- await withFileLock(tasksFilePath, async () => {
1206
+ const lockResult = await withFileLock(tasksFilePath, async () => {
1244
1207
  const tasks = await getTasks(id);
1245
1208
  if (tasks.length === 0) return;
1246
1209
  const sorted = topologicalSort(tasks);
@@ -1249,6 +1212,7 @@ async function reorderByDependencies(sprintId) {
1249
1212
  });
1250
1213
  await saveTasks(sorted, id);
1251
1214
  });
1215
+ if (!lockResult.ok) throw lockResult.error;
1252
1216
  }
1253
1217
  function validateImportTasks(importTasks2, existingTasks, ticketIds) {
1254
1218
  const errors = [];
@@ -1331,7 +1295,7 @@ async function selectProject(message = "Select project:") {
1331
1295
  default: true
1332
1296
  });
1333
1297
  if (create) {
1334
- const { projectAddCommand } = await import("./add-MRGCS3US.mjs");
1298
+ const { projectAddCommand } = await import("./add-DVEYDCTR.mjs");
1335
1299
  await projectAddCommand({ interactive: true });
1336
1300
  const updated = await listProjects();
1337
1301
  if (updated.length === 0) return null;
@@ -1404,7 +1368,7 @@ async function selectSprint(message = "Select sprint:", filter) {
1404
1368
  default: true
1405
1369
  });
1406
1370
  if (create) {
1407
- const { sprintCreateCommand } = await import("./create-MG7E7PLQ.mjs");
1371
+ const { sprintCreateCommand } = await import("./create-MQ4OHZAX.mjs");
1408
1372
  await sprintCreateCommand({ interactive: true });
1409
1373
  const updated = await listSprints();
1410
1374
  const refiltered = filter ? updated.filter((s) => filter.includes(s.status)) : updated;
@@ -1439,7 +1403,7 @@ async function selectTicket(message = "Select ticket:", filter) {
1439
1403
  default: true
1440
1404
  });
1441
1405
  if (create) {
1442
- const { ticketAddCommand } = await import("./add-HGJCLWED.mjs");
1406
+ const { ticketAddCommand } = await import("./add-7LBVENXM.mjs");
1443
1407
  await ticketAddCommand({ interactive: true });
1444
1408
  const updated = await listTickets();
1445
1409
  const refiltered = filter ? updated.filter(filter) : updated;
@@ -1569,7 +1533,7 @@ async function importTasksAppend(tasks, sprintId) {
1569
1533
  const localToRealId = /* @__PURE__ */ new Map();
1570
1534
  const createdTasks = [];
1571
1535
  for (const taskInput of tasks) {
1572
- try {
1536
+ const addR = await wrapAsync(async () => {
1573
1537
  const projectPath = taskInput.projectPath;
1574
1538
  const task = await addTask(
1575
1539
  {
@@ -1583,32 +1547,36 @@ async function importTasksAppend(tasks, sprintId) {
1583
1547
  },
1584
1548
  sprintId
1585
1549
  );
1550
+ return task;
1551
+ }, ensureError);
1552
+ if (addR.ok) {
1553
+ const task = addR.value;
1586
1554
  if (taskInput.id) {
1587
1555
  localToRealId.set(taskInput.id, task.id);
1588
1556
  }
1589
1557
  createdTasks.push({ task: taskInput, realId: task.id });
1590
1558
  log.itemSuccess(`${task.id}: ${task.name}`);
1591
- } catch (err) {
1559
+ } else {
1592
1560
  log.itemError(`Failed to add: ${taskInput.name}`);
1593
- if (err instanceof Error) {
1594
- console.log(muted(` ${err.message}`));
1595
- }
1561
+ console.log(muted(` ${addR.error.message}`));
1596
1562
  }
1597
1563
  }
1598
1564
  const tasksFilePath = getTasksFilePath(sprintId);
1599
- await withFileLock(tasksFilePath, async () => {
1600
- const allTasks = await getTasks(sprintId);
1601
- for (const { task: taskInput, realId } of createdTasks) {
1602
- const blockedBy = (taskInput.blockedBy ?? []).map((localId) => localToRealId.get(localId) ?? "").filter((id) => id !== "");
1603
- if (blockedBy.length > 0) {
1604
- const taskToUpdate = allTasks.find((t) => t.id === realId);
1605
- if (taskToUpdate) {
1606
- taskToUpdate.blockedBy = blockedBy;
1565
+ unwrapOrThrow(
1566
+ await withFileLock(tasksFilePath, async () => {
1567
+ const allTasks = await getTasks(sprintId);
1568
+ for (const { task: taskInput, realId } of createdTasks) {
1569
+ const blockedBy = (taskInput.blockedBy ?? []).map((localId) => localToRealId.get(localId) ?? "").filter((id) => id !== "");
1570
+ if (blockedBy.length > 0) {
1571
+ const taskToUpdate = allTasks.find((t) => t.id === realId);
1572
+ if (taskToUpdate) {
1573
+ taskToUpdate.blockedBy = blockedBy;
1574
+ }
1607
1575
  }
1608
1576
  }
1609
- }
1610
- await saveTasks(allTasks, sprintId);
1611
- });
1577
+ await saveTasks(allTasks, sprintId);
1578
+ })
1579
+ );
1612
1580
  return createdTasks.length;
1613
1581
  }
1614
1582
  async function importTasksReplace(tasks, sprintId) {
@@ -1669,17 +1637,17 @@ async function getSprintContext(sprintName, ticketsByProject, existingTasks) {
1669
1637
  for (const [projectName, tickets] of ticketsByProject) {
1670
1638
  lines.push("");
1671
1639
  lines.push(`## Project: ${projectName}`);
1672
- try {
1673
- const project = await getProject(projectName);
1640
+ const projectR = await wrapAsync(() => getProject(projectName), ensureError);
1641
+ if (projectR.ok) {
1674
1642
  lines.push("");
1675
1643
  lines.push("### Repositories");
1676
- for (const repo of project.repositories) {
1644
+ for (const repo of projectR.value.repositories) {
1677
1645
  lines.push(`- **${repo.name}**: ${repo.path}`);
1678
1646
  if (repo.checkScript) {
1679
1647
  lines.push(` - Check: \`${repo.checkScript}\``);
1680
1648
  }
1681
1649
  }
1682
- } catch {
1650
+ } else {
1683
1651
  lines.push("Repositories: (project not found)");
1684
1652
  }
1685
1653
  lines.push("");
@@ -1758,23 +1726,20 @@ async function invokeAiAuto(prompt, repoPaths, planDir) {
1758
1726
  }
1759
1727
  async function sprintPlanCommand(args) {
1760
1728
  const { sprintId, options } = parseArgs2(args);
1761
- let id;
1762
- try {
1763
- id = await resolveSprintId(sprintId);
1764
- } catch {
1729
+ const idR = await wrapAsync(() => resolveSprintId(sprintId), ensureError);
1730
+ if (!idR.ok) {
1765
1731
  showWarning("No sprint specified and no current sprint set.");
1766
1732
  showNextStep("ralphctl sprint create", "create a new sprint");
1767
1733
  log.newline();
1768
1734
  return;
1769
1735
  }
1736
+ const id = idR.value;
1770
1737
  const sprint = await getSprint(id);
1771
1738
  try {
1772
1739
  assertSprintStatus(sprint, ["draft"], "plan");
1773
1740
  } catch (err) {
1774
- if (err instanceof Error) {
1775
- showError(err.message);
1776
- log.newline();
1777
- }
1741
+ showError(err instanceof Error ? err.message : String(err));
1742
+ log.newline();
1778
1743
  return;
1779
1744
  }
1780
1745
  if (sprint.tickets.length === 0) {
@@ -1831,11 +1796,10 @@ async function sprintPlanCommand(args) {
1831
1796
  const defaultPaths = [];
1832
1797
  for (const ticket of ticketsToProcess) {
1833
1798
  if (reposByProject.has(ticket.projectName)) continue;
1834
- try {
1835
- const project = await getProject(ticket.projectName);
1836
- reposByProject.set(ticket.projectName, project.repositories);
1837
- if (project.repositories[0]) defaultPaths.push(project.repositories[0].path);
1838
- } catch {
1799
+ const projectR = await wrapAsync(() => getProject(ticket.projectName), ensureError);
1800
+ if (projectR.ok) {
1801
+ reposByProject.set(ticket.projectName, projectR.value.repositories);
1802
+ if (projectR.value.repositories[0]) defaultPaths.push(projectR.value.repositories[0].path);
1839
1803
  }
1840
1804
  }
1841
1805
  const savedPaths = /* @__PURE__ */ new Set();
@@ -1900,19 +1864,16 @@ async function sprintPlanCommand(args) {
1900
1864
  const prompt = buildAutoPrompt(context, schema);
1901
1865
  const spinner = createSpinner(`${providerName} is planning tasks...`);
1902
1866
  spinner.start();
1903
- let output;
1904
- try {
1905
- output = await invokeAiAuto(prompt, selectedPaths, planDir);
1906
- spinner.succeed(`${providerName} finished planning`);
1907
- } catch (err) {
1867
+ const outputR = await wrapAsync(() => invokeAiAuto(prompt, selectedPaths, planDir), ensureError);
1868
+ if (!outputR.ok) {
1908
1869
  spinner.fail(`${providerName} planning failed`);
1909
- if (err instanceof Error) {
1910
- showError(`Failed to invoke ${providerName}: ${err.message}`);
1911
- showTip(`Make sure the ${providerName.toLowerCase()} CLI is installed and configured.`);
1912
- log.newline();
1913
- }
1870
+ showError(`Failed to invoke ${providerName}: ${outputR.error.message}`);
1871
+ showTip(`Make sure the ${providerName.toLowerCase()} CLI is installed and configured.`);
1872
+ log.newline();
1914
1873
  return;
1915
1874
  }
1875
+ spinner.succeed(`${providerName} finished planning`);
1876
+ const output = outputR.value;
1916
1877
  const blockedReason = parsePlanningBlocked(output);
1917
1878
  if (blockedReason) {
1918
1879
  showWarning(`Planning blocked: ${blockedReason}`);
@@ -1920,18 +1881,15 @@ async function sprintPlanCommand(args) {
1920
1881
  return;
1921
1882
  }
1922
1883
  console.log(muted("Parsing response..."));
1923
- let parsedTasks;
1924
- try {
1925
- parsedTasks = parseTasksJson(output);
1926
- } catch (err) {
1927
- if (err instanceof Error) {
1928
- showError(`Failed to parse ${providerName} output: ${err.message}`);
1929
- log.dim("Raw output:");
1930
- console.log(output);
1931
- log.newline();
1932
- }
1884
+ const parsedR = Result4.try(() => parseTasksJson(output));
1885
+ if (!parsedR.ok) {
1886
+ showError(`Failed to parse ${providerName} output: ${parsedR.error.message}`);
1887
+ log.dim("Raw output:");
1888
+ console.log(output);
1889
+ log.newline();
1933
1890
  return;
1934
1891
  }
1892
+ const parsedTasks = parsedR.value;
1935
1893
  if (parsedTasks.length === 0) {
1936
1894
  showWarning("No tasks generated.");
1937
1895
  log.newline();
@@ -1972,37 +1930,29 @@ async function sprintPlanCommand(args) {
1972
1930
  ${providerName} will read planning-context.md and explore the repos.`));
1973
1931
  console.log(muted(` When done, ask ${providerName} to write tasks to: ${outputFile}
1974
1932
  `));
1975
- try {
1976
- await invokeAiInteractive(prompt, selectedPaths, planDir);
1977
- } catch (err) {
1978
- if (err instanceof Error) {
1979
- showError(`Failed to invoke ${providerName}: ${err.message}`);
1980
- showTip(`Make sure the ${providerName.toLowerCase()} CLI is installed and configured.`);
1981
- log.newline();
1982
- }
1933
+ const interactiveR = await wrapAsync(() => invokeAiInteractive(prompt, selectedPaths, planDir), ensureError);
1934
+ if (!interactiveR.ok) {
1935
+ showError(`Failed to invoke ${providerName}: ${interactiveR.error.message}`);
1936
+ showTip(`Make sure the ${providerName.toLowerCase()} CLI is installed and configured.`);
1937
+ log.newline();
1983
1938
  return;
1984
1939
  }
1985
1940
  console.log("");
1986
1941
  if (await fileExists(outputFile)) {
1987
1942
  showInfo("Task file found. Processing...");
1988
- let content;
1989
- try {
1990
- content = await readFile3(outputFile, "utf-8");
1991
- } catch {
1943
+ const contentR = await wrapAsync(() => readFile3(outputFile, "utf-8"), ensureError);
1944
+ if (!contentR.ok) {
1992
1945
  showError(`Failed to read task file: ${outputFile}`);
1993
1946
  log.newline();
1994
1947
  return;
1995
1948
  }
1996
- let parsedTasks;
1997
- try {
1998
- parsedTasks = parseTasksJson(content);
1999
- } catch (err) {
2000
- if (err instanceof Error) {
2001
- showError(`Failed to parse task file: ${err.message}`);
2002
- log.newline();
2003
- }
1949
+ const parsedR = Result4.try(() => parseTasksJson(contentR.value));
1950
+ if (!parsedR.ok) {
1951
+ showError(`Failed to parse task file: ${parsedR.error.message}`);
1952
+ log.newline();
2004
1953
  return;
2005
1954
  }
1955
+ const parsedTasks = parsedR.value;
2006
1956
  if (parsedTasks.length === 0) {
2007
1957
  showWarning("No tasks in file.");
2008
1958
  log.newline();
@@ -2038,8 +1988,12 @@ async function sprintPlanCommand(args) {
2038
1988
  }
2039
1989
  }
2040
1990
 
1991
+ // src/commands/sprint/start.ts
1992
+ import { Result as Result8 } from "typescript-result";
1993
+
2041
1994
  // src/ai/runner.ts
2042
1995
  import { confirm as confirm5, input as input2, select as select2 } from "@inquirer/prompts";
1996
+ import { Result as Result7 } from "typescript-result";
2043
1997
 
2044
1998
  // src/ai/executor.ts
2045
1999
  import { confirm as confirm4 } from "@inquirer/prompts";
@@ -2141,11 +2095,13 @@ var RateLimitCoordinator = class {
2141
2095
  import { execSync } from "child_process";
2142
2096
  import { writeFile as writeFile4 } from "fs/promises";
2143
2097
  import { join as join7 } from "path";
2098
+ import { Result as Result6 } from "typescript-result";
2144
2099
 
2145
2100
  // src/ai/permissions.ts
2146
2101
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
2147
2102
  import { join as join6 } from "path";
2148
2103
  import { homedir } from "os";
2104
+ import { Result as Result5 } from "typescript-result";
2149
2105
  function getProviderPermissions(projectPath, provider) {
2150
2106
  const permissions = {
2151
2107
  allow: [],
@@ -2156,30 +2112,34 @@ function getProviderPermissions(projectPath, provider) {
2156
2112
  }
2157
2113
  const projectSettingsPath = join6(projectPath, ".claude", "settings.local.json");
2158
2114
  if (existsSync2(projectSettingsPath)) {
2159
- try {
2115
+ const projectResult = Result5.try(() => {
2160
2116
  const content = readFileSync2(projectSettingsPath, "utf-8");
2161
- const settings = JSON.parse(content);
2117
+ return JSON.parse(content);
2118
+ });
2119
+ if (projectResult.ok) {
2120
+ const settings = projectResult.value;
2162
2121
  if (settings.permissions?.allow) {
2163
2122
  permissions.allow.push(...settings.permissions.allow);
2164
2123
  }
2165
2124
  if (settings.permissions?.deny) {
2166
2125
  permissions.deny.push(...settings.permissions.deny);
2167
2126
  }
2168
- } catch {
2169
2127
  }
2170
2128
  }
2171
2129
  const userSettingsPath = join6(homedir(), ".claude", "settings.json");
2172
2130
  if (existsSync2(userSettingsPath)) {
2173
- try {
2131
+ const userResult = Result5.try(() => {
2174
2132
  const content = readFileSync2(userSettingsPath, "utf-8");
2175
- const settings = JSON.parse(content);
2133
+ return JSON.parse(content);
2134
+ });
2135
+ if (userResult.ok) {
2136
+ const settings = userResult.value;
2176
2137
  if (settings.permissions?.allow) {
2177
2138
  permissions.allow.push(...settings.permissions.allow);
2178
2139
  }
2179
2140
  if (settings.permissions?.deny) {
2180
2141
  permissions.deny.push(...settings.permissions.deny);
2181
2142
  }
2182
- } catch {
2183
2143
  }
2184
2144
  }
2185
2145
  return permissions;
@@ -2251,7 +2211,7 @@ function checkTaskPermissions(projectPath, options) {
2251
2211
 
2252
2212
  // src/ai/task-context.ts
2253
2213
  function getRecentGitHistory(projectPath, count = 20) {
2254
- try {
2214
+ const r = Result6.try(() => {
2255
2215
  assertSafeCwd(projectPath);
2256
2216
  const result = execSync(`git log -${String(count)} --oneline --no-decorate`, {
2257
2217
  cwd: projectPath,
@@ -2259,9 +2219,8 @@ function getRecentGitHistory(projectPath, count = 20) {
2259
2219
  stdio: ["pipe", "pipe", "pipe"]
2260
2220
  });
2261
2221
  return result.trim();
2262
- } catch {
2263
- return "(Unable to retrieve git history)";
2264
- }
2222
+ });
2223
+ return r.ok ? r.value : "(Unable to retrieve git history)";
2265
2224
  }
2266
2225
  function getEffectiveCheckScript(project, projectPath) {
2267
2226
  if (project) {
@@ -2390,14 +2349,10 @@ async function getProjectForTask(task, sprint) {
2390
2349
  if (!task.ticketId) return void 0;
2391
2350
  const ticket = sprint.tickets.find((t) => t.id === task.ticketId);
2392
2351
  if (!ticket) return void 0;
2393
- try {
2394
- return await getProject(ticket.projectName);
2395
- } catch (err) {
2396
- if (err instanceof ProjectNotFoundError) {
2397
- return void 0;
2398
- }
2399
- throw err;
2400
- }
2352
+ const r = await wrapAsync(async () => getProject(ticket.projectName), ensureError);
2353
+ if (r.ok) return r.value;
2354
+ if (r.error instanceof ProjectNotFoundError) return void 0;
2355
+ throw r.error;
2401
2356
  }
2402
2357
  function runPermissionCheck(ctx, noCommit, provider) {
2403
2358
  const checkScript = getEffectiveCheckScript(ctx.project, ctx.task.projectPath);
@@ -2479,10 +2434,7 @@ async function executeTask(ctx, options, sprintId, resumeSessionId, provider, ch
2479
2434
  sessionId: null
2480
2435
  };
2481
2436
  } finally {
2482
- try {
2483
- await unlink2(contextFile);
2484
- } catch {
2485
- }
2437
+ await unlink2(contextFile).catch(() => void 0);
2486
2438
  }
2487
2439
  }
2488
2440
  let spawnResult;
@@ -2552,10 +2504,7 @@ async function executeTask(ctx, options, sprintId, resumeSessionId, provider, ch
2552
2504
  throw err;
2553
2505
  } finally {
2554
2506
  deregister();
2555
- try {
2556
- await unlink2(contextFile);
2557
- } catch {
2558
- }
2507
+ await unlink2(contextFile).catch(() => void 0);
2559
2508
  }
2560
2509
  }
2561
2510
  const parsed = parseExecutionResult(spawnResult.stdout);
@@ -2923,43 +2872,28 @@ Resuming ${String(inProgressTasks.length)} in-progress task(s):`));
2923
2872
  }
2924
2873
  inFlightPaths.add(task.projectPath);
2925
2874
  const taskPromise = (async () => {
2926
- try {
2927
- const ctx = { sprint, task, project };
2928
- const result = await executeTask(
2929
- ctx,
2930
- options,
2931
- sprintId,
2932
- resumeId,
2933
- provider,
2934
- checkResults?.get(task.projectPath)
2935
- );
2936
- if (result.sessionId) {
2937
- taskSessionIds.set(task.id, result.sessionId);
2938
- }
2939
- return { task, result, error: null, isRateLimited: false };
2940
- } catch (err) {
2875
+ const ctx = { sprint, task, project };
2876
+ const resultR = await wrapAsync(
2877
+ () => executeTask(ctx, options, sprintId, resumeId, provider, checkResults?.get(task.projectPath)),
2878
+ ensureError
2879
+ );
2880
+ inFlightPaths.delete(task.projectPath);
2881
+ if (!resultR.ok) {
2882
+ const err = resultR.error;
2941
2883
  if (err instanceof SpawnError && err.rateLimited) {
2942
2884
  if (err.sessionId) {
2943
2885
  taskSessionIds.set(task.id, err.sessionId);
2944
2886
  }
2945
- const delay = err.retryAfterMs ?? 6e4;
2946
- coordinator.pause(delay);
2947
- return {
2948
- task,
2949
- result: null,
2950
- error: err,
2951
- isRateLimited: true
2952
- };
2887
+ coordinator.pause(err.retryAfterMs ?? 6e4);
2888
+ return { task, result: null, error: err, isRateLimited: true };
2953
2889
  }
2954
- return {
2955
- task,
2956
- result: null,
2957
- error: err instanceof Error ? err : new Error(String(err)),
2958
- isRateLimited: false
2959
- };
2960
- } finally {
2961
- inFlightPaths.delete(task.projectPath);
2890
+ return { task, result: null, error: err, isRateLimited: false };
2891
+ }
2892
+ const result = resultR.value;
2893
+ if (result.sessionId) {
2894
+ taskSessionIds.set(task.id, result.sessionId);
2962
2895
  }
2896
+ return { task, result, error: null, isRateLimited: false };
2963
2897
  })();
2964
2898
  running.set(task.id, taskPromise);
2965
2899
  }
@@ -3286,22 +3220,19 @@ async function ensureSprintBranches(sprintId, sprint, branchName) {
3286
3220
  const uniquePaths = [...new Set(remainingTasks.map((t) => t.projectPath))];
3287
3221
  if (uniquePaths.length === 0) return;
3288
3222
  for (const projectPath of uniquePaths) {
3289
- try {
3290
- if (hasUncommittedChanges(projectPath)) {
3291
- throw new Error(
3292
- `Repository at ${projectPath} has uncommitted changes. Commit or stash them before starting the sprint.`
3293
- );
3294
- }
3295
- } catch (err) {
3296
- if (err instanceof Error && err.message.includes("uncommitted changes")) {
3297
- throw err;
3298
- }
3223
+ const uncommittedR = Result7.try(() => hasUncommittedChanges(projectPath));
3224
+ if (!uncommittedR.ok) {
3299
3225
  log.dim(` Skipping ${projectPath} \u2014 not a git repository`);
3300
3226
  continue;
3301
3227
  }
3228
+ if (uncommittedR.value) {
3229
+ throw new Error(
3230
+ `Repository at ${projectPath} has uncommitted changes. Commit or stash them before starting the sprint.`
3231
+ );
3232
+ }
3302
3233
  }
3303
3234
  for (const projectPath of uniquePaths) {
3304
- try {
3235
+ const branchR = Result7.try(() => {
3305
3236
  const currentBranch = getCurrentBranch(projectPath);
3306
3237
  if (currentBranch === branchName) {
3307
3238
  log.dim(` Already on branch '${branchName}' in ${projectPath}`);
@@ -3309,11 +3240,11 @@ async function ensureSprintBranches(sprintId, sprint, branchName) {
3309
3240
  createAndCheckoutBranch(projectPath, branchName);
3310
3241
  log.success(` Branch '${branchName}' ready in ${projectPath}`);
3311
3242
  }
3312
- } catch (err) {
3313
- throw new Error(
3314
- `Failed to create branch '${branchName}' in ${projectPath}: ${err instanceof Error ? err.message : String(err)}`,
3315
- { cause: err }
3316
- );
3243
+ });
3244
+ if (!branchR.ok) {
3245
+ throw new Error(`Failed to create branch '${branchName}' in ${projectPath}: ${branchR.error.message}`, {
3246
+ cause: branchR.error
3247
+ });
3317
3248
  }
3318
3249
  }
3319
3250
  if (sprint.branch !== branchName) {
@@ -3322,16 +3253,13 @@ async function ensureSprintBranches(sprintId, sprint, branchName) {
3322
3253
  }
3323
3254
  }
3324
3255
  function verifySprintBranch(projectPath, expectedBranch) {
3325
- try {
3326
- if (verifyCurrentBranch(projectPath, expectedBranch)) {
3327
- return true;
3328
- }
3256
+ const r = Result7.try(() => {
3257
+ if (verifyCurrentBranch(projectPath, expectedBranch)) return true;
3329
3258
  log.dim(` Branch mismatch in ${projectPath} \u2014 checking out '${expectedBranch}'`);
3330
3259
  createAndCheckoutBranch(projectPath, expectedBranch);
3331
3260
  return verifyCurrentBranch(projectPath, expectedBranch);
3332
- } catch {
3333
- return false;
3334
- }
3261
+ });
3262
+ return r.ok ? r.value : false;
3335
3263
  }
3336
3264
  async function runCheckScripts(sprintId, sprint, refreshCheck = false) {
3337
3265
  const results = /* @__PURE__ */ new Map();
@@ -3461,28 +3389,26 @@ async function runSprint(sprintId, options) {
3461
3389
  log.info(`Branch: ${branchName}`);
3462
3390
  }
3463
3391
  if (branchName) {
3464
- try {
3465
- await ensureSprintBranches(id, sprint, branchName);
3466
- } catch (err) {
3392
+ const ensureR = await wrapAsync(() => ensureSprintBranches(id, sprint, branchName), ensureError);
3393
+ if (!ensureR.ok) {
3467
3394
  log.newline();
3468
- showError(err instanceof Error ? err.message : String(err));
3395
+ showError(ensureR.error.message);
3469
3396
  log.newline();
3470
3397
  return void 0;
3471
3398
  }
3472
3399
  }
3473
- try {
3474
- await reorderByDependencies(id);
3475
- log.dim("Tasks reordered by dependencies");
3476
- } catch (err) {
3477
- if (err instanceof DependencyCycleError) {
3400
+ const reorderR = await wrapAsync(() => reorderByDependencies(id), ensureError);
3401
+ if (!reorderR.ok) {
3402
+ if (reorderR.error instanceof DependencyCycleError) {
3478
3403
  log.newline();
3479
- showWarning(err.message);
3404
+ showWarning(reorderR.error.message);
3480
3405
  log.dim("Fix the dependency cycle before starting.");
3481
3406
  log.newline();
3482
3407
  return void 0;
3483
3408
  }
3484
- throw err;
3409
+ throw reorderR.error;
3485
3410
  }
3411
+ log.dim("Tasks reordered by dependencies");
3486
3412
  const checkResult = await runCheckScripts(id, sprint, options.refreshCheck);
3487
3413
  if (!checkResult.success) {
3488
3414
  log.newline();
@@ -3595,25 +3521,16 @@ function parseArgs3(args) {
3595
3521
  return { sprintId, options };
3596
3522
  }
3597
3523
  async function sprintStartCommand(args) {
3598
- let sprintId;
3599
- let options;
3600
- try {
3601
- const parsed = parseArgs3(args);
3602
- sprintId = parsed.sprintId;
3603
- options = parsed.options;
3604
- } catch (err) {
3605
- if (err instanceof Error) {
3606
- showError(err.message);
3607
- log.newline();
3608
- }
3524
+ const parseR = Result8.try(() => parseArgs3(args));
3525
+ if (!parseR.ok) {
3526
+ showError(parseR.error.message);
3527
+ log.newline();
3609
3528
  exitWithCode(EXIT_ERROR);
3610
3529
  }
3611
- try {
3612
- const summary = await runSprint(sprintId, options);
3613
- if (summary) {
3614
- exitWithCode(summary.exitCode);
3615
- }
3616
- } catch (err) {
3530
+ const { sprintId, options } = parseR.value;
3531
+ const runR = await wrapAsync(() => runSprint(sprintId, options), ensureError);
3532
+ if (!runR.ok) {
3533
+ const err = runR.error;
3617
3534
  if (err instanceof SprintNotFoundError) {
3618
3535
  showError(`Sprint not found: ${sprintId ?? "unknown"}`);
3619
3536
  log.newline();
@@ -3622,7 +3539,7 @@ async function sprintStartCommand(args) {
3622
3539
  showError(err.message);
3623
3540
  log.newline();
3624
3541
  exitWithCode(EXIT_ERROR);
3625
- } else if (err instanceof Error && err.message.includes("No sprint specified")) {
3542
+ } else if (err.message.includes("No sprint specified")) {
3626
3543
  showWarning("No sprint specified and no active sprint set.");
3627
3544
  showNextStep("ralphctl sprint start <id>", "specify a sprint ID");
3628
3545
  log.newline();
@@ -3630,11 +3547,15 @@ async function sprintStartCommand(args) {
3630
3547
  } else {
3631
3548
  throw err;
3632
3549
  }
3550
+ return;
3551
+ }
3552
+ const summary = runR.value;
3553
+ if (summary) {
3554
+ exitWithCode(summary.exitCode);
3633
3555
  }
3634
3556
  }
3635
3557
 
3636
3558
  export {
3637
- TaskNotFoundError,
3638
3559
  getTasks,
3639
3560
  saveTasks,
3640
3561
  getTask,