ralphctl 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/{add-MG26JWBP.mjs → add-DVPVHENV.mjs} +7 -7
  2. package/dist/{add-ZZYL4BSF.mjs → add-YVXM34RP.mjs} +6 -6
  3. package/dist/{chunk-LDSG7G2T.mjs → chunk-BSB4EDGR.mjs} +2 -2
  4. package/dist/{chunk-RQGD5WS6.mjs → chunk-CBMFRQ4Y.mjs} +3 -3
  5. package/dist/{chunk-Q4AVHUZL.mjs → chunk-FNAAA32W.mjs} +3 -3
  6. package/dist/{chunk-EGUFQNRB.mjs → chunk-GQ2WFKBN.mjs} +3 -3
  7. package/dist/{chunk-LCY32RW4.mjs → chunk-OFILN7QL.mjs} +183 -39
  8. package/dist/{chunk-MDE6KPJQ.mjs → chunk-OGEXYSFS.mjs} +5 -5
  9. package/dist/{chunk-TDBEEHTS.mjs → chunk-PYZEQ2VK.mjs} +5 -5
  10. package/dist/{chunk-57UWLHRH.mjs → chunk-VAZ3LJBI.mjs} +12 -1
  11. package/dist/{chunk-D2HWXEHH.mjs → chunk-WDMLPXOD.mjs} +2 -2
  12. package/dist/{chunk-WZTY77GY.mjs → chunk-XN2UIHBY.mjs} +10 -3
  13. package/dist/{chunk-WOMGKKZY.mjs → chunk-XPLYLRIM.mjs} +319 -15
  14. package/dist/{chunk-2FT37OZX.mjs → chunk-ZLWSPLWI.mjs} +53 -7
  15. package/dist/cli.mjs +19 -17
  16. package/dist/create-Z635FQKO.mjs +15 -0
  17. package/dist/{handle-SYVCFI6Y.mjs → handle-23EFF3BE.mjs} +1 -1
  18. package/dist/{mount-2ANLHHQE.mjs → mount-H2IH3MWE.mjs} +1455 -1193
  19. package/dist/{project-JF47ZWMF.mjs → project-DQHF4ISP.mjs} +3 -3
  20. package/dist/prompts/sprint-feedback.md +4 -0
  21. package/dist/prompts/task-evaluation.md +44 -2
  22. package/dist/prompts/task-execution.md +5 -0
  23. package/dist/{resolver-PG2DZEBX.mjs → resolver-OVPYVW6Q.mjs} +3 -3
  24. package/dist/{sprint-54DOSIJK.mjs → sprint-4E26AB5F.mjs} +4 -4
  25. package/dist/start-2WH4BTDB.mjs +19 -0
  26. package/package.json +1 -1
  27. package/dist/create-PQK6KKRD.mjs +0 -15
  28. package/dist/start-2SZTBKGF.mjs +0 -19
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  addSingleTicketInteractive,
4
4
  ticketAddCommand
5
- } from "./chunk-MDE6KPJQ.mjs";
6
- import "./chunk-EGUFQNRB.mjs";
5
+ } from "./chunk-OGEXYSFS.mjs";
6
+ import "./chunk-GQ2WFKBN.mjs";
7
7
  import "./chunk-CFUVE2BP.mjs";
8
8
  import "./chunk-747KW2RW.mjs";
9
- import "./chunk-LDSG7G2T.mjs";
10
- import "./chunk-RQGD5WS6.mjs";
11
- import "./chunk-WZTY77GY.mjs";
9
+ import "./chunk-BSB4EDGR.mjs";
10
+ import "./chunk-CBMFRQ4Y.mjs";
11
+ import "./chunk-XN2UIHBY.mjs";
12
12
  import "./chunk-IWXBJD2D.mjs";
13
- import "./chunk-D2HWXEHH.mjs";
14
- import "./chunk-57UWLHRH.mjs";
13
+ import "./chunk-WDMLPXOD.mjs";
14
+ import "./chunk-VAZ3LJBI.mjs";
15
15
  export {
16
16
  addSingleTicketInteractive,
17
17
  ticketAddCommand
@@ -2,15 +2,15 @@
2
2
  import {
3
3
  addCheckScriptToRepository,
4
4
  projectAddCommand
5
- } from "./chunk-TDBEEHTS.mjs";
6
- import "./chunk-2FT37OZX.mjs";
5
+ } from "./chunk-PYZEQ2VK.mjs";
6
+ import "./chunk-ZLWSPLWI.mjs";
7
7
  import "./chunk-CFUVE2BP.mjs";
