@nathapp/nax 0.67.0-canary.3 → 0.67.0-canary.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 (2) hide show
  1. package/dist/nax.js +709 -612
  2. package/package.json +6 -4
package/dist/nax.js CHANGED
@@ -17904,7 +17904,7 @@ function formatConsole(entry) {
17904
17904
  const timestamp = new Date(entry.timestamp).toLocaleTimeString("en-US", {
17905
17905
  hour12: false
17906
17906
  });
17907
- let levelColor;
17907
+ let levelColor = source_default.gray;
17908
17908
  switch (entry.level) {
17909
17909
  case "error":
17910
17910
  levelColor = source_default.red;
@@ -17916,6 +17916,7 @@ function formatConsole(entry) {
17916
17916
  levelColor = source_default.blue;
17917
17917
  break;
17918
17918
  case "debug":
17919
+ case "silent":
17919
17920
  levelColor = source_default.gray;
17920
17921
  break;
17921
17922
  }
@@ -18064,7 +18065,7 @@ ${JSON.stringify(entry.data, null, 2)}`;
18064
18065
  }
18065
18066
  close() {}
18066
18067
  }
18067
- function initLogger(options = { level: "warn" }) {
18068
+ function initLogger(options = { level: "silent" }) {
18068
18069
  if (instance) {
18069
18070
  throw new Error("Logger already initialized. Call getLogger() to access existing instance.");
18070
18071
  }
@@ -18095,12 +18096,13 @@ var init_logger = __esm(() => {
18095
18096
  init_logging();
18096
18097
  init_formatters();
18097
18098
  LOG_LEVEL_PRIORITY = {
18099
+ silent: -1,
18098
18100
  error: 0,
18099
18101
  warn: 1,
18100
18102
  info: 2,
18101
18103
  debug: 3
18102
18104
  };
18103
- noopLogger = new Logger({ level: "error", useChalk: false, headless: false });
18105
+ noopLogger = new Logger({ level: "silent", useChalk: false, headless: false });
18104
18106
  });
18105
18107
 
18106
18108
  // src/logger/index.ts
@@ -34139,7 +34141,7 @@ var init_autofix_implementer = __esm(() => {
34139
34141
  kind: "run",
34140
34142
  name: "autofix-implementer",
34141
34143
  stage: "rectification",
34142
- session: { role: "implementer", lifetime: "fresh" },
34144
+ session: { role: "implementer", lifetime: "warm" },
34143
34145
  config: autofixConfigSelector,
34144
34146
  build(input, _ctx) {
34145
34147
  const prompt = RectifierPromptBuilder.reviewRectification(input.failedChecks, input.story);
@@ -34213,6 +34215,7 @@ var init_debate_propose = __esm(() => {
34213
34215
  return "fast";
34214
34216
  return { agent: debater.agent, model: debater.model ?? "fast" };
34215
34217
  },
34218
+ timeoutMs: (_input, ctx) => (ctx.config.debate?.stages?.review?.timeoutSeconds ?? 600) * 1000,
34216
34219
  build(input, _ctx) {
34217
34220
  const builder = new DebatePromptBuilder({ taskContext: input.taskContext, outputFormat: input.outputFormat, stage: input.stage }, { debaters: input.debaters, sessionMode: "one-shot", proposers: input.stageConfig?.proposers });
34218
34221
  return builder.proposeSlot(input.debaterIndex);
@@ -34235,6 +34238,7 @@ var init_debate_judge = __esm(() => {
34235
34238
  jsonMode: false,
34236
34239
  config: debateConfigSelector,
34237
34240
  model: (input) => ({ agent: input.resolverAgent, model: input.resolverModel }),
34241
+ timeoutMs: (input, ctx) => (input.timeoutSeconds ?? ctx.config.debate?.stages?.review?.timeoutSeconds ?? 600) * 1000,
34238
34242
  build(input, _ctx) {
34239
34243
  const prompt = DebatePromptBuilder.resolverJudgePrompt(input.proposals, input.critiques, input.debaters);
34240
34244
  return {
@@ -34260,6 +34264,7 @@ var init_debate_synthesis = __esm(() => {
34260
34264
  jsonMode: false,
34261
34265
  config: debateConfigSelector,
34262
34266
  model: (input) => ({ agent: input.resolverAgent, model: input.resolverModel }),
34267
+ timeoutMs: (input, ctx) => (input.timeoutSeconds ?? ctx.config.debate?.stages?.review?.timeoutSeconds ?? 600) * 1000,
34263
34268
  build(input, _ctx) {
34264
34269
  const base = DebatePromptBuilder.resolverSynthesisPrompt(input.proposals, input.critiques, input.debaters);
34265
34270
  const content = input.promptSuffix ? `${base}
@@ -34293,6 +34298,7 @@ var init_debate_rebut = __esm(() => {
34293
34298
  return "fast";
34294
34299
  return { agent: debater.agent, model: debater.model ?? "fast" };
34295
34300
  },
34301
+ timeoutMs: (_input, ctx) => (ctx.config.debate?.stages?.review?.timeoutSeconds ?? 600) * 1000,
34296
34302
  build(input, _ctx) {
34297
34303
  const builder = new DebatePromptBuilder({ taskContext: input.taskContext, outputFormat: "", stage: input.stage }, { debaters: input.debaters, sessionMode: "one-shot" });
34298
34304
  return builder.rebutSlot(input.debaterIndex, input.proposals);
@@ -34339,6 +34345,7 @@ var init_debate_stateful = __esm(() => {
34339
34345
  session: { role: "debate-stateful", lifetime: "fresh" },
34340
34346
  config: debateConfigSelector,
34341
34347
  model: (input) => ({ agent: input.debater.agent, model: input.debater.model ?? "fast" }),
34348
+ timeoutMs: (_input, ctx) => (ctx.config.debate?.stages?.review?.timeoutSeconds ?? 600) * 1000,
34342
34349
  async hopBody(initialPrompt, ctx) {
34343
34350
  const proposal = ctx.input.turnSemaphore ? await ctx.input.turnSemaphore.run(() => ctx.send(initialPrompt)) : await ctx.send(initialPrompt);
34344
34351
  ctx.input.proposalBarriers[ctx.input.index].resolve(proposal.output);
@@ -34510,7 +34517,8 @@ var RESOLVER_FALLBACK_AGENT = "synthesis", RESOLVER_FALLBACK_MODEL = "fast", jud
34510
34517
  critiques: ctx.critiques,
34511
34518
  debaters: ctx.debaters,
34512
34519
  resolverAgent,
34513
- resolverModel
34520
+ resolverModel,
34521
+ timeoutSeconds: ctx.stageConfig.timeoutSeconds
34514
34522
  });
34515
34523
  return {
34516
34524
  outcome: output.trim() ? "passed" : "failed",
@@ -34533,7 +34541,8 @@ var RESOLVER_FALLBACK_AGENT2 = "synthesis", RESOLVER_FALLBACK_MODEL2 = "fast", s
34533
34541
  debaters: ctx.debaters,
34534
34542
  resolverAgent,
34535
34543
  resolverModel,
34536
- promptSuffix: ctx.promptSuffix
34544
+ promptSuffix: ctx.promptSuffix,
34545
+ timeoutSeconds: ctx.stageConfig.timeoutSeconds
34537
34546
  });
34538
34547
  return {
34539
34548
  outcome: output.trim() ? "passed" : "failed",
@@ -34846,6 +34855,7 @@ var init_debate_hybrid = __esm(() => {
34846
34855
  session: { role: "debate-hybrid", lifetime: "fresh" },
34847
34856
  config: debateConfigSelector,
34848
34857
  model: (input) => ({ agent: input.debater.agent, model: input.debater.model ?? "fast" }),
34858
+ timeoutMs: (_input, ctx) => (ctx.config.debate?.stages?.review?.timeoutSeconds ?? 600) * 1000,
34849
34859
  async hopBody(initialPrompt, ctx) {
34850
34860
  const logger = _debateSessionDeps.getSafeLogger();
34851
34861
  let totalCostUsd = 0;
@@ -34909,6 +34919,7 @@ var init_debate_plan = __esm(() => {
34909
34919
  session: { role: "debate-plan", lifetime: "fresh" },
34910
34920
  config: debateConfigSelector,
34911
34921
  model: (input) => ({ agent: input.debater.agent, model: input.debater.model ?? "fast" }),
34922
+ timeoutMs: (_input, ctx) => (ctx.config.debate?.stages?.plan?.timeoutSeconds ?? 600) * 1000,
34912
34923
  fileOutput: (input) => input.outputPath,
34913
34924
  async hopBody(initialPrompt, ctx) {
34914
34925
  const proposal = ctx.input.turnSemaphore ? await ctx.input.turnSemaphore.run(() => ctx.send(initialPrompt)) : await ctx.send(initialPrompt);
@@ -36848,7 +36859,7 @@ function countTotalAttempts(iterations) {
36848
36859
  return iterations.reduce((sum, iter) => sum + iter.fixesApplied.length, 0);
36849
36860
  }
36850
36861
  async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
36851
- const logger = getSafeLogger();
36862
+ const logger = _deps.logger !== undefined ? _deps.logger : getSafeLogger();
36852
36863
  const doCallOp = _deps.callOp ?? _cycleDeps.callOp;
36853
36864
  const now = _deps.now ?? _cycleDeps.now;
36854
36865
  const storyId = ctx.storyId;
@@ -36978,29 +36989,77 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
36978
36989
  costUsd: totalCostUsd
36979
36990
  };
36980
36991
  }
36981
- const provisionalIterations = [...cycle.iterations, { fixesApplied }];
36982
- const allExhausted = group.every((s) => countStrategyAttempts(provisionalIterations, s.name) >= s.maxAttempts);
36992
+ const allExhausted = group.every((s) => {
36993
+ const prior = countStrategyAttempts(cycle.iterations, s.name);
36994
+ const current = fixesApplied.filter((fa) => fa.strategyName === s.name).length;
36995
+ return prior + current >= s.maxAttempts;
36996
+ });
36983
36997
  if (allExhausted) {
36998
+ let liteFindingsAfter;
36999
+ try {
37000
+ liteFindingsAfter = await cycle.validate(ctx, { mode: "lite" });
37001
+ } catch (err) {
37002
+ const finishedAt3 = now();
37003
+ cycle.iterations.push({
37004
+ iterationNum: cycle.iterations.length + 1,
37005
+ findingsBefore,
37006
+ fixesApplied,
37007
+ findingsAfter: cycle.findings,
37008
+ outcome: "unchanged",
37009
+ startedAt,
37010
+ finishedAt: finishedAt3
37011
+ });
37012
+ logger?.warn("findings.cycle", "lite validate failed on terminal exhausted branch", {
37013
+ storyId,
37014
+ packageDir,
37015
+ cycleName,
37016
+ error: errorMessage(err)
37017
+ });
37018
+ return {
37019
+ iterations: cycle.iterations,
37020
+ finalFindings: cycle.findings,
37021
+ exitReason: "max-attempts-per-strategy",
37022
+ exhaustedStrategy: group[0]?.name,
37023
+ costUsd: totalCostUsd
37024
+ };
37025
+ }
37026
+ const outcome2 = classifyOutcome(findingsBefore, liteFindingsAfter);
36984
37027
  const finishedAt2 = now();
36985
37028
  cycle.iterations.push({
36986
37029
  iterationNum: cycle.iterations.length + 1,
36987
37030
  findingsBefore,
36988
37031
  fixesApplied,
36989
- findingsAfter: cycle.findings,
36990
- outcome: "unchanged",
37032
+ findingsAfter: liteFindingsAfter,
37033
+ outcome: outcome2,
36991
37034
  startedAt,
36992
37035
  finishedAt: finishedAt2
36993
37036
  });
36994
- logger?.info("findings.cycle", "cycle exited \u2014 strategy attempt cap reached (skipped final validate)", {
37037
+ cycle.findings = liteFindingsAfter;
37038
+ if (liteFindingsAfter.length === 0) {
37039
+ logger?.info("findings.cycle", "cycle exited \u2014 resolved after terminal lite validate", {
37040
+ storyId,
37041
+ packageDir,
37042
+ cycleName,
37043
+ reason: "resolved"
37044
+ });
37045
+ return {
37046
+ iterations: cycle.iterations,
37047
+ finalFindings: [],
37048
+ exitReason: "resolved",
37049
+ costUsd: totalCostUsd
37050
+ };
37051
+ }
37052
+ logger?.info("findings.cycle", "cycle exited \u2014 strategy attempt cap reached (lite validate)", {
36995
37053
  storyId,
36996
37054
  packageDir,
36997
37055
  cycleName,
36998
37056
  reason: "max-attempts-per-strategy",
36999
- exhaustedStrategy: group[0]?.name
37057
+ exhaustedStrategy: group[0]?.name,
37058
+ liteFindingsAfterCount: liteFindingsAfter.length
37000
37059
  });
37001
37060
  return {
37002
37061
  iterations: cycle.iterations,
37003
- finalFindings: cycle.findings,
37062
+ finalFindings: liteFindingsAfter,
37004
37063
  exitReason: "max-attempts-per-strategy",
37005
37064
  exhaustedStrategy: group[0]?.name,
37006
37065
  costUsd: totalCostUsd
@@ -37010,7 +37069,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
37010
37069
  let validatorAttempt = 0;
37011
37070
  for (;; ) {
37012
37071
  try {
37013
- findingsAfter = await cycle.validate(ctx);
37072
+ findingsAfter = await cycle.validate(ctx, { mode: "full" });
37014
37073
  break;
37015
37074
  } catch (err) {
37016
37075
  if (validatorAttempt >= cycle.config.validatorRetries) {
@@ -39290,8 +39349,486 @@ async function runRetryLoop(input) {
39290
39349
  return { outcome: "exhausted", attempts: input.maxAttempts, finalFailure: currentFailure };
39291
39350
  }
39292
39351
 
39352
+ // src/execution/escalation/escalation.ts
39353
+ function escalateTier(currentTier, tierOrder) {
39354
+ const getName = (t) => t.tier ?? t.name ?? null;
39355
+ const currentIndex = tierOrder.findIndex((t) => getName(t) === currentTier);
39356
+ if (currentIndex === -1 || currentIndex === tierOrder.length - 1) {
39357
+ return null;
39358
+ }
39359
+ const next = tierOrder[currentIndex + 1];
39360
+ const nextName = getName(next);
39361
+ if (!nextName)
39362
+ return null;
39363
+ return { tier: nextName, agent: next.agent };
39364
+ }
39365
+ function calculateMaxIterations(tierOrder) {
39366
+ return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
39367
+ }
39368
+
39369
+ // src/session/naming.ts
39370
+ var exports_naming = {};
39371
+ __export(exports_naming, {
39372
+ formatSessionName: () => formatSessionName
39373
+ });
39374
+ var init_naming = __esm(() => {
39375
+ init_session_name();
39376
+ });
39377
+
39378
+ // src/verification/failure-records.ts
39379
+ function truncateUnmappedFailureOutput(output) {
39380
+ const tailLines = output.split(`
39381
+ `).slice(-UNMAPPED_FAILURE_OUTPUT_MAX_LINES).join(`
39382
+ `);
39383
+ if (tailLines.length <= UNMAPPED_FAILURE_OUTPUT_MAX_CHARS) {
39384
+ return tailLines;
39385
+ }
39386
+ return `... (truncated)
39387
+ ${tailLines.slice(-UNMAPPED_FAILURE_OUTPUT_MAX_CHARS)}`;
39388
+ }
39389
+ function buildFailureRecords(testSummary, rawOutput) {
39390
+ if (testSummary.failures.length > 0) {
39391
+ return testSummary.failures.map((failure) => ({
39392
+ test: failure.testName,
39393
+ file: failure.file,
39394
+ message: failure.error,
39395
+ output: failure.stackTrace.length > 0 ? failure.stackTrace.join(`
39396
+ `) : undefined
39397
+ }));
39398
+ }
39399
+ if (testSummary.failed === 0) {
39400
+ return [];
39401
+ }
39402
+ return [
39403
+ {
39404
+ test: `Unmapped test failures (${testSummary.failed} detected)`,
39405
+ message: "Structured test failure parsing returned no failure records. Diagnose the regression from the raw test output.",
39406
+ output: rawOutput?.trim() ? truncateUnmappedFailureOutput(rawOutput.trim()) : undefined
39407
+ }
39408
+ ];
39409
+ }
39410
+ var UNMAPPED_FAILURE_OUTPUT_MAX_LINES = 200, UNMAPPED_FAILURE_OUTPUT_MAX_CHARS = 8000;
39411
+
39412
+ // src/verification/rectification-loop.ts
39413
+ async function _defaultRunDebate(storyId, stageConfig, prompt, config2, agentManager) {
39414
+ const logger = getSafeLogger();
39415
+ const debaters = stageConfig.debaters ?? [];
39416
+ const manager = agentManager;
39417
+ const resolved = [];
39418
+ for (const debater of debaters) {
39419
+ if (manager.getAgent(debater.agent)) {
39420
+ resolved.push({ debater, agentName: debater.agent });
39421
+ }
39422
+ }
39423
+ if (resolved.length === 0) {
39424
+ return { output: null, totalCostUsd: 0 };
39425
+ }
39426
+ const timeoutMs = (config2.execution?.sessionTimeoutSeconds ?? 600) * 1000;
39427
+ const defaultAgentName = resolveDefaultAgent(config2);
39428
+ const resolvedDebaters = resolved.map(({ debater, agentName }) => {
39429
+ let modelDef;
39430
+ try {
39431
+ modelDef = resolveConfiguredModel(config2.models, agentName, debater.model ?? "balanced", defaultAgentName).modelDef;
39432
+ } catch {
39433
+ modelDef = { provider: "unknown", model: debater.model ?? "default" };
39434
+ }
39435
+ return { debater, agentName, modelDef };
39436
+ });
39437
+ const startMs = Date.now();
39438
+ const proposalSettled = await Promise.allSettled(resolvedDebaters.map(({ agentName, modelDef }) => manager.completeAs(agentName, prompt, {
39439
+ modelDef,
39440
+ workdir: "",
39441
+ storyId,
39442
+ sessionRole: "debate-proposal",
39443
+ timeoutMs
39444
+ }).then((out) => typeof out === "string" ? out : out.output)));
39445
+ const durationMs = Date.now() - startMs;
39446
+ const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
39447
+ if (successful.length === 0) {
39448
+ return { output: null, totalCostUsd: 0 };
39449
+ }
39450
+ const successCount = successful.length;
39451
+ const costPerDebater = estimateCostByDuration("balanced", durationMs / successCount);
39452
+ const totalCostUsd = costPerDebater.cost * successCount;
39453
+ logger?.debug("rectification", "debate diagnosis complete", { storyId, successCount, totalCostUsd });
39454
+ const output = successful.join(`
39455
+
39456
+ `);
39457
+ return { output, totalCostUsd };
39458
+ }
39459
+ async function runRectificationLoop(opts) {
39460
+ const loopStartMs = Date.now();
39461
+ const {
39462
+ config: config2,
39463
+ workdir,
39464
+ story,
39465
+ testCommand,
39466
+ timeoutSeconds,
39467
+ testOutput,
39468
+ promptPrefix,
39469
+ featureName,
39470
+ projectDir,
39471
+ testScopedTemplate,
39472
+ sessionId,
39473
+ runtime
39474
+ } = opts;
39475
+ const logger = getSafeLogger();
39476
+ const agentManager = opts.agentManager ?? _rectificationDeps.agentManager;
39477
+ if (!agentManager) {
39478
+ logger?.warn("rectification", "No agentManager threaded \u2014 skipping rectification loop", {
39479
+ storyId: story.id
39480
+ });
39481
+ return { succeeded: false, cost: 0, durationMs: 0 };
39482
+ }
39483
+ const rectificationConfig = config2.execution.rectification;
39484
+ const testSummary = parseTestOutput(testOutput);
39485
+ const label = promptPrefix ? "regression rectification" : "rectification";
39486
+ const rectificationSessionName = formatSessionName({
39487
+ workdir,
39488
+ featureName,
39489
+ storyId: story.id,
39490
+ role: "implementer"
39491
+ });
39492
+ let costAccum = 0;
39493
+ let currentAttempt = 0;
39494
+ let heldHandle;
39495
+ const initialFailure = {
39496
+ testOutput,
39497
+ testSummary
39498
+ };
39499
+ const outcome = await runRetryLoop({
39500
+ stage: "rectification",
39501
+ storyId: story.id,
39502
+ packageDir: workdir,
39503
+ maxAttempts: rectificationConfig.maxRetries,
39504
+ failure: initialFailure,
39505
+ previousAttempts: [],
39506
+ buildPrompt: (failure) => {
39507
+ currentAttempt++;
39508
+ const diagnosisPrefix = null;
39509
+ const debateStageConfig = config2.debate?.stages?.rectification;
39510
+ let debatePromise = Promise.resolve(null);
39511
+ if (debateStageConfig?.enabled) {
39512
+ const failureSummary = formatFailureSummary(failure.testSummary.failures);
39513
+ const diagnosisPrompt = `Analyze the following test failures and identify the root cause:
39514
+
39515
+ ${failureSummary}`;
39516
+ debatePromise = (async () => {
39517
+ try {
39518
+ const debateResult = await _rectificationDeps.runDebate(story.id, debateStageConfig, diagnosisPrompt, config2, agentManager);
39519
+ if (debateResult.totalCostUsd > 0 && story.routing) {
39520
+ story.routing.estimatedCostUsd = (story.routing.estimatedCostUsd ?? 0) + debateResult.totalCostUsd;
39521
+ }
39522
+ if (debateResult.output !== null) {
39523
+ return `## Root Cause Analysis
39524
+
39525
+ ${debateResult.output}`;
39526
+ }
39527
+ logger?.info("rectification", "debate diagnosis fallback \u2014 all debaters failed", {
39528
+ storyId: story.id,
39529
+ event: "fallback"
39530
+ });
39531
+ return null;
39532
+ } catch (_error) {
39533
+ logger?.info("rectification", "debate diagnosis fallback \u2014 debate threw error", {
39534
+ storyId: story.id,
39535
+ event: "fallback"
39536
+ });
39537
+ return null;
39538
+ }
39539
+ })();
39540
+ }
39541
+ const failureRecords = buildFailureRecords(failure.testSummary, failure.testOutput);
39542
+ const rectPrompt = RectifierPromptBuilder.regressionFailure({
39543
+ story,
39544
+ failures: failureRecords,
39545
+ testCommand,
39546
+ conventions: true
39547
+ });
39548
+ const rectPromise = Promise.resolve(rectPrompt);
39549
+ return (async () => {
39550
+ const [diagnosis, rectificationPrompt] = await Promise.all([debatePromise, rectPromise]);
39551
+ let finalPrompt = rectificationPrompt;
39552
+ if (diagnosis) {
39553
+ finalPrompt = `${diagnosis}
39554
+
39555
+ ${finalPrompt}`;
39556
+ }
39557
+ if (promptPrefix) {
39558
+ finalPrompt = `${promptPrefix}
39559
+
39560
+ ${finalPrompt}`;
39561
+ }
39562
+ return finalPrompt;
39563
+ })();
39564
+ },
39565
+ execute: async (prompt) => {
39566
+ const defaultAgent = agentManager.getDefault();
39567
+ const complexity = story.routing?.complexity ?? "medium";
39568
+ const modelTier = config2.autoMode.complexityRouting?.[complexity] || config2.autoMode.escalation.tierOrder[0]?.tier || "balanced";
39569
+ const modelDef = resolveModelForAgent(config2.models, story.routing?.agent ?? defaultAgent, modelTier, defaultAgent);
39570
+ let agentResult;
39571
+ {
39572
+ let transportRetries = 0;
39573
+ const maxTransportRetries = config2.execution?.sessionErrorRetryableMaxRetries ?? 3;
39574
+ while (true) {
39575
+ if (!heldHandle) {
39576
+ const existingHandle = runtime.sessionManager.getLiveHandle(rectificationSessionName);
39577
+ if (existingHandle && existingHandle.agentName === defaultAgent) {
39578
+ heldHandle = existingHandle;
39579
+ } else {
39580
+ heldHandle = await runtime.sessionManager.openSession(rectificationSessionName, {
39581
+ agentName: defaultAgent,
39582
+ role: "implementer",
39583
+ workdir,
39584
+ pipelineStage: "rectification",
39585
+ modelDef,
39586
+ timeoutSeconds: config2.execution.sessionTimeoutSeconds,
39587
+ featureName,
39588
+ storyId: story.id,
39589
+ signal: runtime.signal
39590
+ });
39591
+ }
39592
+ }
39593
+ try {
39594
+ const turn = await agentManager.runAsSession(defaultAgent, heldHandle, prompt, {
39595
+ storyId: story.id,
39596
+ featureName,
39597
+ workdir,
39598
+ projectDir,
39599
+ pipelineStage: "rectification",
39600
+ sessionRole: "implementer",
39601
+ signal: runtime.signal,
39602
+ maxTurns: config2.agent?.maxInteractionTurns
39603
+ });
39604
+ agentResult = {
39605
+ success: true,
39606
+ exitCode: 0,
39607
+ output: turn.output,
39608
+ rateLimited: false,
39609
+ durationMs: 0,
39610
+ estimatedCostUsd: turn.estimatedCostUsd,
39611
+ ...turn.exactCostUsd !== undefined && { exactCostUsd: turn.exactCostUsd },
39612
+ ...turn.tokenUsage && { tokenUsage: turn.tokenUsage },
39613
+ ...heldHandle.protocolIds && { protocolIds: heldHandle.protocolIds }
39614
+ };
39615
+ break;
39616
+ } catch (err) {
39617
+ const stale = heldHandle;
39618
+ heldHandle = undefined;
39619
+ await runtime.sessionManager.closeSession(stale).catch(() => {});
39620
+ if (err instanceof SessionTurnError && err.retryable && transportRetries < maxTransportRetries) {
39621
+ transportRetries++;
39622
+ getSafeLogger()?.warn("rectification", "fail-adapter-error: same-agent retry with fresh session", {
39623
+ storyId: story.id,
39624
+ attempt: transportRetries,
39625
+ maxAttempts: maxTransportRetries,
39626
+ retriable: true
39627
+ });
39628
+ continue;
39629
+ }
39630
+ throw err;
39631
+ }
39632
+ }
39633
+ }
39634
+ costAccum += agentResult.estimatedCostUsd ?? 0;
39635
+ if (sessionId && agentResult.protocolIds) {
39636
+ try {
39637
+ runtime.sessionManager.bindHandle(sessionId, rectificationSessionName, agentResult.protocolIds);
39638
+ } catch {}
39639
+ }
39640
+ if (agentResult.success) {
39641
+ logger?.info("rectification", `Agent ${label} session complete`, {
39642
+ storyId: story.id,
39643
+ cost: agentResult.estimatedCostUsd
39644
+ });
39645
+ } else {
39646
+ logger?.warn("rectification", `Agent ${label} session failed`, {
39647
+ storyId: story.id,
39648
+ exitCode: agentResult.exitCode
39649
+ });
39650
+ }
39651
+ return {
39652
+ agentSuccess: agentResult.success,
39653
+ cost: agentResult.estimatedCostUsd ?? 0,
39654
+ protocolIds: agentResult.protocolIds
39655
+ };
39656
+ },
39657
+ shouldAbort: (failure) => {
39658
+ if (rectificationConfig.abortOnIncreasingFailures) {
39659
+ return failure.testSummary.failed > initialFailure.testSummary.failed;
39660
+ }
39661
+ return false;
39662
+ },
39663
+ verify: async (result) => {
39664
+ const retryVerification = await _rectificationDeps.runVerification({
39665
+ workdir,
39666
+ expectedFiles: getExpectedFiles(story),
39667
+ command: testCommand,
39668
+ timeoutSeconds,
39669
+ forceExit: config2.quality.forceExit,
39670
+ detectOpenHandles: config2.quality.detectOpenHandles,
39671
+ detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
39672
+ timeoutRetryCount: 0,
39673
+ gracePeriodMs: config2.quality.gracePeriodMs,
39674
+ drainTimeoutMs: config2.quality.drainTimeoutMs,
39675
+ shell: config2.quality.shell,
39676
+ stripEnvVars: config2.quality.stripEnvVars
39677
+ });
39678
+ if (retryVerification.success) {
39679
+ logger?.info("rectification", `[OK] ${label} succeeded!`, {
39680
+ storyId: story.id,
39681
+ attempt: currentAttempt,
39682
+ initialFailures: initialFailure.testSummary.failed
39683
+ });
39684
+ return { passed: true };
39685
+ }
39686
+ if (retryVerification.output) {
39687
+ const newTestSummary = parseTestOutput(retryVerification.output);
39688
+ if (newTestSummary.failed === 0 && retryVerification.status !== "ENVIRONMENTAL_FAILURE" && (retryVerification.status === "SUCCESS" || newTestSummary.passed > 0)) {
39689
+ logger?.info("rectification", `[OK] ${label} succeeded after parsing retry output`, {
39690
+ storyId: story.id,
39691
+ attempt: currentAttempt,
39692
+ initialFailures: initialFailure.testSummary.failed
39693
+ });
39694
+ return { passed: true };
39695
+ }
39696
+ const failingTests = newTestSummary.failures.slice(0, 10).map((failure) => failure.testName);
39697
+ const logData = {
39698
+ storyId: story.id,
39699
+ attempt: currentAttempt,
39700
+ remainingFailures: newTestSummary.failed,
39701
+ failingTests
39702
+ };
39703
+ if (newTestSummary.failures.length > 10 || newTestSummary.failures.length === 0 && newTestSummary.failed > 0) {
39704
+ logData.totalFailingTests = newTestSummary.failed;
39705
+ }
39706
+ logger?.warn("rectification", `${label} still failing after attempt`, logData);
39707
+ return {
39708
+ passed: false,
39709
+ newFailure: {
39710
+ testOutput: retryVerification.output,
39711
+ testSummary: newTestSummary
39712
+ }
39713
+ };
39714
+ }
39715
+ return {
39716
+ passed: false,
39717
+ newFailure: {
39718
+ testOutput: retryVerification.output ?? "",
39719
+ testSummary: initialFailure.testSummary
39720
+ }
39721
+ };
39722
+ }
39723
+ }).finally(async () => {
39724
+ if (heldHandle) {
39725
+ const stale = heldHandle;
39726
+ heldHandle = undefined;
39727
+ await runtime.sessionManager.closeSession(stale).catch(() => {});
39728
+ }
39729
+ });
39730
+ const succeeded = outcome.outcome === "fixed";
39731
+ if (outcome.outcome === "exhausted") {
39732
+ const finalFailure = outcome.finalFailure;
39733
+ const shouldEscalate = rectificationConfig.escalateOnExhaustion !== false && config2.autoMode?.escalation?.enabled === true && finalFailure.testSummary.failed > 0;
39734
+ if (shouldEscalate) {
39735
+ const complexity = story.routing?.complexity ?? "medium";
39736
+ const currentTier = config2.autoMode.complexityRouting?.[complexity] || config2.autoMode.escalation.tierOrder[0]?.tier || "balanced";
39737
+ const tierOrder = config2.autoMode.escalation.tierOrder;
39738
+ const escalationResult = _rectificationDeps.escalateTier(currentTier, tierOrder);
39739
+ const escalatedTier = escalationResult?.tier ?? null;
39740
+ const escalatedAgent = escalationResult?.agent;
39741
+ if (escalatedTier !== null) {
39742
+ const escalationManager = agentManager;
39743
+ const agentName = escalatedAgent ?? story.routing?.agent ?? escalationManager.getDefault();
39744
+ if (escalationManager.getAgent(agentName)) {
39745
+ const escalatedModelDef = resolveModelForAgent(config2.models, agentName, escalatedTier, escalationManager.getDefault());
39746
+ let escalationPrompt = RectifierPromptBuilder.escalated(finalFailure.testSummary.failures, story, outcome.attempts, currentTier, escalatedTier, rectificationConfig, testCommand, testScopedTemplate);
39747
+ if (promptPrefix) {
39748
+ escalationPrompt = `${promptPrefix}
39749
+
39750
+ ${escalationPrompt}`;
39751
+ }
39752
+ const escalationRunResult = await escalationManager.runAs(agentName, {
39753
+ runOptions: {
39754
+ prompt: escalationPrompt,
39755
+ workdir,
39756
+ modelTier: escalatedTier,
39757
+ modelDef: escalatedModelDef,
39758
+ timeoutSeconds: config2.execution.sessionTimeoutSeconds,
39759
+ pipelineStage: "rectification",
39760
+ config: config2,
39761
+ projectDir,
39762
+ maxInteractionTurns: config2.agent?.maxInteractionTurns,
39763
+ featureName,
39764
+ storyId: story.id,
39765
+ sessionRole: "implementer"
39766
+ }
39767
+ });
39768
+ costAccum += escalationRunResult.estimatedCostUsd ?? 0;
39769
+ logger?.info("rectification", "escalated rectification attempt cost", {
39770
+ storyId: story.id,
39771
+ escalatedTier,
39772
+ cost: escalationRunResult.estimatedCostUsd
39773
+ });
39774
+ const escalationVerification = await _rectificationDeps.runVerification({
39775
+ workdir,
39776
+ expectedFiles: getExpectedFiles(story),
39777
+ command: testCommand,
39778
+ timeoutSeconds,
39779
+ forceExit: config2.quality.forceExit,
39780
+ detectOpenHandles: config2.quality.detectOpenHandles,
39781
+ detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
39782
+ timeoutRetryCount: 0,
39783
+ gracePeriodMs: config2.quality.gracePeriodMs,
39784
+ drainTimeoutMs: config2.quality.drainTimeoutMs,
39785
+ shell: config2.quality.shell,
39786
+ stripEnvVars: config2.quality.stripEnvVars
39787
+ });
39788
+ if (escalationVerification.success) {
39789
+ logger?.info("rectification", `${label} escalated from ${currentTier} to ${escalatedTier} and succeeded`, {
39790
+ storyId: story.id,
39791
+ currentTier,
39792
+ escalatedTier
39793
+ });
39794
+ return { succeeded: true, cost: costAccum, durationMs: Date.now() - loopStartMs };
39795
+ }
39796
+ logger?.warn("rectification", "escalated rectification also failed", { storyId: story.id, escalatedTier });
39797
+ }
39798
+ }
39799
+ }
39800
+ logger?.warn("rectification", `${label} exhausted max retries`, {
39801
+ storyId: story.id,
39802
+ attempts: outcome.attempts,
39803
+ remainingFailures: initialFailure.testSummary.failed
39804
+ });
39805
+ }
39806
+ return { succeeded, cost: costAccum, durationMs: Date.now() - loopStartMs };
39807
+ }
39808
+ var _rectificationDeps;
39809
+ var init_rectification_loop = __esm(() => {
39810
+ init_agents();
39811
+ init_cost();
39812
+ init_types3();
39813
+ init_config();
39814
+ init_logger2();
39815
+ init_prd();
39816
+ init_prompts();
39817
+ init_naming();
39818
+ init_parser2();
39819
+ init_parser2();
39820
+ init_runners();
39821
+ _rectificationDeps = {
39822
+ agentManager: undefined,
39823
+ runVerification: fullSuite,
39824
+ escalateTier,
39825
+ runDebate: _defaultRunDebate
39826
+ };
39827
+ });
39828
+
39293
39829
  // src/verification/index.ts
39294
39830
  var init_verification = __esm(() => {
39831
+ init_rectification_loop();
39295
39832
  init_executor();
39296
39833
  init_parser2();
39297
39834
  init_runners();
@@ -40856,7 +41393,7 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
40856
41393
  prompt
40857
41394
  };
40858
41395
  } finally {
40859
- if (hopKind.kind !== "stale-retry") {
41396
+ if (hopKind.kind !== "stale-retry" && !resolvedRunOptions.keepOpen) {
40860
41397
  await sessionManager.closeSession(handle);
40861
41398
  }
40862
41399
  }
@@ -41044,6 +41581,7 @@ async function callOp(ctx, op, input) {
41044
41581
  featureName: ctx.featureName,
41045
41582
  storyId: ctx.storyId,
41046
41583
  callId,
41584
+ ...runOp.session.lifetime === "warm" ? { keepOpen: true } : {},
41047
41585
  ...ctx.scopeId !== undefined ? { scopeId: ctx.scopeId } : {},
41048
41586
  ...ctx.interactionBridge ? { interactionBridge: ctx.interactionBridge } : {},
41049
41587
  ...ctx.maxInteractionTurns !== undefined ? { maxInteractionTurns: ctx.maxInteractionTurns } : {}
@@ -42861,15 +43399,6 @@ var init_manager_sweep = __esm(() => {
42861
43399
  DEFAULT_ORPHAN_TTL_MS = 4 * 60 * 60 * 1000;
42862
43400
  });
42863
43401
 
42864
- // src/session/naming.ts
42865
- var exports_naming = {};
42866
- __export(exports_naming, {
42867
- formatSessionName: () => formatSessionName
42868
- });
42869
- var init_naming = __esm(() => {
42870
- init_session_name();
42871
- });
42872
-
42873
43402
  // src/session/types.ts
42874
43403
  var SESSION_TRANSITIONS;
42875
43404
  var init_types7 = __esm(() => {
@@ -45211,7 +45740,11 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
45211
45740
  const proposalBarriers = resolved.map(() => Promise.withResolvers());
45212
45741
  const rebutBuilder = new DebatePromptBuilder({ taskContext, outputFormat: "", stage: "plan" }, { debaters: resolved.map((e) => e.debater), sessionMode: "stateful" });
45213
45742
  const callOpPromises = resolved.map(({ debater, agentName }, index) => {
45214
- const debaterCtx = { ...ctx.callContext, agentName };
45743
+ const debaterCtx = {
45744
+ ...ctx.callContext,
45745
+ agentName,
45746
+ sessionOverride: { role: `debate-plan-${index}` }
45747
+ };
45215
45748
  return callOp(debaterCtx, planDebaterOp, {
45216
45749
  debater,
45217
45750
  index,
@@ -45276,7 +45809,11 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
45276
45809
  const rebuttalBarriers = resolved.map(() => Promise.withResolvers());
45277
45810
  const rebutBuilder = new DebatePromptBuilder({ taskContext, outputFormat: "", stage: "plan" }, { debaters: resolved.map((e) => e.debater), sessionMode: "stateful" });
45278
45811
  const callOpPromisesB = resolved.map(({ debater, agentName }, index) => {
45279
- const debaterCtx = { ...ctx.callContext, agentName };
45812
+ const debaterCtx = {
45813
+ ...ctx.callContext,
45814
+ agentName,
45815
+ sessionOverride: { role: `debate-plan-${index}` }
45816
+ };
45280
45817
  return callOp(debaterCtx, planDebaterOp, {
45281
45818
  debater,
45282
45819
  index,
@@ -45335,7 +45872,11 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
45335
45872
  for (const barrier of rebuttalBarriersC)
45336
45873
  barrier.resolve("");
45337
45874
  const settled = await allSettledBounded(resolved.map(({ debater, agentName }, index) => async () => {
45338
- const debaterCtx = { ...ctx.callContext, agentName };
45875
+ const debaterCtx = {
45876
+ ...ctx.callContext,
45877
+ agentName,
45878
+ sessionOverride: { role: `debate-plan-${index}` }
45879
+ };
45339
45880
  const result = await callOp(debaterCtx, planDebaterOp, {
45340
45881
  debater,
45341
45882
  index,
@@ -50459,42 +51000,8 @@ var init_greenfield = __esm(() => {
50459
51000
  ]);
50460
51001
  });
50461
51002
 
50462
- // src/verification/failure-records.ts
50463
- function truncateUnmappedFailureOutput(output) {
50464
- const tailLines = output.split(`
50465
- `).slice(-UNMAPPED_FAILURE_OUTPUT_MAX_LINES).join(`
50466
- `);
50467
- if (tailLines.length <= UNMAPPED_FAILURE_OUTPUT_MAX_CHARS) {
50468
- return tailLines;
50469
- }
50470
- return `... (truncated)
50471
- ${tailLines.slice(-UNMAPPED_FAILURE_OUTPUT_MAX_CHARS)}`;
50472
- }
50473
- function buildFailureRecords(testSummary, rawOutput) {
50474
- if (testSummary.failures.length > 0) {
50475
- return testSummary.failures.map((failure) => ({
50476
- test: failure.testName,
50477
- file: failure.file,
50478
- message: failure.error,
50479
- output: failure.stackTrace.length > 0 ? failure.stackTrace.join(`
50480
- `) : undefined
50481
- }));
50482
- }
50483
- if (testSummary.failed === 0) {
50484
- return [];
50485
- }
50486
- return [
50487
- {
50488
- test: `Unmapped test failures (${testSummary.failed} detected)`,
50489
- message: "Structured test failure parsing returned no failure records. Diagnose the regression from the raw test output.",
50490
- output: rawOutput?.trim() ? truncateUnmappedFailureOutput(rawOutput.trim()) : undefined
50491
- }
50492
- ];
50493
- }
50494
- var UNMAPPED_FAILURE_OUTPUT_MAX_LINES = 200, UNMAPPED_FAILURE_OUTPUT_MAX_CHARS = 8000;
50495
-
50496
51003
  // src/tdd/rectification-gate.ts
50497
- async function runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, sessionManager, sessionId, runtime) {
51004
+ async function runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, sessionManager, runtime) {
50498
51005
  const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
50499
51006
  if (!rectificationEnabled) {
50500
51007
  return { passed: false, cost: 0, fullSuiteGatePassed: false, status: "disabled" };
@@ -50522,16 +51029,16 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
50522
51029
  });
50523
51030
  return { passed: true, cost: 0, fullSuiteGatePassed: false, status: "deferred-unattributable" };
50524
51031
  }
50525
- return await runRectificationLoop(story, config2, workdir, agentManager, implementerTier, lite, logger, testSummary, rectificationConfig, effectiveTestCmd, fullSuiteTimeout, fullSuiteResult.output, featureName, projectDir, sessionManager, sessionId, runtime);
51032
+ return await runRectificationLoop2(story, config2, workdir, agentManager, implementerTier, lite, logger, testSummary, rectificationConfig, effectiveTestCmd, fullSuiteTimeout, fullSuiteResult.output, runtime, featureName, projectDir, sessionManager);
50526
51033
  }
50527
51034
  if (testSummary.failures.length > 0) {
50528
- logger.warn("tdd", "Full suite gate: parser counter mismatch \u2014 routing to rectification using failures.length", {
51035
+ logger.warn("tdd", "Full suite gate: parser counter mismatch \u2014 failing gate (unreliable failure count)", {
50529
51036
  storyId: story.id,
50530
51037
  failedCounter: testSummary.failed,
50531
51038
  failuresLength: testSummary.failures.length,
50532
51039
  exitCode: fullSuiteResult.exitCode
50533
51040
  });
50534
- return await runRectificationLoop(story, config2, workdir, agentManager, implementerTier, lite, logger, { ...testSummary, failed: Math.max(testSummary.failed, testSummary.failures.length) }, rectificationConfig, effectiveTestCmd, fullSuiteTimeout, fullSuiteResult.output, featureName, projectDir, sessionManager, sessionId, runtime);
51041
+ return { passed: false, cost: 0, fullSuiteGatePassed: false, status: "execution-failed" };
50535
51042
  }
50536
51043
  if (testSummary.passed > 0) {
50537
51044
  logger.info("tdd", "Full suite gate passed (non-zero exit, 0 failures, tests detected)", {
@@ -50559,7 +51066,7 @@ async function runFullSuiteGate(story, config2, workdir, agentManager, implement
50559
51066
  });
50560
51067
  return { passed: false, cost: 0, fullSuiteGatePassed: false, status: "execution-failed" };
50561
51068
  }
50562
- async function runRectificationLoop(story, config2, workdir, agentManager, implementerTier, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, testOutput, featureName, projectDir, sessionManager, sessionId, runtime) {
51069
+ async function runRectificationLoop2(story, config2, workdir, agentManager, implementerTier, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, testOutput, runtime, featureName, projectDir, sessionManager) {
50563
51070
  logger.warn("tdd", "Full suite gate detected regressions", {
50564
51071
  storyId: story.id,
50565
51072
  failedTests: testSummary.failed,
@@ -50597,7 +51104,6 @@ async function runRectificationLoop(story, config2, workdir, agentManager, imple
50597
51104
  },
50598
51105
  execute: async (prompt) => {
50599
51106
  currentAttempt++;
50600
- const isLastAttempt = currentAttempt >= rectificationConfig.maxRetries;
50601
51107
  const rectifyBeforeRef = await captureGitRef(workdir) ?? "HEAD";
50602
51108
  const defaultAgent = agentManager.getDefault();
50603
51109
  const runOptions = {
@@ -50615,11 +51121,14 @@ async function runRectificationLoop(story, config2, workdir, agentManager, imple
50615
51121
  sessionRole: "implementer"
50616
51122
  };
50617
51123
  let rectifyResult;
50618
- if (runtime) {
50619
- let transportRetries = 0;
50620
- const maxTransportRetries = config2.execution?.sessionErrorRetryableMaxRetries ?? 3;
50621
- while (transportRetries <= maxTransportRetries) {
50622
- if (!heldHandle) {
51124
+ let transportRetries = 0;
51125
+ const maxTransportRetries = config2.execution?.sessionErrorRetryableMaxRetries ?? 3;
51126
+ while (true) {
51127
+ if (!heldHandle) {
51128
+ const existingHandle = sessionManager?.getLiveHandle(rectificationSessionName);
51129
+ if (existingHandle && existingHandle.agentName === defaultAgent) {
51130
+ heldHandle = existingHandle;
51131
+ } else {
50623
51132
  heldHandle = await runtime.sessionManager.openSession(rectificationSessionName, {
50624
51133
  agentName: defaultAgent,
50625
51134
  role: "implementer",
@@ -50632,54 +51141,50 @@ async function runRectificationLoop(story, config2, workdir, agentManager, imple
50632
51141
  signal: runtime.signal
50633
51142
  });
50634
51143
  }
50635
- try {
50636
- const turn = await agentManager.runAsSession(defaultAgent, heldHandle, prompt, {
51144
+ }
51145
+ try {
51146
+ const turn = await agentManager.runAsSession(defaultAgent, heldHandle, prompt, {
51147
+ storyId: story.id,
51148
+ featureName,
51149
+ workdir,
51150
+ projectDir,
51151
+ pipelineStage: "rectification",
51152
+ sessionRole: "implementer",
51153
+ signal: runtime.signal,
51154
+ maxTurns: config2.agent?.maxInteractionTurns
51155
+ });
51156
+ rectifyResult = {
51157
+ success: true,
51158
+ exitCode: 0,
51159
+ output: turn.output,
51160
+ rateLimited: false,
51161
+ durationMs: 0,
51162
+ estimatedCostUsd: turn.estimatedCostUsd,
51163
+ ...turn.exactCostUsd !== undefined && { exactCostUsd: turn.exactCostUsd },
51164
+ ...turn.tokenUsage && { tokenUsage: turn.tokenUsage },
51165
+ ...heldHandle.protocolIds && { protocolIds: heldHandle.protocolIds }
51166
+ };
51167
+ break;
51168
+ } catch (err) {
51169
+ const stale = heldHandle;
51170
+ heldHandle = undefined;
51171
+ await runtime.sessionManager.closeSession(stale).catch(() => {});
51172
+ if (err instanceof SessionTurnError && err.retryable && transportRetries < maxTransportRetries) {
51173
+ transportRetries++;
51174
+ getSafeLogger()?.warn("tdd", "fail-adapter-error: same-agent retry with fresh session", {
50637
51175
  storyId: story.id,
50638
- featureName,
50639
- workdir,
50640
- projectDir,
50641
- pipelineStage: "rectification",
50642
- sessionRole: "implementer",
50643
- signal: runtime.signal,
50644
- maxTurns: config2.agent?.maxInteractionTurns
51176
+ attempt: transportRetries,
51177
+ maxAttempts: maxTransportRetries,
51178
+ retriable: true
50645
51179
  });
50646
- rectifyResult = {
50647
- success: true,
50648
- exitCode: 0,
50649
- output: turn.output,
50650
- rateLimited: false,
50651
- durationMs: 0,
50652
- estimatedCostUsd: turn.estimatedCostUsd,
50653
- ...turn.exactCostUsd !== undefined && { exactCostUsd: turn.exactCostUsd },
50654
- ...turn.tokenUsage && { tokenUsage: turn.tokenUsage },
50655
- ...heldHandle.protocolIds && { protocolIds: heldHandle.protocolIds }
50656
- };
50657
- break;
50658
- } catch (err) {
50659
- const stale = heldHandle;
50660
- heldHandle = undefined;
50661
- await runtime.sessionManager.closeSession(stale).catch(() => {});
50662
- if (err instanceof SessionTurnError && err.retryable && transportRetries < maxTransportRetries) {
50663
- transportRetries++;
50664
- getSafeLogger()?.warn("tdd", "fail-adapter-error: same-agent retry with fresh session", {
50665
- storyId: story.id,
50666
- attempt: transportRetries,
50667
- maxAttempts: maxTransportRetries,
50668
- retriable: true
50669
- });
50670
- continue;
50671
- }
50672
- throw err;
51180
+ continue;
50673
51181
  }
51182
+ throw err;
50674
51183
  }
50675
- } else {
50676
- rectifyResult = await agentManager.run({
50677
- runOptions: { ...runOptions, keepOpen: !isLastAttempt }
50678
- });
50679
51184
  }
50680
- if (sessionManager && sessionId && rectifyResult.protocolIds) {
51185
+ if (sessionManager && heldHandle && rectifyResult.protocolIds) {
50681
51186
  try {
50682
- sessionManager.bindHandle(sessionId, rectificationSessionName, rectifyResult.protocolIds);
51187
+ sessionManager.bindHandle(heldHandle.id, rectificationSessionName, rectifyResult.protocolIds);
50683
51188
  } catch {}
50684
51189
  }
50685
51190
  if (!rectifyResult.success && rectifyResult.pid) {
@@ -50745,7 +51250,7 @@ async function runRectificationLoop(story, config2, workdir, agentManager, imple
50745
51250
  };
50746
51251
  }
50747
51252
  }).finally(async () => {
50748
- if (heldHandle && runtime) {
51253
+ if (heldHandle) {
50749
51254
  const stale = heldHandle;
50750
51255
  heldHandle = undefined;
50751
51256
  await runtime.sessionManager.closeSession(stale).catch(() => {});
@@ -51236,7 +51741,7 @@ async function runThreeSessionTdd(options) {
51236
51741
  };
51237
51742
  }
51238
51743
  const implementerBinding = getTddSessionBinding?.("implementer");
51239
- const fullSuiteGate = await runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, implementerBinding?.sessionManager, implementerBinding?.sessionId, runtime);
51744
+ const fullSuiteGate = await runFullSuiteGate(story, config2, workdir, agentManager, implementerTier, lite, logger, featureName, projectDir, implementerBinding?.sessionManager, runtime);
51240
51745
  const { cost: fullSuiteGateCost, fullSuiteGatePassed } = fullSuiteGate;
51241
51746
  if (fullSuiteGate.status === "rectification-exhausted") {
51242
51747
  const failureCategory = "full-suite-gate-exhausted";
@@ -51522,6 +52027,7 @@ var init_tdd = __esm(() => {
51522
52027
  init_isolation();
51523
52028
  init_orchestrator3();
51524
52029
  init_orchestrator_ctx();
52030
+ init_rectification_gate();
51525
52031
  init_session_op();
51526
52032
  init_cleanup();
51527
52033
  init_verdict();
@@ -51936,9 +52442,9 @@ async function runAgentRectificationV2(ctx, _lintFixCmd, _formatFixCmd, _effecti
51936
52442
  maxAttemptsTotal: maxTotalAttempts,
51937
52443
  validatorRetries: 1
51938
52444
  },
51939
- async validate(_cycleCtx) {
52445
+ async validate(_cycleCtx, _opts) {
51940
52446
  iterationBeforeRef = await _autofixCycleGuardDeps.captureGitRef(ctx.workdir) ?? iterationBeforeRef;
51941
- await _autofixDeps.recheckReview(ctx);
52447
+ await _autofixDeps.recheckReview(ctx, { lite: _opts.mode === "lite" });
51942
52448
  const fresh = collectCurrentFindings(ctx);
51943
52449
  const pending = ctx.testEditDeclarations ?? [];
51944
52450
  if (pending.length === 0)
@@ -52734,7 +53240,7 @@ var init_review2 = __esm(() => {
52734
53240
  const reviewDebateEnabled = ctx.rootConfig?.debate?.enabled && ctx.rootConfig?.debate?.stages?.review?.enabled;
52735
53241
  const dialogueEnabled = ctx.config.review?.dialogue?.enabled ?? false;
52736
53242
  logger.info("review", "Running review phase", { storyId: ctx.story.id });
52737
- if (dialogueEnabled && !reviewDebateEnabled && ctx.reviewerSession) {
53243
+ if (dialogueEnabled && !reviewDebateEnabled && ctx.reviewerSession && !ctx.skipLLMReviewers) {
52738
53244
  try {
52739
53245
  const diff = ctx.storyGitRef ?? "";
52740
53246
  const reReviewResult = await ctx.reviewerSession.reReview(diff);
@@ -52769,7 +53275,7 @@ var init_review2 = __esm(() => {
52769
53275
  });
52770
53276
  }
52771
53277
  }
52772
- if (dialogueEnabled && !ctx.reviewerSession && ctx.agentManager && ctx.sessionManager) {
53278
+ if (dialogueEnabled && !ctx.reviewerSession && ctx.agentManager && ctx.sessionManager && !ctx.skipLLMReviewers) {
52773
53279
  ctx.reviewerSession = _reviewDeps.createReviewerSession(ctx.agentManager, ctx.sessionManager, ctx.story.id, ctx.workdir, ctx.prd.feature ?? "", ctx.config);
52774
53280
  if (!reviewDebateEnabled) {
52775
53281
  const semanticConfig = ctx.config.review?.semantic;
@@ -52873,16 +53379,36 @@ var init_review2 = __esm(() => {
52873
53379
  });
52874
53380
 
52875
53381
  // src/pipeline/stages/autofix.ts
52876
- async function recheckReview(ctx) {
53382
+ async function recheckReview(ctx, opts) {
53383
+ const lite = opts?.lite === true;
53384
+ if (lite) {
53385
+ const origSkipChecks = ctx.retrySkipChecks;
53386
+ const origSkipLLMReviewers = ctx.skipLLMReviewers;
53387
+ ctx.retrySkipChecks = new Set([...ctx.retrySkipChecks ?? [], "adversarial", "semantic"]);
53388
+ ctx.skipLLMReviewers = true;
53389
+ try {
53390
+ await _autofixDeps.runReviewStage(ctx);
53391
+ } finally {
53392
+ ctx.retrySkipChecks = origSkipChecks;
53393
+ ctx.skipLLMReviewers = origSkipLLMReviewers;
53394
+ }
53395
+ return ctx.reviewResult?.success === true;
53396
+ }
52877
53397
  const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review2(), exports_review));
52878
53398
  if (!reviewStage2.enabled(ctx))
52879
53399
  return true;
52880
- await reviewStage2.execute(ctx);
53400
+ await _autofixDeps.runReviewStage(ctx);
52881
53401
  const hasFailOpen = (ctx.reviewResult?.checks ?? []).some((c) => c.failOpen);
52882
53402
  if (hasFailOpen)
52883
53403
  return false;
52884
53404
  return ctx.reviewResult?.success === true;
52885
53405
  }
53406
+ async function runReviewStage(ctx) {
53407
+ const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review2(), exports_review));
53408
+ if (!reviewStage2.enabled(ctx))
53409
+ return;
53410
+ await reviewStage2.execute(ctx);
53411
+ }
52886
53412
  async function runMechanicalFixes(ctx, failedCheckNames) {
52887
53413
  const commands = resolveMechanicalFixCommands(ctx, failedCheckNames);
52888
53414
  for (const resolved of commands) {
@@ -53146,6 +53672,7 @@ var init_autofix = __esm(() => {
53146
53672
  runQualityCommand,
53147
53673
  recheckReview,
53148
53674
  runAgentRectification: runAgentRectificationV2,
53675
+ runReviewStage,
53149
53676
  runTestWriterRectification: (ctx, testWriterChecks, story, agentManager) => runTestWriterRectification(ctx, testWriterChecks, story, agentManager)
53150
53677
  };
53151
53678
  });
@@ -54780,455 +55307,6 @@ var init_queue_check = __esm(() => {
54780
55307
  };
54781
55308
  });
54782
55309
 
54783
- // src/execution/escalation/escalation.ts
54784
- function escalateTier(currentTier, tierOrder) {
54785
- const getName = (t) => t.tier ?? t.name ?? null;
54786
- const currentIndex = tierOrder.findIndex((t) => getName(t) === currentTier);
54787
- if (currentIndex === -1 || currentIndex === tierOrder.length - 1) {
54788
- return null;
54789
- }
54790
- const next = tierOrder[currentIndex + 1];
54791
- const nextName = getName(next);
54792
- if (!nextName)
54793
- return null;
54794
- return { tier: nextName, agent: next.agent };
54795
- }
54796
- function calculateMaxIterations(tierOrder) {
54797
- return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
54798
- }
54799
-
54800
- // src/verification/rectification-loop.ts
54801
- async function _defaultRunDebate(storyId, stageConfig, prompt, config2, agentManager) {
54802
- const logger = getSafeLogger();
54803
- const debaters = stageConfig.debaters ?? [];
54804
- const manager = agentManager;
54805
- const resolved = [];
54806
- for (const debater of debaters) {
54807
- if (manager.getAgent(debater.agent)) {
54808
- resolved.push({ debater, agentName: debater.agent });
54809
- }
54810
- }
54811
- if (resolved.length === 0) {
54812
- return { output: null, totalCostUsd: 0 };
54813
- }
54814
- const timeoutMs = (config2.execution?.sessionTimeoutSeconds ?? 600) * 1000;
54815
- const defaultAgentName = resolveDefaultAgent(config2);
54816
- const resolvedDebaters = resolved.map(({ debater, agentName }) => {
54817
- let modelDef;
54818
- try {
54819
- modelDef = resolveConfiguredModel(config2.models, agentName, debater.model ?? "balanced", defaultAgentName).modelDef;
54820
- } catch {
54821
- modelDef = { provider: "unknown", model: debater.model ?? "default" };
54822
- }
54823
- return { debater, agentName, modelDef };
54824
- });
54825
- const startMs = Date.now();
54826
- const proposalSettled = await Promise.allSettled(resolvedDebaters.map(({ agentName, modelDef }) => manager.completeAs(agentName, prompt, {
54827
- modelDef,
54828
- workdir: "",
54829
- storyId,
54830
- sessionRole: "debate-proposal",
54831
- timeoutMs
54832
- }).then((out) => typeof out === "string" ? out : out.output)));
54833
- const durationMs = Date.now() - startMs;
54834
- const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
54835
- if (successful.length === 0) {
54836
- return { output: null, totalCostUsd: 0 };
54837
- }
54838
- const successCount = successful.length;
54839
- const costPerDebater = estimateCostByDuration("balanced", durationMs / successCount);
54840
- const totalCostUsd = costPerDebater.cost * successCount;
54841
- logger?.debug("rectification", "debate diagnosis complete", { storyId, successCount, totalCostUsd });
54842
- const output = successful.join(`
54843
-
54844
- `);
54845
- return { output, totalCostUsd };
54846
- }
54847
- async function runRectificationLoop2(opts) {
54848
- const loopStartMs = Date.now();
54849
- const {
54850
- config: config2,
54851
- workdir,
54852
- story,
54853
- testCommand,
54854
- timeoutSeconds,
54855
- testOutput,
54856
- promptPrefix,
54857
- featureName,
54858
- projectDir,
54859
- testScopedTemplate,
54860
- sessionManager,
54861
- sessionId,
54862
- runtime
54863
- } = opts;
54864
- const logger = getSafeLogger();
54865
- const agentManager = opts.agentManager ?? _rectificationDeps.agentManager;
54866
- if (!agentManager) {
54867
- logger?.warn("rectification", "No agentManager threaded \u2014 skipping rectification loop", {
54868
- storyId: story.id
54869
- });
54870
- return { succeeded: false, cost: 0, durationMs: 0 };
54871
- }
54872
- const rectificationConfig = config2.execution.rectification;
54873
- const testSummary = parseTestOutput(testOutput);
54874
- const label = promptPrefix ? "regression rectification" : "rectification";
54875
- const rectificationSessionName = formatSessionName({
54876
- workdir,
54877
- featureName,
54878
- storyId: story.id,
54879
- role: "implementer"
54880
- });
54881
- let costAccum = 0;
54882
- let currentAttempt = 0;
54883
- let heldHandle;
54884
- const initialFailure = {
54885
- testOutput,
54886
- testSummary
54887
- };
54888
- const outcome = await runRetryLoop({
54889
- stage: "rectification",
54890
- storyId: story.id,
54891
- packageDir: workdir,
54892
- maxAttempts: rectificationConfig.maxRetries,
54893
- failure: initialFailure,
54894
- previousAttempts: [],
54895
- buildPrompt: (failure) => {
54896
- currentAttempt++;
54897
- const diagnosisPrefix = null;
54898
- const debateStageConfig = config2.debate?.stages?.rectification;
54899
- let debatePromise = Promise.resolve(null);
54900
- if (debateStageConfig?.enabled) {
54901
- const failureSummary = formatFailureSummary(failure.testSummary.failures);
54902
- const diagnosisPrompt = `Analyze the following test failures and identify the root cause:
54903
-
54904
- ${failureSummary}`;
54905
- debatePromise = (async () => {
54906
- try {
54907
- const debateResult = await _rectificationDeps.runDebate(story.id, debateStageConfig, diagnosisPrompt, config2, agentManager);
54908
- if (debateResult.totalCostUsd > 0 && story.routing) {
54909
- story.routing.estimatedCostUsd = (story.routing.estimatedCostUsd ?? 0) + debateResult.totalCostUsd;
54910
- }
54911
- if (debateResult.output !== null) {
54912
- return `## Root Cause Analysis
54913
-
54914
- ${debateResult.output}`;
54915
- }
54916
- logger?.info("rectification", "debate diagnosis fallback \u2014 all debaters failed", {
54917
- storyId: story.id,
54918
- event: "fallback"
54919
- });
54920
- return null;
54921
- } catch (_error) {
54922
- logger?.info("rectification", "debate diagnosis fallback \u2014 debate threw error", {
54923
- storyId: story.id,
54924
- event: "fallback"
54925
- });
54926
- return null;
54927
- }
54928
- })();
54929
- }
54930
- const failureRecords = buildFailureRecords(failure.testSummary, failure.testOutput);
54931
- const rectPrompt = RectifierPromptBuilder.regressionFailure({
54932
- story,
54933
- failures: failureRecords,
54934
- testCommand,
54935
- conventions: true
54936
- });
54937
- const rectPromise = Promise.resolve(rectPrompt);
54938
- return (async () => {
54939
- const [diagnosis, rectificationPrompt] = await Promise.all([debatePromise, rectPromise]);
54940
- let finalPrompt = rectificationPrompt;
54941
- if (diagnosis) {
54942
- finalPrompt = `${diagnosis}
54943
-
54944
- ${finalPrompt}`;
54945
- }
54946
- if (promptPrefix) {
54947
- finalPrompt = `${promptPrefix}
54948
-
54949
- ${finalPrompt}`;
54950
- }
54951
- return finalPrompt;
54952
- })();
54953
- },
54954
- execute: async (prompt) => {
54955
- const defaultAgent = agentManager.getDefault();
54956
- const complexity = story.routing?.complexity ?? "medium";
54957
- const modelTier = config2.autoMode.complexityRouting?.[complexity] || config2.autoMode.escalation.tierOrder[0]?.tier || "balanced";
54958
- const modelDef = resolveModelForAgent(config2.models, story.routing?.agent ?? defaultAgent, modelTier, defaultAgent);
54959
- const isLastAttempt = currentAttempt >= rectificationConfig.maxRetries;
54960
- const runOptions = {
54961
- prompt,
54962
- workdir,
54963
- modelTier,
54964
- modelDef,
54965
- timeoutSeconds: config2.execution.sessionTimeoutSeconds,
54966
- pipelineStage: "rectification",
54967
- config: config2,
54968
- projectDir,
54969
- maxInteractionTurns: config2.agent?.maxInteractionTurns,
54970
- featureName,
54971
- storyId: story.id,
54972
- sessionRole: "implementer"
54973
- };
54974
- let agentResult;
54975
- if (runtime) {
54976
- let transportRetries = 0;
54977
- const maxTransportRetries = config2.execution?.sessionErrorRetryableMaxRetries ?? 3;
54978
- while (true) {
54979
- if (!heldHandle) {
54980
- heldHandle = await runtime.sessionManager.openSession(rectificationSessionName, {
54981
- agentName: defaultAgent,
54982
- role: "implementer",
54983
- workdir,
54984
- pipelineStage: "rectification",
54985
- modelDef,
54986
- timeoutSeconds: config2.execution.sessionTimeoutSeconds,
54987
- featureName,
54988
- storyId: story.id,
54989
- signal: runtime.signal
54990
- });
54991
- }
54992
- try {
54993
- const turn = await agentManager.runAsSession(defaultAgent, heldHandle, prompt, {
54994
- storyId: story.id,
54995
- featureName,
54996
- workdir,
54997
- projectDir,
54998
- pipelineStage: "rectification",
54999
- sessionRole: "implementer",
55000
- signal: runtime.signal,
55001
- maxTurns: config2.agent?.maxInteractionTurns
55002
- });
55003
- agentResult = {
55004
- success: true,
55005
- exitCode: 0,
55006
- output: turn.output,
55007
- rateLimited: false,
55008
- durationMs: 0,
55009
- estimatedCostUsd: turn.estimatedCostUsd,
55010
- ...turn.exactCostUsd !== undefined && { exactCostUsd: turn.exactCostUsd },
55011
- ...turn.tokenUsage && { tokenUsage: turn.tokenUsage },
55012
- ...heldHandle.protocolIds && { protocolIds: heldHandle.protocolIds }
55013
- };
55014
- break;
55015
- } catch (err) {
55016
- const stale = heldHandle;
55017
- heldHandle = undefined;
55018
- await runtime.sessionManager.closeSession(stale).catch(() => {});
55019
- if (err instanceof SessionTurnError && err.retryable && transportRetries < maxTransportRetries) {
55020
- transportRetries++;
55021
- getSafeLogger()?.warn("rectification", "fail-adapter-error: same-agent retry with fresh session", {
55022
- storyId: story.id,
55023
- attempt: transportRetries,
55024
- maxAttempts: maxTransportRetries,
55025
- retriable: true
55026
- });
55027
- continue;
55028
- }
55029
- throw err;
55030
- }
55031
- }
55032
- } else {
55033
- agentResult = await agentManager.run({
55034
- runOptions: { ...runOptions, keepOpen: !isLastAttempt }
55035
- });
55036
- }
55037
- costAccum += agentResult.estimatedCostUsd ?? 0;
55038
- if (sessionManager && sessionId && agentResult.protocolIds) {
55039
- try {
55040
- sessionManager.bindHandle(sessionId, rectificationSessionName, agentResult.protocolIds);
55041
- } catch {}
55042
- }
55043
- if (agentResult.success) {
55044
- logger?.info("rectification", `Agent ${label} session complete`, {
55045
- storyId: story.id,
55046
- cost: agentResult.estimatedCostUsd
55047
- });
55048
- } else {
55049
- logger?.warn("rectification", `Agent ${label} session failed`, {
55050
- storyId: story.id,
55051
- exitCode: agentResult.exitCode
55052
- });
55053
- }
55054
- return {
55055
- agentSuccess: agentResult.success,
55056
- cost: agentResult.estimatedCostUsd ?? 0,
55057
- protocolIds: agentResult.protocolIds
55058
- };
55059
- },
55060
- shouldAbort: (failure) => {
55061
- if (rectificationConfig.abortOnIncreasingFailures) {
55062
- return failure.testSummary.failed > initialFailure.testSummary.failed;
55063
- }
55064
- return false;
55065
- },
55066
- verify: async (result) => {
55067
- const retryVerification = await _rectificationDeps.runVerification({
55068
- workdir,
55069
- expectedFiles: getExpectedFiles(story),
55070
- command: testCommand,
55071
- timeoutSeconds,
55072
- forceExit: config2.quality.forceExit,
55073
- detectOpenHandles: config2.quality.detectOpenHandles,
55074
- detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
55075
- timeoutRetryCount: 0,
55076
- gracePeriodMs: config2.quality.gracePeriodMs,
55077
- drainTimeoutMs: config2.quality.drainTimeoutMs,
55078
- shell: config2.quality.shell,
55079
- stripEnvVars: config2.quality.stripEnvVars
55080
- });
55081
- if (retryVerification.success) {
55082
- logger?.info("rectification", `[OK] ${label} succeeded!`, {
55083
- storyId: story.id,
55084
- attempt: currentAttempt,
55085
- initialFailures: initialFailure.testSummary.failed
55086
- });
55087
- return { passed: true };
55088
- }
55089
- if (retryVerification.output) {
55090
- const newTestSummary = parseTestOutput(retryVerification.output);
55091
- if (newTestSummary.failed === 0 && retryVerification.status !== "ENVIRONMENTAL_FAILURE" && (retryVerification.status === "SUCCESS" || newTestSummary.passed > 0)) {
55092
- logger?.info("rectification", `[OK] ${label} succeeded after parsing retry output`, {
55093
- storyId: story.id,
55094
- attempt: currentAttempt,
55095
- initialFailures: initialFailure.testSummary.failed
55096
- });
55097
- return { passed: true };
55098
- }
55099
- const failingTests = newTestSummary.failures.slice(0, 10).map((failure) => failure.testName);
55100
- const logData = {
55101
- storyId: story.id,
55102
- attempt: currentAttempt,
55103
- remainingFailures: newTestSummary.failed,
55104
- failingTests
55105
- };
55106
- if (newTestSummary.failures.length > 10 || newTestSummary.failures.length === 0 && newTestSummary.failed > 0) {
55107
- logData.totalFailingTests = newTestSummary.failed;
55108
- }
55109
- logger?.warn("rectification", `${label} still failing after attempt`, logData);
55110
- return {
55111
- passed: false,
55112
- newFailure: {
55113
- testOutput: retryVerification.output,
55114
- testSummary: newTestSummary
55115
- }
55116
- };
55117
- }
55118
- return {
55119
- passed: false,
55120
- newFailure: {
55121
- testOutput: retryVerification.output ?? "",
55122
- testSummary: initialFailure.testSummary
55123
- }
55124
- };
55125
- }
55126
- }).finally(async () => {
55127
- if (heldHandle && runtime) {
55128
- const stale = heldHandle;
55129
- heldHandle = undefined;
55130
- await runtime.sessionManager.closeSession(stale).catch(() => {});
55131
- }
55132
- });
55133
- const succeeded = outcome.outcome === "fixed";
55134
- if (outcome.outcome === "exhausted") {
55135
- const finalFailure = outcome.finalFailure;
55136
- const shouldEscalate = rectificationConfig.escalateOnExhaustion !== false && config2.autoMode?.escalation?.enabled === true && finalFailure.testSummary.failed > 0;
55137
- if (shouldEscalate) {
55138
- const complexity = story.routing?.complexity ?? "medium";
55139
- const currentTier = config2.autoMode.complexityRouting?.[complexity] || config2.autoMode.escalation.tierOrder[0]?.tier || "balanced";
55140
- const tierOrder = config2.autoMode.escalation.tierOrder;
55141
- const escalationResult = _rectificationDeps.escalateTier(currentTier, tierOrder);
55142
- const escalatedTier = escalationResult?.tier ?? null;
55143
- const escalatedAgent = escalationResult?.agent;
55144
- if (escalatedTier !== null) {
55145
- const escalationManager = agentManager;
55146
- const agentName = escalatedAgent ?? story.routing?.agent ?? escalationManager.getDefault();
55147
- if (escalationManager.getAgent(agentName)) {
55148
- const escalatedModelDef = resolveModelForAgent(config2.models, agentName, escalatedTier, escalationManager.getDefault());
55149
- let escalationPrompt = RectifierPromptBuilder.escalated(finalFailure.testSummary.failures, story, outcome.attempts, currentTier, escalatedTier, rectificationConfig, testCommand, testScopedTemplate);
55150
- if (promptPrefix) {
55151
- escalationPrompt = `${promptPrefix}
55152
-
55153
- ${escalationPrompt}`;
55154
- }
55155
- const escalationRunResult = await escalationManager.runAs(agentName, {
55156
- runOptions: {
55157
- prompt: escalationPrompt,
55158
- workdir,
55159
- modelTier: escalatedTier,
55160
- modelDef: escalatedModelDef,
55161
- timeoutSeconds: config2.execution.sessionTimeoutSeconds,
55162
- pipelineStage: "rectification",
55163
- config: config2,
55164
- projectDir,
55165
- maxInteractionTurns: config2.agent?.maxInteractionTurns,
55166
- featureName,
55167
- storyId: story.id,
55168
- sessionRole: "implementer"
55169
- }
55170
- });
55171
- costAccum += escalationRunResult.estimatedCostUsd ?? 0;
55172
- logger?.info("rectification", "escalated rectification attempt cost", {
55173
- storyId: story.id,
55174
- escalatedTier,
55175
- cost: escalationRunResult.estimatedCostUsd
55176
- });
55177
- const escalationVerification = await _rectificationDeps.runVerification({
55178
- workdir,
55179
- expectedFiles: getExpectedFiles(story),
55180
- command: testCommand,
55181
- timeoutSeconds,
55182
- forceExit: config2.quality.forceExit,
55183
- detectOpenHandles: config2.quality.detectOpenHandles,
55184
- detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
55185
- timeoutRetryCount: 0,
55186
- gracePeriodMs: config2.quality.gracePeriodMs,
55187
- drainTimeoutMs: config2.quality.drainTimeoutMs,
55188
- shell: config2.quality.shell,
55189
- stripEnvVars: config2.quality.stripEnvVars
55190
- });
55191
- if (escalationVerification.success) {
55192
- logger?.info("rectification", `${label} escalated from ${currentTier} to ${escalatedTier} and succeeded`, {
55193
- storyId: story.id,
55194
- currentTier,
55195
- escalatedTier
55196
- });
55197
- return { succeeded: true, cost: costAccum, durationMs: Date.now() - loopStartMs };
55198
- }
55199
- logger?.warn("rectification", "escalated rectification also failed", { storyId: story.id, escalatedTier });
55200
- }
55201
- }
55202
- }
55203
- logger?.warn("rectification", `${label} exhausted max retries`, {
55204
- storyId: story.id,
55205
- attempts: outcome.attempts,
55206
- remainingFailures: initialFailure.testSummary.failed
55207
- });
55208
- }
55209
- return { succeeded, cost: costAccum, durationMs: Date.now() - loopStartMs };
55210
- }
55211
- var _rectificationDeps;
55212
- var init_rectification_loop = __esm(() => {
55213
- init_agents();
55214
- init_cost();
55215
- init_types3();
55216
- init_config();
55217
- init_logger2();
55218
- init_prd();
55219
- init_prompts();
55220
- init_naming();
55221
- init_parser2();
55222
- init_parser2();
55223
- init_runners();
55224
- _rectificationDeps = {
55225
- agentManager: undefined,
55226
- runVerification: fullSuite,
55227
- escalateTier,
55228
- runDebate: _defaultRunDebate
55229
- };
55230
- });
55231
-
55232
55310
  // src/pipeline/stages/rectify.ts
55233
55311
  var rectifyStage, _rectifyDeps;
55234
55312
  var init_rectify2 = __esm(() => {
@@ -55327,7 +55405,7 @@ var init_rectify2 = __esm(() => {
55327
55405
  }
55328
55406
  };
55329
55407
  _rectifyDeps = {
55330
- runRectificationLoop: (ctx, opts) => runRectificationLoop2({
55408
+ runRectificationLoop: (ctx, opts) => runRectificationLoop({
55331
55409
  config: ctx.config,
55332
55410
  workdir: ctx.workdir,
55333
55411
  story: ctx.story,
@@ -55339,7 +55417,7 @@ var init_rectify2 = __esm(() => {
55339
55417
  agentManager: ctx.agentManager,
55340
55418
  projectDir: ctx.projectDir,
55341
55419
  testScopedTemplate: opts.testScopedTemplate,
55342
- sessionManager: ctx.sessionManager,
55420
+ runtime: ctx.runtime,
55343
55421
  sessionId: ctx.sessionId
55344
55422
  }),
55345
55423
  resolveTestCommands: resolveQualityTestCommands,
@@ -56190,7 +56268,7 @@ async function promptsInitCommand(options) {
56190
56268
  mkdirSync4(templatesDir, { recursive: true });
56191
56269
  const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join50(templatesDir, f)));
56192
56270
  if (existingFiles.length > 0 && !force) {
56193
- console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
56271
+ _promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
56194
56272
  Pass --force to overwrite existing templates.`);
56195
56273
  return [];
56196
56274
  }
@@ -56202,9 +56280,9 @@ async function promptsInitCommand(options) {
56202
56280
  await Bun.write(filePath, content);
56203
56281
  written.push(filePath);
56204
56282
  }
56205
- console.log(`[OK] Written ${written.length} template files to nax/templates/:`);
56283
+ _promptsInitDeps.log(`[OK] Written ${written.length} template files to nax/templates/:`);
56206
56284
  for (const filePath of written) {
56207
- console.log(` - ${filePath.replace(`${workdir}/`, "")}`);
56285
+ _promptsInitDeps.log(` - ${filePath.replace(`${workdir}/`, "")}`);
56208
56286
  }
56209
56287
  if (autoWireConfig) {
56210
56288
  await autoWirePromptsConfig(workdir);
@@ -56225,7 +56303,7 @@ async function autoWirePromptsConfig(workdir) {
56225
56303
  }
56226
56304
  }
56227
56305
  }, null, 2);
56228
- console.log(`
56306
+ _promptsInitDeps.log(`
56229
56307
  No nax.config.json found. To activate overrides, create nax/config.json with:
56230
56308
  ${exampleConfig}`);
56231
56309
  return;
@@ -56234,7 +56312,7 @@ ${exampleConfig}`);
56234
56312
  const configContent = await configFile.text();
56235
56313
  const config2 = JSON.parse(configContent);
56236
56314
  if (config2.prompts?.overrides && Object.keys(config2.prompts.overrides).length > 0) {
56237
- console.log(`[INFO] prompts.overrides already configured in nax.config.json. Skipping auto-wiring.
56315
+ _promptsInitDeps.log(`[INFO] prompts.overrides already configured in nax.config.json. Skipping auto-wiring.
56238
56316
  ` + " To reset overrides, remove the prompts.overrides section and re-run this command.");
56239
56317
  return;
56240
56318
  }
@@ -56251,7 +56329,7 @@ ${exampleConfig}`);
56251
56329
  config2.prompts.overrides = overrides;
56252
56330
  const updatedConfig = formatConfigJson(config2);
56253
56331
  await Bun.write(configPath, updatedConfig);
56254
- console.log("[OK] Auto-wired prompts.overrides in nax.config.json");
56332
+ _promptsInitDeps.log("[OK] Auto-wired prompts.overrides in nax.config.json");
56255
56333
  }
56256
56334
  function formatConfigJson(config2) {
56257
56335
  const lines = ["{"];
@@ -56276,7 +56354,7 @@ function formatConfigJson(config2) {
56276
56354
  return lines.join(`
56277
56355
  `);
56278
56356
  }
56279
- var TEMPLATE_ROLES, TEMPLATE_HEADER = `<!--
56357
+ var _promptsInitDeps, TEMPLATE_ROLES, TEMPLATE_HEADER = `<!--
56280
56358
  This file controls the role-body section of the nax prompt for this role.
56281
56359
  Edit the content below to customize the task instructions given to the agent.
56282
56360
 
@@ -56292,6 +56370,10 @@ var TEMPLATE_ROLES, TEMPLATE_HEADER = `<!--
56292
56370
  `;
56293
56371
  var init_prompts_init = __esm(() => {
56294
56372
  init_role_task();
56373
+ _promptsInitDeps = {
56374
+ log: console.log.bind(console),
56375
+ warn: console.warn.bind(console)
56376
+ };
56295
56377
  TEMPLATE_ROLES = [
56296
56378
  { file: "test-writer.md", role: "test-writer" },
56297
56379
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -56878,7 +56960,8 @@ __export(exports_init, {
56878
56960
  validateProjectName: () => validateProjectName,
56879
56961
  initProject: () => initProject,
56880
56962
  initCommand: () => initCommand,
56881
- checkInitCollision: () => checkInitCollision
56963
+ checkInitCollision: () => checkInitCollision,
56964
+ _initDeps: () => _initDeps
56882
56965
  });
56883
56966
  import { existsSync as existsSync24 } from "fs";
56884
56967
  import { mkdir as mkdir10 } from "fs/promises";
@@ -57105,20 +57188,20 @@ async function initProject(projectRoot, options) {
57105
57188
  }
57106
57189
  await updateGitignore(projectRoot);
57107
57190
  await promptsInitCommand({ workdir: projectRoot, force: false, autoWireConfig: false });
57108
- console.log(`
57191
+ _initDeps.log(`
57109
57192
  [OK] nax init complete. Created files:`);
57110
- console.log(" - .nax/config.json");
57111
- console.log(" - .nax/context.md");
57112
- console.log(" - .nax/constitution.md");
57113
- console.log(" - .nax/hooks/");
57114
- console.log(" - .nax/templates/");
57115
- console.log(`
57193
+ _initDeps.log(" - .nax/config.json");
57194
+ _initDeps.log(" - .nax/context.md");
57195
+ _initDeps.log(" - .nax/constitution.md");
57196
+ _initDeps.log(" - .nax/hooks/");
57197
+ _initDeps.log(" - .nax/templates/");
57198
+ _initDeps.log(`
57116
57199
  Next steps:`);
57117
- console.log(" 1. Review .nax/context.md and fill in TODOs");
57118
- console.log(" 2. Review .nax/config.json and adjust quality commands");
57119
- console.log(" 3. Run: nax generate");
57120
- console.log(" 4. Run: nax plan");
57121
- console.log(" 5. Run: nax run");
57200
+ _initDeps.log(" 1. Review .nax/context.md and fill in TODOs");
57201
+ _initDeps.log(" 2. Review .nax/config.json and adjust quality commands");
57202
+ _initDeps.log(" 3. Run: nax generate");
57203
+ _initDeps.log(" 4. Run: nax plan");
57204
+ _initDeps.log(" 5. Run: nax run");
57122
57205
  logger.info("init", "Project config initialized successfully", { path: projectDir });
57123
57206
  }
57124
57207
  async function initCommand(options = {}) {
@@ -57127,19 +57210,19 @@ async function initCommand(options = {}) {
57127
57210
  } else if (options.package) {
57128
57211
  const projectRoot = options.projectRoot ?? process.cwd();
57129
57212
  await initPackage(projectRoot, options.package);
57130
- console.log(`
57213
+ _initDeps.log(`
57131
57214
  [OK] Package scaffold created.`);
57132
- console.log(` Created: .nax/mono/${options.package}/context.md`);
57133
- console.log(`
57215
+ _initDeps.log(` Created: .nax/mono/${options.package}/context.md`);
57216
+ _initDeps.log(`
57134
57217
  Next steps:`);
57135
- console.log(` 1. Review .nax/mono/${options.package}/context.md and fill in TODOs`);
57136
- console.log(` 2. Run: nax generate --package ${options.package}`);
57218
+ _initDeps.log(` 1. Review .nax/mono/${options.package}/context.md and fill in TODOs`);
57219
+ _initDeps.log(` 2. Run: nax generate --package ${options.package}`);
57137
57220
  } else {
57138
57221
  const projectRoot = options.projectRoot ?? process.cwd();
57139
57222
  await initProject(projectRoot, { name: options.name, force: options.force });
57140
57223
  }
57141
57224
  }
57142
- var MINIMAL_GLOBAL_CONFIG;
57225
+ var _initDeps, MINIMAL_GLOBAL_CONFIG;
57143
57226
  var init_init2 = __esm(() => {
57144
57227
  init_paths();
57145
57228
  init_errors();
@@ -57149,6 +57232,9 @@ var init_init2 = __esm(() => {
57149
57232
  init_init_context();
57150
57233
  init_init_detect();
57151
57234
  init_prompts2();
57235
+ _initDeps = {
57236
+ log: console.log.bind(console)
57237
+ };
57152
57238
  MINIMAL_GLOBAL_CONFIG = {
57153
57239
  version: 1
57154
57240
  };
@@ -58789,7 +58875,7 @@ var package_default;
58789
58875
  var init_package = __esm(() => {
58790
58876
  package_default = {
58791
58877
  name: "@nathapp/nax",
58792
- version: "0.67.0-canary.3",
58878
+ version: "0.67.0-canary.5",
58793
58879
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
58794
58880
  type: "module",
58795
58881
  bin: {
@@ -58799,7 +58885,7 @@ var init_package = __esm(() => {
58799
58885
  prepare: "git config core.hooksPath .githooks",
58800
58886
  dev: "bun run bin/nax.ts",
58801
58887
  build: 'bun build bin/nax.ts --outdir dist --target bun --define "GIT_COMMIT=\\"$(git rev-parse --short HEAD)\\""',
58802
- typecheck: "bun x tsc --noEmit",
58888
+ typecheck: "bun x tsc --noEmit && bun x tsc --noEmit -p tsconfig.contracts.json",
58803
58889
  lint: "bun x biome check src/ bin/ && bun run check:no-real-global-nax && bun run check:alias-internals && bun run check:deep-relatives && bun run check:nax-error && bun run check:logger-storyid",
58804
58890
  "lint:json": "bun x biome check src/ bin/ --reporter json && bun run check:nax-error 1>&2 && bun run check:logger-storyid 1>&2",
58805
58891
  "lint:fix": "bun x biome check --write src/ bin/",
@@ -58812,8 +58898,10 @@ var init_package = __esm(() => {
58812
58898
  "check:logger-storyid": "bun run scripts/check-logger-storyid.ts",
58813
58899
  "check:logger-storyid:update": "bun run scripts/check-logger-storyid.ts --update-baseline",
58814
58900
  release: "bun scripts/release.ts",
58815
- test: "bun run scripts/run-tests.ts",
58816
- "test:bail": "bun run scripts/run-tests.ts --bail",
58901
+ test: "AGENT=1 bun run scripts/run-tests.ts",
58902
+ "test:verbose": "bun run scripts/run-tests.ts",
58903
+ "test:bail": "AGENT=1 bun run scripts/run-tests.ts --bail",
58904
+ "test:bail-verbose": "bun run scripts/run-tests.ts --bail",
58817
58905
  "test:no-wrap": "bun test test/unit/ --timeout=5000 && bun test test/integration/ --timeout=5000 && bun test test/ui/ --timeout=5000",
58818
58906
  "test:watch": "bun test --watch",
58819
58907
  "test:unit": "bun test ./test/unit/ --timeout=60000",
@@ -58882,8 +58970,8 @@ var init_version = __esm(() => {
58882
58970
  NAX_VERSION = package_default.version;
58883
58971
  NAX_COMMIT = (() => {
58884
58972
  try {
58885
- if (/^[0-9a-f]{6,10}$/.test("1ca4b2e1"))
58886
- return "1ca4b2e1";
58973
+ if (/^[0-9a-f]{6,10}$/.test("9cf63261"))
58974
+ return "9cf63261";
58887
58975
  } catch {}
58888
58976
  try {
58889
58977
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -59607,7 +59695,7 @@ async function runAcceptanceFixCycle(ctx, prd, initialFailures, diagnosis, accep
59607
59695
  coRun: "co-run-sequential"
59608
59696
  }
59609
59697
  ],
59610
- validate: async (_ctx) => {
59698
+ validate: async (_ctx, _opts) => {
59611
59699
  const result = await runAcceptanceTestsOnce(ctx, prd);
59612
59700
  if (result.passed)
59613
59701
  return [];
@@ -59825,7 +59913,7 @@ async function findResponsibleStory(testFile, workdir, passedStories) {
59825
59913
  }
59826
59914
  async function runDeferredRegression(options) {
59827
59915
  const logger = getSafeLogger();
59828
- const { config: config2, prd, workdir, agentManager } = options;
59916
+ const { config: config2, prd, workdir, runtime } = options;
59829
59917
  const regressionMode = config2.execution.regressionGate?.mode ?? "deferred";
59830
59918
  if (regressionMode === "disabled") {
59831
59919
  logger?.info("regression", "Deferred regression gate disabled");
@@ -60015,8 +60103,9 @@ async function runDeferredRegression(options) {
60015
60103
  promptPrefix: `# DEFERRED REGRESSION: Full-Suite Failures
60016
60104
 
60017
60105
  Your story ${story.id} broke tests in the full suite. Fix these regressions.`,
60018
- agentManager,
60019
- featureName: prd.feature
60106
+ agentManager: runtime.agentManager,
60107
+ featureName: prd.feature,
60108
+ runtime
60020
60109
  });
60021
60110
  storyCostAccum[story.id] = (storyCostAccum[story.id] ?? 0) + rectResult.cost;
60022
60111
  storyDurationAccum[story.id] = (storyDurationAccum[story.id] ?? 0) + rectResult.durationMs;
@@ -60095,7 +60184,7 @@ var init_run_regression = __esm(() => {
60095
60184
  init_runners();
60096
60185
  _regressionDeps = {
60097
60186
  runVerification: fullSuite,
60098
- runRectificationLoop: runRectificationLoop2,
60187
+ runRectificationLoop,
60099
60188
  parseTestOutput
60100
60189
  };
60101
60190
  });
@@ -60149,7 +60238,7 @@ async function handleRunCompletion(options) {
60149
60238
  config: config2,
60150
60239
  prd,
60151
60240
  workdir,
60152
- agentManager: options.agentManager
60241
+ runtime: options.runtime
60153
60242
  });
60154
60243
  const lastRunAt = new Date().toISOString();
60155
60244
  logger?.info("regression", "Deferred regression gate completed", {
@@ -98394,6 +98483,7 @@ async function extractRunSummary(filePath) {
98394
98483
 
98395
98484
  // src/commands/logs-formatter.ts
98396
98485
  var LOG_LEVEL_PRIORITY2 = {
98486
+ silent: -1,
98397
98487
  debug: 0,
98398
98488
  info: 1,
98399
98489
  warn: 2,
@@ -99277,6 +99367,13 @@ init_helpers();
99277
99367
  init_crash_recovery();
99278
99368
  init_pid_registry();
99279
99369
 
99370
+ // src/execution/lifecycle/index.ts
99371
+ init_acceptance_loop();
99372
+ init_headless_formatter();
99373
+ init_run_completion();
99374
+ init_run_cleanup();
99375
+ init_run_setup();
99376
+ init_run_regression();
99280
99377
  // bin/nax.ts
99281
99378
  init_hooks();
99282
99379
  init_logger2();