@nathapp/nax 0.75.4 → 0.75.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 +662 -331
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -17823,6 +17823,7 @@ var init_schemas3 = __esm(() => {
17823
17823
  quality: exports_external.string().nullable().default(null)
17824
17824
  }).default({ spec: null, quality: null }),
17825
17825
  escalate: exports_external.object({ telegram: exports_external.boolean().default(true) }).default({ telegram: true }),
17826
+ notify: exports_external.object({ mode: exports_external.enum(["escalation", "always", "off"]).default("escalation") }).default({ mode: "escalation" }),
17826
17827
  timeouts: exports_external.object({
17827
17828
  acceptanceMs: exports_external.number().int().positive().default(600000),
17828
17829
  gateMs: exports_external.number().int().positive().default(900000),
@@ -17835,6 +17836,7 @@ var init_schemas3 = __esm(() => {
17835
17836
  defaultAgent: null,
17836
17837
  reviewers: { spec: null, quality: null },
17837
17838
  escalate: { telegram: true },
17839
+ notify: { mode: "escalation" },
17838
17840
  timeouts: { acceptanceMs: 600000, gateMs: 900000, flowMs: 5400000, stepMs: null }
17839
17841
  })
17840
17842
  }).default({
@@ -17844,6 +17846,7 @@ var init_schemas3 = __esm(() => {
17844
17846
  defaultAgent: null,
17845
17847
  reviewers: { spec: null, quality: null },
17846
17848
  escalate: { telegram: true },
17849
+ notify: { mode: "escalation" },
17847
17850
  timeouts: { acceptanceMs: 600000, gateMs: 900000, flowMs: 5400000, stepMs: null }
17848
17851
  }
17849
17852
  }),
@@ -42623,7 +42626,7 @@ var package_default;
42623
42626
  var init_package = __esm(() => {
42624
42627
  package_default = {
42625
42628
  name: "@nathapp/nax",
42626
- version: "0.75.4",
42629
+ version: "0.75.5",
42627
42630
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
42628
42631
  type: "module",
42629
42632
  bin: {
@@ -42727,8 +42730,8 @@ var init_version = __esm(() => {
42727
42730
  NAX_VERSION = package_default.version;
42728
42731
  NAX_COMMIT = (() => {
42729
42732
  try {
42730
- if (/^[0-9a-f]{6,10}$/.test("5aee16bf"))
42731
- return "5aee16bf";
42733
+ if (/^[0-9a-f]{6,10}$/.test("c8f74c5f"))
42734
+ return "c8f74c5f";
42732
42735
  } catch {}
42733
42736
  try {
42734
42737
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -57571,10 +57574,13 @@ ${stderr}` };
57571
57574
 
57572
57575
  // src/context/engine/effectiveness.ts
57573
57576
  function tokenize2(text) {
57574
- if (!text)
57575
- return new Set;
57576
- const raw = text.toLowerCase().split(/[\s_\-./:,;()\[\]{}'"!?]+/).filter((t) => t.length >= MIN_TOKEN_LEN2 && !STOPWORDS2.has(t));
57577
- return new Set(raw);
57577
+ const terms = new Set;
57578
+ for (const match of text.matchAll(TOKEN_PATTERN)) {
57579
+ const term = match[0].toLowerCase();
57580
+ if (term.length >= MIN_TOKEN_LEN2 && !STOPWORDS2.has(term))
57581
+ terms.add(term);
57582
+ }
57583
+ return terms;
57578
57584
  }
57579
57585
  function sharedTermCount2(a, b) {
57580
57586
  let count = 0;
@@ -57584,34 +57590,35 @@ function sharedTermCount2(a, b) {
57584
57590
  }
57585
57591
  return count;
57586
57592
  }
57587
- function classifyEffectiveness(chunkSummary, agentOutput, diffText, findingMessages) {
57588
- const summaryTerms = tokenize2(chunkSummary);
57589
- if (summaryTerms.size < MIN_SIGNIFICANT_TERMS) {
57590
- return { signal: "unknown" };
57593
+ function buildEvidenceTerms(agentOutput, diffText, findingMessages) {
57594
+ const diffTerms = diffText ? _effectivenessDeps.tokenize(diffText) : undefined;
57595
+ const outputTerms = agentOutput ? _effectivenessDeps.tokenize(agentOutput) : undefined;
57596
+ let combined;
57597
+ if (diffTerms || outputTerms) {
57598
+ combined = new Set(diffTerms);
57599
+ for (const term of outputTerms ?? [])
57600
+ combined.add(term);
57591
57601
  }
57592
- for (const finding of findingMessages) {
57593
- const findingTerms = tokenize2(finding);
57594
- if (sharedTermCount2(summaryTerms, findingTerms) >= MIN_SIGNIFICANT_TERMS) {
57595
- return {
57596
- signal: "contradicted",
57597
- evidence: finding.slice(0, 200)
57598
- };
57602
+ return {
57603
+ findings: findingMessages.map((message) => ({ message, terms: _effectivenessDeps.tokenize(message) })),
57604
+ diff: diffTerms,
57605
+ combined
57606
+ };
57607
+ }
57608
+ function classifyWithTerms(chunkSummary, evidence) {
57609
+ const summaryTerms = _effectivenessDeps.tokenize(chunkSummary);
57610
+ if (summaryTerms.size < MIN_SIGNIFICANT_TERMS)
57611
+ return { signal: "unknown" };
57612
+ for (const finding of evidence.findings) {
57613
+ if (sharedTermCount2(summaryTerms, finding.terms) >= MIN_SIGNIFICANT_TERMS) {
57614
+ return { signal: "contradicted", evidence: finding.message.slice(0, 200) };
57599
57615
  }
57600
57616
  }
57601
- if (diffText) {
57602
- const diffTerms = tokenize2(diffText);
57603
- if (sharedTermCount2(summaryTerms, diffTerms) >= MIN_SIGNIFICANT_TERMS) {
57604
- return {
57605
- signal: "followed",
57606
- evidence: "terms found in diff"
57607
- };
57608
- }
57617
+ if (evidence.diff && sharedTermCount2(summaryTerms, evidence.diff) >= MIN_SIGNIFICANT_TERMS) {
57618
+ return { signal: "followed", evidence: "terms found in diff" };
57609
57619
  }
57610
- if (diffText || agentOutput) {
57611
- const combinedTerms = tokenize2(`${diffText} ${agentOutput}`);
57612
- if (sharedTermCount2(summaryTerms, combinedTerms) < MIN_SIGNIFICANT_TERMS) {
57613
- return { signal: "ignored" };
57614
- }
57620
+ if (evidence.combined && sharedTermCount2(summaryTerms, evidence.combined) < MIN_SIGNIFICANT_TERMS) {
57621
+ return { signal: "ignored" };
57615
57622
  }
57616
57623
  return { signal: "unknown" };
57617
57624
  }
@@ -57621,16 +57628,18 @@ async function annotateManifestEffectiveness(projectDir, featureId, storyId, {
57621
57628
  findingMessages
57622
57629
  }) {
57623
57630
  const stored = await loadContextManifests(projectDir, storyId, featureId);
57631
+ let evidenceTerms;
57624
57632
  for (const item of stored) {
57625
57633
  const { manifest } = item;
57626
57634
  if (!manifest.chunkSummaries || manifest.includedChunks.length === 0)
57627
57635
  continue;
57636
+ evidenceTerms ??= buildEvidenceTerms(agentOutput, diffText, findingMessages);
57628
57637
  const effectiveness = {};
57629
57638
  for (const id of manifest.includedChunks) {
57630
57639
  const summary = manifest.chunkSummaries[id];
57631
57640
  if (!summary)
57632
57641
  continue;
57633
- effectiveness[id] = classifyEffectiveness(summary, agentOutput, diffText, findingMessages);
57642
+ effectiveness[id] = classifyWithTerms(summary, evidenceTerms);
57634
57643
  }
57635
57644
  if (Object.keys(effectiveness).length === 0)
57636
57645
  continue;
@@ -57648,12 +57657,13 @@ async function annotateManifestEffectiveness(projectDir, featureId, storyId, {
57648
57657
  }
57649
57658
  }
57650
57659
  }
57651
- var _effectivenessDeps, MIN_SIGNIFICANT_TERMS = 3, STOPWORDS2, MIN_TOKEN_LEN2 = 4;
57660
+ var _effectivenessDeps, MIN_SIGNIFICANT_TERMS = 3, STOPWORDS2, MIN_TOKEN_LEN2 = 4, TOKEN_PATTERN;
57652
57661
  var init_effectiveness = __esm(() => {
57653
57662
  init_logger2();
57654
57663
  init_manifest_store();
57655
57664
  _effectivenessDeps = {
57656
- getLogger
57665
+ getLogger,
57666
+ tokenize: tokenize2
57657
57667
  };
57658
57668
  STOPWORDS2 = new Set([
57659
57669
  "the",
@@ -57695,6 +57705,7 @@ var init_effectiveness = __esm(() => {
57695
57705
  "you",
57696
57706
  "your"
57697
57707
  ]);
57708
+ TOKEN_PATTERN = /[^\s_\-./:,;()\[\]{}'"!?]+/g;
57698
57709
  });
57699
57710
 
57700
57711
  // src/execution/progress.ts
@@ -57711,19 +57722,61 @@ async function appendProgress(featureDir, storyId, status, message) {
57711
57722
  var init_progress = () => {};
57712
57723
 
57713
57724
  // src/pipeline/stages/completion.ts
57725
+ function logHighMemoryCheckpoint(logger, ctx) {
57726
+ const usage = process.memoryUsage();
57727
+ if (usage.heapUsed < HIGH_MEMORY_TELEMETRY_BYTES && usage.rss < HIGH_MEMORY_TELEMETRY_BYTES)
57728
+ return;
57729
+ logger.debug("completion.memory", "High memory at completion boundary", {
57730
+ storyId: ctx.story.id,
57731
+ heapUsedBytes: usage.heapUsed,
57732
+ rssBytes: usage.rss,
57733
+ externalBytes: usage.external,
57734
+ arrayBuffersBytes: usage.arrayBuffers,
57735
+ agentOutputChars: ctx.agentResult?.output.length ?? 0
57736
+ });
57737
+ }
57738
+ async function readTextStreamPrefix(stream, maxChars) {
57739
+ const reader = stream.getReader();
57740
+ const decoder = new TextDecoder;
57741
+ let output = "";
57742
+ try {
57743
+ while (true) {
57744
+ const { done, value } = await reader.read();
57745
+ if (done)
57746
+ break;
57747
+ if (output.length >= maxChars)
57748
+ continue;
57749
+ const decoded = decoder.decode(value, { stream: true });
57750
+ output += decoded.slice(0, maxChars - output.length);
57751
+ }
57752
+ if (output.length < maxChars) {
57753
+ output += decoder.decode().slice(0, maxChars - output.length);
57754
+ }
57755
+ return output;
57756
+ } finally {
57757
+ reader.releaseLock();
57758
+ }
57759
+ }
57714
57760
  async function getDiffText(workdir, baseRef) {
57715
57761
  if (!baseRef)
57716
57762
  return "";
57717
57763
  try {
57718
- const proc = Bun.spawn(["git", "diff", `${baseRef}..HEAD`], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
57719
- const output = await new Response(proc.stdout).text();
57720
- await proc.exited;
57721
- return output.slice(0, 8000);
57764
+ const proc = _completionDeps.spawn(["git", "diff", `${baseRef}..HEAD`], {
57765
+ cwd: workdir,
57766
+ stdout: "pipe",
57767
+ stderr: "pipe"
57768
+ });
57769
+ const [output] = await Promise.all([
57770
+ readTextStreamPrefix(proc.stdout, MAX_EFFECTIVENESS_DIFF_CHARS),
57771
+ readTextStreamPrefix(proc.stderr, 0),
57772
+ proc.exited
57773
+ ]);
57774
+ return output;
57722
57775
  } catch {
57723
57776
  return "";
57724
57777
  }
57725
57778
  }
57726
- var completionStage, _completionDeps;
57779
+ var MAX_EFFECTIVENESS_DIFF_CHARS = 8000, HIGH_MEMORY_TELEMETRY_BYTES, completionStage, _completionDeps;
57727
57780
  var init_completion = __esm(() => {
57728
57781
  init_semantic_verdict();
57729
57782
  init_effectiveness();
@@ -57733,6 +57786,7 @@ var init_completion = __esm(() => {
57733
57786
  init_metrics();
57734
57787
  init_prd();
57735
57788
  init_event_bus();
57789
+ HIGH_MEMORY_TELEMETRY_BYTES = 512 * 1024 * 1024;
57736
57790
  completionStage = {
57737
57791
  name: "completion",
57738
57792
  enabled: () => true,
@@ -57802,6 +57856,7 @@ var init_completion = __esm(() => {
57802
57856
  if (persistPrd) {
57803
57857
  await _completionDeps.savePRD(ctx.prd, prdPath);
57804
57858
  }
57859
+ logHighMemoryCheckpoint(logger, ctx);
57805
57860
  const updatedCounts = countStories(ctx.prd);
57806
57861
  logger.info("completion", "Progress update", {
57807
57862
  storyId: ctx.story.id,
@@ -57817,7 +57872,9 @@ var init_completion = __esm(() => {
57817
57872
  checkReviewGate,
57818
57873
  persistSemanticVerdict,
57819
57874
  savePRD,
57820
- getDiffText
57875
+ getDiffText,
57876
+ readTextStreamPrefix,
57877
+ spawn: Bun.spawn
57821
57878
  };
57822
57879
  });
57823
57880
 
@@ -58520,161 +58577,6 @@ var init_paths3 = __esm(() => {
58520
58577
  init_paths();
58521
58578
  });
58522
58579
 
58523
- // src/execution/non-blocking-fix.ts
58524
- function actionableAdvisoryFindings(findings) {
58525
- return findings.filter((f) => f.actionRequired !== false);
58526
- }
58527
- function shouldRunNonBlockingFix(cfg, advisoryCount) {
58528
- return cfg?.enabled === true && advisoryCount > 0;
58529
- }
58530
- function nonBlockingExcludePhases() {
58531
- return REVIEW_PHASE_KINDS;
58532
- }
58533
- function nonBlockingExtraPhases(cfg) {
58534
- return (cfg.scope === "both" || cfg.scope === "triage") && cfg.verifierGuard ? ["verifier"] : [];
58535
- }
58536
- function createMeasureSourceDiff(args) {
58537
- const packageDirRel = packageDirRelative(args.projectDir, args.packageDir);
58538
- return async (workdir, fromRef) => {
58539
- const resolved = await _nonBlockingFixDeps.resolveTestFilePatterns(args.config, args.projectDir, packageDirRel);
58540
- const isTestFile3 = createTestFileClassifier(resolved);
58541
- const proc = _nonBlockingFixDeps.spawn(["git", "diff", "--numstat", fromRef], {
58542
- cwd: workdir,
58543
- stdout: "pipe",
58544
- stderr: "pipe"
58545
- });
58546
- const stdout = await Bun.readableStreamToText(proc.stdout);
58547
- const stderr = await Bun.readableStreamToText(proc.stderr);
58548
- const exitCode = await proc.exited;
58549
- if (exitCode !== 0) {
58550
- const detail = stderr.trim() || `exit ${exitCode}`;
58551
- throw new Error(`[non-blocking-fix] git diff --numstat failed: ${detail}`);
58552
- }
58553
- let fileCount = 0;
58554
- let sourceLineCount = 0;
58555
- for (const line of stdout.trim().split(`
58556
- `).filter(Boolean)) {
58557
- const [addedStr, _deletedStr, filePath] = line.split("\t");
58558
- if (!filePath || isTestFile3(filePath))
58559
- continue;
58560
- fileCount += 1;
58561
- const added = Number.parseInt(addedStr ?? "", 10);
58562
- if (Number.isFinite(added))
58563
- sourceLineCount += added;
58564
- }
58565
- return { fileCount, sourceLineCount };
58566
- };
58567
- }
58568
- async function runNonBlockingFix(args, overrides = {}) {
58569
- const _deps = { ...DEFAULT_DEPS, ...overrides };
58570
- const logger = getSafeLogger();
58571
- if (!shouldRunNonBlockingFix(args.cfg, args.advisoryFindings.length)) {
58572
- return { ran: false, kept: false, restored: false };
58573
- }
58574
- const phaseOutputsSnapshot = { ...args.phaseOutputs };
58575
- const phaseCostsSnapshot = { ...args.phaseCosts };
58576
- let restoreRef;
58577
- try {
58578
- restoreRef = await _deps.captureSnapshotRef(args.workdir, args.storyId);
58579
- } catch (err) {
58580
- logger?.warn("non-blocking-fix", "snapshot capture failed \u2014 skipping best-effort pass (no rollback point)", {
58581
- storyId: args.storyId,
58582
- error: err instanceof Error ? err.message : String(err)
58583
- });
58584
- return { ran: false, kept: false, restored: false };
58585
- }
58586
- const maxAttempts = 1 + args.cfg.regressionAttempts;
58587
- let exhausted = false;
58588
- try {
58589
- const result = await args.runRectify(maxAttempts);
58590
- exhausted = result.rectificationExhausted === true;
58591
- } catch (err) {
58592
- logger?.warn("non-blocking-fix", "best-effort pass threw \u2014 restoring", {
58593
- storyId: args.storyId,
58594
- error: err instanceof Error ? err.message : String(err)
58595
- });
58596
- exhausted = true;
58597
- }
58598
- if (!exhausted) {
58599
- const gateVerdict = args.keptTreeRegressed?.();
58600
- if (gateVerdict?.regressed) {
58601
- logGateRegression(logger, args.storyId, "kept tree regressed the full-suite gate \u2014 restoring (ADR-024 \xA73)", gateVerdict);
58602
- return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
58603
- }
58604
- const cap = args.cfg.sourceDiffCap;
58605
- if (cap) {
58606
- let metrics;
58607
- try {
58608
- metrics = await _deps.measureSourceDiff(args.workdir, restoreRef);
58609
- } catch (err) {
58610
- logger?.warn("non-blocking-fix", "source-diff measurement threw \u2014 restoring", {
58611
- storyId: args.storyId,
58612
- error: err instanceof Error ? err.message : String(err)
58613
- });
58614
- return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
58615
- }
58616
- if (metrics.fileCount > cap.maxFiles || metrics.sourceLineCount > cap.maxLines) {
58617
- logger?.info("non-blocking-fix", "source diff exceeded cap \u2014 restoring", {
58618
- storyId: args.storyId,
58619
- fileCount: metrics.fileCount,
58620
- sourceLineCount: metrics.sourceLineCount,
58621
- cap
58622
- });
58623
- return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
58624
- }
58625
- }
58626
- logger?.info("non-blocking-fix", "best-effort fix kept", { storyId: args.storyId });
58627
- return { ran: true, kept: true, restored: false };
58628
- }
58629
- const exhaustedGateVerdict = args.keptTreeRegressed?.();
58630
- if (exhaustedGateVerdict?.regressed) {
58631
- logGateRegression(logger, args.storyId, "best-effort fix exhausted with the full-suite gate red", exhaustedGateVerdict);
58632
- }
58633
- return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
58634
- }
58635
- function logGateRegression(logger, storyId, message, verdict) {
58636
- logger?.info("non-blocking-fix", message, {
58637
- storyId,
58638
- regressedKeys: verdict.regressedKeys.slice(0, MAX_LOGGED_REGRESSED_KEYS),
58639
- regressedKeyCount: verdict.regressedKeys.length,
58640
- baselineKeySize: verdict.baselineKeySize,
58641
- keyless: verdict.keyless,
58642
- memoExcludedKeyCount: verdict.memoExcludedKeys.length,
58643
- flakeTriageRan: false
58644
- });
58645
- }
58646
- async function restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger) {
58647
- await _deps.rollbackToRef(args.workdir, restoreRef);
58648
- for (const key of Object.keys(args.phaseOutputs))
58649
- delete args.phaseOutputs[key];
58650
- Object.assign(args.phaseOutputs, phaseOutputsSnapshot);
58651
- for (const key of Object.keys(args.phaseCosts))
58652
- delete args.phaseCosts[key];
58653
- Object.assign(args.phaseCosts, phaseCostsSnapshot);
58654
- logger?.info("non-blocking-fix", "best-effort fix exhausted \u2014 restored to adversarial-passed", {
58655
- storyId: args.storyId
58656
- });
58657
- return { ran: true, kept: false, restored: true };
58658
- }
58659
- var REVIEW_PHASE_KINDS, MAX_LOGGED_REGRESSED_KEYS = 10, _nonBlockingFixDeps, DEFAULT_DEPS;
58660
- var init_non_blocking_fix = __esm(() => {
58661
- init_logger2();
58662
- init_rollback();
58663
- init_test_runners();
58664
- init_bun_deps();
58665
- init_paths3();
58666
- REVIEW_PHASE_KINDS = ["semantic-review", "adversarial-review"];
58667
- _nonBlockingFixDeps = {
58668
- spawn: typedSpawn,
58669
- resolveTestFilePatterns
58670
- };
58671
- DEFAULT_DEPS = {
58672
- captureSnapshotRef,
58673
- rollbackToRef,
58674
- measureSourceDiff: async () => ({ fileCount: 0, sourceLineCount: 0 })
58675
- };
58676
- });
58677
-
58678
58580
  // src/execution/story-orchestrator/types.ts
58679
58581
  var EXHAUSTED_EXIT_REASONS, TDD_OP_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES, STRICT_VERDICT_PHASE_NAMES;
58680
58582
  var init_types9 = __esm(() => {
@@ -58805,7 +58707,8 @@ function gateFindingKey(finding) {
58805
58707
  function isQuarantinedFlake(finding, quarantineMemo) {
58806
58708
  if (finding.source !== "test-runner")
58807
58709
  return false;
58808
- return quarantineMemo?.has(gateFindingKey(finding)) === true;
58710
+ const key = gateFindingKey(finding);
58711
+ return key !== KEYLESS_GATE_FAILURE_KEY && quarantineMemo?.has(key) === true;
58809
58712
  }
58810
58713
  function describeGateRegression(input) {
58811
58714
  const { gateOutput, baselineKeys, gateName, storyId, quarantineMemo } = input;
@@ -58856,6 +58759,245 @@ var init_phase_eval = __esm(() => {
58856
58759
  init_types9();
58857
58760
  });
58858
58761
 
58762
+ // src/execution/story-orchestrator/nbf-flake-triage.ts
58763
+ function isCandidate(input) {
58764
+ if (input.finding.source !== "test-runner" || input.finding.category !== "failed-test")
58765
+ return false;
58766
+ const key = gateFindingKey(input.finding);
58767
+ return !input.transactionInput.baselineKeys.has(key) && !input.memo.has(key) && !input.attemptedKeys.has(key);
58768
+ }
58769
+ function createNbfFlakeTriageTransaction(input) {
58770
+ const pendingKeys = new Set;
58771
+ const attemptedKeys = new Set;
58772
+ let flakeTriageRan = false;
58773
+ const memo2 = {
58774
+ has: (key) => pendingKeys.has(key) || input.baseMemo?.has(key) === true,
58775
+ add: (key) => pendingKeys.add(key)
58776
+ };
58777
+ return {
58778
+ memo: memo2,
58779
+ get flakeTriageRan() {
58780
+ return flakeTriageRan;
58781
+ },
58782
+ candidates: (findings) => findings.filter((finding) => isCandidate({ finding, transactionInput: input, memo: memo2, attemptedKeys })),
58783
+ recordAttempt: (findings, ran) => {
58784
+ if (!ran)
58785
+ return;
58786
+ flakeTriageRan = true;
58787
+ for (const finding of findings)
58788
+ attemptedKeys.add(gateFindingKey(finding));
58789
+ },
58790
+ commit: () => {
58791
+ for (const key of pendingKeys)
58792
+ input.baseMemo?.add(key);
58793
+ }
58794
+ };
58795
+ }
58796
+ async function triageNbfGate(input) {
58797
+ const candidates = input.transaction.candidates(extractPhaseFindings(input.output));
58798
+ if (candidates.length === 0)
58799
+ return;
58800
+ const record2 = input.output;
58801
+ const rawOutput = typeof record2.rawOutput === "string" ? record2.rawOutput : "";
58802
+ try {
58803
+ const [, report] = await input.triage(candidates, {
58804
+ ctx: input.ctx,
58805
+ rawOutput,
58806
+ quarantineMemo: input.transaction.memo
58807
+ });
58808
+ const ran = report.flakeTriageRan ?? true;
58809
+ if (ran) {
58810
+ for (const key of report.quarantinedKeys)
58811
+ input.transaction.memo.add(key);
58812
+ }
58813
+ input.transaction.recordAttempt(candidates, ran);
58814
+ } catch (err) {
58815
+ getSafeLogger()?.warn("story-orchestrator", "NBF flake triage threw \u2014 keeping findings blocking", {
58816
+ storyId: input.ctx.storyId,
58817
+ gateName: input.gateName,
58818
+ error: err instanceof Error ? err.message : String(err)
58819
+ });
58820
+ }
58821
+ }
58822
+ var init_nbf_flake_triage = __esm(() => {
58823
+ init_logger2();
58824
+ init_phase_eval();
58825
+ });
58826
+
58827
+ // src/execution/non-blocking-fix.ts
58828
+ function actionableAdvisoryFindings(findings) {
58829
+ return findings.filter((f) => f.actionRequired !== false);
58830
+ }
58831
+ function shouldRunNonBlockingFix(cfg, advisoryCount) {
58832
+ return cfg?.enabled === true && advisoryCount > 0;
58833
+ }
58834
+ function nonBlockingExcludePhases() {
58835
+ return REVIEW_PHASE_KINDS;
58836
+ }
58837
+ function nonBlockingExtraPhases(cfg) {
58838
+ return (cfg.scope === "both" || cfg.scope === "triage") && cfg.verifierGuard ? ["verifier"] : [];
58839
+ }
58840
+ function createMeasureSourceDiff(args) {
58841
+ const packageDirRel = packageDirRelative(args.projectDir, args.packageDir);
58842
+ return async (workdir, fromRef) => {
58843
+ const resolved = await _nonBlockingFixDeps.resolveTestFilePatterns(args.config, args.projectDir, packageDirRel);
58844
+ const isTestFile3 = createTestFileClassifier(resolved);
58845
+ const proc = _nonBlockingFixDeps.spawn(["git", "diff", "--numstat", fromRef], {
58846
+ cwd: workdir,
58847
+ stdout: "pipe",
58848
+ stderr: "pipe"
58849
+ });
58850
+ const stdout = await Bun.readableStreamToText(proc.stdout);
58851
+ const stderr = await Bun.readableStreamToText(proc.stderr);
58852
+ const exitCode = await proc.exited;
58853
+ if (exitCode !== 0) {
58854
+ const detail = stderr.trim() || `exit ${exitCode}`;
58855
+ throw new Error(`[non-blocking-fix] git diff --numstat failed: ${detail}`);
58856
+ }
58857
+ let fileCount = 0;
58858
+ let sourceLineCount = 0;
58859
+ for (const line of stdout.trim().split(`
58860
+ `).filter(Boolean)) {
58861
+ const [addedStr, _deletedStr, filePath] = line.split("\t");
58862
+ if (!filePath || isTestFile3(filePath))
58863
+ continue;
58864
+ fileCount += 1;
58865
+ const added = Number.parseInt(addedStr ?? "", 10);
58866
+ if (Number.isFinite(added))
58867
+ sourceLineCount += added;
58868
+ }
58869
+ return { fileCount, sourceLineCount };
58870
+ };
58871
+ }
58872
+ async function runNonBlockingFix(args, overrides = {}) {
58873
+ const _deps = { ...DEFAULT_DEPS, ...overrides };
58874
+ const logger = getSafeLogger();
58875
+ if (!shouldRunNonBlockingFix(args.cfg, args.advisoryFindings.length)) {
58876
+ return { ran: false, kept: false, restored: false };
58877
+ }
58878
+ const phaseOutputsSnapshot = { ...args.phaseOutputs };
58879
+ const phaseCostsSnapshot = { ...args.phaseCosts };
58880
+ let restoreRef;
58881
+ try {
58882
+ restoreRef = await _deps.captureSnapshotRef(args.workdir, args.storyId);
58883
+ } catch (err) {
58884
+ logger?.warn("non-blocking-fix", "snapshot capture failed \u2014 skipping best-effort pass (no rollback point)", {
58885
+ storyId: args.storyId,
58886
+ error: err instanceof Error ? err.message : String(err)
58887
+ });
58888
+ return { ran: false, kept: false, restored: false };
58889
+ }
58890
+ const maxAttempts = 1 + args.cfg.regressionAttempts;
58891
+ const flakeTriage = createNbfFlakeTriageTransaction({
58892
+ baseMemo: args.quarantineMemo,
58893
+ baselineKeys: args.gateBaselineKeys ?? new Set
58894
+ });
58895
+ let exhausted = false;
58896
+ try {
58897
+ const result = await args.runRectify(maxAttempts, flakeTriage);
58898
+ exhausted = result.rectificationExhausted === true;
58899
+ } catch (err) {
58900
+ logger?.warn("non-blocking-fix", "best-effort pass threw \u2014 restoring", {
58901
+ storyId: args.storyId,
58902
+ error: err instanceof Error ? err.message : String(err)
58903
+ });
58904
+ exhausted = true;
58905
+ }
58906
+ if (!exhausted) {
58907
+ const gateVerdict = args.keptTreeRegressed?.(flakeTriage.memo);
58908
+ if (gateVerdict?.regressed) {
58909
+ logGateRegression({
58910
+ logger,
58911
+ storyId: args.storyId,
58912
+ message: "kept tree regressed the full-suite gate \u2014 restoring (ADR-024 \xA73)",
58913
+ verdict: gateVerdict,
58914
+ flakeTriageRan: flakeTriage.flakeTriageRan
58915
+ });
58916
+ return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
58917
+ }
58918
+ const cap = args.cfg.sourceDiffCap;
58919
+ if (cap) {
58920
+ let metrics;
58921
+ try {
58922
+ metrics = await _deps.measureSourceDiff(args.workdir, restoreRef);
58923
+ } catch (err) {
58924
+ logger?.warn("non-blocking-fix", "source-diff measurement threw \u2014 restoring", {
58925
+ storyId: args.storyId,
58926
+ error: err instanceof Error ? err.message : String(err)
58927
+ });
58928
+ return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
58929
+ }
58930
+ if (metrics.fileCount > cap.maxFiles || metrics.sourceLineCount > cap.maxLines) {
58931
+ logger?.info("non-blocking-fix", "source diff exceeded cap \u2014 restoring", {
58932
+ storyId: args.storyId,
58933
+ fileCount: metrics.fileCount,
58934
+ sourceLineCount: metrics.sourceLineCount,
58935
+ cap
58936
+ });
58937
+ return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
58938
+ }
58939
+ }
58940
+ flakeTriage.commit();
58941
+ logger?.info("non-blocking-fix", "best-effort fix kept", { storyId: args.storyId });
58942
+ return { ran: true, kept: true, restored: false };
58943
+ }
58944
+ const exhaustedGateVerdict = args.keptTreeRegressed?.(flakeTriage.memo);
58945
+ if (exhaustedGateVerdict?.regressed) {
58946
+ logGateRegression({
58947
+ logger,
58948
+ storyId: args.storyId,
58949
+ message: "best-effort fix exhausted with the full-suite gate red",
58950
+ verdict: exhaustedGateVerdict,
58951
+ flakeTriageRan: flakeTriage.flakeTriageRan
58952
+ });
58953
+ }
58954
+ return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
58955
+ }
58956
+ function logGateRegression(input) {
58957
+ const { logger, storyId, message, verdict, flakeTriageRan } = input;
58958
+ logger?.info("non-blocking-fix", message, {
58959
+ storyId,
58960
+ regressedKeys: verdict.regressedKeys.slice(0, MAX_LOGGED_REGRESSED_KEYS),
58961
+ regressedKeyCount: verdict.regressedKeys.length,
58962
+ baselineKeySize: verdict.baselineKeySize,
58963
+ keyless: verdict.keyless,
58964
+ memoExcludedKeyCount: verdict.memoExcludedKeys.length,
58965
+ flakeTriageRan
58966
+ });
58967
+ }
58968
+ async function restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger) {
58969
+ await _deps.rollbackToRef(args.workdir, restoreRef);
58970
+ for (const key of Object.keys(args.phaseOutputs))
58971
+ delete args.phaseOutputs[key];
58972
+ Object.assign(args.phaseOutputs, phaseOutputsSnapshot);
58973
+ for (const key of Object.keys(args.phaseCosts))
58974
+ delete args.phaseCosts[key];
58975
+ Object.assign(args.phaseCosts, phaseCostsSnapshot);
58976
+ logger?.info("non-blocking-fix", "best-effort fix exhausted \u2014 restored to adversarial-passed", {
58977
+ storyId: args.storyId
58978
+ });
58979
+ return { ran: true, kept: false, restored: true };
58980
+ }
58981
+ var REVIEW_PHASE_KINDS, MAX_LOGGED_REGRESSED_KEYS = 10, _nonBlockingFixDeps, DEFAULT_DEPS;
58982
+ var init_non_blocking_fix = __esm(() => {
58983
+ init_logger2();
58984
+ init_rollback();
58985
+ init_test_runners();
58986
+ init_bun_deps();
58987
+ init_paths3();
58988
+ init_nbf_flake_triage();
58989
+ REVIEW_PHASE_KINDS = ["semantic-review", "adversarial-review"];
58990
+ _nonBlockingFixDeps = {
58991
+ spawn: typedSpawn,
58992
+ resolveTestFilePatterns
58993
+ };
58994
+ DEFAULT_DEPS = {
58995
+ captureSnapshotRef,
58996
+ rollbackToRef,
58997
+ measureSourceDiff: async () => ({ fileCount: 0, sourceLineCount: 0 })
58998
+ };
58999
+ });
59000
+
58859
59001
  // src/execution/story-orchestrator/phase-state.ts
58860
59002
  function isSlot(value) {
58861
59003
  return value !== null && typeof value === "object" && "op" in value && "input" in value && typeof value.op?.kind === "string";
@@ -59049,15 +59191,15 @@ var init_verification = __esm(() => {
59049
59191
  });
59050
59192
 
59051
59193
  // src/execution/story-orchestrator/flake-triage-seam.ts
59052
- var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
59194
+ var productionTriageSeam = async (gateFindings, { ctx, rawOutput, quarantineMemo }) => {
59053
59195
  const config2 = ctx.packageView.config;
59054
59196
  const flakeDetection = config2.execution?.flakeDetection;
59055
59197
  if (!flakeDetection?.enabled) {
59056
- return [gateFindings, { quarantinedKeys: [] }];
59198
+ return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
59057
59199
  }
59058
59200
  const framework = detectFramework(rawOutput);
59059
59201
  if (framework === "unknown") {
59060
- return [gateFindings, { quarantinedKeys: [] }];
59202
+ return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
59061
59203
  }
59062
59204
  const workdir = ctx.runtime.workdir;
59063
59205
  const storyWorkdir = ctx.story?.workdir;
@@ -59066,11 +59208,11 @@ var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
59066
59208
  const { testCommand } = await resolveQualityTestCommands2(config2, workdir, storyWorkdir);
59067
59209
  const baseCommand = testCommand ?? config2.quality?.commands?.test;
59068
59210
  if (!baseCommand) {
59069
- return [gateFindings, { quarantinedKeys: [] }];
59211
+ return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
59070
59212
  }
59071
59213
  const diff = await resolveFlakeBaselineDiff(config2, workdir, storyWorkdir);
59072
59214
  if (diff === null) {
59073
- return [gateFindings, { quarantinedKeys: [] }];
59215
+ return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
59074
59216
  }
59075
59217
  const result = await triageFlakyFindings({
59076
59218
  findings: gateFindings,
@@ -59079,15 +59221,15 @@ var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
59079
59221
  baseCommand,
59080
59222
  cwd: ctx.packageDir,
59081
59223
  framework,
59082
- quarantineMemo: ctx.runtime.quarantineMemo
59224
+ quarantineMemo: quarantineMemo ?? ctx.runtime.quarantineMemo
59083
59225
  });
59084
- return [result.findings, { quarantinedKeys: result.quarantineReport.keys }];
59226
+ return [result.findings, { quarantinedKeys: result.quarantineReport.keys, flakeTriageRan: true }];
59085
59227
  } catch (err) {
59086
59228
  getSafeLogger()?.warn("story-orchestrator", "Flake triage seam failed resolving context \u2014 keeping findings blocking (no quarantine)", {
59087
59229
  storyId: ctx.storyId,
59088
59230
  error: errorMessage(err)
59089
59231
  });
59090
- return [gateFindings, { quarantinedKeys: [] }];
59232
+ return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
59091
59233
  }
59092
59234
  };
59093
59235
  var init_flake_triage_seam = __esm(() => {
@@ -59590,6 +59732,9 @@ function collectRectificationPhases(state) {
59590
59732
  state.adversarialReview
59591
59733
  ].filter((phase) => phase !== undefined);
59592
59734
  }
59735
+ function isQuarantinedOnlyGateFailure(phase, rawFindings, blockingFindings) {
59736
+ return phase.kind === "full-suite-gate" && rawFindings.length > 0 && blockingFindings.length === 0;
59737
+ }
59593
59738
  async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides) {
59594
59739
  const rectification2 = state.rectification;
59595
59740
  const baseValidationPhases = collectRectificationPhases(state);
@@ -59656,9 +59801,21 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
59656
59801
  if (shouldSkipPhaseForRectification({ phase, state, phaseOutputs, nbfPath }))
59657
59802
  continue;
59658
59803
  const output = phaseOutputs[phase.slot.op.name];
59804
+ const nbfFlakeTriage = overrides?.nbfFlakeTriage;
59805
+ if (nbfPath && phase.kind === "full-suite-gate" && nbfFlakeTriage) {
59806
+ await triageNbfGate({
59807
+ output,
59808
+ gateName: phase.slot.op.name,
59809
+ ctx,
59810
+ transaction: nbfFlakeTriage,
59811
+ triage: _storyOrchestratorDeps.triage
59812
+ });
59813
+ }
59659
59814
  const phaseFindings = extractPhaseFindings(output);
59660
- findings.push(...nbfPath ? phaseFindings.filter((f) => !isQuarantinedFlake(f, ctx.runtime.quarantineMemo)) : phaseFindings);
59661
- if (!phasePassed(phase.slot.op.name, output, ctx.storyId)) {
59815
+ const blockingFindings = nbfPath ? phaseFindings.filter((finding) => !isQuarantinedFlake(finding, nbfFlakeTriage?.memo ?? ctx.runtime.quarantineMemo)) : phaseFindings;
59816
+ findings.push(...blockingFindings);
59817
+ const quarantinedOnly = nbfPath && isQuarantinedOnlyGateFailure(phase, phaseFindings, blockingFindings);
59818
+ if (!phasePassed(phase.slot.op.name, output, ctx.storyId) && !quarantinedOnly) {
59662
59819
  getSafeLogger()?.warn("story-orchestrator", "Short-circuiting revalidation on phase failure", {
59663
59820
  storyId: ctx.storyId,
59664
59821
  phase: phase.slot.op.name
@@ -59716,6 +59873,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
59716
59873
  }
59717
59874
  var init_rectification = __esm(() => {
59718
59875
  init_logger2();
59876
+ init_nbf_flake_triage();
59719
59877
  init_phase_eval();
59720
59878
  init_phase_eval();
59721
59879
  init_run_phase();
@@ -59732,13 +59890,13 @@ class ExecutionPlan {
59732
59890
  this.state = state;
59733
59891
  this.isThreeSession = isThreeSession;
59734
59892
  }
59735
- describeGateRegressionNow(phaseOutputs, gateName, baselineKeys) {
59893
+ describeGateRegressionNow(phaseOutputs, gateName, options) {
59736
59894
  return describeGateRegression({
59737
59895
  gateOutput: gateName === undefined ? undefined : phaseOutputs[gateName],
59738
- baselineKeys,
59896
+ baselineKeys: options.baselineKeys,
59739
59897
  gateName,
59740
59898
  storyId: this.ctx.storyId,
59741
- quarantineMemo: this.ctx.runtime.quarantineMemo
59899
+ quarantineMemo: options.quarantineMemo ?? this.ctx.runtime.quarantineMemo
59742
59900
  });
59743
59901
  }
59744
59902
  phaseNames() {
@@ -59886,15 +60044,21 @@ class ExecutionPlan {
59886
60044
  cfg: advCfg,
59887
60045
  phaseOutputs,
59888
60046
  phaseCosts,
59889
- runRectify: (maxAttempts) => runRectification(this.ctx, this.state, phaseCosts, phaseOutputs, {
60047
+ quarantineMemo: this.ctx.runtime.quarantineMemo,
60048
+ gateBaselineKeys: preRectGateFailureKeys,
60049
+ runRectify: (maxAttempts, nbfFlakeTriage) => runRectification(this.ctx, this.state, phaseCosts, phaseOutputs, {
59890
60050
  initialFindings: advisoryFindings,
60051
+ nbfFlakeTriage,
59891
60052
  strategies: this.state.nonBlockingFixStrategies ?? [],
59892
60053
  excludePhaseKinds: nonBlockingExcludePhases(),
59893
60054
  extraRevalidationKinds: nonBlockingExtraPhases(advCfg),
59894
60055
  maxAttempts,
59895
60056
  postValidate: this.state.nonBlockingFixPostValidate
59896
60057
  }),
59897
- keptTreeRegressed: () => this.describeGateRegressionNow(phaseOutputs, gateName, preRectGateFailureKeys)
60058
+ keptTreeRegressed: (quarantineMemo) => this.describeGateRegressionNow(phaseOutputs, gateName, {
60059
+ baselineKeys: preRectGateFailureKeys,
60060
+ quarantineMemo
60061
+ })
59898
60062
  }, {
59899
60063
  measureSourceDiff: createMeasureSourceDiff({
59900
60064
  config: this.ctx.runtime.configLoader.current(),
@@ -59905,7 +60069,9 @@ class ExecutionPlan {
59905
60069
  }
59906
60070
  const verifierName = this.state.verifier?.slot.op.name;
59907
60071
  const verifierExplicitlyPassed = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
59908
- const gateRegressedDuringRect = this.describeGateRegressionNow(phaseOutputs, gateName, preRectGateFailureKeys).regressed;
60072
+ const gateRegressedDuringRect = this.describeGateRegressionNow(phaseOutputs, gateName, {
60073
+ baselineKeys: preRectGateFailureKeys
60074
+ }).regressed;
59909
60075
  const verifierPassedSsot = verifierExplicitlyPassed && !gateRegressedDuringRect;
59910
60076
  if (verifierExplicitlyPassed && gateRegressedDuringRect) {
59911
60077
  logger?.warn("story-orchestrator", "Gate regressed during rectification after verifier passed \u2014 verifier verdict is stale, failing story", { storyId: this.ctx.storyId, packageDir: this.ctx.packageDir });
@@ -60056,6 +60222,7 @@ var init_story_orchestrator = __esm(() => {
60056
60222
  init_execution_plan();
60057
60223
  init_phase_eval();
60058
60224
  init_rectification();
60225
+ init_nbf_flake_triage();
60059
60226
  init_run_phase();
60060
60227
  init_types9();
60061
60228
  });
@@ -64843,6 +65010,7 @@ function getFinishAutoFlowConfig(ctx) {
64843
65010
  quality: autoFlow.reviewers?.quality ?? null
64844
65011
  },
64845
65012
  escalate: { telegram: autoFlow.escalate?.telegram !== false },
65013
+ notify: { mode: autoFlow.notify?.mode ?? defaults.notify.mode },
64846
65014
  timeouts: {
64847
65015
  acceptanceMs: autoFlow.timeouts?.acceptanceMs ?? defaults.timeouts.acceptanceMs,
64848
65016
  gateMs: autoFlow.timeouts?.gateMs ?? defaults.timeouts.gateMs,
@@ -64867,11 +65035,38 @@ var init_config2 = __esm(() => {
64867
65035
  defaultAgent: null,
64868
65036
  reviewers: { spec: null, quality: null },
64869
65037
  escalate: { telegram: true },
65038
+ notify: { mode: "escalation" },
64870
65039
  timeouts: { acceptanceMs: 600000, gateMs: 900000, flowMs: 5400000, stepMs: null }
64871
65040
  };
64872
65041
  });
64873
65042
 
65043
+ // src/plugins/builtin/nax-finish/output.ts
65044
+ function logTail(stream) {
65045
+ if (stream.length <= LOG_TAIL_CHARS)
65046
+ return stream;
65047
+ return `[\u2026${stream.length - LOG_TAIL_CHARS} chars truncated\u2026]
65048
+ ${stream.slice(-LOG_TAIL_CHARS)}`;
65049
+ }
65050
+ function stderrTail(stderr) {
65051
+ const trimmed = stderr.trim();
65052
+ if (!trimmed)
65053
+ return "";
65054
+ const tail = trimmed.length > STDERR_TAIL_CHARS ? `\u2026${trimmed.slice(-STDERR_TAIL_CHARS)}` : trimmed;
65055
+ return tail.replace(/\s+/g, " ");
65056
+ }
65057
+ var STDERR_TAIL_CHARS = 400, LOG_TAIL_CHARS = 20000;
65058
+
64874
65059
  // src/plugins/builtin/nax-finish/telegram.ts
65060
+ function buildTerminalMessage(options) {
65061
+ const lines = [`nax-finish ${options.status} ${options.feature}`];
65062
+ if (options.detail)
65063
+ lines.push(options.detail);
65064
+ if (options.url)
65065
+ lines.push(options.url);
65066
+ const message = lines.join(`
65067
+ `);
65068
+ return message.length <= TELEGRAM_MAX_MESSAGE_CHARS ? message : `${message.slice(0, TELEGRAM_MAX_MESSAGE_CHARS - 1)}\u2026`;
65069
+ }
64875
65070
  function buildEscalationMessage(feature, reason, findings) {
64876
65071
  const head = `nax-finish escalated ${feature}: ${reason}`;
64877
65072
  if (findings.length === 0)
@@ -64908,19 +65103,6 @@ var init_telegram2 = __esm(() => {
64908
65103
 
64909
65104
  // src/plugins/builtin/nax-finish/index.ts
64910
65105
  import * as path21 from "path";
64911
- function logTail(stream) {
64912
- if (stream.length <= LOG_TAIL_CHARS)
64913
- return stream;
64914
- return `[\u2026${stream.length - LOG_TAIL_CHARS} chars truncated\u2026]
64915
- ${stream.slice(-LOG_TAIL_CHARS)}`;
64916
- }
64917
- function stderrTail(stderr) {
64918
- const trimmed = stderr.trim();
64919
- if (!trimmed)
64920
- return "";
64921
- const tail = trimmed.length > STDERR_TAIL_CHARS ? `\u2026${trimmed.slice(-STDERR_TAIL_CHARS)}` : trimmed;
64922
- return tail.replace(/\s+/g, " ");
64923
- }
64924
65106
  async function defaultRun2(cmd, opts) {
64925
65107
  const proc = Bun.spawn(cmd, { cwd: opts.cwd, env: opts.env, stdout: "pipe", stderr: "pipe" });
64926
65108
  let timedOut = false;
@@ -64951,6 +65133,11 @@ async function defaultReadResult(workdir) {
64951
65133
  return null;
64952
65134
  return JSON.parse(await f.text());
64953
65135
  }
65136
+ async function defaultClearResult(workdir) {
65137
+ const file3 = Bun.file(path21.join(workdir, ".nax", "nax-finish-result.json"));
65138
+ if (await file3.exists())
65139
+ await file3.delete();
65140
+ }
64954
65141
  function isFeatureBranch(b) {
64955
65142
  return b !== "main" && b !== "master" && b.length > 0;
64956
65143
  }
@@ -64994,13 +65181,133 @@ function buildFlowEnv(cfg) {
64994
65181
  env2.NAX_FINISH_QUALITY_PROFILE = cfg.reviewers.quality;
64995
65182
  return env2;
64996
65183
  }
64997
- var PLUGIN_NAME4 = "nax-finish", PLUGIN_VERSION4 = "0.1.0", PACKAGE_ROOT_SEARCH_DEPTH = 6, STDERR_TAIL_CHARS = 400, LOG_TAIL_CHARS = 20000, _naxFinishDeps, naxFinishAction, naxFinishPlugin;
65184
+ function missingResultOutcome(ctx, res, escalateTelegram) {
65185
+ ctx.logger.warn("nax-finish flow produced no result file", {
65186
+ exitCode: res.exitCode,
65187
+ stdout: logTail(res.stdout),
65188
+ stderr: logTail(res.stderr)
65189
+ });
65190
+ const tail = stderrTail(res.stderr);
65191
+ return {
65192
+ actionResult: {
65193
+ success: false,
65194
+ message: `nax-finish flow exited ${res.exitCode} (no result file)${tail ? `: ${tail}` : ""}`
65195
+ },
65196
+ escalateTelegram
65197
+ };
65198
+ }
65199
+ async function executeFinishFlow(options) {
65200
+ const { ctx, cfg, escalateTelegram } = options;
65201
+ const flowPath = await resolveFlowPath(ctx.workdir, cfg.flowPath);
65202
+ if (!flowPath) {
65203
+ return {
65204
+ actionResult: {
65205
+ success: false,
65206
+ message: `nax-finish: flow module "${cfg.flowPath}" not found in the nax install or ${ctx.workdir}`
65207
+ },
65208
+ escalateTelegram
65209
+ };
65210
+ }
65211
+ await _naxFinishDeps.clearResult(ctx.workdir);
65212
+ const input = {
65213
+ feature: ctx.feature,
65214
+ workdir: ctx.workdir,
65215
+ branch: ctx.branch,
65216
+ prdPath: ctx.prdPath,
65217
+ escalateTelegram,
65218
+ timeouts: { acceptanceMs: cfg.timeouts.acceptanceMs, gateMs: cfg.timeouts.gateMs }
65219
+ };
65220
+ const cmd = buildFlowArgv(flowPath, JSON.stringify(input), cfg.defaultAgent, cfg.timeouts.stepMs);
65221
+ const res = await _naxFinishDeps.run(cmd, {
65222
+ cwd: ctx.workdir,
65223
+ env: buildFlowEnv(cfg),
65224
+ timeoutMs: cfg.timeouts.flowMs
65225
+ });
65226
+ const result = await _naxFinishDeps.readResult(ctx.workdir);
65227
+ if (!result)
65228
+ return missingResultOutcome(ctx, res, escalateTelegram);
65229
+ return {
65230
+ actionResult: { success: true, message: `nax-finish: ${result.status}`, url: result.url },
65231
+ result,
65232
+ escalateTelegram
65233
+ };
65234
+ }
65235
+ async function settleFinishFlow(options) {
65236
+ const escalateTelegram = options.cfg.notify.mode !== "off" && options.cfg.escalate.telegram && options.creds !== null;
65237
+ try {
65238
+ return await executeFinishFlow({ ...options, escalateTelegram });
65239
+ } catch (error48) {
65240
+ options.ctx.logger.warn("nax-finish execute failed", { error: errorMessage(error48) });
65241
+ return {
65242
+ actionResult: { success: false, message: `nax-finish failed: ${errorMessage(error48)}` },
65243
+ escalateTelegram
65244
+ };
65245
+ }
65246
+ }
65247
+ async function notifyBestEffort(ctx, creds, message) {
65248
+ if (!creds) {
65249
+ ctx.logger.warn("nax-finish terminal notification skipped: Telegram credentials are unavailable");
65250
+ return;
65251
+ }
65252
+ try {
65253
+ if (!await _naxFinishDeps.notify(creds, message)) {
65254
+ ctx.logger.warn("nax-finish terminal notification was rejected", { feature: ctx.feature });
65255
+ }
65256
+ } catch (error48) {
65257
+ ctx.logger.warn("nax-finish terminal notification failed", { feature: ctx.feature, error: errorMessage(error48) });
65258
+ }
65259
+ }
65260
+ async function finalizeEscalation(ctx, outcome, creds) {
65261
+ const result = outcome.result;
65262
+ if (!result)
65263
+ return outcome.actionResult;
65264
+ const problems = [];
65265
+ let delivered = !outcome.escalateTelegram && !result.deliveryError;
65266
+ if (outcome.escalateTelegram && creds) {
65267
+ try {
65268
+ delivered = await _naxFinishDeps.notify(creds, buildEscalationMessage(result.feature, result.escalationReason ?? "", result.findings ?? []));
65269
+ if (!delivered)
65270
+ problems.push("Telegram rejected the message");
65271
+ } catch (error48) {
65272
+ problems.push(`Telegram failed: ${errorMessage(error48)}`);
65273
+ }
65274
+ }
65275
+ if (delivered)
65276
+ return outcome.actionResult;
65277
+ if (result.deliveryError)
65278
+ problems.push(`the flow could not post it: ${result.deliveryError}`);
65279
+ if (problems.length === 0)
65280
+ problems.push("no escalation channel was reachable");
65281
+ ctx.logger.warn("nax-finish escalation was not delivered", {
65282
+ feature: result.feature,
65283
+ reasons: problems,
65284
+ escalationReason: result.escalationReason
65285
+ });
65286
+ return {
65287
+ success: false,
65288
+ message: `nax-finish: escalated but undelivered \u2014 ${problems.join("; ")}`,
65289
+ url: result.url
65290
+ };
65291
+ }
65292
+ async function finalizeFinishOutcome(options) {
65293
+ const { ctx, cfg, creds, outcome } = options;
65294
+ if (outcome.result?.status === "escalated")
65295
+ return finalizeEscalation(ctx, outcome, creds);
65296
+ if (cfg.notify.mode === "always") {
65297
+ const status = outcome.result?.status ?? "failed";
65298
+ const detail = outcome.result ? undefined : outcome.actionResult.message;
65299
+ await notifyBestEffort(ctx, creds, buildTerminalMessage({ feature: ctx.feature, status, detail, url: outcome.actionResult.url }));
65300
+ }
65301
+ return outcome.actionResult;
65302
+ }
65303
+ var PLUGIN_NAME4 = "nax-finish", PLUGIN_VERSION4 = "0.1.0", PACKAGE_ROOT_SEARCH_DEPTH = 6, _naxFinishDeps, naxFinishAction, naxFinishPlugin;
64998
65304
  var init_nax_finish = __esm(() => {
64999
65305
  init_config2();
65000
65306
  init_telegram2();
65001
65307
  _naxFinishDeps = {
65002
65308
  run: defaultRun2,
65003
65309
  readResult: defaultReadResult,
65310
+ clearResult: defaultClearResult,
65004
65311
  exists: (p) => Bun.file(p).exists(),
65005
65312
  moduleDir: import.meta.dir,
65006
65313
  notify: sendTelegramNotify
@@ -65018,76 +65325,10 @@ var init_nax_finish = __esm(() => {
65018
65325
  return isFeatureBranch(ctx.branch);
65019
65326
  },
65020
65327
  async execute(ctx) {
65021
- try {
65022
- const cfg = getFinishAutoFlowConfig(ctx);
65023
- const flowPath = await resolveFlowPath(ctx.workdir, cfg.flowPath);
65024
- if (!flowPath) {
65025
- return {
65026
- success: false,
65027
- message: `nax-finish: flow module "${cfg.flowPath}" not found in the nax install or ${ctx.workdir}`
65028
- };
65029
- }
65030
- const creds = telegramCreds(ctx.config);
65031
- const escalateTelegram = cfg.escalate.telegram && creds !== null;
65032
- const input = {
65033
- feature: ctx.feature,
65034
- workdir: ctx.workdir,
65035
- branch: ctx.branch,
65036
- prdPath: ctx.prdPath,
65037
- escalateTelegram,
65038
- timeouts: { acceptanceMs: cfg.timeouts.acceptanceMs, gateMs: cfg.timeouts.gateMs }
65039
- };
65040
- const cmd = buildFlowArgv(flowPath, JSON.stringify(input), cfg.defaultAgent, cfg.timeouts.stepMs);
65041
- const res = await _naxFinishDeps.run(cmd, {
65042
- cwd: ctx.workdir,
65043
- env: buildFlowEnv(cfg),
65044
- timeoutMs: cfg.timeouts.flowMs
65045
- });
65046
- const result = await _naxFinishDeps.readResult(ctx.workdir);
65047
- if (!result) {
65048
- ctx.logger.warn("nax-finish flow produced no result file", {
65049
- exitCode: res.exitCode,
65050
- stdout: logTail(res.stdout),
65051
- stderr: logTail(res.stderr)
65052
- });
65053
- const tail = stderrTail(res.stderr);
65054
- return {
65055
- success: false,
65056
- message: `nax-finish flow exited ${res.exitCode} (no result file)${tail ? `: ${tail}` : ""}`
65057
- };
65058
- }
65059
- if (result.status === "escalated") {
65060
- const problems = [];
65061
- let delivered = !escalateTelegram && !result.deliveryError;
65062
- if (escalateTelegram && creds) {
65063
- const sent = await _naxFinishDeps.notify(creds, buildEscalationMessage(result.feature, result.escalationReason ?? "", result.findings ?? []));
65064
- if (sent)
65065
- delivered = true;
65066
- else
65067
- problems.push("Telegram rejected the message");
65068
- }
65069
- if (!delivered) {
65070
- if (result.deliveryError)
65071
- problems.push(`the flow could not post it: ${result.deliveryError}`);
65072
- if (problems.length === 0)
65073
- problems.push("no escalation channel was reachable");
65074
- ctx.logger.warn("nax-finish escalation was not delivered", {
65075
- feature: result.feature,
65076
- reasons: problems,
65077
- escalationReason: result.escalationReason
65078
- });
65079
- return {
65080
- success: false,
65081
- message: `nax-finish: escalated but undelivered \u2014 ${problems.join("; ")}`,
65082
- url: result.url
65083
- };
65084
- }
65085
- }
65086
- return { success: true, message: `nax-finish: ${result.status}`, url: result.url };
65087
- } catch (err) {
65088
- ctx.logger.warn("nax-finish execute failed", { error: String(err) });
65089
- return { success: false, message: `nax-finish failed: ${String(err)}` };
65090
- }
65328
+ const cfg = getFinishAutoFlowConfig(ctx);
65329
+ const creds = telegramCreds(ctx.config);
65330
+ const outcome = await settleFinishFlow({ ctx, cfg, creds });
65331
+ return finalizeFinishOutcome({ ctx, cfg, creds, outcome });
65091
65332
  }
65092
65333
  };
65093
65334
  naxFinishPlugin = {
@@ -66134,7 +66375,7 @@ class PluginRegistry {
66134
66375
  sources;
66135
66376
  builtinPostRunActions;
66136
66377
  constructor(loadedPlugins, builtinPostRunActions = []) {
66137
- this.builtinPostRunActions = builtinPostRunActions;
66378
+ this.builtinPostRunActions = builtinPostRunActions.map((registration) => ("action" in registration) ? registration : { pluginName: registration.name, action: registration });
66138
66379
  if (loadedPlugins.length > 0 && "plugin" in loadedPlugins[0]) {
66139
66380
  const typed = loadedPlugins;
66140
66381
  this.plugins = typed.map((lp) => lp.plugin);
@@ -66173,7 +66414,13 @@ class PluginRegistry {
66173
66414
  return this.plugins.filter((p) => p.provides.includes("reporter")).map((p) => p.extensions.reporter).filter((reporter) => reporter !== undefined);
66174
66415
  }
66175
66416
  getPostRunActions() {
66176
- const pluginActions = this.plugins.filter((p) => p.provides.includes("post-run-action")).map((p) => p.extensions.postRunAction).filter((action) => action !== undefined);
66417
+ return this.getPostRunActionRegistrations().map(({ action }) => action);
66418
+ }
66419
+ getPostRunActionRegistrations() {
66420
+ const pluginActions = this.plugins.flatMap((plugin) => {
66421
+ const action = plugin.extensions.postRunAction;
66422
+ return plugin.provides.includes("post-run-action") && action ? [{ pluginName: plugin.name, action }] : [];
66423
+ });
66177
66424
  return [...pluginActions, ...this.builtinPostRunActions];
66178
66425
  }
66179
66426
  async teardownAll() {
@@ -66488,7 +66735,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
66488
66735
  }
66489
66736
  const autoPrAction2 = autoPrPlugin.extensions.postRunAction;
66490
66737
  if (autoPrAction2) {
66491
- builtinPostRunActions.push(autoPrAction2);
66738
+ builtinPostRunActions.push({ pluginName: autoPrPlugin.name, action: autoPrAction2 });
66492
66739
  }
66493
66740
  } else {
66494
66741
  logger?.info("plugins", `Skipping disabled plugin: '${autoPrPlugin.name}' (built-in)`);
@@ -66500,7 +66747,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
66500
66747
  }
66501
66748
  const action = naxFinishPlugin.extensions.postRunAction;
66502
66749
  if (action)
66503
- builtinPostRunActions.push(action);
66750
+ builtinPostRunActions.push({ pluginName: naxFinishPlugin.name, action });
66504
66751
  } else {
66505
66752
  logger?.info("plugins", `Skipping disabled plugin: '${naxFinishPlugin.name}' (built-in)`);
66506
66753
  }
@@ -66511,7 +66758,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
66511
66758
  }
66512
66759
  const autoRouteAction2 = autoRoutePlugin.extensions.postRunAction;
66513
66760
  if (autoRouteAction2) {
66514
- builtinPostRunActions.push(autoRouteAction2);
66761
+ builtinPostRunActions.push({ pluginName: autoRoutePlugin.name, action: autoRouteAction2 });
66515
66762
  }
66516
66763
  } else {
66517
66764
  logger?.info("plugins", `Skipping disabled plugin: '${autoRoutePlugin.name}' (built-in)`);
@@ -66905,6 +67152,25 @@ var init_checkpoint = __esm(() => {
66905
67152
  init_resume_cli();
66906
67153
  });
66907
67154
 
67155
+ // src/hooks/types.ts
67156
+ var HOOK_EVENTS;
67157
+ var init_types10 = __esm(() => {
67158
+ HOOK_EVENTS = [
67159
+ "on-start",
67160
+ "on-story-start",
67161
+ "on-story-complete",
67162
+ "on-story-fail",
67163
+ "on-pause",
67164
+ "on-resume",
67165
+ "on-session-end",
67166
+ "on-all-stories-complete",
67167
+ "on-complete",
67168
+ "on-error",
67169
+ "on-final-regression-fail",
67170
+ "on-post-run-action"
67171
+ ];
67172
+ });
67173
+
66908
67174
  // src/hooks/runner.ts
66909
67175
  import { join as join84 } from "path";
66910
67176
  function createDrainDeadline2(deadlineMs) {
@@ -66966,6 +67232,12 @@ function buildEnv(ctx) {
66966
67232
  env2.NAX_AGENT = escapeEnvValue(ctx.agent);
66967
67233
  if (ctx.iteration !== undefined)
66968
67234
  env2.NAX_ITERATION = String(ctx.iteration);
67235
+ if (ctx.pluginName)
67236
+ env2.NAX_PLUGIN_NAME = escapeEnvValue(ctx.pluginName);
67237
+ if (ctx.actionName)
67238
+ env2.NAX_ACTION_NAME = escapeEnvValue(ctx.actionName);
67239
+ if (ctx.url)
67240
+ env2.NAX_RESULT_URL = escapeEnvValue(ctx.url);
66969
67241
  return env2;
66970
67242
  }
66971
67243
  function hasShellOperators(command) {
@@ -67096,9 +67368,11 @@ var init_runner5 = __esm(() => {
67096
67368
  var exports_hooks = {};
67097
67369
  __export(exports_hooks, {
67098
67370
  loadHooksConfig: () => loadHooksConfig,
67099
- fireHook: () => fireHook
67371
+ fireHook: () => fireHook,
67372
+ HOOK_EVENTS: () => HOOK_EVENTS
67100
67373
  });
67101
67374
  var init_hooks = __esm(() => {
67375
+ init_types10();
67102
67376
  init_runner5();
67103
67377
  });
67104
67378
 
@@ -69746,7 +70020,7 @@ function buildPreviewRouting(story, config2) {
69746
70020
 
69747
70021
  // src/worktree/types.ts
69748
70022
  var WorktreeDependencyPreparationError;
69749
- var init_types10 = __esm(() => {
70023
+ var init_types11 = __esm(() => {
69750
70024
  WorktreeDependencyPreparationError = class WorktreeDependencyPreparationError extends Error {
69751
70025
  mode;
69752
70026
  failureCategory = "dependency-prep";
@@ -69816,7 +70090,7 @@ var PHASE_ONE_INHERIT_UNSUPPORTED_FILES, _worktreeDependencyDeps;
69816
70090
  var init_dependencies = __esm(() => {
69817
70091
  init_bun_deps();
69818
70092
  init_command_argv();
69819
- init_types10();
70093
+ init_types11();
69820
70094
  PHASE_ONE_INHERIT_UNSUPPORTED_FILES = [
69821
70095
  "package.json",
69822
70096
  "bun.lock",
@@ -71033,6 +71307,22 @@ var init_pipeline_result_handler = __esm(() => {
71033
71307
  // src/execution/iteration-runner.ts
71034
71308
  import { existsSync as existsSync35 } from "fs";
71035
71309
  import { join as join91 } from "path";
71310
+ function releaseHeavyPipelineContext(ctx) {
71311
+ ctx.agentResult = undefined;
71312
+ ctx.prompt = undefined;
71313
+ ctx.contextMarkdown = undefined;
71314
+ ctx.featureContextMarkdown = undefined;
71315
+ ctx.builtContext = undefined;
71316
+ ctx.contextBundle = undefined;
71317
+ ctx.constitution = undefined;
71318
+ ctx.acceptanceFailures = undefined;
71319
+ ctx.autofixPriorIterations = undefined;
71320
+ ctx.priorSemanticIterations = undefined;
71321
+ ctx.priorAdversarialIterations = undefined;
71322
+ ctx.reviewFindings = undefined;
71323
+ ctx.selfVerification = undefined;
71324
+ ctx.tddIsolations = undefined;
71325
+ }
71036
71326
  async function runIteration(ctx, prd, selection, iterations, totalCost2, allStoryMetrics) {
71037
71327
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
71038
71328
  if (ctx.dryRun) {
@@ -71213,11 +71503,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost2, allStor
71213
71503
  subStoryCount: pipelineResult.subStoryCount
71214
71504
  };
71215
71505
  }
71216
- pipelineContext.agentResult = undefined;
71217
- pipelineContext.prompt = undefined;
71218
- pipelineContext.contextMarkdown = undefined;
71219
- pipelineContext.builtContext = undefined;
71220
- pipelineContext.constitution = undefined;
71506
+ releaseHeavyPipelineContext(pipelineContext);
71221
71507
  return iterResult;
71222
71508
  }
71223
71509
  var _iterationRunnerDeps;
@@ -73463,8 +73749,63 @@ async function runSetupPhase(options) {
73463
73749
  var exports_run_cleanup = {};
73464
73750
  __export(exports_run_cleanup, {
73465
73751
  cleanupRun: () => cleanupRun,
73466
- buildPostRunContext: () => buildPostRunContext
73752
+ buildPostRunContext: () => buildPostRunContext,
73753
+ _runCleanupDeps: () => _runCleanupDeps
73467
73754
  });
73755
+ async function settlePostRunAction(action, ctx) {
73756
+ try {
73757
+ if (!await action.shouldRun(ctx))
73758
+ return { status: "skipped", reason: "shouldRun=false" };
73759
+ return outcomeFromResult(await action.execute(ctx));
73760
+ } catch (error48) {
73761
+ return { status: "error", reason: errorMessage(error48) };
73762
+ }
73763
+ }
73764
+ function outcomeFromResult(result) {
73765
+ if (result.skipped)
73766
+ return { status: "skipped", reason: result.reason ?? result.message };
73767
+ if (!result.success)
73768
+ return { status: "failed", message: result.message, url: result.url };
73769
+ return { status: "succeeded", message: result.message, url: result.url };
73770
+ }
73771
+ function logPostRunOutcome(actionName, outcome) {
73772
+ const logger = getSafeLogger();
73773
+ if (outcome.status === "skipped") {
73774
+ const level = outcome.reason === "shouldRun=false" ? "debug" : "info";
73775
+ logger?.[level]("post-run", `[post-run] ${actionName}: skipped \u2014 ${outcome.reason}`);
73776
+ } else if (outcome.status === "failed") {
73777
+ logger?.warn("post-run", `[post-run] ${actionName}: failed \u2014 ${outcome.message}`);
73778
+ } else if (outcome.status === "error") {
73779
+ logger?.warn("post-run", `[post-run] ${actionName}: error \u2014 ${outcome.reason}`);
73780
+ } else {
73781
+ const suffix = outcome.url ? `${outcome.message} (${outcome.url})` : outcome.message;
73782
+ logger?.info("post-run", `[post-run] ${actionName}: ${suffix}`);
73783
+ }
73784
+ }
73785
+ function postRunHookContext(feature, registration, outcome) {
73786
+ const reason = outcome.status === "succeeded" || outcome.status === "failed" ? outcome.message : outcome.reason;
73787
+ return {
73788
+ event: "on-post-run-action",
73789
+ feature,
73790
+ pluginName: registration.pluginName,
73791
+ actionName: registration.action.name,
73792
+ status: outcome.status,
73793
+ reason,
73794
+ url: "url" in outcome ? outcome.url : undefined
73795
+ };
73796
+ }
73797
+ async function runPostRunActions(options, ctx) {
73798
+ const registrations = options.pluginRegistry.getPostRunActionRegistrations();
73799
+ for (const registration of registrations) {
73800
+ const outcome = await settlePostRunAction(registration.action, ctx);
73801
+ logPostRunOutcome(registration.action.name, outcome);
73802
+ try {
73803
+ await _runCleanupDeps.fireHook(options.hooks, "on-post-run-action", postRunHookContext(options.feature, registration, outcome), options.workdir);
73804
+ } catch (error48) {
73805
+ getSafeLogger()?.warn("hooks", `on-post-run-action hook failed for '${registration.pluginName}'`, { error: error48 });
73806
+ }
73807
+ }
73808
+ }
73468
73809
  function buildPostRunContext(opts, durationMs, logger) {
73469
73810
  const {
73470
73811
  runId,
@@ -73537,7 +73878,6 @@ async function cleanupRun(options) {
73537
73878
  }
73538
73879
  }
73539
73880
  }
73540
- const actions = pluginRegistry.getPostRunActions();
73541
73881
  const pluginLogger = {
73542
73882
  debug: (msg, data) => logger?.debug("post-run", msg, data),
73543
73883
  info: (msg, data) => logger?.info("post-run", msg, data),
@@ -73545,26 +73885,7 @@ async function cleanupRun(options) {
73545
73885
  error: (msg, data) => logger?.error("post-run", msg, data)
73546
73886
  };
73547
73887
  const ctx = buildPostRunContext(options, durationMs, pluginLogger);
73548
- for (const action of actions) {
73549
- try {
73550
- const shouldRun = await action.shouldRun(ctx);
73551
- if (!shouldRun) {
73552
- logger?.debug("post-run", `[post-run] ${action.name}: shouldRun=false, skipping`);
73553
- continue;
73554
- }
73555
- const result = await action.execute(ctx);
73556
- if (result.skipped) {
73557
- logger?.info("post-run", `[post-run] ${action.name}: skipped \u2014 ${result.reason}`);
73558
- } else if (!result.success) {
73559
- logger?.warn("post-run", `[post-run] ${action.name}: failed \u2014 ${result.message}`);
73560
- } else {
73561
- const msg = result.url ? `[post-run] ${action.name}: ${result.message} (${result.url})` : `[post-run] ${action.name}: ${result.message}`;
73562
- logger?.info("post-run", msg);
73563
- }
73564
- } catch (error48) {
73565
- logger?.warn("post-run", `[post-run] ${action.name}: error \u2014 ${error48}`);
73566
- }
73567
- }
73888
+ await runPostRunActions(options, ctx);
73568
73889
  try {
73569
73890
  await pluginRegistry.teardownAll();
73570
73891
  } catch (error48) {
@@ -73581,11 +73902,14 @@ async function cleanupRun(options) {
73581
73902
  disposeFeatureResolver(workdir);
73582
73903
  await releaseLock(workdir);
73583
73904
  }
73905
+ var _runCleanupDeps;
73584
73906
  var init_run_cleanup = __esm(() => {
73585
73907
  init_context();
73908
+ init_hooks();
73586
73909
  init_logger2();
73587
73910
  init_prd();
73588
73911
  init_helpers();
73912
+ _runCleanupDeps = { fireHook };
73589
73913
  });
73590
73914
 
73591
73915
  // src/execution/runner.ts
@@ -73782,6 +74106,7 @@ async function run(options) {
73782
74106
  prdPath,
73783
74107
  branch,
73784
74108
  version: NAX_VERSION,
74109
+ hooks,
73785
74110
  runCompleted,
73786
74111
  outputDir: runtime.outputDir,
73787
74112
  globalDir: runtime.globalDir,
@@ -73840,12 +74165,14 @@ __export(exports_execution, {
73840
74165
  startHeartbeat: () => startHeartbeat2,
73841
74166
  runRectification: () => runRectification,
73842
74167
  runPhase: () => runPhase,
74168
+ runNonBlockingFix: () => runNonBlockingFix,
73843
74169
  runDeferredRegression: () => runDeferredRegression,
73844
74170
  runCompletionPhase: () => runCompletionPhase,
73845
74171
  run: () => run,
73846
74172
  resolveMaxAttemptsOutcome: () => resolveMaxAttemptsOutcome,
73847
74173
  resetCrashHandlers: () => resetCrashHandlers,
73848
74174
  releaseLock: () => releaseLock,
74175
+ releaseHeavyPipelineContext: () => releaseHeavyPipelineContext,
73849
74176
  refreshReviewInputForDispatch: () => refreshReviewInputForDispatch,
73850
74177
  recordOscillations: () => recordOscillations,
73851
74178
  readQueueFile: () => readQueueFile,
@@ -73878,6 +74205,7 @@ __export(exports_execution, {
73878
74205
  describeGateRegression: () => describeGateRegression,
73879
74206
  deriveTddFailureCategory: () => deriveTddFailureCategory,
73880
74207
  decideStageAction: () => decideStageAction,
74208
+ createNbfFlakeTriageTransaction: () => createNbfFlakeTriageTransaction,
73881
74209
  createCheckpointWriter: () => createCheckpointWriter,
73882
74210
  countOscillationOutcomes: () => countOscillationOutcomes,
73883
74211
  clearQueueFile: () => clearQueueFile,
@@ -73900,6 +74228,7 @@ __export(exports_execution, {
73900
74228
  _runnerDeps: () => _runnerDeps,
73901
74229
  _runnerCompletionDeps: () => _runnerCompletionDeps,
73902
74230
  _runCompletionDeps: () => _runCompletionDeps,
74231
+ _runCleanupDeps: () => _runCleanupDeps,
73903
74232
  _regressionDeps: () => _regressionDeps,
73904
74233
  _postRunDeps: () => _postRunDeps,
73905
74234
  _pidRegistryDeps: () => _pidRegistryDeps,
@@ -73917,6 +74246,7 @@ var init_execution2 = __esm(() => {
73917
74246
  init_oscillation_breaker();
73918
74247
  init_runner6();
73919
74248
  init_progress();
74249
+ init_iteration_runner();
73920
74250
  init_escalation();
73921
74251
  init_queue_handler();
73922
74252
  init_ensure_package_dirs();
@@ -73928,6 +74258,7 @@ var init_execution2 = __esm(() => {
73928
74258
  init_story_orchestrator();
73929
74259
  init_story_orchestrator_logging();
73930
74260
  init_plan_inputs();
74261
+ init_non_blocking_fix();
73931
74262
  init_build_plan_for_strategy();
73932
74263
  init_checkpoint();
73933
74264
  init_runner_completion();