8
8
  import "./chunk-747KW2RW.mjs";
9
- import "./chunk-LDSG7G2T.mjs";
10
- import "./chunk-WZTY77GY.mjs";
9
+ import "./chunk-BSB4EDGR.mjs";
10
+ import "./chunk-XN2UIHBY.mjs";
11
11
  import "./chunk-IWXBJD2D.mjs";
12
- import "./chunk-D2HWXEHH.mjs";
13
- import "./chunk-57UWLHRH.mjs";
12
+ import "./chunk-WDMLPXOD.mjs";
13
+ import "./chunk-VAZ3LJBI.mjs";
14
14
  export {
15
15
  addCheckScriptToRepository,
16
16
  projectAddCommand
@@ -8,13 +8,13 @@ import {
8
8
  readValidatedJson,
9
9
  validateProjectPath,
10
10
  writeValidatedJson
11
- } from "./chunk-D2HWXEHH.mjs";
11
+ } from "./chunk-WDMLPXOD.mjs";
12
12
  import {
13
13
  ParseError,
14
14
  ProjectExistsError,
15
15
  ProjectNotFoundError,
16
16
  ValidationError
17
- } from "./chunk-57UWLHRH.mjs";
17
+ } from "./chunk-VAZ3LJBI.mjs";
18
18
 
19
19
  // src/integration/persistence/project.ts
20
20
  import { basename, resolve } from "path";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getCurrentSprint,
4
4
  log
5
- } from "./chunk-WZTY77GY.mjs";
5
+ } from "./chunk-XN2UIHBY.mjs";
6
6
  import {
7
7
  SprintSchema,
8
8
  TasksSchema,
@@ -21,14 +21,14 @@ import {
21
21
  readValidatedJson,
22
22
  removeDir,
23
23
  writeValidatedJson
24
- } from "./chunk-D2HWXEHH.mjs";
24
+ } from "./chunk-WDMLPXOD.mjs";
25
25
  import {
26
26
  LockError,
27
27
  NoCurrentSprintError,
28
28
  SprintNotFoundError,
29
29
  SprintStatusError,
30
30
  StorageError
31
- } from "./chunk-57UWLHRH.mjs";
31
+ } from "./chunk-VAZ3LJBI.mjs";
32
32
 
33
33
  // src/integration/persistence/progress.ts
34
34
  import { execSync } from "child_process";
@@ -8,10 +8,10 @@ import {
8
8
  } from "./chunk-747KW2RW.mjs";
9
9
  import {
10
10
  listProjects
11
- } from "./chunk-LDSG7G2T.mjs";
11
+ } from "./chunk-BSB4EDGR.mjs";
12
12
  import {
13
13
  createSprint
14
- } from "./chunk-RQGD5WS6.mjs";
14
+ } from "./chunk-CBMFRQ4Y.mjs";
15
15
  import {
16
16
  emoji,
17
17
  field,
@@ -22,7 +22,7 @@ import {
22
22
  showNextStep,
23
23
  showRandomQuote,
24
24
  showSuccess
25
- } from "./chunk-WZTY77GY.mjs";
25
+ } from "./chunk-XN2UIHBY.mjs";
26
26
 
27
27
  // src/integration/cli/commands/sprint/create.ts
28
28
  async function sprintCreateCommand(options = {}) {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  assertSprintStatus,
4
4
  resolveSprintId
5
- } from "./chunk-RQGD5WS6.mjs";
5
+ } from "./chunk-CBMFRQ4Y.mjs";
6
6
  import {
7
7
  unwrapOrThrow
8
8
  } from "./chunk-IWXBJD2D.mjs";
@@ -12,11 +12,11 @@ import {
12
12
  getSprintFilePath,
13
13
  readValidatedJson,
14
14
  writeValidatedJson
15
- } from "./chunk-D2HWXEHH.mjs";
15
+ } from "./chunk-WDMLPXOD.mjs";
16
16
  import {
17
17
  IssueFetchError,
18
18
  TicketNotFoundError
19
- } from "./chunk-57UWLHRH.mjs";
19
+ } from "./chunk-VAZ3LJBI.mjs";
20
20
 
21
21
  // src/domain/strings.ts
