@nathapp/nax 0.36.1 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nax.js CHANGED
@@ -18047,7 +18047,9 @@ var init_schemas3 = __esm(() => {
18047
18047
  storySizeGate: StorySizeGateConfigSchema
18048
18048
  });
18049
18049
  PromptsConfigSchema = exports_external.object({
18050
- overrides: exports_external.record(exports_external.enum(["test-writer", "implementer", "verifier", "single-session"]), exports_external.string().min(1, "Override path must be non-empty")).optional()
18050
+ overrides: exports_external.record(exports_external.string().refine((key) => ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"].includes(key), {
18051
+ message: "Role must be one of: test-writer, implementer, verifier, single-session, tdd-simple"
18052
+ }), exports_external.string().min(1, "Override path must be non-empty")).optional()
18051
18053
  });
18052
18054
  DecomposeConfigSchema = exports_external.object({
18053
18055
  trigger: exports_external.enum(["auto", "confirm", "disabled"]).default("auto"),
@@ -20063,6 +20065,22 @@ function computeStoryContentHash(story) {
20063
20065
  }
20064
20066
 
20065
20067
  // src/routing/index.ts
20068
+ var exports_routing = {};
20069
+ __export(exports_routing, {
20070
+ tryLlmBatchRoute: () => tryLlmBatchRoute,
20071
+ routeTask: () => routeTask,
20072
+ routeStory: () => routeStory,
20073
+ manualStrategy: () => manualStrategy,
20074
+ loadCustomStrategy: () => loadCustomStrategy,
20075
+ llmStrategy: () => llmStrategy,
20076
+ keywordStrategy: () => keywordStrategy,
20077
+ determineTestStrategy: () => determineTestStrategy2,
20078
+ computeStoryContentHash: () => computeStoryContentHash,
20079
+ complexityToModelTier: () => complexityToModelTier2,
20080
+ classifyComplexity: () => classifyComplexity2,
20081
+ buildStrategyChain: () => buildStrategyChain,
20082
+ StrategyChain: () => StrategyChain
20083
+ });
20066
20084
  var init_routing = __esm(() => {
20067
20085
  init_router();
20068
20086
  init_chain();
@@ -20657,7 +20675,7 @@ var package_default;
20657
20675
  var init_package = __esm(() => {
20658
20676
  package_default = {
20659
20677
  name: "@nathapp/nax",
20660
- version: "0.36.1",
20678
+ version: "0.37.0",
20661
20679
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
20662
20680
  type: "module",
20663
20681
  bin: {
@@ -20718,8 +20736,8 @@ var init_version = __esm(() => {
20718
20736
  NAX_VERSION = package_default.version;
20719
20737
  NAX_COMMIT = (() => {
20720
20738
  try {
20721
- if (/^[0-9a-f]{6,10}$/.test("b241bab"))
20722
- return "b241bab";
20739
+ if (/^[0-9a-f]{6,10}$/.test("0a7a065"))
20740
+ return "0a7a065";
20723
20741
  } catch {}
20724
20742
  try {
20725
20743
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -20791,9 +20809,10 @@ function collectStoryMetrics(ctx, storyStartTime) {
20791
20809
  const routing = ctx.routing;
20792
20810
  const agentResult = ctx.agentResult;
20793
20811
  const escalationCount = story.escalations?.length || 0;
20794
- const attempts = Math.max(1, story.attempts || 1);
20812
+ const priorFailureCount = story.priorFailures?.length || 0;
20813
+ const attempts = priorFailureCount + Math.max(1, story.attempts || 1);
20795
20814
  const finalTier = escalationCount > 0 ? story.escalations[escalationCount - 1].toTier : routing.modelTier;
20796
- const firstPassSuccess = agentResult?.success === true && escalationCount === 0;
20815
+ const firstPassSuccess = agentResult?.success === true && escalationCount === 0 && priorFailureCount === 0;
20797
20816
  const modelEntry = ctx.config.models[routing.modelTier];
20798
20817
  const modelDef = modelEntry ? resolveModel(modelEntry) : null;
20799
20818
  const modelUsed = modelDef?.model || routing.modelTier;
@@ -20809,12 +20828,13 @@ function collectStoryMetrics(ctx, storyStartTime) {
20809
20828
  attempts,
20810
20829
  finalTier,
20811
20830
  success: agentResult?.success || false,
20812
- cost: agentResult?.estimatedCost || 0,
20831
+ cost: (ctx.accumulatedAttemptCost ?? 0) + (agentResult?.estimatedCost || 0),
20813
20832
  durationMs: agentResult?.durationMs || 0,
20814
20833
  firstPassSuccess,
20815
20834
  startedAt: storyStartTime,
20816
20835
  completedAt: new Date().toISOString(),
20817
- fullSuiteGatePassed
20836
+ fullSuiteGatePassed,
20837
+ runtimeCrashes: ctx.storyRuntimeCrashes ?? 0
20818
20838
  };
20819
20839
  }
20820
20840
  function collectBatchMetrics(ctx, storyStartTime) {
@@ -20844,7 +20864,8 @@ function collectBatchMetrics(ctx, storyStartTime) {
20844
20864
  firstPassSuccess: true,
20845
20865
  startedAt: storyStartTime,
20846
20866
  completedAt: new Date().toISOString(),
20847
- fullSuiteGatePassed: false
20867
+ fullSuiteGatePassed: false,
20868
+ runtimeCrashes: 0
20848
20869
  };
20849
20870
  });
20850
20871
  }
@@ -22227,6 +22248,11 @@ var init_interaction = __esm(() => {
22227
22248
  });
22228
22249
 
22229
22250
  // src/pipeline/runner.ts
22251
+ var exports_runner = {};
22252
+ __export(exports_runner, {
22253
+ runPipeline: () => runPipeline,
22254
+ MAX_STAGE_RETRIES: () => MAX_STAGE_RETRIES
22255
+ });
22230
22256
  async function runPipeline(stages, context, eventEmitter) {
22231
22257
  const logger = getLogger();
22232
22258
  const retryCountMap = new Map;
@@ -22657,18 +22683,24 @@ var init_runner2 = __esm(() => {
22657
22683
 
22658
22684
  // src/review/orchestrator.ts
22659
22685
  var {spawn: spawn2 } = globalThis.Bun;
22660
- async function getChangedFiles(workdir) {
22686
+ async function getChangedFiles(workdir, baseRef) {
22661
22687
  try {
22662
- const [stagedProc, unstagedProc] = [
22663
- spawn2({ cmd: ["git", "diff", "--name-only", "--cached"], cwd: workdir, stdout: "pipe", stderr: "pipe" }),
22664
- spawn2({ cmd: ["git", "diff", "--name-only"], cwd: workdir, stdout: "pipe", stderr: "pipe" })
22688
+ const diffArgs = ["diff", "--name-only"];
22689
+ const [stagedProc, unstagedProc, baseProc] = [
22690
+ spawn2({ cmd: ["git", ...diffArgs, "--cached"], cwd: workdir, stdout: "pipe", stderr: "pipe" }),
22691
+ spawn2({ cmd: ["git", ...diffArgs], cwd: workdir, stdout: "pipe", stderr: "pipe" }),
22692
+ baseRef ? spawn2({ cmd: ["git", ...diffArgs, `${baseRef}...HEAD`], cwd: workdir, stdout: "pipe", stderr: "pipe" }) : null
22665
22693
  ];
22666
- await Promise.all([stagedProc.exited, unstagedProc.exited]);
22667
- const staged = (await new Response(stagedProc.stdout).text()).trim().split(`
22668
- `).filter(Boolean);
22669
- const unstaged = (await new Response(unstagedProc.stdout).text()).trim().split(`
22670
- `).filter(Boolean);
22671
- return Array.from(new Set([...staged, ...unstaged]));
22694
+ await Promise.all([stagedProc.exited, unstagedProc.exited, baseProc?.exited]);
22695
+ const [staged, unstaged, based] = await Promise.all([
22696
+ new Response(stagedProc.stdout).text().then((t) => t.trim().split(`
22697
+ `).filter(Boolean)),
22698
+ new Response(unstagedProc.stdout).text().then((t) => t.trim().split(`
22699
+ `).filter(Boolean)),
22700
+ baseProc ? new Response(baseProc.stdout).text().then((t) => t.trim().split(`
22701
+ `).filter(Boolean)) : Promise.resolve([])
22702
+ ]);
22703
+ return Array.from(new Set([...staged, ...unstaged, ...based]));
22672
22704
  } catch {
22673
22705
  return [];
22674
22706
  }
@@ -22684,7 +22716,8 @@ class ReviewOrchestrator {
22684
22716
  if (plugins) {
22685
22717
  const reviewers = plugins.getReviewers();
22686
22718
  if (reviewers.length > 0) {
22687
- const changedFiles = await getChangedFiles(workdir);
22719
+ const baseRef = executionConfig?.storyGitRef;
22720
+ const changedFiles = await getChangedFiles(workdir, baseRef);
22688
22721
  const pluginResults = [];
22689
22722
  for (const reviewer of reviewers) {
22690
22723
  logger?.info("review", `Running plugin reviewer: ${reviewer.name}`, {
@@ -22946,7 +22979,7 @@ var init_completion = __esm(() => {
22946
22979
  storyId: completedStory.id,
22947
22980
  story: completedStory,
22948
22981
  passed: true,
22949
- durationMs: storyMetric?.durationMs ?? 0,
22982
+ runElapsedMs: storyMetric?.durationMs ?? 0,
22950
22983
  cost: costPerStory,
22951
22984
  modelTier: ctx.routing?.modelTier,
22952
22985
  testStrategy: ctx.routing?.testStrategy
@@ -27456,6 +27489,22 @@ var init_routing2 = __esm(() => {
27456
27489
  };
27457
27490
  });
27458
27491
 
27492
+ // src/verification/crash-detector.ts
27493
+ function detectRuntimeCrash(output) {
27494
+ if (!output)
27495
+ return false;
27496
+ return CRASH_PATTERNS.some((pattern) => output.includes(pattern));
27497
+ }
27498
+ var CRASH_PATTERNS;
27499
+ var init_crash_detector = __esm(() => {
27500
+ CRASH_PATTERNS = [
27501
+ "panic(main thread)",
27502
+ "Segmentation fault",
27503
+ "Bun has crashed",
27504
+ "oh no: Bun has crashed"
27505
+ ];
27506
+ });
27507
+
27459
27508
  // src/pipeline/stages/verify.ts
27460
27509
  function coerceSmartTestRunner(val) {
27461
27510
  if (val === undefined || val === true)
@@ -27473,6 +27522,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
27473
27522
  var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
27474
27523
  var init_verify = __esm(() => {
27475
27524
  init_logger2();
27525
+ init_crash_detector();
27476
27526
  init_runners();
27477
27527
  init_smart_runner();
27478
27528
  DEFAULT_SMART_RUNNER_CONFIG2 = {
@@ -27544,7 +27594,7 @@ var init_verify = __esm(() => {
27544
27594
  });
27545
27595
  ctx.verifyResult = {
27546
27596
  success: result.success,
27547
- status: result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : "TEST_FAILURE",
27597
+ status: result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : detectRuntimeCrash(result.output) ? "RUNTIME_CRASH" : "TEST_FAILURE",
27548
27598
  storyId: ctx.story.id,
27549
27599
  strategy: "scoped",
27550
27600
  passCount: result.passCount ?? 0,
@@ -27595,6 +27645,25 @@ var init_verify = __esm(() => {
27595
27645
  });
27596
27646
 
27597
27647
  // src/pipeline/stages/index.ts
27648
+ var exports_stages = {};
27649
+ __export(exports_stages, {
27650
+ verifyStage: () => verifyStage,
27651
+ routingStage: () => routingStage,
27652
+ reviewStage: () => reviewStage,
27653
+ regressionStage: () => regressionStage,
27654
+ rectifyStage: () => rectifyStage,
27655
+ queueCheckStage: () => queueCheckStage,
27656
+ promptStage: () => promptStage,
27657
+ postRunPipeline: () => postRunPipeline,
27658
+ optimizerStage: () => optimizerStage,
27659
+ executionStage: () => executionStage,
27660
+ defaultPipeline: () => defaultPipeline,
27661
+ contextStage: () => contextStage,
27662
+ constitutionStage: () => constitutionStage,
27663
+ completionStage: () => completionStage,
27664
+ autofixStage: () => autofixStage,
27665
+ acceptanceStage: () => acceptanceStage
27666
+ });
27598
27667
  var defaultPipeline, postRunPipeline;
27599
27668
  var init_stages = __esm(() => {
27600
27669
  init_acceptance2();
@@ -29244,11 +29313,12 @@ var init_crash_recovery = __esm(() => {
29244
29313
 
29245
29314
  // src/execution/escalation/escalation.ts
29246
29315
  function escalateTier(currentTier, tierOrder) {
29247
- const currentIndex = tierOrder.findIndex((t) => t.tier === currentTier);
29316
+ const getName = (t) => t.tier ?? t.name ?? null;
29317
+ const currentIndex = tierOrder.findIndex((t) => getName(t) === currentTier);
29248
29318
  if (currentIndex === -1 || currentIndex === tierOrder.length - 1) {
29249
29319
  return null;
29250
29320
  }
29251
- return tierOrder[currentIndex + 1].tier;
29321
+ return getName(tierOrder[currentIndex + 1]);
29252
29322
  }
29253
29323
  function calculateMaxIterations(tierOrder) {
29254
29324
  return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
@@ -29345,13 +29415,14 @@ var init_tier_outcome = __esm(() => {
29345
29415
  });
29346
29416
 
29347
29417
  // src/execution/escalation/tier-escalation.ts
29348
- function buildEscalationFailure(story, currentTier, reviewFindings) {
29418
+ function buildEscalationFailure(story, currentTier, reviewFindings, cost) {
29349
29419
  return {
29350
29420
  attempt: (story.attempts ?? 0) + 1,
29351
29421
  modelTier: currentTier,
29352
29422
  stage: "escalation",
29353
29423
  summary: `Failed with tier ${currentTier}, escalating to next tier`,
29354
29424
  reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
29425
+ cost: cost ?? 0,
29355
29426
  timestamp: new Date().toISOString()
29356
29427
  };
29357
29428
  }
@@ -29364,6 +29435,8 @@ function resolveMaxAttemptsOutcome(failureCategory) {
29364
29435
  case "verifier-rejected":
29365
29436
  case "greenfield-no-tests":
29366
29437
  return "pause";
29438
+ case "runtime-crash":
29439
+ return "pause";
29367
29440
  case "session-failure":
29368
29441
  case "tests-failing":
29369
29442
  return "fail";
@@ -29387,8 +29460,17 @@ async function tryLlmBatchRoute2(config2, stories, label = "routing") {
29387
29460
  });
29388
29461
  }
29389
29462
  }
29463
+ function shouldRetrySameTier(verifyResult) {
29464
+ return verifyResult?.status === "RUNTIME_CRASH";
29465
+ }
29390
29466
  async function handleTierEscalation(ctx) {
29391
29467
  const logger = getSafeLogger();
29468
+ if (shouldRetrySameTier(ctx.verifyResult)) {
29469
+ logger?.warn("escalation", "Runtime crash detected \u2014 retrying same tier (transient, not a code issue)", {
29470
+ storyId: ctx.story.id
29471
+ });
29472
+ return { outcome: "retry-same", prdDirty: false, prd: ctx.prd };
29473
+ }
29392
29474
  const nextTier = escalateTier(ctx.routing.modelTier, ctx.config.autoMode.escalation.tierOrder);
29393
29475
  const escalateWholeBatch = ctx.config.autoMode.escalation.escalateEntireBatch ?? true;
29394
29476
  const storiesToEscalate = ctx.isBatchExecution && escalateWholeBatch ? ctx.storiesToExecute : [ctx.story];
@@ -29442,7 +29524,7 @@ async function handleTierEscalation(ctx) {
29442
29524
  const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
29443
29525
  const isChangingTier = currentStoryTier !== nextTier;
29444
29526
  const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
29445
- const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings);
29527
+ const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
29446
29528
  return {
29447
29529
  ...s,
29448
29530
  attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
@@ -29452,7 +29534,7 @@ async function handleTierEscalation(ctx) {
29452
29534
  };
29453
29535
  })
29454
29536
  };
29455
- await savePRD(updatedPrd, ctx.prdPath);
29537
+ await _tierEscalationDeps.savePRD(updatedPrd, ctx.prdPath);
29456
29538
  for (const story of storiesToEscalate) {
29457
29539
  clearCacheForStory(story.id);
29458
29540
  }
@@ -29465,6 +29547,7 @@ async function handleTierEscalation(ctx) {
29465
29547
  prd: updatedPrd
29466
29548
  };
29467
29549
  }
29550
+ var _tierEscalationDeps;
29468
29551
  var init_tier_escalation = __esm(() => {
29469
29552
  init_hooks();
29470
29553
  init_logger2();
@@ -29474,6 +29557,9 @@ var init_tier_escalation = __esm(() => {
29474
29557
  init_helpers();
29475
29558
  init_progress();
29476
29559
  init_tier_outcome();
29560
+ _tierEscalationDeps = {
29561
+ savePRD
29562
+ };
29477
29563
  });
29478
29564
 
29479
29565
  // src/execution/escalation/index.ts
@@ -30075,6 +30161,10 @@ var init_headless_formatter = __esm(() => {
30075
30161
  });
30076
30162
 
30077
30163
  // src/worktree/manager.ts
30164
+ var exports_manager = {};
30165
+ __export(exports_manager, {
30166
+ WorktreeManager: () => WorktreeManager
30167
+ });
30078
30168
  import { existsSync as existsSync26, symlinkSync } from "fs";
30079
30169
  import { join as join32 } from "path";
30080
30170
 
@@ -30219,6 +30309,11 @@ var init_manager = __esm(() => {
30219
30309
  });
30220
30310
 
30221
30311
  // src/worktree/merge.ts
30312
+ var exports_merge = {};
30313
+ __export(exports_merge, {
30314
+ MergeEngine: () => MergeEngine
30315
+ });
30316
+
30222
30317
  class MergeEngine {
30223
30318
  worktreeManager;
30224
30319
  constructor(worktreeManager) {
@@ -30497,10 +30592,12 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
30497
30592
  const logger = getSafeLogger();
30498
30593
  const worktreeManager = new WorktreeManager;
30499
30594
  const results = {
30500
- successfulStories: [],
30501
- failedStories: [],
30595
+ pipelinePassed: [],
30596
+ merged: [],
30597
+ failed: [],
30502
30598
  totalCost: 0,
30503
- conflictedStories: []
30599
+ mergeConflicts: [],
30600
+ storyCosts: new Map
30504
30601
  };
30505
30602
  const worktreeSetup = [];
30506
30603
  for (const story of stories) {
@@ -30513,7 +30610,7 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
30513
30610
  worktreePath
30514
30611
  });
30515
30612
  } catch (error48) {
30516
- results.failedStories.push({
30613
+ results.failed.push({
30517
30614
  story,
30518
30615
  error: `Failed to create worktree: ${error48 instanceof Error ? error48.message : String(error48)}`
30519
30616
  });
@@ -30528,14 +30625,15 @@ async function executeParallelBatch(stories, projectRoot, config2, prd, context,
30528
30625
  const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
30529
30626
  const executePromise = executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter).then((result) => {
30530
30627
  results.totalCost += result.cost;
30628
+ results.storyCosts.set(story.id, result.cost);
30531
30629
  if (result.success) {
30532
- results.successfulStories.push(story);
30630
+ results.pipelinePassed.push(story);
30533
30631
  logger?.info("parallel", "Story execution succeeded", {
30534
30632
  storyId: story.id,
30535
30633
  cost: result.cost
30536
30634
  });
30537
30635
  } else {
30538
- results.failedStories.push({ story, error: result.error || "Unknown error" });
30636
+ results.failed.push({ story, error: result.error || "Unknown error" });
30539
30637
  logger?.error("parallel", "Story execution failed", {
30540
30638
  storyId: story.id,
30541
30639
  error: result.error
@@ -30575,6 +30673,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30575
30673
  let storiesCompleted = 0;
30576
30674
  let totalCost = 0;
30577
30675
  const currentPrd = prd;
30676
+ const allMergeConflicts = [];
30578
30677
  for (let batchIndex = 0;batchIndex < batches.length; batchIndex++) {
30579
30678
  const batch = batches[batchIndex];
30580
30679
  logger?.info("parallel", `Executing batch ${batchIndex + 1}/${batches.length}`, {
@@ -30591,8 +30690,8 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30591
30690
  };
30592
30691
  const batchResult = await executeParallelBatch(batch, projectRoot, config2, currentPrd, baseContext, maxConcurrency, eventEmitter);
30593
30692
  totalCost += batchResult.totalCost;
30594
- if (batchResult.successfulStories.length > 0) {
30595
- const successfulIds = batchResult.successfulStories.map((s) => s.id);
30693
+ if (batchResult.pipelinePassed.length > 0) {
30694
+ const successfulIds = batchResult.pipelinePassed.map((s) => s.id);
30596
30695
  const deps = buildDependencyMap(batch);
30597
30696
  logger?.info("parallel", "Merging successful stories", {
30598
30697
  storyIds: successfulIds
@@ -30602,15 +30701,19 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30602
30701
  if (mergeResult.success) {
30603
30702
  markStoryPassed(currentPrd, mergeResult.storyId);
30604
30703
  storiesCompleted++;
30704
+ const mergedStory = batchResult.pipelinePassed.find((s) => s.id === mergeResult.storyId);
30705
+ if (mergedStory)
30706
+ batchResult.merged.push(mergedStory);
30605
30707
  logger?.info("parallel", "Story merged successfully", {
30606
30708
  storyId: mergeResult.storyId,
30607
30709
  retryCount: mergeResult.retryCount
30608
30710
  });
30609
30711
  } else {
30610
30712
  markStoryFailed(currentPrd, mergeResult.storyId);
30611
- batchResult.conflictedStories.push({
30713
+ batchResult.mergeConflicts.push({
30612
30714
  storyId: mergeResult.storyId,
30613
- conflictFiles: mergeResult.conflictFiles || []
30715
+ conflictFiles: mergeResult.conflictFiles || [],
30716
+ originalCost: batchResult.storyCosts.get(mergeResult.storyId) ?? 0
30614
30717
  });
30615
30718
  logger?.error("parallel", "Merge conflict", {
30616
30719
  storyId: mergeResult.storyId,
@@ -30623,7 +30726,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30623
30726
  }
30624
30727
  }
30625
30728
  }
30626
- for (const { story, error: error48 } of batchResult.failedStories) {
30729
+ for (const { story, error: error48 } of batchResult.failed) {
30627
30730
  markStoryFailed(currentPrd, story.id);
30628
30731
  logger?.error("parallel", "Cleaning up failed story worktree", {
30629
30732
  storyId: story.id,
@@ -30639,10 +30742,12 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30639
30742
  }
30640
30743
  }
30641
30744
  await savePRD(currentPrd, prdPath);
30745
+ allMergeConflicts.push(...batchResult.mergeConflicts);
30642
30746
  logger?.info("parallel", `Batch ${batchIndex + 1} complete`, {
30643
- successful: batchResult.successfulStories.length,
30644
- failed: batchResult.failedStories.length,
30645
- conflicts: batchResult.conflictedStories.length,
30747
+ pipelinePassed: batchResult.pipelinePassed.length,
30748
+ merged: batchResult.merged.length,
30749
+ failed: batchResult.failed.length,
30750
+ mergeConflicts: batchResult.mergeConflicts.length,
30646
30751
  batchCost: batchResult.totalCost
30647
30752
  });
30648
30753
  }
@@ -30650,7 +30755,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
30650
30755
  storiesCompleted,
30651
30756
  totalCost
30652
30757
  });
30653
- return { storiesCompleted, totalCost, updatedPrd: currentPrd };
30758
+ return { storiesCompleted, totalCost, updatedPrd: currentPrd, mergeConflicts: allMergeConflicts };
30654
30759
  }
30655
30760
  var init_parallel = __esm(() => {
30656
30761
  init_logger2();
@@ -30742,6 +30847,123 @@ __export(exports_parallel_executor, {
30742
30847
  });
30743
30848
  import * as os5 from "os";
30744
30849
  import path15 from "path";
30850
+ async function rectifyConflictedStory(options) {
30851
+ const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter } = options;
30852
+ const logger = getSafeLogger();
30853
+ logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
30854
+ try {
30855
+ const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
30856
+ const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_merge(), exports_merge));
30857
+ const { runPipeline: runPipeline2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
30858
+ const { defaultPipeline: defaultPipeline2 } = await Promise.resolve().then(() => (init_stages(), exports_stages));
30859
+ const { routeTask: routeTask2 } = await Promise.resolve().then(() => (init_routing(), exports_routing));
30860
+ const worktreeManager = new WorktreeManager2;
30861
+ const mergeEngine = new MergeEngine2(worktreeManager);
30862
+ try {
30863
+ await worktreeManager.remove(workdir, storyId);
30864
+ } catch {}
30865
+ await worktreeManager.create(workdir, storyId);
30866
+ const worktreePath = path15.join(workdir, ".nax-wt", storyId);
30867
+ const story = prd.userStories.find((s) => s.id === storyId);
30868
+ if (!story) {
30869
+ return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
30870
+ }
30871
+ const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
30872
+ const pipelineContext = {
30873
+ config: config2,
30874
+ prd,
30875
+ story,
30876
+ stories: [story],
30877
+ workdir: worktreePath,
30878
+ featureDir: undefined,
30879
+ hooks,
30880
+ plugins: pluginRegistry,
30881
+ storyStartTime: new Date().toISOString(),
30882
+ routing
30883
+ };
30884
+ const pipelineResult = await runPipeline2(defaultPipeline2, pipelineContext, eventEmitter);
30885
+ const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
30886
+ if (!pipelineResult.success) {
30887
+ logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
30888
+ return { success: false, storyId, cost, finalConflict: false, pipelineFailure: true };
30889
+ }
30890
+ const mergeResults = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
30891
+ const mergeResult = mergeResults[0];
30892
+ if (!mergeResult || !mergeResult.success) {
30893
+ const conflictFiles = mergeResult?.conflictFiles ?? [];
30894
+ logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
30895
+ return { success: false, storyId, cost, finalConflict: true, conflictFiles };
30896
+ }
30897
+ logger?.info("parallel", "Rectification succeeded - story merged", {
30898
+ storyId,
30899
+ originalCost: options.originalCost,
30900
+ rectificationCost: cost
30901
+ });
30902
+ return { success: true, storyId, cost };
30903
+ } catch (error48) {
30904
+ logger?.error("parallel", "Rectification failed - preserving worktree", {
30905
+ storyId,
30906
+ error: error48 instanceof Error ? error48.message : String(error48)
30907
+ });
30908
+ return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
30909
+ }
30910
+ }
30911
+ async function runRectificationPass(conflictedStories, options, prd) {
30912
+ const logger = getSafeLogger();
30913
+ const { workdir, config: config2, hooks, pluginRegistry, eventEmitter } = options;
30914
+ const rectificationMetrics = [];
30915
+ let rectifiedCount = 0;
30916
+ let stillConflictingCount = 0;
30917
+ let additionalCost = 0;
30918
+ logger?.info("parallel", "Starting merge conflict rectification", {
30919
+ stories: conflictedStories.map((s) => s.storyId),
30920
+ totalConflicts: conflictedStories.length
30921
+ });
30922
+ for (const conflictInfo of conflictedStories) {
30923
+ const result = await _parallelExecutorDeps.rectifyConflictedStory({
30924
+ ...conflictInfo,
30925
+ workdir,
30926
+ config: config2,
30927
+ hooks,
30928
+ pluginRegistry,
30929
+ prd,
30930
+ eventEmitter
30931
+ });
30932
+ additionalCost += result.cost;
30933
+ if (result.success) {
30934
+ markStoryPassed(prd, result.storyId);
30935
+ rectifiedCount++;
30936
+ rectificationMetrics.push({
30937
+ storyId: result.storyId,
30938
+ complexity: "unknown",
30939
+ modelTier: "parallel",
30940
+ modelUsed: "parallel",
30941
+ attempts: 1,
30942
+ finalTier: "parallel",
30943
+ success: true,
30944
+ cost: result.cost,
30945
+ durationMs: 0,
30946
+ firstPassSuccess: false,
30947
+ startedAt: new Date().toISOString(),
30948
+ completedAt: new Date().toISOString(),
30949
+ source: "rectification",
30950
+ rectifiedFromConflict: true,
30951
+ originalCost: conflictInfo.originalCost,
30952
+ rectificationCost: result.cost
30953
+ });
30954
+ } else {
30955
+ const isFinalConflict = result.finalConflict === true;
30956
+ if (isFinalConflict) {
30957
+ stillConflictingCount++;
30958
+ }
30959
+ }
30960
+ }
30961
+ logger?.info("parallel", "Rectification complete", {
30962
+ rectified: rectifiedCount,
30963
+ stillConflicting: stillConflictingCount
30964
+ });
30965
+ return { rectifiedCount, stillConflictingCount, additionalCost, updatedPrd: prd, rectificationMetrics };
30966
+ }
30745
30967
  async function runParallelExecution(options, initialPrd) {
30746
30968
  const logger = getSafeLogger();
30747
30969
  const {
@@ -30766,7 +30988,14 @@ async function runParallelExecution(options, initialPrd) {
30766
30988
  const readyStories = getAllReadyStories(prd);
30767
30989
  if (readyStories.length === 0) {
30768
30990
  logger?.info("parallel", "No stories ready for parallel execution");
30769
- return { prd, totalCost, storiesCompleted, completed: false };
30991
+ return {
30992
+ prd,
30993
+ totalCost,
30994
+ storiesCompleted,
30995
+ completed: false,
30996
+ storyMetrics: [],
30997
+ rectificationStats: { rectified: 0, stillConflicting: 0 }
30998
+ };
30770
30999
  }
30771
31000
  const maxConcurrency = parallelCount === 0 ? os5.cpus().length : Math.max(1, parallelCount);
30772
31001
  logger?.info("parallel", "Starting parallel execution mode", {
@@ -30784,15 +31013,45 @@ async function runParallelExecution(options, initialPrd) {
30784
31013
  }))
30785
31014
  }
30786
31015
  });
31016
+ const initialPassedIds = new Set(initialPrd.userStories.filter((s) => s.status === "passed").map((s) => s.id));
31017
+ const batchStartedAt = new Date().toISOString();
31018
+ const batchStartMs = Date.now();
31019
+ const batchStoryMetrics = [];
31020
+ let conflictedStories = [];
30787
31021
  try {
30788
- const parallelResult = await executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter);
31022
+ const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter);
31023
+ const batchDurationMs = Date.now() - batchStartMs;
31024
+ const batchCompletedAt = new Date().toISOString();
30789
31025
  prd = parallelResult.updatedPrd;
30790
31026
  storiesCompleted += parallelResult.storiesCompleted;
30791
31027
  totalCost += parallelResult.totalCost;
30792
- logger?.info("parallel", "Parallel execution complete", {
30793
- storiesCompleted: parallelResult.storiesCompleted,
30794
- totalCost: parallelResult.totalCost
30795
- });
31028
+ conflictedStories = parallelResult.mergeConflicts ?? [];
31029
+ const newlyPassedStories = prd.userStories.filter((s) => s.status === "passed" && !initialPassedIds.has(s.id));
31030
+ const costPerStory = newlyPassedStories.length > 0 ? parallelResult.totalCost / newlyPassedStories.length : 0;
31031
+ for (const story of newlyPassedStories) {
31032
+ batchStoryMetrics.push({
31033
+ storyId: story.id,
31034
+ complexity: "unknown",
31035
+ modelTier: "parallel",
31036
+ modelUsed: "parallel",
31037
+ attempts: 1,
31038
+ finalTier: "parallel",
31039
+ success: true,
31040
+ cost: costPerStory,
31041
+ durationMs: batchDurationMs,
31042
+ firstPassSuccess: true,
31043
+ startedAt: batchStartedAt,
31044
+ completedAt: batchCompletedAt,
31045
+ source: "parallel"
31046
+ });
31047
+ }
31048
+ allStoryMetrics.push(...batchStoryMetrics);
31049
+ for (const conflict of conflictedStories) {
31050
+ logger?.info("parallel", "Merge conflict detected - scheduling for rectification", {
31051
+ storyId: conflict.storyId,
31052
+ conflictFiles: conflict.conflictFiles
31053
+ });
31054
+ }
30796
31055
  statusWriter.setPrd(prd);
30797
31056
  await statusWriter.update(totalCost, iterations, {
30798
31057
  parallel: {
@@ -30810,6 +31069,19 @@ async function runParallelExecution(options, initialPrd) {
30810
31069
  });
30811
31070
  throw error48;
30812
31071
  }
31072
+ let rectificationStats = { rectified: 0, stillConflicting: 0 };
31073
+ if (conflictedStories.length > 0) {
31074
+ const rectResult = await runRectificationPass(conflictedStories, options, prd);
31075
+ prd = rectResult.updatedPrd;
31076
+ storiesCompleted += rectResult.rectifiedCount;
31077
+ totalCost += rectResult.additionalCost;
31078
+ rectificationStats = {
31079
+ rectified: rectResult.rectifiedCount,
31080
+ stillConflicting: rectResult.stillConflictingCount
31081
+ };
31082
+ batchStoryMetrics.push(...rectResult.rectificationMetrics);
31083
+ allStoryMetrics.push(...rectResult.rectificationMetrics);
31084
+ }
30813
31085
  if (isComplete(prd)) {
30814
31086
  logger?.info("execution", "All stories complete!", {
30815
31087
  feature,
@@ -30859,10 +31131,12 @@ async function runParallelExecution(options, initialPrd) {
30859
31131
  totalCost,
30860
31132
  storiesCompleted,
30861
31133
  completed: true,
30862
- durationMs
31134
+ durationMs,
31135
+ storyMetrics: batchStoryMetrics,
31136
+ rectificationStats
30863
31137
  };
30864
31138
  }
30865
- return { prd, totalCost, storiesCompleted, completed: false };
31139
+ return { prd, totalCost, storiesCompleted, completed: false, storyMetrics: batchStoryMetrics, rectificationStats };
30866
31140
  }
30867
31141
  var _parallelExecutorDeps;
30868
31142
  var init_parallel_executor = __esm(() => {
@@ -30872,7 +31146,9 @@ var init_parallel_executor = __esm(() => {
30872
31146
  init_helpers();
30873
31147
  init_parallel();
30874
31148
  _parallelExecutorDeps = {
30875
- fireHook
31149
+ fireHook,
31150
+ executeParallel,
31151
+ rectifyConflictedStory
30876
31152
  };
30877
31153
  });
30878
31154
 
@@ -31114,7 +31390,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
31114
31390
  runId,
31115
31391
  storyId: ev.storyId,
31116
31392
  status: "completed",
31117
- durationMs: ev.durationMs,
31393
+ runElapsedMs: ev.runElapsedMs,
31118
31394
  cost: ev.cost ?? 0,
31119
31395
  tier: ev.modelTier ?? "balanced",
31120
31396
  testStrategy: ev.testStrategy ?? "test-after"
@@ -31136,7 +31412,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
31136
31412
  runId,
31137
31413
  storyId: ev.storyId,
31138
31414
  status: "failed",
31139
- durationMs: Date.now() - startTime,
31415
+ runElapsedMs: Date.now() - startTime,
31140
31416
  cost: 0,
31141
31417
  tier: "balanced",
31142
31418
  testStrategy: "test-after"
@@ -31158,7 +31434,7 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
31158
31434
  runId,
31159
31435
  storyId: ev.storyId,
31160
31436
  status: "paused",
31161
- durationMs: Date.now() - startTime,
31437
+ runElapsedMs: Date.now() - startTime,
31162
31438
  cost: 0,
31163
31439
  tier: "balanced",
31164
31440
  testStrategy: "test-after"
@@ -31236,7 +31512,7 @@ async function handleDryRun(ctx) {
31236
31512
  storyId: s.id,
31237
31513
  story: s,
31238
31514
  passed: true,
31239
- durationMs: 0,
31515
+ runElapsedMs: 0,
31240
31516
  cost: 0,
31241
31517
  modelTier: ctx.routing.modelTier,
31242
31518
  testStrategy: ctx.routing.testStrategy
@@ -31268,7 +31544,7 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
31268
31544
  storyId: completedStory.id,
31269
31545
  storyTitle: completedStory.title,
31270
31546
  totalCost: ctx.totalCost + costDelta,
31271
- durationMs: now - ctx.startTime,
31547
+ runElapsedMs: now - ctx.startTime,
31272
31548
  storyDurationMs: ctx.storyStartTime ? now - ctx.storyStartTime : undefined
31273
31549
  });
31274
31550
  pipelineEventBus.emit({
@@ -31276,7 +31552,7 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
31276
31552
  storyId: completedStory.id,
31277
31553
  story: completedStory,
31278
31554
  passed: true,
31279
- durationMs: Date.now() - ctx.startTime,
31555
+ runElapsedMs: Date.now() - ctx.startTime,
31280
31556
  cost: costDelta,
31281
31557
  modelTier: ctx.routing.modelTier,
31282
31558
  testStrategy: ctx.routing.testStrategy
@@ -31357,7 +31633,8 @@ async function handlePipelineFailure(ctx, pipelineResult) {
31357
31633
  hooks: ctx.hooks,
31358
31634
  feature: ctx.feature,
31359
31635
  totalCost: ctx.totalCost,
31360
- workdir: ctx.workdir
31636
+ workdir: ctx.workdir,
31637
+ attemptCost: pipelineResult.context.agentResult?.estimatedCost || 0
31361
31638
  });
31362
31639
  prd = escalationResult.prd;
31363
31640
  prdDirty = escalationResult.prdDirty;
@@ -31399,6 +31676,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
31399
31676
  }
31400
31677
  const storyStartTime = Date.now();
31401
31678
  const storyGitRef = await captureGitRef(ctx.workdir);
31679
+ const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
31402
31680
  const pipelineContext = {
31403
31681
  config: ctx.config,
31404
31682
  prd,
@@ -31412,7 +31690,8 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
31412
31690
  plugins: ctx.pluginRegistry,
31413
31691
  storyStartTime: new Date().toISOString(),
31414
31692
  storyGitRef: storyGitRef ?? undefined,
31415
- interaction: ctx.interactionChain ?? undefined
31693
+ interaction: ctx.interactionChain ?? undefined,
31694
+ accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
31416
31695
  };
31417
31696
  ctx.statusWriter.setPrd(prd);
31418
31697
  ctx.statusWriter.setCurrentStory({
@@ -64469,7 +64748,8 @@ var TEMPLATE_ROLES = [
64469
64748
  { file: "test-writer.md", role: "test-writer" },
64470
64749
  { file: "implementer.md", role: "implementer", variant: "standard" },
64471
64750
  { file: "verifier.md", role: "verifier" },
64472
- { file: "single-session.md", role: "single-session" }
64751
+ { file: "single-session.md", role: "single-session" },
64752
+ { file: "tdd-simple.md", role: "tdd-simple" }
64473
64753
  ];
64474
64754
  var TEMPLATE_HEADER = `<!--
64475
64755
  This file controls the role-body section of the nax prompt for this role.
@@ -64486,7 +64766,7 @@ var TEMPLATE_HEADER = `<!--
64486
64766
 
64487
64767
  `;
64488
64768
  async function promptsInitCommand(options) {
64489
- const { workdir, force = false } = options;
64769
+ const { workdir, force = false, autoWireConfig = true } = options;
64490
64770
  const templatesDir = join18(workdir, "nax", "templates");
64491
64771
  mkdirSync3(templatesDir, { recursive: true });
64492
64772
  const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync15(join18(templatesDir, f)));
@@ -64507,7 +64787,9 @@ async function promptsInitCommand(options) {
64507
64787
  for (const filePath of written) {
64508
64788
  console.log(` - ${filePath.replace(`${workdir}/`, "")}`);
64509
64789
  }
64510
- await autoWirePromptsConfig(workdir);
64790
+ if (autoWireConfig) {
64791
+ await autoWirePromptsConfig(workdir);
64792
+ }
64511
64793
  return written;
64512
64794
  }
64513
64795
  async function autoWirePromptsConfig(workdir) {
@@ -64519,7 +64801,8 @@ async function autoWirePromptsConfig(workdir) {
64519
64801
  "test-writer": "nax/templates/test-writer.md",
64520
64802
  implementer: "nax/templates/implementer.md",
64521
64803
  verifier: "nax/templates/verifier.md",
64522
- "single-session": "nax/templates/single-session.md"
64804
+ "single-session": "nax/templates/single-session.md",
64805
+ "tdd-simple": "nax/templates/tdd-simple.md"
64523
64806
  }
64524
64807
  }
64525
64808
  }, null, 2);
@@ -64540,7 +64823,8 @@ ${exampleConfig}`);
64540
64823
  "test-writer": "nax/templates/test-writer.md",
64541
64824
  implementer: "nax/templates/implementer.md",
64542
64825
  verifier: "nax/templates/verifier.md",
64543
- "single-session": "nax/templates/single-session.md"
64826
+ "single-session": "nax/templates/single-session.md",
64827
+ "tdd-simple": "nax/templates/tdd-simple.md"
64544
64828
  };
64545
64829
  if (!config2.prompts) {
64546
64830
  config2.prompts = {};
@@ -64573,6 +64857,33 @@ function formatConfigJson(config2) {
64573
64857
  return lines.join(`
64574
64858
  `);
64575
64859
  }
64860
+ var VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"];
64861
+ async function exportPromptCommand(options) {
64862
+ const { role, out } = options;
64863
+ if (!VALID_EXPORT_ROLES.includes(role)) {
64864
+ console.error(`[ERROR] Invalid role: "${role}". Valid roles: ${VALID_EXPORT_ROLES.join(", ")}`);
64865
+ process.exit(1);
64866
+ }
64867
+ const stubStory = {
64868
+ id: "EXAMPLE",
64869
+ title: "Example story",
64870
+ description: "Story ID: EXAMPLE. This is a placeholder story used to demonstrate the default prompt.",
64871
+ acceptanceCriteria: ["AC-1: Example criterion"],
64872
+ tags: [],
64873
+ dependencies: [],
64874
+ status: "pending",
64875
+ passes: false,
64876
+ escalations: [],
64877
+ attempts: 0
64878
+ };
64879
+ const prompt = await PromptBuilder.for(role).story(stubStory).build();
64880
+ if (out) {
64881
+ await Bun.write(out, prompt);
64882
+ console.log(`[OK] Exported prompt for "${role}" to ${out}`);
64883
+ } else {
64884
+ console.log(prompt);
64885
+ }
64886
+ }
64576
64887
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
64577
64888
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
64578
64889
  PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).build(),
@@ -66499,7 +66810,8 @@ init_story_context();
66499
66810
  init_escalation();
66500
66811
  init_escalation();
66501
66812
  var _runnerDeps = {
66502
- fireHook
66813
+ fireHook,
66814
+ runParallelExecution: null
66503
66815
  };
66504
66816
  async function run(options) {
66505
66817
  const {
@@ -66601,7 +66913,7 @@ async function run(options) {
66601
66913
  await tryLlmBatchRoute(config2, getAllReadyStories(prd), "routing");
66602
66914
  }
66603
66915
  if (options.parallel !== undefined) {
66604
- const { runParallelExecution: runParallelExecution2 } = await Promise.resolve().then(() => (init_parallel_executor(), exports_parallel_executor));
66916
+ const runParallelExecution2 = _runnerDeps.runParallelExecution ?? (await Promise.resolve().then(() => (init_parallel_executor(), exports_parallel_executor))).runParallelExecution;
66605
66917
  const parallelResult = await runParallelExecution2({
66606
66918
  prdPath,
66607
66919
  workdir,
@@ -66626,6 +66938,7 @@ async function run(options) {
66626
66938
  prd = parallelResult.prd;
66627
66939
  totalCost = parallelResult.totalCost;
66628
66940
  storiesCompleted = parallelResult.storiesCompleted;
66941
+ allStoryMetrics.push(...parallelResult.storyMetrics);
66629
66942
  if (parallelResult.completed && parallelResult.durationMs !== undefined) {
66630
66943
  return {
66631
66944
  success: true,
@@ -66656,8 +66969,8 @@ async function run(options) {
66656
66969
  }, prd);
66657
66970
  prd = sequentialResult.prd;
66658
66971
  iterations = sequentialResult.iterations;
66659
- storiesCompleted = sequentialResult.storiesCompleted;
66660
- totalCost = sequentialResult.totalCost;
66972
+ totalCost += sequentialResult.totalCost;
66973
+ storiesCompleted += sequentialResult.storiesCompleted;
66661
66974
  allStoryMetrics.push(...sequentialResult.allStoryMetrics);
66662
66975
  if (config2.acceptance.enabled && isComplete(prd)) {
66663
66976
  const { runAcceptanceLoop: runAcceptanceLoop2 } = await Promise.resolve().then(() => (init_acceptance_loop(), exports_acceptance_loop));
@@ -74144,13 +74457,29 @@ Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cu
74144
74457
 
74145
74458
  **Note:** Customize this file to match your project's specific needs.
74146
74459
  `);
74460
+ try {
74461
+ await promptsInitCommand({
74462
+ workdir,
74463
+ force: options.force,
74464
+ autoWireConfig: false
74465
+ });
74466
+ } catch (err) {
74467
+ console.error(source_default.red(`Failed to initialize templates: ${err.message}`));
74468
+ process.exit(1);
74469
+ }
74147
74470
  console.log(source_default.green("\u2705 Initialized nax"));
74148
74471
  console.log(source_default.dim(` ${naxDir}/`));
74149
74472
  console.log(source_default.dim(" \u251C\u2500\u2500 config.json"));
74150
74473
  console.log(source_default.dim(" \u251C\u2500\u2500 context.md"));
74151
74474
  console.log(source_default.dim(" \u251C\u2500\u2500 hooks.json"));
74152
74475
  console.log(source_default.dim(" \u251C\u2500\u2500 features/"));
74153
- console.log(source_default.dim(" \u2514\u2500\u2500 hooks/"));
74476
+ console.log(source_default.dim(" \u251C\u2500\u2500 hooks/"));
74477
+ console.log(source_default.dim(" \u2514\u2500\u2500 templates/"));
74478
+ console.log(source_default.dim(" \u251C\u2500\u2500 test-writer.md"));
74479
+ console.log(source_default.dim(" \u251C\u2500\u2500 implementer.md"));
74480
+ console.log(source_default.dim(" \u251C\u2500\u2500 verifier.md"));
74481
+ console.log(source_default.dim(" \u251C\u2500\u2500 single-session.md"));
74482
+ console.log(source_default.dim(" \u2514\u2500\u2500 tdd-simple.md"));
74154
74483
  console.log(source_default.dim(`
74155
74484
  Next: nax features create <name>`));
74156
74485
  });
@@ -74611,7 +74940,7 @@ program2.command("accept").description("Override failed acceptance criteria").re
74611
74940
  process.exit(1);
74612
74941
  }
74613
74942
  });
74614
- program2.command("prompts").description("Assemble or initialize prompts").option("-f, --feature <name>", "Feature name (required unless using --init)").option("--init", "Initialize default prompt templates", false).option("--force", "Overwrite existing template files", false).option("--story <id>", "Filter to a single story ID (e.g., US-003)").option("--out <dir>", "Output directory for prompt files (default: stdout)").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
74943
+ program2.command("prompts").description("Assemble or initialize prompts").option("-f, --feature <name>", "Feature name (required unless using --init or --export)").option("--init", "Initialize default prompt templates", false).option("--export <role>", "Export default prompt for a role to stdout or --out file").option("--force", "Overwrite existing template files", false).option("--story <id>", "Filter to a single story ID (e.g., US-003)").option("--out <path>", "Output file path for --export, or directory for regular prompts (default: stdout)").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
74615
74944
  let workdir;
74616
74945
  try {
74617
74946
  workdir = validateDirectory(options.dir);
@@ -74631,8 +74960,20 @@ program2.command("prompts").description("Assemble or initialize prompts").option
74631
74960
  }
74632
74961
  return;
74633
74962
  }
74963
+ if (options.export) {
74964
+ try {
74965
+ await exportPromptCommand({
74966
+ role: options.export,
74967
+ out: options.out
74968
+ });
74969
+ } catch (err) {
74970
+ console.error(source_default.red(`Error: ${err.message}`));
74971
+ process.exit(1);
74972
+ }
74973
+ return;
74974
+ }
74634
74975
  if (!options.feature) {
74635
- console.error(source_default.red("Error: --feature is required (unless using --init)"));
74976
+ console.error(source_default.red("Error: --feature is required (unless using --init or --export)"));
74636
74977
  process.exit(1);
74637
74978
  }
74638
74979
  const config2 = await loadConfig(workdir);