@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.
- package/dist/nax.js +662 -331
- 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.
|
|
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("
|
|
42731
|
-
return "
|
|
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
|
-
|
|
57575
|
-
|
|
57576
|
-
|
|
57577
|
-
|
|
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
|
|
57588
|
-
const
|
|
57589
|
-
|
|
57590
|
-
|
|
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
|
-
|
|
57593
|
-
|
|
57594
|
-
|
|
57595
|
-
|
|
57596
|
-
|
|
57597
|
-
|
|
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 (
|
|
57602
|
-
|
|
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 (
|
|
57611
|
-
|
|
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] =
|
|
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 =
|
|
57719
|
-
|
|
57720
|
-
|
|
57721
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59661
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
65022
|
-
|
|
65023
|
-
|
|
65024
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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();
|