22
22
  function truncate(str, max) {
@@ -13,13 +13,13 @@ import {
13
13
  buildTicketRefinePrompt,
14
14
  getActiveProvider,
15
15
  spawnInteractive
16
- } from "./chunk-2FT37OZX.mjs";
16
+ } from "./chunk-ZLWSPLWI.mjs";
17
17
  import {
18
18
  fetchIssueFromUrl,
19
19
  formatIssueContext,
20
20
  formatTicketDisplay,
21
21
  truncate
22
- } from "./chunk-EGUFQNRB.mjs";
22
+ } from "./chunk-GQ2WFKBN.mjs";
23
23
  import {
24
24
  EXIT_ERROR,
25
25
  EXIT_NO_TASKS,
@@ -31,15 +31,16 @@ import {
31
31
  } from "./chunk-747KW2RW.mjs";
32
32
  import {
33
33
  updateProject
34
- } from "./chunk-LDSG7G2T.mjs";
34
+ } from "./chunk-BSB4EDGR.mjs";
35
35
  import {
36
36
  assertSprintStatus,
37
37
  closeSprint,
38
38
  getSprint,
39
39
  resolveSprintId,
40
40
  withFileLock
41
- } from "./chunk-RQGD5WS6.mjs";
41
+ } from "./chunk-CBMFRQ4Y.mjs";
42
42
  import {
43
+ isTTY,
43
44
  log,
44
45
  printHeader,
45
46
  renderTable,
@@ -49,7 +50,7 @@ import {
49
50
  showSuccess,
50
51
  showWarning,
51
52
  terminalBell
52
- } from "./chunk-WZTY77GY.mjs";
53
+ } from "./chunk-XN2UIHBY.mjs";
53
54
  import {
54
55
  ensureError,
55
56
  unwrapOrThrow,
@@ -68,7 +69,7 @@ import {
68
69
  getTasksFilePath,
69
70
  readValidatedJson,
70
71
  writeValidatedJson
71
- } from "./chunk-D2HWXEHH.mjs";
72
+ } from "./chunk-WDMLPXOD.mjs";
72
73
  import {
73
74
  BranchPreflightError,
74
75
  DependencyCycleError,
@@ -81,7 +82,7 @@ import {
81
82
  StepError,
82
83
  StorageError,
83
84
  TaskNotFoundError
84
- } from "./chunk-57UWLHRH.mjs";
85
+ } from "./chunk-VAZ3LJBI.mjs";
85
86
 
86
87
  // src/integration/persistence/task.ts
