@nathapp/nax 0.67.18 → 0.68.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.
Files changed (2) hide show
  1. package/dist/nax.js +298 -369
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -16763,7 +16763,6 @@ var init_schemas_debate = __esm(() => {
16763
16763
  exports_external.object({ kind: exports_external.literal("majority-fail-closed") }),
16764
16764
  exports_external.object({ kind: exports_external.literal("majority-fail-open") }),
16765
16765
  exports_external.object({ kind: exports_external.literal("judge") }),
16766
- exports_external.object({ kind: exports_external.literal("dialogue-verdict") }),
16767
16766
  exports_external.object({
16768
16767
  kind: exports_external.literal("verifier-pick"),
16769
16768
  patch: exports_external.object({
@@ -17187,7 +17186,7 @@ var init_schemas_infra = __esm(() => {
17187
17186
  });
17188
17187
 
17189
17188
  // src/config/schemas-review.ts
17190
- var SemanticReviewConfigSchema, ReviewDialogueConfigSchema, AdversarialReviewConfigSchema, ReviewConfigSchema;
17189
+ var SemanticReviewConfigSchema, AdversarialReviewConfigSchema, ReviewConfigSchema;
17191
17190
  var init_schemas_review = __esm(() => {
17192
17191
  init_zod();
17193
17192
  init_schemas_model();
@@ -17206,11 +17205,6 @@ var init_schemas_review = __esm(() => {
17206
17205
  }),
17207
17206
  excludePatterns: exports_external.array(exports_external.string()).optional()
17208
17207
  });
17209
- ReviewDialogueConfigSchema = exports_external.object({
17210
- enabled: exports_external.boolean().default(false),
17211
- maxClarificationsPerAttempt: exports_external.number().int().min(0).max(10).default(2),
17212
- maxDialogueMessages: exports_external.number().int().min(5).max(100).default(20)
17213
- });
17214
17208
  AdversarialReviewConfigSchema = exports_external.object({
17215
17209
  model: ConfiguredModelSchema.default("balanced"),
17216
17210
  diffMode: exports_external.enum(["embedded", "ref"]).default("ref"),
@@ -17242,6 +17236,7 @@ var init_schemas_review = __esm(() => {
17242
17236
  }),
17243
17237
  audit: exports_external.object({ enabled: exports_external.boolean().default(false) }).default({ enabled: false }),
17244
17238
  blockingThreshold: exports_external.enum(["error", "warning", "info"]).default("error"),
17239
+ pluginMode: exports_external.enum(["observational", "gating"]).default("observational"),
17245
17240
  semantic: SemanticReviewConfigSchema.optional(),
17246
17241
  adversarial: AdversarialReviewConfigSchema.optional()
17247
17242
  });
@@ -17426,6 +17421,7 @@ var init_schemas3 = __esm(() => {
17426
17421
  commands: {},
17427
17422
  audit: { enabled: false },
17428
17423
  blockingThreshold: "error",
17424
+ pluginMode: "observational",
17429
17425
  semantic: {
17430
17426
  model: "balanced",
17431
17427
  diffMode: "ref",
@@ -18748,10 +18744,10 @@ function _applyLegacyReviewExecutionShim(conf, warn = (msg) => {
18748
18744
  const review = result.review ?? conf.review;
18749
18745
  if (review && typeof review === "object") {
18750
18746
  let newReview = review;
18751
- const LEGACY_PLUGIN_MODE = "pluginMode";
18752
- if (LEGACY_PLUGIN_MODE in review) {
18753
- warn("review.pluginMode is a legacy field that has been removed. Remove it from your config.");
18754
- const { [LEGACY_PLUGIN_MODE]: _pm, ...rest } = review;
18747
+ const LEGACY_PLUGIN_MODE_VALUE = "per-story";
18748
+ if ("pluginMode" in review && review.pluginMode === LEGACY_PLUGIN_MODE_VALUE) {
18749
+ warn('review.pluginMode: "per-story" is a legacy value that has been removed. Remove it from your config (or set to "observational"/"gating").');
18750
+ const { pluginMode: _pm, ...rest } = review;
18755
18751
  newReview = rest;
18756
18752
  }
18757
18753
  const dialogue = newReview.dialogue;
@@ -30228,9 +30224,6 @@ function resolvePersonas(debaters, stage, autoPersona) {
30228
30224
  return { ...d, persona: assigned };
30229
30225
  });
30230
30226
  }
30231
- function buildDebaterLabel(debater) {
30232
- return debater.persona ? `${debater.agent} (${debater.persona})` : debater.agent;
30233
- }
30234
30227
  var PERSONA_FRAGMENTS, PLAN_ROTATION, REVIEW_ROTATION;
30235
30228
  var init_personas = __esm(() => {
30236
30229
  PERSONA_FRAGMENTS = {
@@ -31242,7 +31235,7 @@ ${STEP2}${frameworkLine}
31242
31235
  ${STEP3_HEADER}
31243
31236
  ${STEP3_SHARED_RULES}
31244
31237
  - **File output (REQUIRED)**: Write the acceptance test file DIRECTLY to the path shown below. Do NOT output the test code in your response. After writing the file, reply with a brief confirmation.
31245
- - **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${p.targetTestFilePath}\`. Import from package sources using relative paths like \`../../../src/...\` (3 levels up from \`.nax/features/<name>/\` to the package root).
31238
+ - **Path anchor (CRITICAL \u2014 do NOT deviate)**: Write the test file to this exact path: \`${p.targetTestFilePath}\`. This path is intentional and computed by the orchestrator \u2014 do not change it based on what you observe in the project. In particular: if you see a \`.nax/features/\` directory at the repo root, that is for stories scoped to the repo root. When a story belongs to a specific package (e.g. \`packages/core\`), its acceptance test lives inside that package's \`.nax/features/\` directory so the test runner can resolve the package's imports correctly. The package root is 3 levels above the test file (\`../../../\` relative to the test file).
31246
31239
  - **Process cwd**: When spawning child processes to invoke a CLI or binary, set the working directory to the **package root** (\`join(import.meta.dir, "../../..")\`) as your default \u2014 unless your Step 2 exploration reveals the CLI uses a different working directory convention (e.g. reads config from \`~/.config/\`, or resolves paths relative to a flag value). Always check how the CLI resolves file paths before assuming.${implSection}`;
31247
31240
  }
31248
31241
  buildGeneratorFromSpecPrompt(p) {
@@ -32482,33 +32475,6 @@ function llmFindingToReviewFinding(f, opts = {}) {
32482
32475
  function llmFindingsToReviewFindings(findings, opts = {}) {
32483
32476
  return findings.map((f) => llmFindingToReviewFinding(f, opts));
32484
32477
  }
32485
- function findingToReviewFinding(f, opts = {}) {
32486
- const narrowed = narrowSeverity(f.severity);
32487
- const ruleId = f.rule?.trim() ? f.rule.trim() : deriveRuleId(f.category, f.message);
32488
- const result = {
32489
- ruleId,
32490
- severity: narrowed,
32491
- file: f.file ?? "",
32492
- line: f.line ?? 0,
32493
- message: f.message
32494
- };
32495
- if (f.category)
32496
- result.category = f.category;
32497
- const effectiveSource = opts.source ?? f.source;
32498
- if (effectiveSource)
32499
- result.source = effectiveSource;
32500
- const meta3 = { ...f.meta ?? {} };
32501
- if (f.suggestion)
32502
- meta3.suggestion = f.suggestion;
32503
- if (f.severity !== narrowed)
32504
- meta3.originalSeverity = f.severity;
32505
- if (Object.keys(meta3).length > 0)
32506
- result.meta = meta3;
32507
- return result;
32508
- }
32509
- function findingsToReviewFindings(findings, opts = {}) {
32510
- return findings.map((f) => findingToReviewFinding(f, opts));
32511
- }
32512
32478
  var SEVERITY_MAP, RULE_ID_SLUG_TOKENS = 6;
32513
32479
  var init_finding_projection = __esm(() => {
32514
32480
  SEVERITY_MAP = {
@@ -33716,6 +33682,35 @@ function acFailureToFinding(acId, output) {
33716
33682
  fixTarget: "source"
33717
33683
  };
33718
33684
  }
33685
+ function executionFailureToFinding(params) {
33686
+ const tail = tailLines(params.output, 40);
33687
+ const exitStr = params.exitCode !== undefined ? ` (exit ${params.exitCode})` : "";
33688
+ const message = `Test runner exited non-zero without structured failures${exitStr}. Command: \`${params.command}\`
33689
+
33690
+ --- runner output (last 40 lines) ---
33691
+ ${tail}`;
33692
+ return {
33693
+ source: "test-runner",
33694
+ severity: "error",
33695
+ category: "execution-failed",
33696
+ message,
33697
+ fixTarget: "source",
33698
+ meta: {
33699
+ command: params.command,
33700
+ exitCode: params.exitCode,
33701
+ packageDir: params.packageDir,
33702
+ cwd: params.cwd
33703
+ }
33704
+ };
33705
+ }
33706
+ function tailLines(s, n) {
33707
+ if (!s)
33708
+ return "(no output)";
33709
+ const lines = s.split(`
33710
+ `);
33711
+ return lines.slice(Math.max(0, lines.length - n)).join(`
33712
+ `);
33713
+ }
33719
33714
  function acSentinelToFinding(sentinel, _output) {
33720
33715
  if (sentinel === "AC-HOOK") {
33721
33716
  return {
@@ -35612,7 +35607,9 @@ var init_autofix_implementer = __esm(() => {
35612
35607
  session: { role: "implementer", lifetime: "warm" },
35613
35608
  config: autofixConfigSelector,
35614
35609
  build(input, _ctx) {
35615
- const prompt = RectifierPromptBuilder.reviewRectification(input.failedChecks, input.story, {
35610
+ const verifierFindings = input.findings?.filter((f) => f.source === "tdd-verifier");
35611
+ const useVerifierContext = verifierFindings !== undefined && verifierFindings.length > 0 && verifierFindings.length === input.findings?.length;
35612
+ const prompt = useVerifierContext ? RectifierPromptBuilder.verifierContext(verifierFindings) : RectifierPromptBuilder.reviewRectification(input.failedChecks, input.story, {
35616
35613
  blockingThreshold: input.blockingThreshold
35617
35614
  });
35618
35615
  return {
@@ -35840,6 +35837,29 @@ var init_debate_stateful = __esm(() => {
35840
35837
  };
35841
35838
  });
35842
35839
 
35840
+ // src/debate/selectors/judge.ts
35841
+ var RESOLVER_FALLBACK_AGENT = "synthesis", RESOLVER_FALLBACK_MODEL = "fast", judgeSelector = async (ctx) => {
35842
+ const resolverAgent = ctx.stageConfig.resolver.agent ?? RESOLVER_FALLBACK_AGENT;
35843
+ const resolverModel = ctx.stageConfig.resolver.model ?? RESOLVER_FALLBACK_MODEL;
35844
+ const proposals = ctx.proposals.map((p) => p.output);
35845
+ const output = await callOp(ctx.callContext, judgeOp, {
35846
+ proposals,
35847
+ critiques: ctx.critiques,
35848
+ debaters: ctx.debaters,
35849
+ resolverAgent,
35850
+ resolverModel,
35851
+ timeoutSeconds: ctx.stageConfig.timeoutSeconds
35852
+ });
35853
+ return {
35854
+ outcome: output.trim() ? "passed" : "failed",
35855
+ output
35856
+ };
35857
+ };
35858
+ var init_judge = __esm(() => {
35859
+ init_operations();
35860
+ init_operations();
35861
+ });
35862
+
35843
35863
  // src/debate/selectors/majority.ts
35844
35864
  function stripMarkdownFence(text) {
35845
35865
  const match = text.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/);
@@ -35881,125 +35901,6 @@ var majorityFailClosedSelector = async (ctx) => {
35881
35901
  return { outcome: computeMajority(proposalOutputs, true) };
35882
35902
  };
35883
35903
 
35884
- // src/debate/resolvers.ts
35885
- function majorityResolver(proposals, failOpen) {
35886
- return computeMajority(proposals, failOpen);
35887
- }
35888
- var init_resolvers = __esm(() => {
35889
- init_prompts();
35890
- });
35891
-
35892
- // src/debate/selectors/pick.ts
35893
- function pickSelectorKind(stageConfig, ctx) {
35894
- if (stageConfig.selector) {
35895
- return stageConfig.selector.kind;
35896
- }
35897
- if (ctx.reviewerSession && ctx.resolverContextInput) {
35898
- return "dialogue-verdict";
35899
- }
35900
- return pickBaseSelectorKind(stageConfig);
35901
- }
35902
- function pickBaseSelectorKind(stageConfig) {
35903
- switch (stageConfig.resolver.type) {
35904
- case "synthesis":
35905
- return "synthesis";
35906
- case "majority-fail-closed":
35907
- return "majority-fail-closed";
35908
- case "majority-fail-open":
35909
- return "majority-fail-open";
35910
- case "custom":
35911
- return "judge";
35912
- }
35913
- return "synthesis";
35914
- }
35915
-
35916
- // src/debate/selectors/dialogue-verdict.ts
35917
- var dialogueVerdictSelector = async (ctx) => {
35918
- if (ctx.reviewerSession && ctx.resolverContextInput) {
35919
- try {
35920
- const debateCtx = {
35921
- resolverType: ctx.stageConfig.resolver.type
35922
- };
35923
- if (ctx.stageConfig.resolver.type === "majority-fail-closed" || ctx.stageConfig.resolver.type === "majority-fail-open") {
35924
- const failOpen = ctx.stageConfig.resolver.type === "majority-fail-open";
35925
- const rawOutcome = majorityResolver(ctx.proposals.map((p) => p.output), failOpen);
35926
- let passCount = 0;
35927
- let failCount = 0;
35928
- for (const proposal of ctx.proposals) {
35929
- const parsed = tryParseLLMJson(proposal.output);
35930
- if (parsed !== null && typeof parsed.passed === "boolean" && parsed.passed) {
35931
- passCount++;
35932
- } else if (failOpen) {
35933
- passCount++;
35934
- } else {
35935
- failCount++;
35936
- }
35937
- }
35938
- debateCtx.majorityVote = { passed: rawOutcome === "passed", passCount, failCount };
35939
- }
35940
- const story = {
35941
- id: ctx.resolverContextInput.story.id,
35942
- title: ctx.resolverContextInput.story.title,
35943
- description: "",
35944
- acceptanceCriteria: ctx.resolverContextInput.story.acceptanceCriteria
35945
- };
35946
- const rcRecord = ctx.resolverContextInput;
35947
- const diffContext = ctx.resolverContextInput.diffMode === "ref" ? {
35948
- mode: "ref",
35949
- storyGitRef: rcRecord.storyGitRef ?? "",
35950
- stat: rcRecord.stat ?? undefined,
35951
- productionExcludePatterns: rcRecord.productionExcludePatterns
35952
- } : { mode: "embedded", diff: rcRecord.diff ?? "" };
35953
- const labeledProposals = ctx.labeledProposals ?? ctx.proposals.map((p) => ({
35954
- debater: p.debater.agent,
35955
- output: p.output
35956
- }));
35957
- let dialogueResult;
35958
- if (ctx.resolverContextInput.isReReview) {
35959
- dialogueResult = await ctx.reviewerSession.reReviewDebate(labeledProposals, ctx.critiques, diffContext, debateCtx);
35960
- } else {
35961
- dialogueResult = await ctx.reviewerSession.resolveDebate(labeledProposals, ctx.critiques, diffContext, story, ctx.resolverContextInput.semanticConfig, debateCtx);
35962
- }
35963
- const outcome = dialogueResult.checkResult.success ? "passed" : "failed";
35964
- return {
35965
- outcome,
35966
- findings: dialogueResult.checkResult.findings,
35967
- dialogueResult
35968
- };
35969
- } catch {}
35970
- }
35971
- const baseKind = pickBaseSelectorKind(ctx.stageConfig);
35972
- const baseSelector = resolveSelector(baseKind);
35973
- return baseSelector(ctx);
35974
- };
35975
- var init_dialogue_verdict = __esm(() => {
35976
- init_resolvers();
35977
- init_registry2();
35978
- });
35979
-
35980
- // src/debate/selectors/judge.ts
35981
- var RESOLVER_FALLBACK_AGENT = "synthesis", RESOLVER_FALLBACK_MODEL = "fast", judgeSelector = async (ctx) => {
35982
- const resolverAgent = ctx.stageConfig.resolver.agent ?? RESOLVER_FALLBACK_AGENT;
35983
- const resolverModel = ctx.stageConfig.resolver.model ?? RESOLVER_FALLBACK_MODEL;
35984
- const proposals = ctx.proposals.map((p) => p.output);
35985
- const output = await callOp(ctx.callContext, judgeOp, {
35986
- proposals,
35987
- critiques: ctx.critiques,
35988
- debaters: ctx.debaters,
35989
- resolverAgent,
35990
- resolverModel,
35991
- timeoutSeconds: ctx.stageConfig.timeoutSeconds
35992
- });
35993
- return {
35994
- outcome: output.trim() ? "passed" : "failed",
35995
- output
35996
- };
35997
- };
35998
- var init_judge = __esm(() => {
35999
- init_operations();
36000
- init_operations();
36001
- });
36002
-
36003
35904
  // src/debate/selectors/synthesis.ts
36004
35905
  var RESOLVER_FALLBACK_AGENT2 = "synthesis", RESOLVER_FALLBACK_MODEL2 = "fast", synthesisSelector = async (ctx) => {
36005
35906
  const resolverAgent = ctx.stageConfig.resolver.agent ?? RESOLVER_FALLBACK_AGENT2;
@@ -36176,7 +36077,6 @@ function registerSelector(kind, strategy) {
36176
36077
  var STRATEGIES;
36177
36078
  var init_registry2 = __esm(() => {
36178
36079
  init_errors();
36179
- init_dialogue_verdict();
36180
36080
  init_judge();
36181
36081
  init_synthesis();
36182
36082
  init_verifier_pick();
@@ -36185,16 +36085,34 @@ var init_registry2 = __esm(() => {
36185
36085
  registerSelector("majority-fail-closed", majorityFailClosedSelector);
36186
36086
  registerSelector("majority-fail-open", majorityFailOpenSelector);
36187
36087
  registerSelector("judge", judgeSelector);
36188
- registerSelector("dialogue-verdict", dialogueVerdictSelector);
36189
36088
  registerSelector("verifier-pick", (ctx) => verifierPickSelector(ctx));
36190
36089
  });
36191
36090
 
36091
+ // src/debate/selectors/pick.ts
36092
+ function pickSelectorKind(stageConfig) {
36093
+ if (stageConfig.selector) {
36094
+ return stageConfig.selector.kind;
36095
+ }
36096
+ return pickBaseSelectorKind(stageConfig);
36097
+ }
36098
+ function pickBaseSelectorKind(stageConfig) {
36099
+ switch (stageConfig.resolver.type) {
36100
+ case "synthesis":
36101
+ return "synthesis";
36102
+ case "majority-fail-closed":
36103
+ return "majority-fail-closed";
36104
+ case "majority-fail-open":
36105
+ return "majority-fail-open";
36106
+ case "custom":
36107
+ return "judge";
36108
+ }
36109
+ }
36110
+
36192
36111
  // src/debate/selectors/index.ts
36193
36112
  var init_selectors2 = __esm(() => {
36194
36113
  init_registry2();
36195
36114
  init_synthesis();
36196
36115
  init_judge();
36197
- init_dialogue_verdict();
36198
36116
  init_verifier_pick();
36199
36117
  });
36200
36118
 
@@ -36222,14 +36140,10 @@ function buildResolverCallContext(provided, agentManager, storyId, workdir, feat
36222
36140
  ...sessionOverride !== undefined ? { sessionOverride } : {}
36223
36141
  };
36224
36142
  }
36225
- async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, callContext, storyId, timeoutMs, workdir, featureName, reviewerSession, resolverContext, promptSuffix, debaters, agentManager) {
36143
+ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, callContext, storyId, timeoutMs, workdir, featureName, promptSuffix, debaters, agentManager) {
36226
36144
  const logger = _debateSessionDeps.getSafeLogger();
36227
- if (reviewerSession && !resolverContext) {
36228
- logger?.warn("debate", "ReviewerSession provided but resolverContext is undefined \u2014 falling back to stateless resolver", { storyId });
36229
- }
36230
- const resolverContextInput = resolverContext ? (({ labeledProposals: _lp, ...rest }) => rest)(resolverContext) : undefined;
36231
- const kind = pickSelectorKind(stageConfig, { reviewerSession, resolverContextInput });
36232
- if ((kind === "majority-fail-closed" || kind === "majority-fail-open") && workdir !== undefined && !reviewerSession) {
36145
+ const kind = pickSelectorKind(stageConfig);
36146
+ if ((kind === "majority-fail-closed" || kind === "majority-fail-open") && workdir !== undefined) {
36233
36147
  logger?.warn("debate", "majority resolver does not support implementer session resumption \u2014 switch to synthesis or custom resolver for context-aware semantic review");
36234
36148
  }
36235
36149
  const proposalList = debaters ? debaters.map((debater, i) => ({
@@ -36256,48 +36170,20 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
36256
36170
  stageConfig,
36257
36171
  config: effectiveConfig,
36258
36172
  proposals: proposalList,
36259
- labeledProposals: resolverContext?.labeledProposals,
36260
36173
  critiques: critiqueOutputs,
36261
36174
  workdir: workdir ?? "",
36262
36175
  featureName: featureName ?? "",
36263
36176
  timeoutMs,
36264
36177
  agentManager: effectiveAgentManager,
36265
- reviewerSession,
36266
- resolverContextInput,
36267
36178
  promptSuffix,
36268
36179
  debaters: debaters ?? [],
36269
36180
  callContext: effectiveCallContext
36270
36181
  };
36271
- const resolverTypeMappedKind = pickBaseSelectorKind(stageConfig);
36272
- if (kind === "dialogue-verdict") {
36273
- try {
36274
- const result2 = await resolveSelector(kind)(selectorCtx);
36275
- return {
36276
- outcome: result2.outcome,
36277
- output: result2.output,
36278
- findings: result2.findings,
36279
- dialogueResult: result2.dialogueResult
36280
- };
36281
- } catch (err) {
36282
- logger?.warn("debate", "dialogue-verdict selector failed, falling back to stateless", {
36283
- storyId,
36284
- error: err instanceof Error ? err.message : String(err)
36285
- });
36286
- const fallbackResult = await resolveSelector(resolverTypeMappedKind)(selectorCtx);
36287
- return {
36288
- outcome: fallbackResult.outcome,
36289
- output: fallbackResult.output,
36290
- findings: fallbackResult.findings,
36291
- dialogueResult: fallbackResult.dialogueResult
36292
- };
36293
- }
36294
- }
36295
36182
  const result = await resolveSelector(kind)(selectorCtx);
36296
36183
  return {
36297
36184
  outcome: result.outcome,
36298
36185
  output: result.output,
36299
- findings: result.findings,
36300
- dialogueResult: result.dialogueResult
36186
+ findings: result.findings
36301
36187
  };
36302
36188
  }
36303
36189
  var RESOLVER_FALLBACK_AGENT3 = "synthesis", _debateSessionDeps;
@@ -36937,6 +36823,46 @@ var init_verdict = __esm(() => {
36937
36823
  });
36938
36824
 
36939
36825
  // src/operations/verify.ts
36826
+ function buildVerifierFindings(verdict, categorization) {
36827
+ if (categorization.success)
36828
+ return [];
36829
+ switch (categorization.failureCategory) {
36830
+ case "verifier-rejected": {
36831
+ const files = verdict.testModifications.files;
36832
+ return [
36833
+ {
36834
+ source: "tdd-verifier",
36835
+ severity: "error",
36836
+ category: "illegitimate-test-edits",
36837
+ fixTarget: "test",
36838
+ message: files.length > 0 ? `Implementer edited test files illegitimately: ${files.join(", ")}` : "Implementer made illegitimate test modifications",
36839
+ meta: {
36840
+ reasoning: verdict.testModifications.reasoning,
36841
+ files
36842
+ }
36843
+ }
36844
+ ];
36845
+ }
36846
+ case "tests-failing": {
36847
+ return [
36848
+ {
36849
+ source: "tdd-verifier",
36850
+ severity: "error",
36851
+ category: "tests-failed",
36852
+ fixTarget: "source",
36853
+ message: `${verdict.tests.failCount} story-scoped test(s) failed (verifier)`,
36854
+ meta: {
36855
+ passCount: verdict.tests.passCount,
36856
+ failCount: verdict.tests.failCount,
36857
+ reasoning: verdict.reasoning
36858
+ }
36859
+ }
36860
+ ];
36861
+ }
36862
+ default:
36863
+ return [];
36864
+ }
36865
+ }
36940
36866
  function parseVerdictFromStdout(output, _input, _ctx) {
36941
36867
  if (!output || !output.trim()) {
36942
36868
  throw new ParseValidationError("verifier produced no stdout");
@@ -36956,6 +36882,7 @@ function parseVerdictFromStdout(output, _input, _ctx) {
36956
36882
  estimatedCostUsd: 0,
36957
36883
  durationMs: 0,
36958
36884
  output,
36885
+ normalizedFindings: buildVerifierFindings(verdict, categorization),
36959
36886
  ...categorization.failureCategory && { failureCategory: categorization.failureCategory },
36960
36887
  ...categorization.reviewReason && { reviewReason: categorization.reviewReason }
36961
36888
  };
@@ -37028,6 +36955,7 @@ var init_verify = __esm(() => {
37028
36955
  estimatedCostUsd: 0,
37029
36956
  durationMs: 0,
37030
36957
  output: "",
36958
+ normalizedFindings: buildVerifierFindings(verdict, categorization),
37031
36959
  ...categorization.failureCategory && { failureCategory: categorization.failureCategory },
37032
36960
  ...categorization.reviewReason && { reviewReason: categorization.reviewReason },
37033
36961
  ...isolation && { isolation }
@@ -37039,6 +36967,7 @@ var init_verify = __esm(() => {
37039
36967
  estimatedCostUsd: 0,
37040
36968
  durationMs: 0,
37041
36969
  output: "",
36970
+ normalizedFindings: [],
37042
36971
  reviewReason: "verifier produced unparseable verdict in stdout after retries and no usable verdict file on disk"
37043
36972
  };
37044
36973
  } finally {
@@ -37792,7 +37721,9 @@ async function runVerificationCore(options) {
37792
37721
  success: options.acceptOnTimeout ?? false,
37793
37722
  countsTowardEscalation: false,
37794
37723
  error: execution.error,
37795
- output: execution.output
37724
+ output: execution.output,
37725
+ exitCode: execution.exitCode,
37726
+ command: finalCommand
37796
37727
  };
37797
37728
  }
37798
37729
  const exitCode = execution.exitCode ?? 1;
@@ -37806,7 +37737,9 @@ async function runVerificationCore(options) {
37806
37737
  error: analysis.error,
37807
37738
  output: execution.output,
37808
37739
  passCount: analysis.passCount,
37809
- failCount: analysis.failCount
37740
+ failCount: analysis.failCount,
37741
+ exitCode,
37742
+ command: finalCommand
37810
37743
  };
37811
37744
  }
37812
37745
  return {
@@ -37815,10 +37748,19 @@ async function runVerificationCore(options) {
37815
37748
  countsTowardEscalation: true,
37816
37749
  output: execution.output,
37817
37750
  passCount: analysis.passCount,
37818
- failCount: analysis.failCount
37751
+ failCount: analysis.failCount,
37752
+ exitCode,
37753
+ command: finalCommand
37819
37754
  };
37820
37755
  }
37821
- return { status: "SUCCESS", success: true, countsTowardEscalation: true, output: execution.output };
37756
+ return {
37757
+ status: "SUCCESS",
37758
+ success: true,
37759
+ countsTowardEscalation: true,
37760
+ output: execution.output,
37761
+ exitCode,
37762
+ command: finalCommand
37763
+ };
37822
37764
  }
37823
37765
  async function fullSuite(options) {
37824
37766
  return runVerificationCore(options);
@@ -37895,7 +37837,9 @@ var init_full_suite_gate = __esm(() => {
37895
37837
  failed: parsedSummary.failed ?? 0,
37896
37838
  output: result.output ?? "",
37897
37839
  parsedSummary,
37898
- timedOut: result.status === "TIMEOUT"
37840
+ timedOut: result.status === "TIMEOUT",
37841
+ exitCode: result.exitCode,
37842
+ command: result.command ?? gateCtx.testCmd
37899
37843
  };
37900
37844
  }
37901
37845
  };
@@ -37922,6 +37866,13 @@ var init_full_suite_gate = __esm(() => {
37922
37866
  };
37923
37867
  }
37924
37868
  const gateCtx = await deps.resolveGateContext(input, ctx);
37869
+ logger.info("verify[regression]", "Running full-suite gate", {
37870
+ storyId: input.story.id,
37871
+ packageDir: input.story.workdir,
37872
+ cwd: input.workdir,
37873
+ command: gateCtx.testCmd,
37874
+ timeoutSeconds: gateCtx.fullSuiteTimeout
37875
+ });
37925
37876
  const testResult = await deps.runTests(input, gateCtx);
37926
37877
  if (testResult.passed) {
37927
37878
  return { success: true, passed: true, status: "passed", estimatedCostUsd: 0, attempts: 0, findings: [] };
@@ -37955,13 +37906,27 @@ var init_full_suite_gate = __esm(() => {
37955
37906
  }
37956
37907
  const findings = testSummaryToFindings(testResult.parsedSummary);
37957
37908
  if (findings.length === 0) {
37909
+ const cmd = testResult.command ?? gateCtx.testCmd;
37910
+ const synth = executionFailureToFinding({
37911
+ command: cmd,
37912
+ exitCode: testResult.exitCode,
37913
+ output: testResult.output,
37914
+ packageDir: input.story.workdir,
37915
+ cwd: input.workdir
37916
+ });
37917
+ logger.warn("verify[regression]", "Full-suite gate execution-failed \u2014 emitting synth finding", {
37918
+ storyId: input.story.id,
37919
+ command: cmd,
37920
+ exitCode: testResult.exitCode,
37921
+ packageDir: input.story.workdir
37922
+ });
37958
37923
  return {
37959
37924
  success: false,
37960
37925
  passed: false,
37961
37926
  status: "execution-failed",
37962
37927
  estimatedCostUsd: 0,
37963
37928
  attempts: 0,
37964
- findings: []
37929
+ findings: [synth]
37965
37930
  };
37966
37931
  }
37967
37932
  return { success: false, passed: false, status: "failed", estimatedCostUsd: 0, attempts: 0, findings };
@@ -37973,7 +37938,7 @@ var init_full_suite_gate = __esm(() => {
37973
37938
  function makeFullSuiteRectifyStrategy(story, config2) {
37974
37939
  return {
37975
37940
  name: "full-suite-rectify",
37976
- appliesTo: (finding) => finding.source === "test-runner" && finding.category === "failed-test",
37941
+ appliesTo: (finding) => finding.source === "test-runner" && (finding.category === "failed-test" || finding.category === "execution-failed"),
37977
37942
  fixOp: implementerOp,
37978
37943
  buildInput: (findings) => ({
37979
37944
  story,
@@ -38016,7 +37981,8 @@ var init__finding_to_check = __esm(() => {
38016
37981
  "semantic-review": "semantic",
38017
37982
  "adversarial-review": "adversarial",
38018
37983
  lint: "lint",
38019
- typecheck: "typecheck"
37984
+ typecheck: "typecheck",
37985
+ "tdd-verifier": "test"
38020
37986
  };
38021
37987
  });
38022
37988
 
@@ -38028,7 +37994,8 @@ function makeAutofixImplementerStrategy(story, config2, sink) {
38028
37994
  fixOp: implementerRectifyOp,
38029
37995
  buildInput: (findings, _prior, _cycleCtx) => ({
38030
37996
  failedChecks: findingsToFailedChecks(findings),
38031
- story
37997
+ story,
37998
+ findings
38032
37999
  }),
38033
38000
  extractApplied: (output) => {
38034
38001
  for (const decl of output.testEditDeclarations) {
@@ -38051,7 +38018,7 @@ var IMPLEMENTER_SOURCES;
38051
38018
  var init_autofix_implementer_strategy = __esm(() => {
38052
38019
  init__finding_to_check();
38053
38020
  init_autofix_implementer();
38054
- IMPLEMENTER_SOURCES = new Set(["lint", "typecheck", "semantic-review"]);
38021
+ IMPLEMENTER_SOURCES = new Set(["lint", "typecheck", "semantic-review", "tdd-verifier"]);
38055
38022
  });
38056
38023
 
38057
38024
  // src/operations/autofix-test-writer-strategy.ts
@@ -38665,6 +38632,15 @@ var init_verify_scoped = __esm(() => {
38665
38632
  command: selection.effectiveCommand
38666
38633
  });
38667
38634
  }
38635
+ const scopedTimeout = ctxConfig.execution?.regressionGate?.timeoutSeconds ?? 600;
38636
+ logger.info("verify[scoped]", "Running scoped tests", {
38637
+ storyId: input.storyId,
38638
+ packageDir: input.packageDir,
38639
+ cwd: input.workdir,
38640
+ command: selection.effectiveCommand,
38641
+ timeoutSeconds: scopedTimeout,
38642
+ isFullSuite: selection.isFullSuite
38643
+ });
38668
38644
  const start = Date.now();
38669
38645
  const result = await deps.regression({
38670
38646
  workdir: input.workdir,
@@ -38785,6 +38761,9 @@ var init_operations = __esm(() => {
38785
38761
  });
38786
38762
 
38787
38763
  // src/findings/cycle.ts
38764
+ function normalizeValidateResult(r) {
38765
+ return Array.isArray(r) ? { findings: r, shortCircuited: false } : r;
38766
+ }
38788
38767
  function classifySingleSource(before, after) {
38789
38768
  const beforeKeys = new Set(before.map(findingKey));
38790
38769
  const afterKeys = new Set(after.map(findingKey));
@@ -38981,11 +38960,12 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
38981
38960
  });
38982
38961
  if (allExhausted) {
38983
38962
  let liteFindingsAfter;
38963
+ let liteShortCircuited = false;
38984
38964
  try {
38985
- liteFindingsAfter = await cycle.validate(ctx, {
38986
- mode: "lite",
38987
- strategiesRun: group.map((s) => s.name)
38988
- });
38965
+ const liteRaw = await cycle.validate(ctx, { mode: "lite", strategiesRun: group.map((s) => s.name) });
38966
+ const liteResult = normalizeValidateResult(liteRaw);
38967
+ liteFindingsAfter = liteResult.findings;
38968
+ liteShortCircuited = liteResult.shortCircuited ?? false;
38989
38969
  } catch (err) {
38990
38970
  const finishedAt3 = now();
38991
38971
  cycle.iterations.push({
@@ -39023,7 +39003,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
39023
39003
  finishedAt: finishedAt2
39024
39004
  });
39025
39005
  cycle.findings = liteFindingsAfter;
39026
- if (liteFindingsAfter.length === 0) {
39006
+ if (liteFindingsAfter.length === 0 && !liteShortCircuited) {
39027
39007
  logger?.info("findings.cycle", "cycle exited \u2014 resolved after terminal lite validate", {
39028
39008
  storyId,
39029
39009
  packageDir,
@@ -39037,6 +39017,21 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
39037
39017
  costUsd: totalCostUsd
39038
39018
  };
39039
39019
  }
39020
+ if (liteShortCircuited) {
39021
+ logger?.info("findings.cycle", "cycle exited \u2014 validate short-circuited", {
39022
+ storyId,
39023
+ packageDir,
39024
+ cycleName,
39025
+ reason: "validate-short-circuit",
39026
+ liteFindingsAfterCount: liteFindingsAfter.length
39027
+ });
39028
+ return {
39029
+ iterations: cycle.iterations,
39030
+ finalFindings: liteFindingsAfter,
39031
+ exitReason: "validate-short-circuit",
39032
+ costUsd: totalCostUsd
39033
+ };
39034
+ }
39040
39035
  logger?.info("findings.cycle", "cycle exited \u2014 strategy attempt cap reached (lite validate)", {
39041
39036
  storyId,
39042
39037
  packageDir,
@@ -39057,7 +39052,8 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
39057
39052
  let validatorAttempt = 0;
39058
39053
  for (;; ) {
39059
39054
  try {
39060
- findingsAfter = await cycle.validate(ctx, { mode: "full", strategiesRun: group.map((s) => s.name) });
39055
+ const fullRaw = await cycle.validate(ctx, { mode: "full", strategiesRun: group.map((s) => s.name) });
39056
+ findingsAfter = normalizeValidateResult(fullRaw).findings;
39061
39057
  break;
39062
39058
  } catch (err) {
39063
39059
  if (validatorAttempt >= cycle.config.validatorRetries) {
@@ -39701,7 +39697,6 @@ async function runSemanticDebate(opts) {
39701
39697
  agentManager,
39702
39698
  featureName,
39703
39699
  story,
39704
- resolverSession,
39705
39700
  diffMode,
39706
39701
  diff,
39707
39702
  stat,
@@ -39719,10 +39714,9 @@ async function runSemanticDebate(opts) {
39719
39714
  ...configuredStageConfig,
39720
39715
  sessionMode: "one-shot",
39721
39716
  mode: "panel",
39722
- selector: { kind: "dialogue-verdict" },
39717
+ selector: { kind: pickBaseSelectorKind(configuredStageConfig) },
39723
39718
  postDebateVerifier: { kind: "review-grounding-filter" }
39724
39719
  };
39725
- const isReReview = resolverSession !== undefined && resolverSession.history.length > 0;
39726
39720
  const semanticAgentName = agentManager && typeof agentManager.getDefault === "function" ? agentManager.getDefault() : "claude";
39727
39721
  const semanticCallCtx = {
39728
39722
  runtime,
@@ -39739,89 +39733,10 @@ async function runSemanticDebate(opts) {
39739
39733
  config: naxConfig,
39740
39734
  workdir,
39741
39735
  featureName,
39742
- timeoutSeconds: naxConfig.execution?.sessionTimeoutSeconds,
39743
- reviewerSession: resolverSession,
39744
- resolverContextInput: resolverSession ? {
39745
- diffMode,
39746
- ...diffMode === "ref" ? { storyGitRef: effectiveRef, stat, productionExcludePatterns } : { diff },
39747
- story: { id: story.id, title: story.title, acceptanceCriteria: story.acceptanceCriteria },
39748
- semanticConfig,
39749
- blockingThreshold,
39750
- resolverType: reviewStageConfig.resolver.type,
39751
- isReReview
39752
- } : undefined
39736
+ timeoutSeconds: naxConfig.execution?.sessionTimeoutSeconds
39753
39737
  });
39754
- const historyLenBefore = resolverSession?.history.length ?? 0;
39755
39738
  const debateResult = await debateRunner.run(prompt);
39756
39739
  const debateCost = debateResult.totalCostUsd ?? 0;
39757
- const sessionUsed = resolverSession && resolverSession.history.length > historyLenBefore;
39758
- if (sessionUsed) {
39759
- const durationMs2 = Date.now() - startTime;
39760
- try {
39761
- const verdict = resolverSession.getVerdict();
39762
- const findings = verdict.findings ?? [];
39763
- if (!verdict.passed && findings.length > 0) {
39764
- logger?.warn("review", `Semantic review failed (debate+dialogue): ${findings.length} findings`, {
39765
- storyId: story.id,
39766
- durationMs: durationMs2
39767
- });
39768
- recordSemanticDebateAudit({
39769
- runtime,
39770
- workdir,
39771
- storyId: story.id,
39772
- featureName,
39773
- parsed: true,
39774
- passed: false,
39775
- blockingThreshold,
39776
- result: {
39777
- passed: false,
39778
- findings: findingsToReviewFindings(findings, { source: "semantic-debate-review" })
39779
- }
39780
- });
39781
- return {
39782
- check: "semantic",
39783
- success: false,
39784
- command: "",
39785
- exitCode: 1,
39786
- output: `Semantic review failed:
39787
-
39788
- ${findings.map((f) => `${f.rule ?? "semantic"}: ${f.message}`).join(`
39789
- `)}`,
39790
- durationMs: durationMs2,
39791
- findings,
39792
- cost: debateCost
39793
- };
39794
- }
39795
- const label = verdict.passed ? "Semantic review passed (debate+dialogue)" : "Semantic review passed (debate+dialogue, all findings non-blocking)";
39796
- logger?.info("review", label, { storyId: story.id, durationMs: durationMs2 });
39797
- recordSemanticDebateAudit({
39798
- runtime,
39799
- workdir,
39800
- storyId: story.id,
39801
- featureName,
39802
- parsed: true,
39803
- passed: true,
39804
- blockingThreshold,
39805
- result: {
39806
- passed: true,
39807
- findings: findingsToReviewFindings(findings, { source: "semantic-debate-review" })
39808
- }
39809
- });
39810
- return {
39811
- check: "semantic",
39812
- success: true,
39813
- command: "",
39814
- exitCode: 0,
39815
- output: label,
39816
- durationMs: durationMs2,
39817
- cost: debateCost
39818
- };
39819
- } catch {
39820
- logger?.warn("review", "getVerdict() failed after debate+dialogue \u2014 falling back to stateless verdict", {
39821
- storyId: story.id
39822
- });
39823
- }
39824
- }
39825
39740
  const resolverPassed = debateResult.outcome === "passed";
39826
39741
  const allFindings = [];
39827
39742
  for (const p of debateResult.proposals) {
@@ -39941,6 +39856,7 @@ ${formatFindings2(debateBlocking)}`,
39941
39856
  };
39942
39857
  }
39943
39858
  var init_semantic_debate = __esm(() => {
39859
+ init_debate();
39944
39860
  init_logger2();
39945
39861
  init_ac_quote_validator();
39946
39862
  init_finding_projection();
@@ -39975,7 +39891,6 @@ async function runSemanticReview(opts) {
39975
39891
  agentManager,
39976
39892
  naxConfig,
39977
39893
  featureName,
39978
- resolverSession,
39979
39894
  priorSemanticIterations,
39980
39895
  blockingThreshold,
39981
39896
  featureContextMarkdown,
@@ -40109,7 +40024,6 @@ async function runSemanticReview(opts) {
40109
40024
  agentManager: effectiveAgentManager,
40110
40025
  featureName,
40111
40026
  story,
40112
- resolverSession,
40113
40027
  diffMode,
40114
40028
  diff,
40115
40029
  stat,
@@ -40476,7 +40390,6 @@ async function runReview(opts) {
40476
40390
  naxConfig,
40477
40391
  retrySkipChecks,
40478
40392
  featureName,
40479
- resolverSession,
40480
40393
  priorFailures,
40481
40394
  priorSemanticIterations,
40482
40395
  featureContextMarkdown,
@@ -40556,7 +40469,6 @@ async function runReview(opts) {
40556
40469
  agentManager,
40557
40470
  naxConfig,
40558
40471
  featureName,
40559
- resolverSession,
40560
40472
  priorSemanticIterations,
40561
40473
  blockingThreshold: config2.blockingThreshold,
40562
40474
  featureContextMarkdown,
@@ -41494,6 +41406,26 @@ Tests are failing. Fix the source so all tests pass \u2014 not just the ones lis
41494
41406
  parts.push(escapeHatchFor(opts.story));
41495
41407
  return parts.join("");
41496
41408
  }
41409
+ static verifierContext(findings) {
41410
+ if (findings.length === 0) {
41411
+ return "The verifier found no issues.";
41412
+ }
41413
+ const lines = [
41414
+ `Fix the following ${findings.length} verifier finding${findings.length === 1 ? "" : "s"}:
41415
+ `
41416
+ ];
41417
+ for (const f of findings) {
41418
+ const reasoning = f.meta?.reasoning ?? "";
41419
+ const detail = reasoning ? ` Reasoning: ${reasoning}
41420
+ ` : "";
41421
+ lines.push(`- [${f.category}] ${f.message}
41422
+ ${detail}`);
41423
+ }
41424
+ lines.push(`
41425
+ Fix the implementation to resolve all verifier findings.`);
41426
+ return lines.join(`
41427
+ `);
41428
+ }
41497
41429
  static failingTestContext(findings) {
41498
41430
  if (findings.length === 0) {
41499
41431
  return "The full test suite has failing tests. Fix the implementation to make all tests pass.";
@@ -45977,17 +45909,6 @@ function createDebaterCallContext(ctx, agentName) {
45977
45909
  }
45978
45910
  };
45979
45911
  }
45980
- function buildResolverContext(successfulProposals, resolverContextInput) {
45981
- if (!resolverContextInput)
45982
- return;
45983
- return {
45984
- ...resolverContextInput,
45985
- labeledProposals: successfulProposals.map((proposal) => ({
45986
- debater: buildDebaterLabel(proposal.debater),
45987
- output: proposal.output
45988
- }))
45989
- };
45990
- }
45991
45912
  async function runZeroSuccessFallback(ctx, prompt, firstDebater) {
45992
45913
  if (!firstDebater)
45993
45914
  return null;
@@ -46016,7 +45937,6 @@ var init_runner_stateful_helpers = __esm(() => {
46016
45937
  init_call();
46017
45938
  init_debate_stateful();
46018
45939
  init_prompts();
46019
- init_personas();
46020
45940
  DEFAULT_ABORT_SIGNAL = new AbortController().signal;
46021
45941
  });
46022
45942
 
@@ -46138,13 +46058,7 @@ async function runHybrid(ctx, prompt) {
46138
46058
  }
46139
46059
  }
46140
46060
  const proposalOutputs = successfulResults.map((p) => p.output);
46141
- const resolveResult = await resolveOutcome(proposalOutputs, rebuttals.map((r) => r.output), ctx.stageConfig, ctx.config, { ...ctx.callContext, scopeId: ctx.resolverCallContext?.scopeId ?? ctx.callContext.scopeId }, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, ctx.resolverContextInput ? {
46142
- ...ctx.resolverContextInput,
46143
- labeledProposals: successfulResults.map((p) => ({
46144
- debater: buildDebaterLabel(p.debater),
46145
- output: p.output
46146
- }))
46147
- } : undefined, undefined, successfulResults.map((p) => p.debater), agentManager);
46061
+ const resolveResult = await resolveOutcome(proposalOutputs, rebuttals.map((r) => r.output), ctx.stageConfig, ctx.config, { ...ctx.callContext, scopeId: ctx.resolverCallContext?.scopeId ?? ctx.callContext.scopeId }, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, undefined, successfulResults.map((p) => p.debater), agentManager);
46148
46062
  return {
46149
46063
  storyId: ctx.storyId,
46150
46064
  stage: ctx.stage,
@@ -46573,9 +46487,7 @@ async function scoreAndDispatchVerifierPick(resolved, rebuttalSettled, ctx, opts
46573
46487
  timeoutMs: (ctx.stageConfig.timeoutSeconds ?? 600) * 1000,
46574
46488
  agentManager,
46575
46489
  debaters: resolved.map((e) => e.debater),
46576
- callContext: ctx.callContext,
46577
- ...ctx.reviewerSession ? { reviewerSession: ctx.reviewerSession } : {},
46578
- ...ctx.resolverContextInput ? { resolverContextInput: ctx.resolverContextInput } : {}
46490
+ callContext: ctx.callContext
46579
46491
  };
46580
46492
  const manifest = extractManifestFromContext(scoringCtx);
46581
46493
  const scored = await Promise.all(rebutProposals.map(async (p) => ({ proposal: p, score: await computeScore(p, manifest) })));
@@ -46654,9 +46566,7 @@ async function finalizePlanRun(ctx, opts, successful, rebuttalList, outputPaths,
46654
46566
  timeoutMs: (ctx.stageConfig.timeoutSeconds ?? 600) * 1000,
46655
46567
  agentManager,
46656
46568
  debaters: finalizedProposals.map((p) => p.debater),
46657
- callContext: ctx.callContext,
46658
- ...ctx.reviewerSession ? { reviewerSession: ctx.reviewerSession } : {},
46659
- ...ctx.resolverContextInput ? { resolverContextInput: ctx.resolverContextInput } : {}
46569
+ callContext: ctx.callContext
46660
46570
  };
46661
46571
  const manifest = extractManifestFromContext(selectorCtx);
46662
46572
  const scored = await Promise.all(selectorCtx.proposals.map(async (proposal) => ({ proposal, score: await computeScore(proposal, manifest) })));
@@ -46670,7 +46580,7 @@ async function finalizePlanRun(ctx, opts, successful, rebuttalList, outputPaths,
46670
46580
  const outcome = selectorKind === "verifier-pick" ? {
46671
46581
  outcome: "passed",
46672
46582
  output: selectionSummary.winnerOutput ?? finalizedProposals[0]?.output
46673
- } : await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.callContext, ctx.storyId, resolverTimeoutMs, opts.workdir, opts.feature, ctx.reviewerSession, buildResolverContext(finalizedProposals.map((p) => ({ debater: p.debater, agentName: p.agentName, output: p.output, cost: 0 })), ctx.resolverContextInput), buildPlanSynthesisSuffix(opts.specContent), finalizedProposals.map((p) => p.debater), agentManager);
46583
+ } : await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.callContext, ctx.storyId, resolverTimeoutMs, opts.workdir, opts.feature, buildPlanSynthesisSuffix(opts.specContent), finalizedProposals.map((p) => p.debater), agentManager);
46674
46584
  let finalOutcome = outcome.outcome;
46675
46585
  let winningOutput = outcome.output ?? finalizedProposals[0]?.output;
46676
46586
  winningOutput = await readWinnerOutput(selectionSummary.winnerOutputPath ?? outputPaths[0], winningOutput);
@@ -46711,7 +46621,6 @@ async function finalizePlanRun(ctx, opts, successful, rebuttalList, outputPaths,
46711
46621
  var _runPlanDeps, FILE_OUTPUT_INSTRUCTION = "Write the complete PRD JSON to this file path and then reply with a short confirmation:";
46712
46622
  var init_runner_plan_helpers = __esm(() => {
46713
46623
  init_pre_phase();
46714
- init_runner_stateful_helpers();
46715
46624
  init_verifier_pick();
46716
46625
  init_session_helpers();
46717
46626
  init_verifiers();
@@ -46967,9 +46876,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
46967
46876
  stage: ctx.stage,
46968
46877
  stageConfig: ctx.stageConfig,
46969
46878
  config: ctx.config,
46970
- callContext: ctx.callContext,
46971
- reviewerSession: ctx.reviewerSession,
46972
- resolverContextInput: ctx.resolverContextInput
46879
+ callContext: ctx.callContext
46973
46880
  }, { workdir: opts.workdir, feature: opts.feature, specContent: opts.specContent }, successful, rebuttalList, outputPaths, totalCostUsd, agentManager, selectorKind, includeHybridRebuttals);
46974
46881
  }
46975
46882
  var DEFAULT_MAX_CONCURRENT_DEBATERS = 2;
@@ -47103,7 +47010,7 @@ async function runStateful(ctx, prompt) {
47103
47010
  }).then((result) => ({ ...result, resolvedIndex: proposal.resolvedIndex }))), concurrencyLimit)).flatMap((result) => result.status === "fulfilled" && result.value.success ? [{ debater: resolved[result.value.resolvedIndex].debater, round: 1, output: result.value.rebut }] : []) : [];
47104
47011
  throwIfAborted2();
47105
47012
  const proposalOutputs = successfulProposals.map((proposal) => proposal.output);
47106
- const outcome = await resolveOutcome(proposalOutputs, rebuttals.map((rebuttal) => rebuttal.output), ctx.stageConfig, ctx.config, { ...ctx.callContext, scopeId: ctx.resolverCallContext?.scopeId ?? ctx.callContext.scopeId }, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, buildResolverContext(successfulProposals, ctx.resolverContextInput), undefined, successfulProposals.map((proposal) => proposal.debater), ctx.agentManager);
47013
+ const outcome = await resolveOutcome(proposalOutputs, rebuttals.map((rebuttal) => rebuttal.output), ctx.stageConfig, ctx.config, { ...ctx.callContext, scopeId: ctx.resolverCallContext?.scopeId ?? ctx.callContext.scopeId }, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, undefined, successfulProposals.map((proposal) => proposal.debater), ctx.agentManager);
47107
47014
  logger?.info("debate", "debate:result", {
47108
47015
  storyId: ctx.storyId,
47109
47016
  stage: ctx.stage,
@@ -47146,8 +47053,6 @@ class DebateRunner {
47146
47053
  featureName;
47147
47054
  timeoutSeconds;
47148
47055
  sessionManager;
47149
- reviewerSession;
47150
- resolverContextInput;
47151
47056
  constructor(opts) {
47152
47057
  this.ctx = opts.ctx;
47153
47058
  this.stage = opts.stage;
@@ -47157,8 +47062,6 @@ class DebateRunner {
47157
47062
  this.featureName = opts.featureName ?? opts.stage;
47158
47063
  this.timeoutSeconds = opts.timeoutSeconds ?? opts.stageConfig.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
47159
47064
  this.sessionManager = opts.sessionManager ?? opts.ctx.runtime?.sessionManager;
47160
- this.reviewerSession = opts.reviewerSession;
47161
- this.resolverContextInput = opts.resolverContextInput;
47162
47065
  }
47163
47066
  async run(prompt) {
47164
47067
  const sessionMode = this.stageConfig.sessionMode ?? "one-shot";
@@ -47352,14 +47255,7 @@ ${prompt}`;
47352
47255
  critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
47353
47256
  }
47354
47257
  const proposalOutputs = successful.map((p) => p.output);
47355
- const fullResolverContext = this.resolverContextInput ? {
47356
- ...this.resolverContextInput,
47357
- labeledProposals: successful.map((s) => ({
47358
- debater: buildDebaterLabel(s.debater),
47359
- output: s.output
47360
- }))
47361
- } : undefined;
47362
- const selectorOutcome = await resolveOutcome(proposalOutputs, critiqueOutputs, this.stageConfig, this.config, { ...this.ctx, scopeId: resolverScope.scopeId }, this.ctx.storyId ?? "", this.timeoutSeconds * 1000, this.workdir, this.featureName, this.reviewerSession, fullResolverContext, undefined, successful.map((s) => s.debater), agentManager);
47258
+ const selectorOutcome = await resolveOutcome(proposalOutputs, critiqueOutputs, this.stageConfig, this.config, { ...this.ctx, scopeId: resolverScope.scopeId }, this.ctx.storyId ?? "", this.timeoutSeconds * 1000, this.workdir, this.featureName, undefined, successful.map((s) => s.debater), agentManager);
47363
47259
  let finalOutcome = selectorOutcome.outcome;
47364
47260
  if (config2.postDebateVerifier) {
47365
47261
  const verifierCtx = {
@@ -47369,8 +47265,8 @@ ${prompt}`;
47369
47265
  selectorResult: selectorOutcome,
47370
47266
  workdir: this.workdir,
47371
47267
  ctx: { ...this.ctx, scopeId: verifierScope.scopeId },
47372
- acceptanceCriteria: this.resolverContextInput?.story.acceptanceCriteria,
47373
- blockingThreshold: this.resolverContextInput?.blockingThreshold
47268
+ acceptanceCriteria: undefined,
47269
+ blockingThreshold: undefined
47374
47270
  };
47375
47271
  const verifierResult = await resolvePostDebateVerifier(config2.postDebateVerifier.kind)(verifierCtx);
47376
47272
  finalOutcome = verifierResult.outcome;
@@ -47407,9 +47303,7 @@ ${prompt}`;
47407
47303
  agentManager: this.ctx.runtime.agentManager,
47408
47304
  sessionManager: this.sessionManager ?? this.ctx.runtime.sessionManager,
47409
47305
  runtime: this.ctx.runtime,
47410
- abortSignal: this.ctx.runtime.signal,
47411
- reviewerSession: this.reviewerSession,
47412
- resolverContextInput: this.resolverContextInput
47306
+ abortSignal: this.ctx.runtime.signal
47413
47307
  };
47414
47308
  }
47415
47309
  toPlanCtx() {
@@ -47443,6 +47337,11 @@ var init_runner3 = __esm(() => {
47443
47337
  init_verifiers();
47444
47338
  });
47445
47339
 
47340
+ // src/debate/resolvers.ts
47341
+ var init_resolvers = __esm(() => {
47342
+ init_prompts();
47343
+ });
47344
+
47446
47345
  // src/debate/index.ts
47447
47346
  var init_debate = __esm(() => {
47448
47347
  init_runner3();
@@ -51491,9 +51390,9 @@ var init_acceptance2 = __esm(() => {
51491
51390
  function logTestOutput(logger, stage, output, opts = {}) {
51492
51391
  if (!logger || !output)
51493
51392
  return;
51494
- const tailLines = opts.tailLines ?? 20;
51393
+ const tailLines2 = opts.tailLines ?? 20;
51495
51394
  const lines = output.split(`
51496
- `).slice(-tailLines).join(`
51395
+ `).slice(-tailLines2).join(`
51497
51396
  `);
51498
51397
  logger.debug(stage, "Test output (tail)", {
51499
51398
  ...opts.storyId !== undefined && { storyId: opts.storyId },
@@ -52970,7 +52869,7 @@ function extractPhaseFindings(output) {
52970
52869
  return [];
52971
52870
  }
52972
52871
  const record2 = output;
52973
- const rawArray = Array.isArray(record2.normalizedFindings) ? record2.normalizedFindings : Array.isArray(record2.findings) ? record2.findings : [];
52872
+ const rawArray = Array.isArray(record2.normalizedFindings) && record2.normalizedFindings.length > 0 ? record2.normalizedFindings : Array.isArray(record2.findings) ? record2.findings : [];
52974
52873
  const findings = rawArray.filter(isFinding);
52975
52874
  const success2 = "success" in record2 ? record2.success === true : ("passed" in record2) ? record2.passed === true : findings.length === 0;
52976
52875
  return success2 ? [] : findings;
@@ -53260,7 +53159,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
53260
53159
  config: { maxAttemptsTotal: rectification.maxAttempts, validatorRetries: 1 },
53261
53160
  validate: async (_validateCtx, opts) => {
53262
53161
  if (ctx.runtime.signal?.aborted)
53263
- return [];
53162
+ return { findings: [], shortCircuited: false };
53264
53163
  const lite = (opts?.mode ?? "full") === "lite";
53265
53164
  const phases = phasesToRevalidate(opts?.strategiesRun, validationPhases);
53266
53165
  getSafeLogger()?.debug("story-orchestrator", "rectification validate scope", {
@@ -53270,6 +53169,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
53270
53169
  phasesSelected: phases.map((p) => p.kind)
53271
53170
  });
53272
53171
  const findings = [];
53172
+ let shortCircuited = false;
53273
53173
  for (const phase of phases) {
53274
53174
  if (lite && phase.kind === "full-suite-gate") {
53275
53175
  continue;
@@ -53284,10 +53184,12 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
53284
53184
  storyId: ctx.storyId,
53285
53185
  phase: phase.slot.op.name
53286
53186
  });
53187
+ shortCircuited = true;
53287
53188
  break;
53288
53189
  }
53289
53190
  }
53290
- return rectification.postValidate ? await rectification.postValidate(findings, _validateCtx) : findings;
53191
+ const validated = rectification.postValidate ? await rectification.postValidate(findings, _validateCtx) : findings;
53192
+ return { findings: validated, shortCircuited };
53291
53193
  }
53292
53194
  };
53293
53195
  const cycleResult = await _storyOrchestratorDeps.runFixCycle(cycle, ctx, "story-orchestrator-rectification", { callOp: wrappedCallOp });
@@ -53319,6 +53221,9 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
53319
53221
  if (EXHAUSTED_EXIT_REASONS.has(cycleResult.exitReason) && cycleResult.finalFindings.length > 0) {
53320
53222
  return { rectificationExhausted: true, unfixedFindings: cycleResult.finalFindings };
53321
53223
  }
53224
+ if (cycleResult.exitReason === "validate-short-circuit") {
53225
+ return { liteScopeIncomplete: true };
53226
+ }
53322
53227
  return {};
53323
53228
  }
53324
53229
 
@@ -53363,7 +53268,7 @@ class ExecutionPlan {
53363
53268
  }
53364
53269
  }
53365
53270
  const rectResult = await runRectification(this.ctx, this.state, phaseCosts, phaseOutputs);
53366
- if (this.state.rectification && !rectResult.rectificationExhausted) {
53271
+ if (this.state.rectification && (!rectResult.rectificationExhausted || rectResult.liteScopeIncomplete)) {
53367
53272
  let resumeRectifyUsed = false;
53368
53273
  for (const phase of collectOrderedPhases(this.state)) {
53369
53274
  const name = phase.slot.op.name;
@@ -53527,7 +53432,8 @@ var init_story_orchestrator = __esm(() => {
53527
53432
  "max-attempts-per-strategy",
53528
53433
  "bail-when",
53529
53434
  "no-strategy",
53530
- "agent-gave-up"
53435
+ "agent-gave-up",
53436
+ "validate-short-circuit"
53531
53437
  ]);
53532
53438
  TDD_OP_NAMES = new Set(["test-writer", "implementer", "verifier"]);
53533
53439
  STRICT_VERDICT_PHASE_NAMES = new Set([
@@ -57943,7 +57849,7 @@ var package_default;
57943
57849
  var init_package = __esm(() => {
57944
57850
  package_default = {
57945
57851
  name: "@nathapp/nax",
57946
- version: "0.67.18",
57852
+ version: "0.68.0",
57947
57853
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
57948
57854
  type: "module",
57949
57855
  bin: {
@@ -58038,8 +57944,8 @@ var init_version = __esm(() => {
58038
57944
  NAX_VERSION = package_default.version;
58039
57945
  NAX_COMMIT = (() => {
58040
57946
  try {
58041
- if (/^[0-9a-f]{6,10}$/.test("cc7adcea"))
58042
- return "cc7adcea";
57947
+ if (/^[0-9a-f]{6,10}$/.test("d56db412"))
57948
+ return "d56db412";
58043
57949
  } catch {}
58044
57950
  try {
58045
57951
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -59388,6 +59294,18 @@ async function handleRunCompletion(options) {
59388
59294
  }
59389
59295
  }
59390
59296
  }
59297
+ let pluginGateFailed = false;
59298
+ const deferredReview = options.deferredReview;
59299
+ if (deferredReview?.anyFailed) {
59300
+ const failedReviewers = deferredReview.reviewerResults.filter((r) => !r.passed).map((r) => r.name);
59301
+ pluginGateFailed = config2.review.pluginMode === "gating";
59302
+ logger?.warn("review", "Deferred plugin reviewer(s) reported failures", {
59303
+ storyId: runId,
59304
+ failedReviewers,
59305
+ pluginMode: config2.review.pluginMode,
59306
+ gating: pluginGateFailed
59307
+ });
59308
+ }
59391
59309
  const aggSnap = options.runtime.costAggregator.snapshot();
59392
59310
  const aggregatorTotal = aggSnap.totalCostUsd;
59393
59311
  const reportedTotal = Math.max(totalCost, aggregatorTotal);
@@ -59536,7 +59454,8 @@ async function handleRunCompletion(options) {
59536
59454
  failed: finalCounts.failed,
59537
59455
  skipped: finalCounts.skipped,
59538
59456
  pending: finalCounts.pending
59539
- }
59457
+ },
59458
+ pluginGateFailed
59540
59459
  };
59541
59460
  }
59542
59461
  var _runCompletionDeps;
@@ -98101,12 +98020,13 @@ async function runCompletionPhase(options) {
98101
98020
  skipRegression: regressionAlreadyPassed,
98102
98021
  sessionManager: options.sessionManager,
98103
98022
  pluginProviderCache: options.pluginProviderCache,
98023
+ deferredReview: options.deferredReview,
98104
98024
  runtime: options.runtime,
98105
98025
  abortSignal: options.abortSignal
98106
98026
  });
98107
- const { durationMs, runCompletedAt, finalCounts, reportedTotal } = completionResult;
98027
+ const { durationMs, runCompletedAt, finalCounts, reportedTotal, pluginGateFailed } = completionResult;
98108
98028
  if (options.featureDir) {
98109
- const finalStatus = isComplete(options.prd) ? "completed" : "failed";
98029
+ const finalStatus = isComplete(options.prd) && !pluginGateFailed ? "completed" : "failed";
98110
98030
  options.statusWriter.setRunStatus(finalStatus);
98111
98031
  await options.statusWriter.writeFeatureStatus(options.featureDir, reportedTotal, options.iterations);
98112
98032
  }
@@ -98136,7 +98056,8 @@ async function runCompletionPhase(options) {
98136
98056
  return {
98137
98057
  durationMs,
98138
98058
  runCompletedAt,
98139
- acceptancePassed
98059
+ acceptancePassed,
98060
+ pluginGateFailed
98140
98061
  };
98141
98062
  }
98142
98063
 
@@ -98228,7 +98149,14 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
98228
98149
  storiesCompleted,
98229
98150
  totalCost
98230
98151
  });
98231
- return { prd, iterations, storiesCompleted, totalCost, allStoryMetrics };
98152
+ return {
98153
+ prd,
98154
+ iterations,
98155
+ storiesCompleted,
98156
+ totalCost,
98157
+ allStoryMetrics,
98158
+ deferredReview: unifiedResult.deferredReview
98159
+ };
98232
98160
  }
98233
98161
 
98234
98162
  // src/execution/runner-setup.ts
@@ -98392,13 +98320,14 @@ async function run(options) {
98392
98320
  sessionManager,
98393
98321
  agentManager,
98394
98322
  pluginProviderCache,
98323
+ deferredReview: executionResult.deferredReview,
98395
98324
  runtime,
98396
98325
  abortSignal: shutdownController.signal
98397
98326
  });
98398
- const { durationMs, acceptancePassed } = completionResult;
98327
+ const { durationMs, acceptancePassed, pluginGateFailed } = completionResult;
98399
98328
  runCompleted = true;
98400
98329
  return {
98401
- success: isComplete(prd) && acceptancePassed,
98330
+ success: isComplete(prd) && acceptancePassed && !pluginGateFailed,
98402
98331
  iterations,
98403
98332
  storiesCompleted,
98404
98333
  totalCost,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.67.18",
3
+ "version": "0.68.0",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {