@nathapp/nax 0.36.1 → 0.36.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nax.js CHANGED
@@ -20063,6 +20063,22 @@ function computeStoryContentHash(story) {
20063
20063
  }
20064
20064
 
20065
20065
  // src/routing/index.ts
20066
+ var exports_routing = {};
20067
+ __export(exports_routing, {
20068
+ tryLlmBatchRoute: () => tryLlmBatchRoute,
20069
+ routeTask: () => routeTask,
20070
+ routeStory: () => routeStory,
20071
+ manualStrategy: () => manualStrategy,
20072
+ loadCustomStrategy: () => loadCustomStrategy,
20073
+ llmStrategy: () => llmStrategy,
20074
+ keywordStrategy: () => keywordStrategy,
20075
+ determineTestStrategy: () => determineTestStrategy2,
20076
+ computeStoryContentHash: () => computeStoryContentHash,
20077
+ complexityToModelTier: () => complexityToModelTier2,
20078
+ classifyComplexity: () => classifyComplexity2,
20079
+ buildStrategyChain: () => buildStrategyChain,
20080
+ StrategyChain: () => StrategyChain
20081
+ });
20066
20082
  var init_routing = __esm(() => {
20067
20083
  init_router();
20068
20084
  init_chain();
@@ -20657,7 +20673,7 @@ var package_default;
20657
20673
  var init_package = __esm(() => {
20658
20674
  package_default = {
20659
20675
  name: "@nathapp/nax",
20660
- version: "0.36.1",
20676
+ version: "0.36.2",
20661
20677
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
20662
20678
  type: "module",
20663
20679
  bin: {
@@ -20718,8 +20734,8 @@ var init_version = __esm(() => {
20718
20734
  NAX_VERSION = package_default.version;
20719
20735
  NAX_COMMIT = (() => {
20720
20736
  try {
20721
- if (/^[0-9a-f]{6,10}$/.test("b241bab"))
20722
- return "b241bab";
20737
+ if (/^[0-9a-f]{6,10}$/.test("eb77e7d"))
20738
+ return "eb77e7d";
20723
20739
  } catch {}
20724
20740
  try {
20725
20741
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -20791,9 +20807,10 @@ function collectStoryMetrics(ctx, storyStartTime) {
20791
20807
  const routing = ctx.routing;
20792
20808
  const agentResult = ctx.agentResult;
20793
20809
  const escalationCount = story.escalations?.length || 0;
20794
- const attempts = Math.max(1, story.attempts || 1);
20810
+ const priorFailureCount = story.priorFailures?.length || 0;
20811
+ const attempts = priorFailureCount + Math.max(1, story.attempts || 1);
20795
20812
  const finalTier = escalationCount > 0 ? story.escalations[escalationCount - 1].toTier : routing.modelTier;
20796
- const firstPassSuccess = agentResult?.success === true && escalationCount === 0;
20813
+ const firstPassSuccess = agentResult?.success === true && escalationCount === 0 && priorFailureCount === 0;
20797
20814
  const modelEntry = ctx.config.models[routing.modelTier];
20798
20815
  const modelDef = modelEntry ? resolveModel(modelEntry) : null;
20799
20816
  const modelUsed = modelDef?.model || routing.modelTier;
@@ -20809,12 +20826,13 @@ function collectStoryMetrics(ctx, storyStartTime) {
20809
20826
  attempts,
20810
20827
  finalTier,
20811
20828
  success: agentResult?.success || false,
20812
- cost: agentResult?.estimatedCost || 0,
20829
+ cost: (ctx.accumulatedAttemptCost ?? 0) + (agentResult?.estimatedCost || 0),
20813
20830
  durationMs: agentResult?.durationMs || 0,
20814
20831
  firstPassSuccess,
20815
20832
  startedAt: storyStartTime,
20816
20833
  completedAt: new Date().toISOString(),
20817
- fullSuiteGatePassed
20834
+ fullSuiteGatePassed,
20835
+ runtimeCrashes: ctx.storyRuntimeCrashes ?? 0
20818
20836
  };
20819
20837
  }
20820
20838
  function collectBatchMetrics(ctx, storyStartTime) {
@@ -20844,7 +20862,8 @@ function collectBatchMetrics(ctx, storyStartTime) {
20844
20862
  firstPassSuccess: true,
20845
20863
  startedAt: storyStartTime,
20846
20864
  completedAt: new Date().toISOString(),
20847
- fullSuiteGatePassed: false
20865
+ fullSuiteGatePassed: false,
20866
+ runtimeCrashes: 0
20848
20867
  };
20849
20868
  });
20850
20869
  }
@@ -22227,6 +22246,11 @@ var init_interaction = __esm(() => {
22227
22246
  });
22228
22247
 
22229
22248
  // src/pipeline/runner.ts
22249
+ var exports_runner = {};
22250
+ __export(exports_runner, {
22251
+ runPipeline: () => runPipeline,
22252
+ MAX_STAGE_RETRIES: () => MAX_STAGE_RETRIES
22253
+ });
22230
22254
  async function runPipeline(stages, context, eventEmitter) {
22231
22255
  const logger = getLogger();
22232
22256
  const retryCountMap = new Map;
@@ -22946,7 +22970,7 @@ var init_completion = __esm(() => {
22946
22970
  storyId: completedStory.id,
22947
22971
  story: completedStory,
22948
22972
  passed: true,
22949
- durationMs: storyMetric?.durationMs ?? 0,
22973
+ runElapsedMs: storyMetric?.durationMs ?? 0,
22950
22974
  cost: costPerStory,
22951
22975
  modelTier: ctx.routing?.modelTier,
22952
22976
  testStrategy: ctx.routing?.testStrategy
@@ -27456,6 +27480,22 @@ var init_routing2 = __esm(() => {
27456
27480
  };
27457
27481
  });
27458
27482
 
27483
+ // src/verification/crash-detector.ts
27484
+ function detectRuntimeCrash(output) {
27485
+ if (!output)
27486
+ return false;
27487
+ return CRASH_PATTERNS.some((pattern) => output.includes(pattern));
27488
+ }
27489
+ var CRASH_PATTERNS;
27490
+ var init_crash_detector = __esm(() => {
27491
+ CRASH_PATTERNS = [
27492
+ "panic(main thread)",
27493
+ "Segmentation fault",
27494
+ "Bun has crashed",
27495
+ "oh no: Bun has crashed"
27496
+ ];
27497
+ });
27498
+
27459
27499
  // src/pipeline/stages/verify.ts
27460
27500
  function coerceSmartTestRunner(val) {
27461
27501
  if (val === undefined || val === true)
@@ -27473,6 +27513,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
27473
27513
  var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
27474
27514
  var init_verify = __esm(() => {
27475
27515
  init_logger2();
27516
+ init_crash_detector();
27476
27517
  init_runners();
27477
27518
  init_smart_runner();
27478
27519
  DEFAULT_SMART_RUNNER_CONFIG2 = {
@@ -27544,7 +27585,7 @@ var init_verify = __esm(() => {
27544
27585
  });
27545
27586
  ctx.verifyResult = {
27546
27587
  success: result.success,
27547
- status: result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : "TEST_FAILURE",
27588
+ status: result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : detectRuntimeCrash(result.output) ? "RUNTIME_CRASH" : "TEST_FAILURE",
27548
27589
  storyId: ctx.story.id,
27549
27590
  strategy: "scoped",
27550
27591
  passCount: result.passCount ?? 0,
@@ -27595,6 +27636,25 @@ var init_verify = __esm(() => {
27595
27636
  });
27596
27637
 
27597
27638
  // src/pipeline/stages/index.ts
27639
+ var exports_stages = {};
27640
+ __export(exports_stages, {
27641
+ verifyStage: () => verifyStage,
27642
+ routingStage: () => routingStage,
27643
+ reviewStage: () => reviewStage,
27644
+ regressionStage: () => regressionStage,
27645
+ rectifyStage: () => rectifyStage,
27646
+ queueCheckStage: () => queueCheckStage,
27647
+ promptStage: () => promptStage,
27648
+ postRunPipeline: () => postRunPipeline,
27649
+ optimizerStage: () => optimizerStage,
27650
+ executionStage: () => executionStage,
27651
+ defaultPipeline: () => defaultPipeline,
27652
+ contextStage: () => contextStage,
27653
+ constitutionStage: () => constitutionStage,
27654
+ completionStage: () => completionStage,
27655
+ autofixStage: () => autofixStage,
27656
+ acceptanceStage: () => acceptanceStage
27657
+ });
27598
27658
  var defaultPipeline, postRunPipeline;
27599
27659
  var init_stages = __esm(() => {
27600
27660
  init_acceptance2();
@@ -29244,11 +29304,12 @@ var init_crash_recovery = __esm(() => {
29244
29304
 
29245
29305
  // src/execution/escalation/escalation.ts
29246
29306
  function escalateTier(currentTier, tierOrder) {
29247
- const currentIndex = tierOrder.findIndex((t) => t.tier === currentTier);
29307
+ const getName = (t) => t.tier ?? t.name ?? null;
29308
+ const currentIndex = tierOrder.findIndex((t) => getName(t) === currentTier);
29248
29309
  if (currentIndex === -1 || currentIndex === tierOrder.length - 1) {
29249
29310
  return null;
29250
29311
  }
29251
- return tierOrder[currentIndex + 1].tier;
29312
+ return getName(tierOrder[currentIndex + 1]);
29252
29313
  }
29253
29314
  function calculateMaxIterations(tierOrder) {
29254
29315
  return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
@@ -29345,13 +29406,14 @@ var init_tier_outcome = __esm(() => {
29345
29406
  });
29346
29407
 
29347
29408
  // src/execution/escalation/tier-escalation.ts
29348
- function buildEscalationFailure(story, currentTier, reviewFindings) {
29409
+ function buildEscalationFailure(story, currentTier, reviewFindings, cost) {
29349
29410
  return {
29350
29411
  attempt: (story.attempts ?? 0) + 1,
29351
29412
  modelTier: currentTier,
29352
29413
  stage: "escalation",
29353
29414
  summary: `Failed with tier ${currentTier}, escalating to next tier`,
29354
29415
  reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
29416
+ cost: cost ?? 0,
29355
29417
  timestamp: new Date().toISOString()
29356
29418
  };
29357
29419
  }
@@ -29364,6 +29426,8 @@ function resolveMaxAttemptsOutcome(failureCategory) {
29364
29426
  case "verifier-rejected":
29365
29427
  case "greenfield-no-tests":
29366
29428
  return "pause";
29429
+ case "runtime-crash":
29430
+ return "pause";
29367
29431
  case "session-failure":
29368
29432
  case "tests-failing":
29369
29433
  return "fail";
@@ -29387,8 +29451,17 @@ async function tryLlmBatchRoute2(config2, stories, label = "routing") {
29387
29451
  });
29388
29452
  }
29389
29453
  }
29454
+ function shouldRetrySameTier(verifyResult) {
29455
+ return verifyResult?.status === "RUNTIME_CRASH";
29456
+ }
29390
29457
  async function handleTierEscalation(ctx) {
29391
29458
  const logger = getSafeLogger();
29459
+ if (shouldRetrySameTier(ctx.verifyResult)) {
29460
+ logger?.warn("escalation", "Runtime crash detected \u2014 retrying same tier (transient, not a code issue)", {
29461
+ storyId: ctx.story.id
29462
+ });
29463
+ return { outcome: "retry-same", prdDirty: false, prd: ctx.prd };
29464
+ }
29392
29465
  const nextTier = escalateTier(ctx.routing.modelTier, ctx.config.autoMode.escalation.tierOrder);
29393
29466
  const escalateWholeBatch = ctx.config.autoMode.escalation.escalateEntireBatch ?? true;
29394
29467
  const storiesToEscalate = ctx.isBatchExecution && escalateWholeBatch ? ctx.storiesToExecute : [ctx.story];
@@ -29442,7 +29515,7 @@ async function handleTierEscalation(ctx) {
29442
29515
  const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
29443
29516
  const isChangingTier = currentStoryTier !== nextTier;
29444
29517
  const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
29445
- const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings);
29518
+ const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
29446
29519
  return {
29447
29520
  ...s,
29448
29521
  attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
@@ -29452,7 +29525,7 @@ async function handleTierEscalation(ctx) {
29452
29525
  };
29453
29526
  })
29454
29527
  };
29455
- await savePRD(updatedPrd, ctx.prdPath);
29528
+ await _tierEscalationDeps.savePRD(updatedPrd, ctx.prdPath);
29456
29529
  for (const story of storiesToEscalate) {
29457
29530
  clearCacheForStory(story.id);
29458
29531
  }
@@ -29465,6 +29538,7 @@ async function handleTierEscalation(ctx) {
29465
29538
  prd: updatedPrd
29466
29539
  };
29467
29540
  }
29541
+ var _tierEscalationDeps;
29468
29542
  var init_tier_escalation = __esm(() => {
29469
29543
  init_hooks();
29470
29544
  init_logger2();
@@ -29474,6 +29548,9 @@ var init_tier_escalation = __esm(() => {
29474
29548
  init_helpers();
29475
29549
  init_progress();
29476
29550
  init_tier_outcome();
29551
+ _tierEscalationDeps = {
29552
+ savePRD
29553
+ };
29477
29554
  });
29478
29555
 
29479
29556
  // src/execution/escalation/index.ts
@@ -30075,6 +30152,10 @@ var init_headless_formatter = __esm(() => {
30075
30152
  });
30076
30153
 
30077
30154
  // src/worktree/manager.ts
30155
+ var exports_manager = {};
30156
+ __export(exports_manager, {
30157
+ WorktreeManager: () => WorktreeManager
30158
+ });
30078
30159
  import { existsSync as existsSync26, symlinkSync } from "fs";
30079
30160
  import { join as join32 } from "path";
30080
30161
 
@@ -30219,6 +30300,11 @@ var init_manager = __esm(() => {
30219
30300
  });
30220
30301
 
30221
30302
  // src/worktree/merge.ts
30303
+ var exports_merge = {};
30304
+ __export(exports_merge, {
30305
+ MergeEngine: () => MergeEngine
30306
+ });
30307
+
30222
30308
  class MergeEngine {
30223
30309
  worktreeManager;
30224
30310
  constructor(worktreeManager) {
@@ -30497,10 +30583,12 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
30497
30583
  const logger = getSafeLogger();
30498
30584
  const worktreeManager = new WorktreeManager;
30499
30585
  const results = {
30500
- successfulStories: [],
30501
- failedStories: [],
30586
+ pipelinePassed: [],
30587
+ merged: [],
30588
+ failed: [],
30502
30589
  totalCost: 0,
30503
- conflictedStories: []
30590
+ mergeConflicts: [],
30591
+ storyCosts: new Map
30504
30592
  };
30505
30593
  const worktreeSetup = [];
30506
30594
  for (const story of stories) {
@@ -30513,7 +30601,7 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
30513
30601
  worktreePath
30514
30602
  });
30515
30603
  } catch (error48) {
30516
- results.failedStories.push({
30604
+ results.failed.push({
30517
30605
  story,
30518
30606
  error: `Failed to create worktree: ${error48 instanceof Error ? error48.message : String(error48)}`
30519
30607
  });
@@ -30528,14 +30616,15 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
30528
30616
  const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
30529
30617
  const executePromise = executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter).then((result) => {
30530
30618
  results.totalCost += result.cost;
30619
+ results.storyCosts.set(story.id, result.cost);
30531
30620
  if (result.success) {
30532
- results.successfulStories.push(story);
30621
+ results.pipelinePassed.push(story);
30533
30622
  logger?.info("parallel", "Story execution succeeded", {
30534
30623
  storyId: story.id,
30535
30624
  cost: result.cost
30536
30625
  });
30537
30626
  } else {
30538
- results.failedStories.push({ story, error: result.error || "Unknown error" });
30627
+ results.failed.push({ story, error: result.error || "Unknown error" });
30539
30628
  logger?.error("parallel", "Story execution failed", {
30540
30629
  storyId: story.id,
30541
30630
  error: result.error
@@ -30575,6 +30664,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30575
30664
  let storiesCompleted = 0;
30576
30665
  let totalCost = 0;
30577
30666
  const currentPrd = prd;
30667
+ const allMergeConflicts = [];
30578
30668
  for (let batchIndex = 0;batchIndex < batches.length; batchIndex++) {
30579
30669
  const batch = batches[batchIndex];
30580
30670
  logger?.info("parallel", `Executing batch ${batchIndex + 1}/${batches.length}`, {
@@ -30591,8 +30681,8 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30591
30681
  };
30592
30682
  const batchResult = await executeParallelBatch(batch, projectRoot, config2, currentPrd, baseContext, maxConcurrency, eventEmitter);
30593
30683
  totalCost += batchResult.totalCost;
30594
- if (batchResult.successfulStories.length > 0) {
30595
- const successfulIds = batchResult.successfulStories.map((s) => s.id);
30684
+ if (batchResult.pipelinePassed.length > 0) {
30685
+ const successfulIds = batchResult.pipelinePassed.map((s) => s.id);
30596
30686
  const deps = buildDependencyMap(batch);
30597
30687
  logger?.info("parallel", "Merging successful stories", {
30598
30688
  storyIds: successfulIds
@@ -30602,15 +30692,19 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30602
30692
  if (mergeResult.success) {
30603
30693
  markStoryPassed(currentPrd, mergeResult.storyId);
30604
30694
  storiesCompleted++;
30695
+ const mergedStory = batchResult.pipelinePassed.find((s) => s.id === mergeResult.storyId);
30696
+ if (mergedStory)
30697
+ batchResult.merged.push(mergedStory);
30605
30698
  logger?.info("parallel", "Story merged successfully", {
30606
30699
  storyId: mergeResult.storyId,
30607
30700
  retryCount: mergeResult.retryCount
30608
30701
  });
30609
30702
  } else {
30610
30703
  markStoryFailed(currentPrd, mergeResult.storyId);
30611
- batchResult.conflictedStories.push({
30704
+ batchResult.mergeConflicts.push({
30612
30705
  storyId: mergeResult.storyId,
30613
- conflictFiles: mergeResult.conflictFiles || []
30706
+ conflictFiles: mergeResult.conflictFiles || [],
30707
+ originalCost: batchResult.storyCosts.get(mergeResult.storyId) ?? 0
30614
30708
  });
30615
30709
  logger?.error("parallel", "Merge conflict", {
30616
30710
  storyId: mergeResult.storyId,
@@ -30623,7 +30717,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30623
30717
  }
30624
30718
  }
30625
30719
  }
30626
- for (const { story, error: error48 } of batchResult.failedStories) {
30720
+ for (const { story, error: error48 } of batchResult.failed) {
30627
30721
  markStoryFailed(currentPrd, story.id);
30628
30722
  logger?.error("parallel", "Cleaning up failed story worktree", {
30629
30723
  storyId: story.id,
@@ -30639,10 +30733,12 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30639
30733
  }
30640
30734
  }
30641
30735
  await savePRD(currentPrd, prdPath);
30736
+ allMergeConflicts.push(...batchResult.mergeConflicts);
30642
30737
  logger?.info("parallel", `Batch ${batchIndex + 1} complete`, {
30643
- successful: batchResult.successfulStories.length,
30644
- failed: batchResult.failedStories.length,
30645
- conflicts: batchResult.conflictedStories.length,
30738
+ pipelinePassed: batchResult.pipelinePassed.length,
30739
+ merged: batchResult.merged.length,
30740
+ failed: batchResult.failed.length,
30741
+ mergeConflicts: batchResult.mergeConflicts.length,
30646
30742
  batchCost: batchResult.totalCost
30647
30743
  });
30648
30744
  }
@@ -30650,7 +30746,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30650
30746
  storiesCompleted,
30651
30747
  totalCost
30652
30748
  });
30653
- return { storiesCompleted, totalCost, updatedPrd: currentPrd };
30749
+ return { storiesCompleted, totalCost, updatedPrd: currentPrd, mergeConflicts: allMergeConflicts };
30654
30750
  }
30655
30751
  var init_parallel = __esm(() => {
30656
30752
  init_logger2();
@@ -30742,6 +30838,123 @@ __export(exports_parallel_executor, {
30742
30838
  });
30743
30839
  import * as os5 from "os";
30744
30840
  import path15 from "path";
30841
+ async function rectifyConflictedStory(options) {
30842
+ const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter } = options;
30843
+ const logger = getSafeLogger();
30844
+ logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
30845
+ try {
30846
+ const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
30847
+ const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_merge(), exports_merge));
30848
+ const { runPipeline: runPipeline2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
30849
+ const { defaultPipeline: defaultPipeline2 } = await Promise.resolve().then(() => (init_stages(), exports_stages));
30850
+ const { routeTask: routeTask2 } = await Promise.resolve().then(() => (init_routing(), exports_routing));
30851
+ const worktreeManager = new WorktreeManager2;
30852
+ const mergeEngine = new MergeEngine2(worktreeManager);
30853
+ try {
30854
+ await worktreeManager.remove(workdir, storyId);
30855
+ } catch {}
30856
+ await worktreeManager.create(workdir, storyId);
30857
+ const worktreePath = path15.join(workdir, ".nax-wt", storyId);
30858
+ const story = prd.userStories.find((s) => s.id === storyId);
30859
+ if (!story) {
30860
+ return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
30861
+ }
30862
+ const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
30863
+ const pipelineContext = {
30864
+ config: config2,
30865
+ prd,
30866
+ story,
30867
+ stories: [story],
30868
+ workdir: worktreePath,
30869
+ featureDir: undefined,
30870
+ hooks,
30871
+ plugins: pluginRegistry,
30872
+ storyStartTime: new Date().toISOString(),
30873
+ routing
30874
+ };
30875
+ const pipelineResult = await runPipeline2(defaultPipeline2, pipelineContext, eventEmitter);
30876
+ const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
30877
+ if (!pipelineResult.success) {
30878
+ logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
30879
+ return { success: false, storyId, cost, finalConflict: false, pipelineFailure: true };
30880
+ }
30881
+ const mergeResults = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
30882
+ const mergeResult = mergeResults[0];
30883
+ if (!mergeResult || !mergeResult.success) {
30884
+ const conflictFiles = mergeResult?.conflictFiles ?? [];
30885
+ logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
30886
+ return { success: false, storyId, cost, finalConflict: true, conflictFiles };
30887
+ }
30888
+ logger?.info("parallel", "Rectification succeeded - story merged", {
30889
+ storyId,
30890
+ originalCost: options.originalCost,
30891
+ rectificationCost: cost
30892
+ });
30893
+ return { success: true, storyId, cost };
30894
+ } catch (error48) {
30895
+ logger?.error("parallel", "Rectification failed - preserving worktree", {
30896
+ storyId,
30897
+ error: error48 instanceof Error ? error48.message : String(error48)
30898
+ });
30899
+ return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
30900
+ }
30901
+ }
30902
+ async function runRectificationPass(conflictedStories, options, prd) {
30903
+ const logger = getSafeLogger();
30904
+ const { workdir, config: config2, hooks, pluginRegistry, eventEmitter } = options;
30905
+ const rectificationMetrics = [];
30906
+ let rectifiedCount = 0;
30907
+ let stillConflictingCount = 0;
30908
+ let additionalCost = 0;
30909
+ logger?.info("parallel", "Starting merge conflict rectification", {
30910
+ stories: conflictedStories.map((s) => s.storyId),
30911
+ totalConflicts: conflictedStories.length
30912
+ });
30913
+ for (const conflictInfo of conflictedStories) {
30914
+ const result = await _parallelExecutorDeps.rectifyConflictedStory({
30915
+ ...conflictInfo,
30916
+ workdir,
30917
+ config: config2,
30918
+ hooks,
30919
+ pluginRegistry,
30920
+ prd,
30921
+ eventEmitter
30922
+ });
30923
+ additionalCost += result.cost;
30924
+ if (result.success) {
30925
+ markStoryPassed(prd, result.storyId);
30926
+ rectifiedCount++;
30927
+ rectificationMetrics.push({
30928
+ storyId: result.storyId,
30929
+ complexity: "unknown",
30930
+ modelTier: "parallel",
30931
+ modelUsed: "parallel",
30932
+ attempts: 1,
30933
+ finalTier: "parallel",
30934
+ success: true,
30935
+ cost: result.cost,
30936
+ durationMs: 0,
30937
+ firstPassSuccess: false,
30938
+ startedAt: new Date().toISOString(),
30939
+ completedAt: new Date().toISOString(),
30940
+ source: "rectification",
30941
+ rectifiedFromConflict: true,
30942
+ originalCost: conflictInfo.originalCost,
30943
+ rectificationCost: result.cost
30944
+ });
30945
+ } else {
30946
+ const isFinalConflict = result.finalConflict === true;
30947
+ if (isFinalConflict) {
30948
+ stillConflictingCount++;
30949
+ }
30950
+ }
30951
+ }
30952
+ logger?.info("parallel", "Rectification complete", {
30953
+ rectified: rectifiedCount,
30954
+ stillConflicting: stillConflictingCount
30955
+ });
30956
+ return { rectifiedCount, stillConflictingCount, additionalCost, updatedPrd: prd, rectificationMetrics };
30957
+ }
30745
30958
  async function runParallelExecution(options, initialPrd) {
30746
30959
  const logger = getSafeLogger();
30747
30960
  const {
@@ -30766,7 +30979,14 @@ async function runParallelExecution(options, initialPrd) {
30766
30979
  const readyStories = getAllReadyStories(prd);
30767
30980
  if (readyStories.length === 0) {
30768
30981
  logger?.info("parallel", "No stories ready for parallel execution");
30769
- return { prd, totalCost, storiesCompleted, completed: false };
30982
+ return {
30983
+ prd,
30984
+ totalCost,
30985
+ storiesCompleted,
30986
+ completed: false,
30987
+ storyMetrics: [],
30988
+ rectificationStats: { rectified: 0, stillConflicting: 0 }
30989
+ };
30770
30990
  }
30771
30991
  const maxConcurrency = parallelCount === 0 ? os5.cpus().length : Math.max(1, parallelCount);
30772
30992
  logger?.info("parallel", "Starting parallel execution mode", {
@@ -30784,15 +31004,45 @@ async function runParallelExecution(options, initialPrd) {
30784
31004
  }))
30785
31005
  }
30786
31006
  });
31007
+ const initialPassedIds = new Set(initialPrd.userStories.filter((s) => s.status === "passed").map((s) => s.id));
31008
+ const batchStartedAt = new Date().toISOString();
31009
+ const batchStartMs = Date.now();
31010
+ const batchStoryMetrics = [];
31011
+ let conflictedStories = [];
30787
31012
  try {
30788
- const parallelResult = await executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter);
31013
+ const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter);
31014
+ const batchDurationMs = Date.now() - batchStartMs;
31015
+ const batchCompletedAt = new Date().toISOString();
30789
31016
  prd = parallelResult.updatedPrd;
30790
31017
  storiesCompleted += parallelResult.storiesCompleted;
30791
31018
  totalCost += parallelResult.totalCost;
30792
- logger?.info("parallel", "Parallel execution complete", {
30793
- storiesCompleted: parallelResult.storiesCompleted,
30794
- totalCost: parallelResult.totalCost
30795
- });
31019
+ conflictedStories = parallelResult.mergeConflicts ?? [];
31020
+ const newlyPassedStories = prd.userStories.filter((s) => s.status === "passed" && !initialPassedIds.has(s.id));
31021
+ const costPerStory = newlyPassedStories.length > 0 ? parallelResult.totalCost / newlyPassedStories.length : 0;
31022
+ for (const story of newlyPassedStories) {
31023
+ batchStoryMetrics.push({
31024
+ storyId: story.id,
31025
+ complexity: "unknown",
31026
+ modelTier: "parallel",
31027
+ modelUsed: "parallel",
31028
+ attempts: 1,
31029
+ finalTier: "parallel",
31030
+ success: true,
31031
+ cost: costPerStory,
31032
+ durationMs: batchDurationMs,
31033
+ firstPassSuccess: true,
31034
+ startedAt: batchStartedAt,
31035
+ completedAt: batchCompletedAt,
31036
+ source: "parallel"
31037
+ });
31038
+ }
31039
+ allStoryMetrics.push(...batchStoryMetrics);
31040
+ for (const conflict of conflictedStories) {
31041
+ logger?.info("parallel", "Merge conflict detected - scheduling for rectification", {
31042
+ storyId: conflict.storyId,
31043
+ conflictFiles: conflict.conflictFiles
31044
+ });
31045
+ }
30796
31046
  statusWriter.setPrd(prd);
30797
31047
  await statusWriter.update(totalCost, iterations, {
30798
31048
  parallel: {
@@ -30810,6 +31060,19 @@ async function runParallelExecution(options, initialPrd) {
30810
31060
  });
30811
31061
  throw error48;
30812
31062
  }
31063
+ let rectificationStats = { rectified: 0, stillConflicting: 0 };
31064
+ if (conflictedStories.length > 0) {
31065
+ const rectResult = await runRectificationPass(conflictedStories, options, prd);
31066
+ prd = rectResult.updatedPrd;
31067
+ storiesCompleted += rectResult.rectifiedCount;
31068
+ totalCost += rectResult.additionalCost;
31069
+ rectificationStats = {
31070
+ rectified: rectResult.rectifiedCount,
31071
+ stillConflicting: rectResult.stillConflictingCount
31072
+ };
31073
+ batchStoryMetrics.push(...rectResult.rectificationMetrics);
31074
+ allStoryMetrics.push(...rectResult.rectificationMetrics);
31075
+ }
30813
31076
  if (isComplete(prd)) {
30814
31077
  logger?.info("execution", "All stories complete!", {
30815
31078
  feature,
@@ -30859,10 +31122,12 @@ async function runParallelExecution(options, initialPrd) {
30859
31122
  totalCost,
30860
31123
  storiesCompleted,
30861
31124
  completed: true,
30862
- durationMs
31125
+ durationMs,
31126
+ storyMetrics: batchStoryMetrics,
31127
+ rectificationStats
30863
31128
  };
30864
31129
  }
30865
- return { prd, totalCost, storiesCompleted, completed: false };
31130
+ return { prd, totalCost, storiesCompleted, completed: false, storyMetrics: batchStoryMetrics, rectificationStats };
30866
31131
  }
30867
31132
  var _parallelExecutorDeps;
30868
31133
  var init_parallel_executor = __esm(() => {
@@ -30872,7 +31137,9 @@ var init_parallel_executor = __esm(() => {
30872
31137
  init_helpers();
30873
31138
  init_parallel();
30874
31139
  _parallelExecutorDeps = {
30875
- fireHook
31140
+ fireHook,
31141
+ executeParallel,
31142
+ rectifyConflictedStory
30876
31143
  };
30877
31144
  });
30878
31145
 
@@ -31114,7 +31381,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
31114
31381
  runId,
31115
31382
  storyId: ev.storyId,
31116
31383
  status: "completed",
31117
- durationMs: ev.durationMs,
31384
+ runElapsedMs: ev.runElapsedMs,
31118
31385
  cost: ev.cost ?? 0,
31119
31386
  tier: ev.modelTier ?? "balanced",
31120
31387
  testStrategy: ev.testStrategy ?? "test-after"
@@ -31136,7 +31403,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
31136
31403
  runId,
31137
31404
  storyId: ev.storyId,
31138
31405
  status: "failed",
31139
- durationMs: Date.now() - startTime,
31406
+ runElapsedMs: Date.now() - startTime,
31140
31407
  cost: 0,
31141
31408
  tier: "balanced",
31142
31409
  testStrategy: "test-after"
@@ -31158,7 +31425,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
31158
31425
  runId,
31159
31426
  storyId: ev.storyId,
31160
31427
  status: "paused",
31161
- durationMs: Date.now() - startTime,
31428
+ runElapsedMs: Date.now() - startTime,
31162
31429
  cost: 0,
31163
31430
  tier: "balanced",
31164
31431
  testStrategy: "test-after"
@@ -31236,7 +31503,7 @@ async function handleDryRun(ctx) {
31236
31503
  storyId: s.id,
31237
31504
  story: s,
31238
31505
  passed: true,
31239
- durationMs: 0,
31506
+ runElapsedMs: 0,
31240
31507
  cost: 0,
31241
31508
  modelTier: ctx.routing.modelTier,
31242
31509
  testStrategy: ctx.routing.testStrategy
@@ -31268,7 +31535,7 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
31268
31535
  storyId: completedStory.id,
31269
31536
  storyTitle: completedStory.title,
31270
31537
  totalCost: ctx.totalCost + costDelta,
31271
- durationMs: now - ctx.startTime,
31538
+ runElapsedMs: now - ctx.startTime,
31272
31539
  storyDurationMs: ctx.storyStartTime ? now - ctx.storyStartTime : undefined
31273
31540
  });
31274
31541
  pipelineEventBus.emit({
@@ -31276,7 +31543,7 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
31276
31543
  storyId: completedStory.id,
31277
31544
  story: completedStory,
31278
31545
  passed: true,
31279
- durationMs: Date.now() - ctx.startTime,
31546
+ runElapsedMs: Date.now() - ctx.startTime,
31280
31547
  cost: costDelta,
31281
31548
  modelTier: ctx.routing.modelTier,
31282
31549
  testStrategy: ctx.routing.testStrategy
@@ -31357,7 +31624,8 @@ async function handlePipelineFailure(ctx, pipelineResult) {
31357
31624
  hooks: ctx.hooks,
31358
31625
  feature: ctx.feature,
31359
31626
  totalCost: ctx.totalCost,
31360
- workdir: ctx.workdir
31627
+ workdir: ctx.workdir,
31628
+ attemptCost: pipelineResult.context.agentResult?.estimatedCost || 0
31361
31629
  });
31362
31630
  prd = escalationResult.prd;
31363
31631
  prdDirty = escalationResult.prdDirty;
@@ -31399,6 +31667,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
31399
31667
  }
31400
31668
  const storyStartTime = Date.now();
31401
31669
  const storyGitRef = await captureGitRef(ctx.workdir);
31670
+ const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
31402
31671
  const pipelineContext = {
31403
31672
  config: ctx.config,
31404
31673
  prd,
@@ -31412,7 +31681,8 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
31412
31681
  plugins: ctx.pluginRegistry,
31413
31682
  storyStartTime: new Date().toISOString(),
31414
31683
  storyGitRef: storyGitRef ?? undefined,
31415
- interaction: ctx.interactionChain ?? undefined
31684
+ interaction: ctx.interactionChain ?? undefined,
31685
+ accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
31416
31686
  };
31417
31687
  ctx.statusWriter.setPrd(prd);
31418
31688
  ctx.statusWriter.setCurrentStory({
@@ -66499,7 +66769,8 @@ init_story_context();
66499
66769
  init_escalation();
66500
66770
  init_escalation();
66501
66771
  var _runnerDeps = {
66502
- fireHook
66772
+ fireHook,
66773
+ runParallelExecution: null
66503
66774
  };
66504
66775
  async function run(options) {
66505
66776
  const {
@@ -66601,7 +66872,7 @@ async function run(options) {
66601
66872
  await tryLlmBatchRoute(config2, getAllReadyStories(prd), "routing");
66602
66873
  }
66603
66874
  if (options.parallel !== undefined) {
66604
- const { runParallelExecution: runParallelExecution2 } = await Promise.resolve().then(() => (init_parallel_executor(), exports_parallel_executor));
66875
+ const runParallelExecution2 = _runnerDeps.runParallelExecution ?? (await Promise.resolve().then(() => (init_parallel_executor(), exports_parallel_executor))).runParallelExecution;
66605
66876
  const parallelResult = await runParallelExecution2({
66606
66877
  prdPath,
66607
66878
  workdir,
@@ -66626,6 +66897,7 @@ async function run(options) {
66626
66897
  prd = parallelResult.prd;
66627
66898
  totalCost = parallelResult.totalCost;
66628
66899
  storiesCompleted = parallelResult.storiesCompleted;
66900
+ allStoryMetrics.push(...parallelResult.storyMetrics);
66629
66901
  if (parallelResult.completed && parallelResult.durationMs !== undefined) {
66630
66902
  return {
66631
66903
  success: true,
@@ -66656,8 +66928,8 @@ async function run(options) {
66656
66928
  }, prd);
66657
66929
  prd = sequentialResult.prd;
66658
66930
  iterations = sequentialResult.iterations;
66659
- storiesCompleted = sequentialResult.storiesCompleted;
66660
- totalCost = sequentialResult.totalCost;
66931
+ totalCost += sequentialResult.totalCost;
66932
+ storiesCompleted += sequentialResult.storiesCompleted;
66661
66933
  allStoryMetrics.push(...sequentialResult.allStoryMetrics);
66662
66934
  if (config2.acceptance.enabled && isComplete(prd)) {
66663
66935
  const { runAcceptanceLoop: runAcceptanceLoop2 } = await Promise.resolve().then(() => (init_acceptance_loop(), exports_acceptance_loop));