87
88
  async function getTasks(sprintId) {
@@ -386,7 +387,9 @@ function unwrapError(result) {
386
387
  async function executePipeline(pipeline2, initialContext) {
387
388
  let ctx = { ...initialContext };
388
389
  const stepResults = [];
390
+ let stepsRun = 0;
389
391
  for (const step2 of pipeline2.steps) {
392
+ if (stepsRun > 0 && ctx.abortSignal?.aborted) break;
390
393
  const startTime = Date.now();
391
394
  try {
392
395
  if (step2.hooks?.pre) {
@@ -439,6 +442,7 @@ async function executePipeline(pipeline2, initialContext) {
439
442
  status: "success",
440
443
  durationMs: Date.now() - startTime
441
444
  });
445
+ stepsRun++;
442
446
  } catch (err) {
443
447
  const error = err instanceof Error ? err : new Error(String(err));
444
448
  stepResults.push({
@@ -1373,14 +1377,16 @@ async function runScheduler(opts, ctx) {
1373
1377
  const itemKey = opts.itemKey ?? DEFAULT_ITEM_KEY;
1374
1378
  const maxConcurrency = opts.strategy.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY;
1375
1379
  const mutexKeyFn = opts.strategy.mutexKey ?? identityMutexKey;
1376
- const services = opts.createServices?.() ?? defaultServices();
1380
+ const abortSignal = ctx.abortSignal;
1381
+ const services = opts.createServices?.({ abortSignal }) ?? defaultServices();
1377
1382
  const state = {
1378
1383
  stats: {
1379
1384
  completed: 0,
1380
1385
  failed: 0,
1381
1386
  requeued: 0,
1382
1387
  inFlight: 0,
1383
- pausedRepos: /* @__PURE__ */ new Set()
1388
+ pausedRepos: /* @__PURE__ */ new Set(),
1389
+ cancelled: false
1384
1390
  },
1385
1391
  retryNowQueue: [],
1386
1392
  requeueQueue: [],
@@ -1394,6 +1400,10 @@ async function runScheduler(opts, ctx) {
1394
1400
  let concurrencyLimit = -1;
1395
1401
  try {
1396
1402
  for (; ; ) {
1403
+ if (abortSignal?.aborted) {
1404
+ state.stats.cancelled = true;
1405
+ break;
1406
+ }
1397
1407
  if (state.stopRequested) break;
1398
1408
  if (state.terminalError) break;
1399
1409
  if (opts.strategy.stopWhen?.(state.stats)) break;
@@ -1433,6 +1443,11 @@ async function runScheduler(opts, ctx) {
1433
1443
  }
1434
1444
  continue;
1435
1445
  }
1446
+ if (abortSignal?.aborted) {
1447
+ state.stats.cancelled = true;
1448
+ opts.policies.onSettle?.(item, "failed");
1449
+ break;
1450
+ }
1436
1451
  const attempt = (state.attempts.get(item) ?? 0) + 1;
1437
1452
  state.attempts.set(item, attempt);
1438
1453
  const action = opts.policies.retryPolicy(item, error, attempt);
@@ -1441,6 +1456,9 @@ async function runScheduler(opts, ctx) {
1441
1456
  if (state.terminalError && state.shouldDrainOnFail && state.inFlight.size > 0) {
1442
1457
  await Promise.allSettled(state.inFlight.values());
1443
1458
  }
1459
+ if (state.stats.cancelled && state.inFlight.size > 0) {
1460
+ await Promise.allSettled(state.inFlight.values());
1461
+ }
1444
1462
  } finally {
1445
1463
  if (opts.disposeServices) {
1446
1464
  opts.disposeServices(services);
@@ -1821,7 +1839,8 @@ ${instructions}`;
1821
1839
  args,
1822
1840
  env: this.aiSession.getSpawnEnv(),
1823
1841
  maxRetries: options?.maxRetries,
1824
- resumeSessionId: options?.resumeSessionId
1842
+ resumeSessionId: options?.resumeSessionId,
1843
+ abortSignal: options?.abortSignal
1825
1844
  });
1826
1845
  spinner.succeed(`${this.aiSession.getProviderDisplayName()} completed: ${task.name}`);
1827
1846
  const ctx = { sprintId: sprint.id, taskId: task.id, projectPath: repoPath };
@@ -1958,7 +1977,8 @@ ${instructions}`;
1958
1977
  cwd: repoPath,
1959
1978
  args: ["--add-dir", sprintDir],
1960
1979
  env: this.aiSession.getSpawnEnv(),
1961
- maxTurns: options?.maxTurns
1980
+ maxTurns: options?.maxTurns,
1981
+ abortSignal: options?.abortSignal
1962
1982
  });
1963
1983
  spinner.succeed(`${this.aiSession.getProviderDisplayName()} completed: ${syntheticTask.name}`);
1964
1984
  const ctx = { sprintId: sprint.id, taskId: syntheticTask.id, projectPath: repoPath };
@@ -2304,7 +2324,8 @@ function executeTask(deps) {
2304
2324
  const result = await deps.useCase.executeOneTask(task, sprint, {
2305
2325
  ...deps.options,
2306
2326
  ...resumeSessionId ? { resumeSessionId } : {},
2307
- ...ctx.contractPath ? { contractPath: ctx.contractPath } : {}
2327
+ ...ctx.contractPath ? { contractPath: ctx.contractPath } : {},
2328
+ ...ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}
2308
2329
  });
2309
2330
  if (!result.success) {
2310
2331
  return Result.error(new ParseError(`Task not completed: ${result.blocked ?? "Unknown reason"}`));
@@ -2555,7 +2576,8 @@ var EvaluateTaskUseCase = class {
2555
2576
  result = await this.aiSession.spawnWithRetry(prompt, {
2556
2577
  cwd: repoPath,
2557
2578
  args,
2558
- env: this.aiSession.getSpawnEnv()
2579
+ env: this.aiSession.getSpawnEnv(),
2580
+ abortSignal: options?.abortSignal
2559
2581
  });
2560
2582
  } catch (err) {
2561
2583
  this.logger.warning(
@@ -2613,7 +2635,8 @@ var EvaluateTaskUseCase = class {
2613
2635
  cwd: repoPath,
2614
2636
  args: ["--add-dir", sprintDir],
2615
2637
  env: this.aiSession.getSpawnEnv(),
2616
- maxTurns: options?.maxTurns
2638
+ maxTurns: options?.maxTurns,
2639
+ abortSignal: options?.abortSignal
2617
2640
  });
2618
2641
  spinner.succeed(`Fix attempt completed: ${task.name}`);
2619
2642
  const signals = this.parser.parseExecutionSignals(result.output);
@@ -2732,7 +2755,8 @@ function runEvaluatorLoopStep(useCase, options) {
2732
2755
  const result = await useCase.execute(ctx.sprintId, ctx.taskId, {
2733
2756
  iterations: options.iterations,
2734
2757
  maxTurns: options.maxTurns,
2735
- fallbackModel: ctx.generatorModel ?? void 0
2758
+ fallbackModel: ctx.generatorModel ?? void 0,
2759
+ abortSignal: ctx.abortSignal ?? options.abortSignal
2736
2760
  });
2737
2761
  if (!result.ok) {
2738
2762
  return Result.error(result.error);
@@ -2781,13 +2805,15 @@ function evaluateTask(deps) {
2781
2805
  },
2782
2806
  {
2783
2807
  iterations: evalCfg.iterations,
2784
- maxTurns: deps.options.maxTurns
2808
+ maxTurns: deps.options.maxTurns,
2809
+ abortSignal: ctx.abortSignal
2785
2810
  }
2786
2811
  );
2787
2812
  const innerCtx = {
2788
2813
  sprintId: ctx.sprint.id,
2789
2814
  taskId: ctx.task.id,
2790
- generatorModel: ctx.generatorModel ?? null
2815
+ generatorModel: ctx.generatorModel ?? null,
2816
+ abortSignal: ctx.abortSignal
2791
2817
  };
2792
2818
  let stepNames = [];
2793
2819
  try {
@@ -2933,11 +2959,59 @@ function withStepTrace(signalBus) {
2933
2959
  });
2934
2960
  }
2935
2961
 
2962
+ // src/business/pipelines/execute/resolve-dirty-tree.ts
2963
+ async function resolveDirtyTree(deps) {
2964
+ const { repoPath, options, prompt, isTTY: isTTY2, logger, external } = deps;
2965
+ let dirty;
2966
+ try {
2967
+ dirty = external.hasUncommittedChanges(repoPath);
2968
+ } catch {
2969
+ return;
2970
+ }
2971
+ if (!dirty) return;
2972
+ if (options.resetOnResume) {
2973
+ logger.warning(`Resetting working tree to HEAD in ${repoPath}...`);
2974
+ external.hardResetWorkingTree(repoPath);
2975
+ logger.success(`Working tree reset in ${repoPath}`);
2976
+ return;
2977
+ }
2978
+ if (options.resumeDirty) {
2979
+ logger.info(`Resuming with existing changes in ${repoPath}`);
2980
+ return;
2981
+ }
2982
+ if (isTTY2) {
2983
+ const keepDirty = await prompt.confirm({
2984
+ message: `Repository at ${repoPath} has uncommitted changes. Resume with existing changes?`,
2985
+ default: true
2986
+ });
2987
+ if (keepDirty) {
2988
+ logger.info(`Resuming with existing changes in ${repoPath}`);
2989
+ return;
2990
+ }
2991
+ const doReset = await prompt.confirm({
2992
+ message: "Reset to latest commit and resume?",
2993
+ default: false
2994
+ });
2995
+ if (doReset) {
2996
+ logger.warning(`Resetting working tree to HEAD in ${repoPath}...`);
2997
+ external.hardResetWorkingTree(repoPath);
2998
+ logger.success(`Working tree reset in ${repoPath}`);
2999
+ return;
3000
+ }
3001
+ throw new StorageError("Aborted: commit, stash, or discard changes before resuming.");
3002
+ }
3003
+ throw new StorageError(
3004
+ `Repository at ${repoPath} has uncommitted changes. Commit or stash them before starting.
3005
+ Hint: pass --resume-dirty to resume with the changes intact, or --reset-on-resume to discard them.`
3006
+ );
3007
+ }
3008
+
2936
3009
  // src/business/pipelines/execute.ts
2937
3010
  var EXIT_SUCCESS = 0;
2938
3011
  var EXIT_ERROR2 = 1;
2939
3012
  var EXIT_NO_TASKS2 = 2;
2940
3013
  var EXIT_ALL_BLOCKED = 3;
3014
+ var EXIT_INTERRUPTED = 130;
2941
3015
  var MAX_CONCURRENCY = 10;
2942
3016
  var MAX_BRANCH_RETRIES = 3;
2943
3017
  function checkPreconditionsStep(persistence, ui, logger, options) {
@@ -3111,7 +3185,7 @@ function prepareTasksStep(persistence) {
3111
3185
  }
3112
3186
  });
3113
3187
  }
3114
- function ensureBranchesStep(external, persistence, logger) {
3188
+ function ensureBranchesStep(external, persistence, logger, prompt, isTTY2, options) {
3115
3189
  return step("ensure-branches", async (ctx) => {
3116
3190
  if (ctx.proceedAfterPrecondition === false || ctx.tasksEmpty) {
3117
3191
  const empty = {};
@@ -3146,17 +3220,14 @@ function ensureBranchesStep(external, persistence, logger) {
3146
3220
  }
3147
3221
  try {
3148
3222
  for (const projectPath of uniquePaths) {
3149
- try {
3150
- if (external.hasUncommittedChanges(projectPath)) {
3151
- return Result.error(
3152
- new StorageError(
3153
- `Repository at ${projectPath} has uncommitted changes. Commit or stash them before starting.`
3154
- )
3155
- );
3156
- }
3157
- } catch (err) {
3158
- if (err instanceof StorageError) return Result.error(err);
3159
- }
3223
+ await resolveDirtyTree({
3224
+ repoPath: projectPath,
3225
+ options,
3226
+ prompt,
3227
+ isTTY: isTTY2(),
3228
+ logger,
3229
+ external
3230
+ });
3160
3231
  }
3161
3232
  for (const projectPath of uniquePaths) {
3162
3233
  const currentBranch = external.getCurrentBranch(projectPath);
@@ -3228,6 +3299,7 @@ function executeTasksStep(deps, options) {
3228
3299
  );
3229
3300
  const taskSessionIds = /* @__PURE__ */ new Map();
3230
3301
  const failedRepos = /* @__PURE__ */ new Set();
3302
+ const launchedTaskIds = /* @__PURE__ */ new Set();
3231
3303
  let firstBlockedReason = null;
3232
3304
  const forceSequential = options.session === true || options.step === true;
3233
3305
  const uniqueRepoIds = new Set(allTasks.map((t) => t.repoId));
@@ -3360,6 +3432,7 @@ function executeTasksStep(deps, options) {
3360
3432
  deps.signalBus.emit({ type: "rate-limit-resumed", timestamp: /* @__PURE__ */ new Date() });
3361
3433
  },
3362
3434
  onLaunch: (task) => {
3435
+ launchedTaskIds.add(task.id);
3363
3436
  const resumeId = taskSessionIds.get(task.id);
3364
3437
  const action = resumeId ? "Resuming" : "Starting";
3365
3438
  deps.logger.info(`--- ${action} task ${String(task.order)}: ${task.name} ---`);
@@ -3386,9 +3459,36 @@ function executeTasksStep(deps, options) {
3386
3459
  failed: 0,
3387
3460
  requeued: 0,
3388
3461
  inFlight: 0,
3389
- pausedRepos: /* @__PURE__ */ new Set()
3462
+ pausedRepos: /* @__PURE__ */ new Set(),
3463
+ cancelled: false
3390
3464
  };
3391
3465
  const stats = schedResult.ok ? schedResult.value.schedulerStats ?? emptyStats : emptyStats;
3466
+ if (stats.cancelled && launchedTaskIds.size > 0) {
3467
+ try {
3468
+ const currentTasks = await deps.persistence.getTasks(sprint.id);
3469
+ const toCancel = currentTasks.filter((t) => launchedTaskIds.has(t.id) && t.status === "in_progress");
3470
+ if (toCancel.length > 0) {
3471
+ const updated = currentTasks.map(
3472
+ (t) => launchedTaskIds.has(t.id) && t.status === "in_progress" ? { ...t, status: "cancelled" } : t
3473
+ );
3474
+ await deps.persistence.saveTasks(updated, sprint.id);
3475
+ for (const t of toCancel) {
3476
+ deps.signalBus.emit({
3477
+ type: "task-finished",
3478
+ sprintId: sprint.id,
3479
+ taskId: t.id,
3480
+ status: "cancelled",
3481
+ timestamp: /* @__PURE__ */ new Date()
3482
+ });
3483
+ }
3484
+ deps.logger.warning(`Cancelled ${String(toCancel.length)} in-progress task(s).`);
3485
+ }
3486
+ } catch (err) {
3487
+ deps.logger.warning(
3488
+ `Failed to flip in-progress tasks to cancelled: ${err instanceof Error ? err.message : String(err)}`
3489
+ );
3490
+ }
3491
+ }
3392
3492
  const summary = await buildExecutionSummary({
3393
3493
  persistence: deps.persistence,
3394
3494
  sprintId: sprint.id,
@@ -3442,6 +3542,15 @@ async function buildExecutionSummary(args) {
3442
3542
  const remaining = await persistence.getRemainingTasks(sprintId);
3443
3543
  const currentTasks = await persistence.getTasks(sprintId);
3444
3544
  const blocked = remaining.filter((t) => isBlocked(t, currentTasks));
3545
+ if (stats.cancelled) {
3546
+ return {
3547
+ completed: stats.completed,
3548
+ remaining: remaining.length,
3549
+ blocked: blocked.length,
3550
+ stopReason: "cancelled",
3551
+ exitCode: EXIT_INTERRUPTED
3552
+ };
3553
+ }
3445
3554
  if (failedRepos.size > 0) {
3446
3555
  logger.warning(`Repos with failed checks: ${[...failedRepos].join(", ")}`);
3447
3556
  }
@@ -3562,7 +3671,7 @@ function createExecuteSprintPipeline(deps, options = {}) {
3562
3671
  autoActivateStep(deps.persistence),
3563
3672
  assertActiveStep(),
3564
3673
  prepareTasksStep(deps.persistence),
3565
- ensureBranchesStep(deps.external, deps.persistence, deps.logger),
3674
+ ensureBranchesStep(deps.external, deps.persistence, deps.logger, deps.prompt, deps.isTTY, options),
3566
3675
  sprintStartCheckStep(deps.external, deps.persistence, deps.logger, options),
3567
3676
  executeTasksStep(deps, options),
3568
3677
  feedbackLoopStep(deps, options)
@@ -4013,10 +4122,12 @@ async function importTasksAppend(tasks, sprintId) {
4013
4122
  name: taskInput.name,
4014
4123
  description: taskInput.description,
4015
4124
  steps: taskInput.steps ?? [],
4125
+ verificationCriteria: taskInput.verificationCriteria ?? [],
4016
4126
  ticketId: taskInput.ticketId,
4017
4127
  blockedBy: [],
4018
4128
  // Set later
4019
- repoId: taskInput.repoId
4129
+ repoId: taskInput.repoId,
4130
+ extraDimensions: taskInput.extraDimensions
4020
4131
  },
4021
4132
  sprintId
4022
4133
  );
@@ -4069,7 +4180,8 @@ async function importTasksReplace(tasks, sprintId) {
4069
4180
  // Set in second pass
4070
4181
  repoId: taskInput.repoId,
4071
4182
  evaluated: false,
4072
- verified: false
4183
+ verified: false,
4184
+ extraDimensions: taskInput.extraDimensions
4073
4185
  });
4074
4186
  }
4075
4187
  for (let i = 0; i < tasks.length; i++) {
@@ -4142,7 +4254,7 @@ async function runAiSession(workingDir, prompt, ticketTitle) {
4142
4254
  }
4143
4255
 
4144
4256
  // src/integration/ai/evaluator.ts
4145
- var DIMENSION_LINE = /\*\*([A-Za-z][A-Za-z0-9]{2,29})\*\*\s*:\s*(PASS|FAIL)\s*(?:—|-)\s*(.+)/gi;
4257
+ var DIMENSION_LINE = /\*\*([A-Za-z][A-Za-z0-9]{2,29})\*\*\s*:\s*(PASS|FAIL)(?:\s*(?:—|-)\s*([^\n]*\S))?/gi;
4146
4258
  function parseDimensionScores(output) {
4147
4259
  const scores = [];
4148
4260
  const seen = /* @__PURE__ */ new Set();
@@ -4151,15 +4263,16 @@ function parseDimensionScores(output) {
4151
4263
  while ((match = DIMENSION_LINE.exec(output)) !== null) {
4152
4264
  const rawName = match[1];
4153
4265
  const verdict = match[2];
4154
- const finding = match[3];
4155
- if (!rawName || !verdict || !finding) continue;
4266
+ const finding = (match[3] ?? "").trim();
4267
+ if (!rawName || !verdict) continue;
4156
4268
  const name = rawName.toLowerCase();
4157
4269
  if (seen.has(name)) continue;
4158
4270
  seen.add(name);
4271
+ const hasJustification = finding.length > 0;
4159
4272
  scores.push({
4160
4273
  dimension: name,
4161
- passed: verdict.toUpperCase() === "PASS",
4162
- finding: finding.trim()
4274
+ passed: verdict.toUpperCase() === "PASS" && hasJustification,
4275
+ finding
4163
4276
  });
4164
4277
  }
4165
4278
  return scores;
@@ -4651,6 +4764,25 @@ function hasUncommittedChanges(cwd) {
4651
4764
  }
4652
4765
  return result.stdout.trim().length > 0;
4653
4766
  }
4767
+ function hardResetWorkingTree(cwd) {
4768
+ assertSafeCwd(cwd);
4769
+ const reset = spawnSync2("git", ["reset", "--hard", "HEAD"], {
4770
+ cwd,
4771
+ encoding: "utf-8",
4772
+ stdio: ["pipe", "pipe", "pipe"]
4773
+ });
4774
+ if (reset.status !== 0) {
4775
+ throw new StorageError(`Failed to reset working tree in ${cwd}: ${reset.stderr.trim() || reset.stdout.trim()}`);
4776
+ }
4777
+ const clean = spawnSync2("git", ["clean", "-fd"], {
4778
+ cwd,
4779
+ encoding: "utf-8",
4780
+ stdio: ["pipe", "pipe", "pipe"]
4781
+ });
4782
+ if (clean.status !== 0) {
4783
+ throw new StorageError(`Failed to clean working tree in ${cwd}: ${clean.stderr.trim() || clean.stdout.trim()}`);
4784
+ }
4785
+ }
4654
4786
  function autoCommit(cwd, message) {
4655
4787
  assertSafeCwd(cwd);
4656
4788
  const add = spawnSync2("git", ["add", "-A"], {
@@ -4729,6 +4861,9 @@ var DefaultExternalAdapter = class {
4729
4861
  hasUncommittedChanges(projectPath) {
4730
4862
  return hasUncommittedChanges(projectPath);
4731
4863
  }
4864
+ hardResetWorkingTree(projectPath) {
4865
+ hardResetWorkingTree(projectPath);
4866
+ }
4732
4867
  autoCommit(projectPath, message) {
4733
4868
  autoCommit(projectPath, message);
4734
4869
  return Promise.resolve();
@@ -5106,7 +5241,9 @@ function createExecuteSprintPipeline2(shared, options = {}) {
5106
5241
  signalHandler: shared.signalHandler,
5107
5242
  signalBus: shared.signalBus,
5108
5243
  createRateLimitCoordinator: shared.createRateLimitCoordinator,
5109
- processLifecycle: shared.processLifecycle
5244
+ processLifecycle: shared.processLifecycle,
5245
+ prompt: shared.prompt,
5246
+ isTTY
5110
5247
  },
5111
5248
  options
5112
5249
  );
@@ -5186,10 +5323,17 @@ function parseArgs(args) {
5186
5323
  options.noEvaluate = true;
5187
5324
  } else if (arg === "--no-feedback") {
5188
5325
  options.noFeedback = true;
5326
+ } else if (arg === "--resume-dirty") {
5327
+ options.resumeDirty = true;
5328
+ } else if (arg === "--reset-on-resume") {
5329
+ options.resetOnResume = true;
5189
5330
  } else if (!arg?.startsWith("-")) {
5190
5331
  sprintId = arg;
5191
5332
  }
5192
5333
  }
5334
+ if (options.resumeDirty && options.resetOnResume) {
5335
+ throw new Error("--resume-dirty and --reset-on-resume are mutually exclusive");
5336
+ }
5193
5337
  return { sprintId, options };
5194
5338
  }
5195
5339
  async function sprintStartCommand(args) {
@@ -3,7 +3,7 @@ import {
3
3
  addTicket,
4
4
  fetchIssueFromUrl,
5
5
  truncate
6
- } from "./chunk-EGUFQNRB.mjs";
6
+ } from "./chunk-GQ2WFKBN.mjs";
7
7
  import {
8
8
  EXIT_ERROR,
9
9
  exitWithCode
@@ -13,10 +13,10 @@ import {
13
13
  } from "./chunk-747KW2RW.mjs";
14
14
  import {
15
15
  getProjectById
16
- } from "./chunk-LDSG7G2T.mjs";
16
+ } from "./chunk-BSB4EDGR.mjs";
17
17
  import {
18
18
  getCurrentSprintOrThrow
19
- } from "./chunk-RQGD5WS6.mjs";
19
+ } from "./chunk-CBMFRQ4Y.mjs";
20
20
  import {
21
21
  createSpinner,
22
22
  emoji,
@@ -29,7 +29,7 @@ import {
29
29
  showError,
30
30
  showSuccess,
31
31
  showWarning
32
- } from "./chunk-WZTY77GY.mjs";
32
+ } from "./chunk-XN2UIHBY.mjs";
33
33
  import {
34
34
  ensureError,
35
35
  wrapAsync
@@ -37,7 +37,7 @@ import {
37
37
  import {
38
38
  IOError,
39
39
  SprintStatusError
40
- } from "./chunk-57UWLHRH.mjs";
40
+ } from "./chunk-VAZ3LJBI.mjs";
41
41
 
42
42
  // src/integration/cli/commands/ticket/add.ts
43
43
  import { Result as Result2 } from "typescript-result";