@nathapp/nax 0.75.4 → 0.75.6
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 +858 -427
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -17377,7 +17377,11 @@ var init_schemas_review = __esm(() => {
|
|
|
17377
17377
|
maxRequotes: 5
|
|
17378
17378
|
}),
|
|
17379
17379
|
excludePatterns: exports_external.array(exports_external.string()).optional(),
|
|
17380
|
-
demandInspectionTrail: exports_external.boolean().default(true)
|
|
17380
|
+
demandInspectionTrail: exports_external.boolean().default(true),
|
|
17381
|
+
recurrenceDemotion: exports_external.object({
|
|
17382
|
+
enabled: exports_external.boolean().default(false),
|
|
17383
|
+
maxBlockingRounds: exports_external.number().int().min(1).default(2)
|
|
17384
|
+
}).default({ enabled: false, maxBlockingRounds: 2 })
|
|
17381
17385
|
});
|
|
17382
17386
|
AdversarialReviewConfigSchema = exports_external.object({
|
|
17383
17387
|
model: ConfiguredModelSchema.default("balanced"),
|
|
@@ -17641,6 +17645,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17641
17645
|
rules: [],
|
|
17642
17646
|
timeoutMs: 600000,
|
|
17643
17647
|
demandInspectionTrail: true,
|
|
17648
|
+
recurrenceDemotion: { enabled: false, maxBlockingRounds: 2 },
|
|
17644
17649
|
substantiation: {
|
|
17645
17650
|
requote: true,
|
|
17646
17651
|
maxRequotes: 5
|
|
@@ -17823,6 +17828,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17823
17828
|
quality: exports_external.string().nullable().default(null)
|
|
17824
17829
|
}).default({ spec: null, quality: null }),
|
|
17825
17830
|
escalate: exports_external.object({ telegram: exports_external.boolean().default(true) }).default({ telegram: true }),
|
|
17831
|
+
notify: exports_external.object({ mode: exports_external.enum(["escalation", "always", "off"]).default("escalation") }).default({ mode: "escalation" }),
|
|
17826
17832
|
timeouts: exports_external.object({
|
|
17827
17833
|
acceptanceMs: exports_external.number().int().positive().default(600000),
|
|
17828
17834
|
gateMs: exports_external.number().int().positive().default(900000),
|
|
@@ -17835,6 +17841,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17835
17841
|
defaultAgent: null,
|
|
17836
17842
|
reviewers: { spec: null, quality: null },
|
|
17837
17843
|
escalate: { telegram: true },
|
|
17844
|
+
notify: { mode: "escalation" },
|
|
17838
17845
|
timeouts: { acceptanceMs: 600000, gateMs: 900000, flowMs: 5400000, stepMs: null }
|
|
17839
17846
|
})
|
|
17840
17847
|
}).default({
|
|
@@ -17844,6 +17851,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17844
17851
|
defaultAgent: null,
|
|
17845
17852
|
reviewers: { spec: null, quality: null },
|
|
17846
17853
|
escalate: { telegram: true },
|
|
17854
|
+
notify: { mode: "escalation" },
|
|
17847
17855
|
timeouts: { acceptanceMs: 600000, gateMs: 900000, flowMs: 5400000, stepMs: null }
|
|
17848
17856
|
}
|
|
17849
17857
|
}),
|
|
@@ -21654,7 +21662,7 @@ class AcpAgentAdapter {
|
|
|
21654
21662
|
}
|
|
21655
21663
|
}
|
|
21656
21664
|
}
|
|
21657
|
-
var
|
|
21665
|
+
var INTERACTION_TIMEOUT_MS, AGENT_REGISTRY, DEFAULT_ENTRY, ACP_ADAPTER_NAMES;
|
|
21658
21666
|
var init_adapter = __esm(() => {
|
|
21659
21667
|
init_errors();
|
|
21660
21668
|
init_logger2();
|
|
@@ -22944,11 +22952,38 @@ var init_compose = __esm(() => {
|
|
|
22944
22952
|
|
|
22945
22953
|
// src/review/truncation.ts
|
|
22946
22954
|
function looksLikeTruncatedJson(raw) {
|
|
22947
|
-
|
|
22955
|
+
const text = raw.trimEnd();
|
|
22956
|
+
if (text.length === 0)
|
|
22957
|
+
return false;
|
|
22958
|
+
let depth = 0;
|
|
22959
|
+
let inString = false;
|
|
22960
|
+
let escaped = false;
|
|
22961
|
+
let opened = false;
|
|
22962
|
+
for (const ch of text) {
|
|
22963
|
+
if (escaped) {
|
|
22964
|
+
escaped = false;
|
|
22965
|
+
continue;
|
|
22966
|
+
}
|
|
22967
|
+
if (inString) {
|
|
22968
|
+
if (ch === "\\")
|
|
22969
|
+
escaped = true;
|
|
22970
|
+
else if (ch === '"')
|
|
22971
|
+
inString = false;
|
|
22972
|
+
continue;
|
|
22973
|
+
}
|
|
22974
|
+
if (ch === '"') {
|
|
22975
|
+
inString = true;
|
|
22976
|
+
continue;
|
|
22977
|
+
}
|
|
22978
|
+
if (ch === "{" || ch === "[") {
|
|
22979
|
+
depth++;
|
|
22980
|
+
opened = true;
|
|
22981
|
+
} else if (ch === "}" || ch === "]") {
|
|
22982
|
+
depth--;
|
|
22983
|
+
}
|
|
22984
|
+
}
|
|
22985
|
+
return opened && (inString || depth > 0);
|
|
22948
22986
|
}
|
|
22949
|
-
var init_truncation = __esm(() => {
|
|
22950
|
-
init_adapter();
|
|
22951
|
-
});
|
|
22952
22987
|
|
|
22953
22988
|
// src/utils/llm-json.ts
|
|
22954
22989
|
function extractJsonFromMarkdown(text) {
|
|
@@ -23091,9 +23126,9 @@ function makeParseRetryStrategy(opts) {
|
|
|
23091
23126
|
}
|
|
23092
23127
|
};
|
|
23093
23128
|
}
|
|
23129
|
+
var UNPARSED_PREVIEW_BYTES = 600;
|
|
23094
23130
|
var init_parse_retry = __esm(() => {
|
|
23095
23131
|
init_logger2();
|
|
23096
|
-
init_truncation();
|
|
23097
23132
|
init_types4();
|
|
23098
23133
|
});
|
|
23099
23134
|
|
|
@@ -23111,7 +23146,7 @@ function makeTieredParseRetryStrategy(opts) {
|
|
|
23111
23146
|
if (attempt >= opts.maxAttempts - 1) {
|
|
23112
23147
|
return { retry: false, fallback: opts.exhaustedFallback(inspection, ctx.lastOutput) };
|
|
23113
23148
|
}
|
|
23114
|
-
const isTruncated = ctx.lastOutput
|
|
23149
|
+
const isTruncated = looksLikeTruncatedJson(ctx.lastOutput);
|
|
23115
23150
|
const logger = opts._logger ?? getSafeLogger();
|
|
23116
23151
|
logger?.warn(opts.reviewerKind, `Parse retry \u2014 ${inspection.kind ?? "unknown"}`, {
|
|
23117
23152
|
storyId: ctx.storyId,
|
|
@@ -23125,7 +23160,6 @@ function makeTieredParseRetryStrategy(opts) {
|
|
|
23125
23160
|
}
|
|
23126
23161
|
var init_tiered_parse_retry = __esm(() => {
|
|
23127
23162
|
init_logger2();
|
|
23128
|
-
init_adapter();
|
|
23129
23163
|
init_types4();
|
|
23130
23164
|
});
|
|
23131
23165
|
|
|
@@ -36186,6 +36220,89 @@ var init_finding_filters = __esm(() => {
|
|
|
36186
36220
|
init_ac_quote_validator();
|
|
36187
36221
|
});
|
|
36188
36222
|
|
|
36223
|
+
// src/review/recurrence-demotion.ts
|
|
36224
|
+
function normalizeIssueText(s) {
|
|
36225
|
+
return s.replace(/`/g, "").replace(/\s+/g, " ").trim().toLowerCase().slice(0, MAX_ISSUE_PREFIX);
|
|
36226
|
+
}
|
|
36227
|
+
function normalizeFingerprintPath(file3) {
|
|
36228
|
+
return (file3 ?? "").replace(/\\/g, "/").replace(/^(?:\.{1,2}\/)+/, "");
|
|
36229
|
+
}
|
|
36230
|
+
function fingerprintFor(file3, category, text, acIndex) {
|
|
36231
|
+
const normFile = normalizeFingerprintPath(file3);
|
|
36232
|
+
if (typeof acIndex === "number" && Number.isInteger(acIndex) && acIndex >= 1) {
|
|
36233
|
+
return `${normFile}|ac${acIndex}`;
|
|
36234
|
+
}
|
|
36235
|
+
return `${normFile}|${category ?? ""}|${normalizeIssueText(text).slice(0, FP_ISSUE_PREFIX)}`;
|
|
36236
|
+
}
|
|
36237
|
+
function lookupPriorAppearance(priorCounts, finding) {
|
|
36238
|
+
const acKey = finding.acIndex === undefined ? undefined : priorCounts.get(fingerprintFor(finding.file, finding.category, finding.issue, finding.acIndex));
|
|
36239
|
+
const proseKey = priorCounts.get(fingerprintFor(finding.file, finding.category, finding.issue));
|
|
36240
|
+
if (!acKey)
|
|
36241
|
+
return proseKey;
|
|
36242
|
+
if (!proseKey)
|
|
36243
|
+
return acKey;
|
|
36244
|
+
return acKey.count >= proseKey.count ? acKey : proseKey;
|
|
36245
|
+
}
|
|
36246
|
+
function countPriorAppearances(priorIterations, source = "adversarial-review") {
|
|
36247
|
+
const counts = new Map;
|
|
36248
|
+
for (const it of priorIterations) {
|
|
36249
|
+
const seenThisIter = new Map;
|
|
36250
|
+
for (const f of it.findingsAfter ?? []) {
|
|
36251
|
+
if (f.source !== source)
|
|
36252
|
+
continue;
|
|
36253
|
+
const acIndex = typeof f.meta?.acIndex === "number" ? f.meta.acIndex : undefined;
|
|
36254
|
+
seenThisIter.set(fingerprintFor(f.file, f.category, f.message), f.severity);
|
|
36255
|
+
if (acIndex !== undefined) {
|
|
36256
|
+
seenThisIter.set(fingerprintFor(f.file, f.category, f.message, acIndex), f.severity);
|
|
36257
|
+
}
|
|
36258
|
+
}
|
|
36259
|
+
for (const [fp, sev] of seenThisIter) {
|
|
36260
|
+
const cur = counts.get(fp);
|
|
36261
|
+
counts.set(fp, { count: (cur?.count ?? 0) + 1, lastSeverity: sev });
|
|
36262
|
+
}
|
|
36263
|
+
}
|
|
36264
|
+
return counts;
|
|
36265
|
+
}
|
|
36266
|
+
function tagCoverageGap(findings) {
|
|
36267
|
+
return findings.map((f) => ({ ...f, meta: { ...f.meta ?? {}, coverageGap: true } }));
|
|
36268
|
+
}
|
|
36269
|
+
function classifyRecurrence(accepted, priorIterations, cfg, testFileMatch, threshold, source = "adversarial-review") {
|
|
36270
|
+
const blocking = [];
|
|
36271
|
+
const advisory = [];
|
|
36272
|
+
const demoted = [];
|
|
36273
|
+
if (!cfg.enabled) {
|
|
36274
|
+
for (const f of accepted)
|
|
36275
|
+
(isBlockingSeverity(f.severity, threshold) ? blocking : advisory).push(f);
|
|
36276
|
+
return { blocking, advisory, demoted };
|
|
36277
|
+
}
|
|
36278
|
+
const priorCounts = countPriorAppearances(priorIterations, source);
|
|
36279
|
+
for (const f of accepted) {
|
|
36280
|
+
if (f.category === "test-gap" && testFileMatch(f.file) && isBlockingSeverity(f.severity, threshold)) {
|
|
36281
|
+
blocking.push(f);
|
|
36282
|
+
continue;
|
|
36283
|
+
}
|
|
36284
|
+
if (!isBlockingSeverity(f.severity, threshold)) {
|
|
36285
|
+
advisory.push(f);
|
|
36286
|
+
continue;
|
|
36287
|
+
}
|
|
36288
|
+
const prior = lookupPriorAppearance(priorCounts, f);
|
|
36289
|
+
const n = (prior?.count ?? 0) + 1;
|
|
36290
|
+
const prevWasBlocking = prior !== undefined && isBlockingSeverity(prior.lastSeverity, threshold);
|
|
36291
|
+
if (n >= cfg.maxBlockingRounds + 1) {
|
|
36292
|
+
demoted.push(f);
|
|
36293
|
+
} else if (n === 1 || prevWasBlocking) {
|
|
36294
|
+
blocking.push(f);
|
|
36295
|
+
} else {
|
|
36296
|
+
advisory.push(f);
|
|
36297
|
+
}
|
|
36298
|
+
}
|
|
36299
|
+
return { blocking, advisory, demoted };
|
|
36300
|
+
}
|
|
36301
|
+
var MAX_ISSUE_PREFIX = 160, FP_ISSUE_PREFIX = 48;
|
|
36302
|
+
var init_recurrence_demotion = __esm(() => {
|
|
36303
|
+
init_adversarial_helpers();
|
|
36304
|
+
});
|
|
36305
|
+
|
|
36189
36306
|
// src/review/requote-response.ts
|
|
36190
36307
|
function parseRequoteResponse(output) {
|
|
36191
36308
|
const parsed = tryParseLLMJson(output);
|
|
@@ -36233,6 +36350,17 @@ function isRecord(value) {
|
|
|
36233
36350
|
}
|
|
36234
36351
|
var init_requote_response = () => {};
|
|
36235
36352
|
|
|
36353
|
+
// src/operations/_review-fallback.ts
|
|
36354
|
+
function reviewExhaustedFallback(lastOutput, failOpen) {
|
|
36355
|
+
const unparsedPreview = previewOutput(lastOutput, UNPARSED_PREVIEW_BYTES);
|
|
36356
|
+
if (!/"passed"\s*:\s*false/.test(lastOutput))
|
|
36357
|
+
return { ...failOpen, unparsedPreview };
|
|
36358
|
+
return { ...failOpen, passed: false, failOpen: false, looksLikeFail: true, unparsedPreview };
|
|
36359
|
+
}
|
|
36360
|
+
var init__review_fallback = __esm(() => {
|
|
36361
|
+
init_retry();
|
|
36362
|
+
});
|
|
36363
|
+
|
|
36236
36364
|
// src/operations/semantic-review.ts
|
|
36237
36365
|
function withRepromptMarker(output, info) {
|
|
36238
36366
|
const parsed = tryParseLLMJson(output);
|
|
@@ -36451,7 +36579,9 @@ var init_semantic_review = __esm(() => {
|
|
|
36451
36579
|
init_logger2();
|
|
36452
36580
|
init_prompts();
|
|
36453
36581
|
init_finding_filters();
|
|
36582
|
+
init_recurrence_demotion();
|
|
36454
36583
|
init_requote_response();
|
|
36584
|
+
init__review_fallback();
|
|
36455
36585
|
FAIL_OPEN = {
|
|
36456
36586
|
passed: true,
|
|
36457
36587
|
findings: [],
|
|
@@ -36475,7 +36605,8 @@ var init_semantic_review = __esm(() => {
|
|
|
36475
36605
|
invalid: () => ReviewPromptBuilder.jsonRetry(),
|
|
36476
36606
|
truncated: () => ReviewPromptBuilder.jsonRetryCondensed({ blockingThreshold: input.blockingThreshold })
|
|
36477
36607
|
},
|
|
36478
|
-
exhaustedFallback: (lastOutput) =>
|
|
36608
|
+
exhaustedFallback: (lastOutput) => reviewExhaustedFallback(lastOutput, FAIL_OPEN),
|
|
36609
|
+
outputPreviewBytes: UNPARSED_PREVIEW_BYTES,
|
|
36479
36610
|
logContext: { blockingThreshold: input.blockingThreshold ?? "error" }
|
|
36480
36611
|
}),
|
|
36481
36612
|
hopBody: semanticReviewHopBody,
|
|
@@ -36507,6 +36638,7 @@ var init_semantic_review = __esm(() => {
|
|
|
36507
36638
|
repromptEvent
|
|
36508
36639
|
};
|
|
36509
36640
|
}
|
|
36641
|
+
const unparsedPreview = previewOutput(output, UNPARSED_PREVIEW_BYTES);
|
|
36510
36642
|
if (/"passed"\s*:\s*false/.test(output)) {
|
|
36511
36643
|
return {
|
|
36512
36644
|
passed: false,
|
|
@@ -36514,10 +36646,11 @@ var init_semantic_review = __esm(() => {
|
|
|
36514
36646
|
normalizedFindings: [],
|
|
36515
36647
|
acDropped: [],
|
|
36516
36648
|
looksLikeFail: true,
|
|
36649
|
+
unparsedPreview,
|
|
36517
36650
|
repromptEvent
|
|
36518
36651
|
};
|
|
36519
36652
|
}
|
|
36520
|
-
return FAIL_OPEN;
|
|
36653
|
+
return { ...FAIL_OPEN, unparsedPreview };
|
|
36521
36654
|
},
|
|
36522
36655
|
async verify(parsed, input, _verifyCtx) {
|
|
36523
36656
|
if (parsed.failOpen || parsed.looksLikeFail)
|
|
@@ -36529,84 +36662,30 @@ var init_semantic_review = __esm(() => {
|
|
|
36529
36662
|
const sanitized = sanitizeRefModeFindings(findings, input.mode, threshold);
|
|
36530
36663
|
const substantiated = await substantiateSemanticEvidence(sanitized, input.mode, input.workdir, input.story.id, threshold, input.repoRoot);
|
|
36531
36664
|
const { accepted, dropped } = filterByAcGroundingMinimal(substantiated, input.story.acceptanceCriteria);
|
|
36532
|
-
const
|
|
36665
|
+
const isTestFile3 = semanticTestFileMatch(input);
|
|
36666
|
+
const recurrenceCfg = input.semanticConfig.recurrenceDemotion ?? { enabled: false, maxBlockingRounds: 2 };
|
|
36667
|
+
const {
|
|
36668
|
+
blocking,
|
|
36669
|
+
advisory: subThreshold,
|
|
36670
|
+
demoted
|
|
36671
|
+
} = classifyRecurrence(accepted, input.priorSemanticIterations ?? [], recurrenceCfg, isTestFile3, threshold, "semantic-review");
|
|
36672
|
+
const advisoryFindings = [
|
|
36673
|
+
...toReviewFindings(subThreshold.filter((f) => isBlockingSeverity(f.severity, threshold)), { isTestFile: isTestFile3 }),
|
|
36674
|
+
...tagCoverageGap(toReviewFindings(demoted, { isTestFile: isTestFile3 }))
|
|
36675
|
+
];
|
|
36533
36676
|
const passed = blocking.length === 0 && (parsed.passed || accepted.length > 0);
|
|
36534
36677
|
return {
|
|
36535
36678
|
...parsed,
|
|
36536
36679
|
passed,
|
|
36537
36680
|
findings: accepted,
|
|
36538
|
-
normalizedFindings: toReviewFindings(blocking, { isTestFile:
|
|
36681
|
+
normalizedFindings: toReviewFindings(blocking, { isTestFile: isTestFile3 }),
|
|
36682
|
+
advisoryFindings,
|
|
36539
36683
|
acDropped: dropped
|
|
36540
36684
|
};
|
|
36541
36685
|
}
|
|
36542
36686
|
};
|
|
36543
36687
|
});
|
|
36544
36688
|
|
|
36545
|
-
// src/review/recurrence-demotion.ts
|
|
36546
|
-
function normalizeIssueText(s) {
|
|
36547
|
-
return s.replace(/`/g, "").replace(/\s+/g, " ").trim().toLowerCase().slice(0, MAX_ISSUE_PREFIX);
|
|
36548
|
-
}
|
|
36549
|
-
function fingerprintFor(file3, category, text) {
|
|
36550
|
-
const normFile = (file3 ?? "").replace(/\\/g, "/");
|
|
36551
|
-
return `${normFile}|${category ?? ""}|${normalizeIssueText(text).slice(0, FP_ISSUE_PREFIX)}`;
|
|
36552
|
-
}
|
|
36553
|
-
function countPriorAppearances(priorIterations) {
|
|
36554
|
-
const counts = new Map;
|
|
36555
|
-
for (const it of priorIterations) {
|
|
36556
|
-
const seenThisIter = new Map;
|
|
36557
|
-
for (const f of it.findingsAfter ?? []) {
|
|
36558
|
-
if (f.source !== "adversarial-review")
|
|
36559
|
-
continue;
|
|
36560
|
-
const fp = fingerprintFor(f.file, f.category, f.message);
|
|
36561
|
-
seenThisIter.set(fp, f.severity);
|
|
36562
|
-
}
|
|
36563
|
-
for (const [fp, sev] of seenThisIter) {
|
|
36564
|
-
const cur = counts.get(fp);
|
|
36565
|
-
counts.set(fp, { count: (cur?.count ?? 0) + 1, lastSeverity: sev });
|
|
36566
|
-
}
|
|
36567
|
-
}
|
|
36568
|
-
return counts;
|
|
36569
|
-
}
|
|
36570
|
-
function tagCoverageGap(findings) {
|
|
36571
|
-
return findings.map((f) => ({ ...f, meta: { ...f.meta ?? {}, coverageGap: true } }));
|
|
36572
|
-
}
|
|
36573
|
-
function classifyRecurrence(accepted, priorIterations, cfg, testFileMatch, threshold) {
|
|
36574
|
-
const blocking = [];
|
|
36575
|
-
const advisory = [];
|
|
36576
|
-
const demoted = [];
|
|
36577
|
-
if (!cfg.enabled) {
|
|
36578
|
-
for (const f of accepted)
|
|
36579
|
-
(isBlockingSeverity(f.severity, threshold) ? blocking : advisory).push(f);
|
|
36580
|
-
return { blocking, advisory, demoted };
|
|
36581
|
-
}
|
|
36582
|
-
const priorCounts = countPriorAppearances(priorIterations);
|
|
36583
|
-
for (const f of accepted) {
|
|
36584
|
-
if (f.category === "test-gap" && testFileMatch(f.file) && isBlockingSeverity(f.severity, threshold)) {
|
|
36585
|
-
blocking.push(f);
|
|
36586
|
-
continue;
|
|
36587
|
-
}
|
|
36588
|
-
if (!isBlockingSeverity(f.severity, threshold)) {
|
|
36589
|
-
advisory.push(f);
|
|
36590
|
-
continue;
|
|
36591
|
-
}
|
|
36592
|
-
const prior = priorCounts.get(fingerprintFor(f.file, f.category, f.issue));
|
|
36593
|
-
const n = (prior?.count ?? 0) + 1;
|
|
36594
|
-
const prevWasBlocking = prior !== undefined && isBlockingSeverity(prior.lastSeverity, threshold);
|
|
36595
|
-
if (n >= cfg.maxBlockingRounds + 1) {
|
|
36596
|
-
demoted.push(f);
|
|
36597
|
-
} else if (n === 1 || prevWasBlocking) {
|
|
36598
|
-
blocking.push(f);
|
|
36599
|
-
} else {
|
|
36600
|
-
advisory.push(f);
|
|
36601
|
-
}
|
|
36602
|
-
}
|
|
36603
|
-
return { blocking, advisory, demoted };
|
|
36604
|
-
}
|
|
36605
|
-
var MAX_ISSUE_PREFIX = 160, FP_ISSUE_PREFIX = 48;
|
|
36606
|
-
var init_recurrence_demotion = __esm(() => {
|
|
36607
|
-
init_adversarial_helpers();
|
|
36608
|
-
});
|
|
36609
|
-
|
|
36610
36689
|
// src/operations/adversarial-review.ts
|
|
36611
36690
|
function withRepromptMarker2(output, info) {
|
|
36612
36691
|
const parsed = tryParseLLMJson(output);
|
|
@@ -36791,7 +36870,8 @@ var FAIL_OPEN2, ADVERSARIAL_REQUOTE_RECOVERED_EVENT = "review.adversarial.findin
|
|
|
36791
36870
|
invalid: () => ReviewPromptBuilder.jsonRetry(),
|
|
36792
36871
|
truncated: () => ReviewPromptBuilder.jsonRetryCondensed({ blockingThreshold: input.blockingThreshold })
|
|
36793
36872
|
},
|
|
36794
|
-
exhaustedFallback: (lastOutput) =>
|
|
36873
|
+
exhaustedFallback: (lastOutput) => reviewExhaustedFallback(lastOutput, FAIL_OPEN2),
|
|
36874
|
+
outputPreviewBytes: UNPARSED_PREVIEW_BYTES,
|
|
36795
36875
|
logContext: { blockingThreshold: input.blockingThreshold ?? "error" }
|
|
36796
36876
|
}), adversarialReviewOp;
|
|
36797
36877
|
var init_adversarial_review = __esm(() => {
|
|
@@ -36803,6 +36883,7 @@ var init_adversarial_review = __esm(() => {
|
|
|
36803
36883
|
init_finding_filters();
|
|
36804
36884
|
init_recurrence_demotion();
|
|
36805
36885
|
init_requote_response();
|
|
36886
|
+
init__review_fallback();
|
|
36806
36887
|
FAIL_OPEN2 = {
|
|
36807
36888
|
passed: true,
|
|
36808
36889
|
findings: [],
|
|
@@ -38723,7 +38804,6 @@ var init_ground = __esm(() => {
|
|
|
38723
38804
|
init_errors();
|
|
38724
38805
|
init_logger2();
|
|
38725
38806
|
init_prompts();
|
|
38726
|
-
init_truncation();
|
|
38727
38807
|
groundOp = {
|
|
38728
38808
|
kind: "run",
|
|
38729
38809
|
name: "ground",
|
|
@@ -42160,11 +42240,11 @@ var init_findings = __esm(() => {
|
|
|
42160
42240
|
init_cycle();
|
|
42161
42241
|
});
|
|
42162
42242
|
|
|
42163
|
-
// src/review/
|
|
42164
|
-
function
|
|
42243
|
+
// src/review/review-iteration-store.ts
|
|
42244
|
+
function getReviewIterations(store, storyId) {
|
|
42165
42245
|
return store.get(storyId) ?? [];
|
|
42166
42246
|
}
|
|
42167
|
-
function
|
|
42247
|
+
function recordReviewIteration(store, storyId, roundFindings) {
|
|
42168
42248
|
const prior = store.get(storyId) ?? [];
|
|
42169
42249
|
const findingsBefore = prior.length > 0 ? prior[prior.length - 1].findingsAfter : [];
|
|
42170
42250
|
const findingsAfter = [...roundFindings];
|
|
@@ -42180,7 +42260,7 @@ function recordAdversarialIteration(store, storyId, roundFindings) {
|
|
|
42180
42260
|
};
|
|
42181
42261
|
store.set(storyId, [...prior, iteration]);
|
|
42182
42262
|
}
|
|
42183
|
-
var
|
|
42263
|
+
var init_review_iteration_store = __esm(() => {
|
|
42184
42264
|
init_findings();
|
|
42185
42265
|
});
|
|
42186
42266
|
|
|
@@ -42623,7 +42703,7 @@ var package_default;
|
|
|
42623
42703
|
var init_package = __esm(() => {
|
|
42624
42704
|
package_default = {
|
|
42625
42705
|
name: "@nathapp/nax",
|
|
42626
|
-
version: "0.75.
|
|
42706
|
+
version: "0.75.6",
|
|
42627
42707
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
42628
42708
|
type: "module",
|
|
42629
42709
|
bin: {
|
|
@@ -42727,8 +42807,8 @@ var init_version = __esm(() => {
|
|
|
42727
42807
|
NAX_VERSION = package_default.version;
|
|
42728
42808
|
NAX_COMMIT = (() => {
|
|
42729
42809
|
try {
|
|
42730
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
42731
|
-
return "
|
|
42810
|
+
if (/^[0-9a-f]{6,10}$/.test("064f9083"))
|
|
42811
|
+
return "064f9083";
|
|
42732
42812
|
} catch {}
|
|
42733
42813
|
try {
|
|
42734
42814
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -42775,6 +42855,8 @@ function toPersistedEntry(entry, epochMs) {
|
|
|
42775
42855
|
blockingThreshold: entry.blockingThreshold ?? "error",
|
|
42776
42856
|
result: entry.result,
|
|
42777
42857
|
advisoryFindings: entry.advisoryFindings ?? null,
|
|
42858
|
+
acDropped: entry.acDropped ?? null,
|
|
42859
|
+
...entry.parsed ? {} : { unparsedPreview: entry.unparsedPreview ?? null },
|
|
42778
42860
|
diffAvailable: entry.diffAvailable ?? null,
|
|
42779
42861
|
adversarialDropAnalysis: entry.adversarialDropAnalysis ?? null,
|
|
42780
42862
|
adversarialAcceptAnalysis: entry.adversarialAcceptAnalysis ?? null
|
|
@@ -44746,7 +44828,7 @@ var init_review = __esm(() => {
|
|
|
44746
44828
|
init_category_fix_target();
|
|
44747
44829
|
init_finding_filters();
|
|
44748
44830
|
init_ac_quote_validator();
|
|
44749
|
-
|
|
44831
|
+
init_review_iteration_store();
|
|
44750
44832
|
init_ac_structural_counterfactual();
|
|
44751
44833
|
init_adversarial();
|
|
44752
44834
|
init_semantic_evidence();
|
|
@@ -48169,6 +48251,8 @@ function attachReviewAuditSubscriber(bus, auditor, runId) {
|
|
|
48169
48251
|
blockingThreshold: event.blockingThreshold,
|
|
48170
48252
|
result: event.result,
|
|
48171
48253
|
advisoryFindings: event.advisoryFindings,
|
|
48254
|
+
acDropped: event.acDropped ? [...event.acDropped] : undefined,
|
|
48255
|
+
unparsedPreview: event.unparsedPreview,
|
|
48172
48256
|
diffAvailable: event.diffAvailable,
|
|
48173
48257
|
adversarialDropAnalysis: event.adversarialDropAnalysis,
|
|
48174
48258
|
adversarialAcceptAnalysis: event.adversarialAcceptAnalysis
|
|
@@ -49860,6 +49944,7 @@ function createRuntime(config2, workdir, opts) {
|
|
|
49860
49944
|
const logger = getLogger();
|
|
49861
49945
|
const quarantineMemo = createQuarantineMemo();
|
|
49862
49946
|
const adversarialIterations = new Map;
|
|
49947
|
+
const semanticIterations = new Map;
|
|
49863
49948
|
const rectificationOscillations = new Map;
|
|
49864
49949
|
let closed = false;
|
|
49865
49950
|
return {
|
|
@@ -49883,6 +49968,7 @@ function createRuntime(config2, workdir, opts) {
|
|
|
49883
49968
|
logger,
|
|
49884
49969
|
quarantineMemo,
|
|
49885
49970
|
adversarialIterations,
|
|
49971
|
+
semanticIterations,
|
|
49886
49972
|
rectificationOscillations,
|
|
49887
49973
|
get signal() {
|
|
49888
49974
|
return controller.signal;
|
|
@@ -57571,10 +57657,13 @@ ${stderr}` };
|
|
|
57571
57657
|
|
|
57572
57658
|
// src/context/engine/effectiveness.ts
|
|
57573
57659
|
function tokenize2(text) {
|
|
57574
|
-
|
|
57575
|
-
|
|
57576
|
-
|
|
57577
|
-
|
|
57660
|
+
const terms = new Set;
|
|
57661
|
+
for (const match of text.matchAll(TOKEN_PATTERN)) {
|
|
57662
|
+
const term = match[0].toLowerCase();
|
|
57663
|
+
if (term.length >= MIN_TOKEN_LEN2 && !STOPWORDS2.has(term))
|
|
57664
|
+
terms.add(term);
|
|
57665
|
+
}
|
|
57666
|
+
return terms;
|
|
57578
57667
|
}
|
|
57579
57668
|
function sharedTermCount2(a, b) {
|
|
57580
57669
|
let count = 0;
|
|
@@ -57584,34 +57673,35 @@ function sharedTermCount2(a, b) {
|
|
|
57584
57673
|
}
|
|
57585
57674
|
return count;
|
|
57586
57675
|
}
|
|
57587
|
-
function
|
|
57588
|
-
const
|
|
57589
|
-
|
|
57590
|
-
|
|
57676
|
+
function buildEvidenceTerms(agentOutput, diffText, findingMessages) {
|
|
57677
|
+
const diffTerms = diffText ? _effectivenessDeps.tokenize(diffText) : undefined;
|
|
57678
|
+
const outputTerms = agentOutput ? _effectivenessDeps.tokenize(agentOutput) : undefined;
|
|
57679
|
+
let combined;
|
|
57680
|
+
if (diffTerms || outputTerms) {
|
|
57681
|
+
combined = new Set(diffTerms);
|
|
57682
|
+
for (const term of outputTerms ?? [])
|
|
57683
|
+
combined.add(term);
|
|
57591
57684
|
}
|
|
57592
|
-
|
|
57593
|
-
|
|
57594
|
-
|
|
57595
|
-
|
|
57596
|
-
|
|
57597
|
-
|
|
57598
|
-
|
|
57685
|
+
return {
|
|
57686
|
+
findings: findingMessages.map((message) => ({ message, terms: _effectivenessDeps.tokenize(message) })),
|
|
57687
|
+
diff: diffTerms,
|
|
57688
|
+
combined
|
|
57689
|
+
};
|
|
57690
|
+
}
|
|
57691
|
+
function classifyWithTerms(chunkSummary, evidence) {
|
|
57692
|
+
const summaryTerms = _effectivenessDeps.tokenize(chunkSummary);
|
|
57693
|
+
if (summaryTerms.size < MIN_SIGNIFICANT_TERMS)
|
|
57694
|
+
return { signal: "unknown" };
|
|
57695
|
+
for (const finding of evidence.findings) {
|
|
57696
|
+
if (sharedTermCount2(summaryTerms, finding.terms) >= MIN_SIGNIFICANT_TERMS) {
|
|
57697
|
+
return { signal: "contradicted", evidence: finding.message.slice(0, 200) };
|
|
57599
57698
|
}
|
|
57600
57699
|
}
|
|
57601
|
-
if (
|
|
57602
|
-
|
|
57603
|
-
if (sharedTermCount2(summaryTerms, diffTerms) >= MIN_SIGNIFICANT_TERMS) {
|
|
57604
|
-
return {
|
|
57605
|
-
signal: "followed",
|
|
57606
|
-
evidence: "terms found in diff"
|
|
57607
|
-
};
|
|
57608
|
-
}
|
|
57700
|
+
if (evidence.diff && sharedTermCount2(summaryTerms, evidence.diff) >= MIN_SIGNIFICANT_TERMS) {
|
|
57701
|
+
return { signal: "followed", evidence: "terms found in diff" };
|
|
57609
57702
|
}
|
|
57610
|
-
if (
|
|
57611
|
-
|
|
57612
|
-
if (sharedTermCount2(summaryTerms, combinedTerms) < MIN_SIGNIFICANT_TERMS) {
|
|
57613
|
-
return { signal: "ignored" };
|
|
57614
|
-
}
|
|
57703
|
+
if (evidence.combined && sharedTermCount2(summaryTerms, evidence.combined) < MIN_SIGNIFICANT_TERMS) {
|
|
57704
|
+
return { signal: "ignored" };
|
|
57615
57705
|
}
|
|
57616
57706
|
return { signal: "unknown" };
|
|
57617
57707
|
}
|
|
@@ -57621,16 +57711,18 @@ async function annotateManifestEffectiveness(projectDir, featureId, storyId, {
|
|
|
57621
57711
|
findingMessages
|
|
57622
57712
|
}) {
|
|
57623
57713
|
const stored = await loadContextManifests(projectDir, storyId, featureId);
|
|
57714
|
+
let evidenceTerms;
|
|
57624
57715
|
for (const item of stored) {
|
|
57625
57716
|
const { manifest } = item;
|
|
57626
57717
|
if (!manifest.chunkSummaries || manifest.includedChunks.length === 0)
|
|
57627
57718
|
continue;
|
|
57719
|
+
evidenceTerms ??= buildEvidenceTerms(agentOutput, diffText, findingMessages);
|
|
57628
57720
|
const effectiveness = {};
|
|
57629
57721
|
for (const id of manifest.includedChunks) {
|
|
57630
57722
|
const summary = manifest.chunkSummaries[id];
|
|
57631
57723
|
if (!summary)
|
|
57632
57724
|
continue;
|
|
57633
|
-
effectiveness[id] =
|
|
57725
|
+
effectiveness[id] = classifyWithTerms(summary, evidenceTerms);
|
|
57634
57726
|
}
|
|
57635
57727
|
if (Object.keys(effectiveness).length === 0)
|
|
57636
57728
|
continue;
|
|
@@ -57648,12 +57740,13 @@ async function annotateManifestEffectiveness(projectDir, featureId, storyId, {
|
|
|
57648
57740
|
}
|
|
57649
57741
|
}
|
|
57650
57742
|
}
|
|
57651
|
-
var _effectivenessDeps, MIN_SIGNIFICANT_TERMS = 3, STOPWORDS2, MIN_TOKEN_LEN2 = 4;
|
|
57743
|
+
var _effectivenessDeps, MIN_SIGNIFICANT_TERMS = 3, STOPWORDS2, MIN_TOKEN_LEN2 = 4, TOKEN_PATTERN;
|
|
57652
57744
|
var init_effectiveness = __esm(() => {
|
|
57653
57745
|
init_logger2();
|
|
57654
57746
|
init_manifest_store();
|
|
57655
57747
|
_effectivenessDeps = {
|
|
57656
|
-
getLogger
|
|
57748
|
+
getLogger,
|
|
57749
|
+
tokenize: tokenize2
|
|
57657
57750
|
};
|
|
57658
57751
|
STOPWORDS2 = new Set([
|
|
57659
57752
|
"the",
|
|
@@ -57695,6 +57788,7 @@ var init_effectiveness = __esm(() => {
|
|
|
57695
57788
|
"you",
|
|
57696
57789
|
"your"
|
|
57697
57790
|
]);
|
|
57791
|
+
TOKEN_PATTERN = /[^\s_\-./:,;()\[\]{}'"!?]+/g;
|
|
57698
57792
|
});
|
|
57699
57793
|
|
|
57700
57794
|
// src/execution/progress.ts
|
|
@@ -57711,19 +57805,61 @@ async function appendProgress(featureDir, storyId, status, message) {
|
|
|
57711
57805
|
var init_progress = () => {};
|
|
57712
57806
|
|
|
57713
57807
|
// src/pipeline/stages/completion.ts
|
|
57808
|
+
function logHighMemoryCheckpoint(logger, ctx) {
|
|
57809
|
+
const usage = process.memoryUsage();
|
|
57810
|
+
if (usage.heapUsed < HIGH_MEMORY_TELEMETRY_BYTES && usage.rss < HIGH_MEMORY_TELEMETRY_BYTES)
|
|
57811
|
+
return;
|
|
57812
|
+
logger.debug("completion.memory", "High memory at completion boundary", {
|
|
57813
|
+
storyId: ctx.story.id,
|
|
57814
|
+
heapUsedBytes: usage.heapUsed,
|
|
57815
|
+
rssBytes: usage.rss,
|
|
57816
|
+
externalBytes: usage.external,
|
|
57817
|
+
arrayBuffersBytes: usage.arrayBuffers,
|
|
57818
|
+
agentOutputChars: ctx.agentResult?.output.length ?? 0
|
|
57819
|
+
});
|
|
57820
|
+
}
|
|
57821
|
+
async function readTextStreamPrefix(stream, maxChars) {
|
|
57822
|
+
const reader = stream.getReader();
|
|
57823
|
+
const decoder = new TextDecoder;
|
|
57824
|
+
let output = "";
|
|
57825
|
+
try {
|
|
57826
|
+
while (true) {
|
|
57827
|
+
const { done, value } = await reader.read();
|
|
57828
|
+
if (done)
|
|
57829
|
+
break;
|
|
57830
|
+
if (output.length >= maxChars)
|
|
57831
|
+
continue;
|
|
57832
|
+
const decoded = decoder.decode(value, { stream: true });
|
|
57833
|
+
output += decoded.slice(0, maxChars - output.length);
|
|
57834
|
+
}
|
|
57835
|
+
if (output.length < maxChars) {
|
|
57836
|
+
output += decoder.decode().slice(0, maxChars - output.length);
|
|
57837
|
+
}
|
|
57838
|
+
return output;
|
|
57839
|
+
} finally {
|
|
57840
|
+
reader.releaseLock();
|
|
57841
|
+
}
|
|
57842
|
+
}
|
|
57714
57843
|
async function getDiffText(workdir, baseRef) {
|
|
57715
57844
|
if (!baseRef)
|
|
57716
57845
|
return "";
|
|
57717
57846
|
try {
|
|
57718
|
-
const proc =
|
|
57719
|
-
|
|
57720
|
-
|
|
57721
|
-
|
|
57847
|
+
const proc = _completionDeps.spawn(["git", "diff", `${baseRef}..HEAD`], {
|
|
57848
|
+
cwd: workdir,
|
|
57849
|
+
stdout: "pipe",
|
|
57850
|
+
stderr: "pipe"
|
|
57851
|
+
});
|
|
57852
|
+
const [output] = await Promise.all([
|
|
57853
|
+
readTextStreamPrefix(proc.stdout, MAX_EFFECTIVENESS_DIFF_CHARS),
|
|
57854
|
+
readTextStreamPrefix(proc.stderr, 0),
|
|
57855
|
+
proc.exited
|
|
57856
|
+
]);
|
|
57857
|
+
return output;
|
|
57722
57858
|
} catch {
|
|
57723
57859
|
return "";
|
|
57724
57860
|
}
|
|
57725
57861
|
}
|
|
57726
|
-
var completionStage, _completionDeps;
|
|
57862
|
+
var MAX_EFFECTIVENESS_DIFF_CHARS = 8000, HIGH_MEMORY_TELEMETRY_BYTES, completionStage, _completionDeps;
|
|
57727
57863
|
var init_completion = __esm(() => {
|
|
57728
57864
|
init_semantic_verdict();
|
|
57729
57865
|
init_effectiveness();
|
|
@@ -57733,6 +57869,7 @@ var init_completion = __esm(() => {
|
|
|
57733
57869
|
init_metrics();
|
|
57734
57870
|
init_prd();
|
|
57735
57871
|
init_event_bus();
|
|
57872
|
+
HIGH_MEMORY_TELEMETRY_BYTES = 512 * 1024 * 1024;
|
|
57736
57873
|
completionStage = {
|
|
57737
57874
|
name: "completion",
|
|
57738
57875
|
enabled: () => true,
|
|
@@ -57802,6 +57939,7 @@ var init_completion = __esm(() => {
|
|
|
57802
57939
|
if (persistPrd) {
|
|
57803
57940
|
await _completionDeps.savePRD(ctx.prd, prdPath);
|
|
57804
57941
|
}
|
|
57942
|
+
logHighMemoryCheckpoint(logger, ctx);
|
|
57805
57943
|
const updatedCounts = countStories(ctx.prd);
|
|
57806
57944
|
logger.info("completion", "Progress update", {
|
|
57807
57945
|
storyId: ctx.story.id,
|
|
@@ -57817,7 +57955,9 @@ var init_completion = __esm(() => {
|
|
|
57817
57955
|
checkReviewGate,
|
|
57818
57956
|
persistSemanticVerdict,
|
|
57819
57957
|
savePRD,
|
|
57820
|
-
getDiffText
|
|
57958
|
+
getDiffText,
|
|
57959
|
+
readTextStreamPrefix,
|
|
57960
|
+
spawn: Bun.spawn
|
|
57821
57961
|
};
|
|
57822
57962
|
});
|
|
57823
57963
|
|
|
@@ -58520,161 +58660,6 @@ var init_paths3 = __esm(() => {
|
|
|
58520
58660
|
init_paths();
|
|
58521
58661
|
});
|
|
58522
58662
|
|
|
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
58663
|
// src/execution/story-orchestrator/types.ts
|
|
58679
58664
|
var EXHAUSTED_EXIT_REASONS, TDD_OP_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES, STRICT_VERDICT_PHASE_NAMES;
|
|
58680
58665
|
var init_types9 = __esm(() => {
|
|
@@ -58805,7 +58790,8 @@ function gateFindingKey(finding) {
|
|
|
58805
58790
|
function isQuarantinedFlake(finding, quarantineMemo) {
|
|
58806
58791
|
if (finding.source !== "test-runner")
|
|
58807
58792
|
return false;
|
|
58808
|
-
|
|
58793
|
+
const key = gateFindingKey(finding);
|
|
58794
|
+
return key !== KEYLESS_GATE_FAILURE_KEY && quarantineMemo?.has(key) === true;
|
|
58809
58795
|
}
|
|
58810
58796
|
function describeGateRegression(input) {
|
|
58811
58797
|
const { gateOutput, baselineKeys, gateName, storyId, quarantineMemo } = input;
|
|
@@ -58856,6 +58842,245 @@ var init_phase_eval = __esm(() => {
|
|
|
58856
58842
|
init_types9();
|
|
58857
58843
|
});
|
|
58858
58844
|
|
|
58845
|
+
// src/execution/story-orchestrator/nbf-flake-triage.ts
|
|
58846
|
+
function isCandidate(input) {
|
|
58847
|
+
if (input.finding.source !== "test-runner" || input.finding.category !== "failed-test")
|
|
58848
|
+
return false;
|
|
58849
|
+
const key = gateFindingKey(input.finding);
|
|
58850
|
+
return !input.transactionInput.baselineKeys.has(key) && !input.memo.has(key) && !input.attemptedKeys.has(key);
|
|
58851
|
+
}
|
|
58852
|
+
function createNbfFlakeTriageTransaction(input) {
|
|
58853
|
+
const pendingKeys = new Set;
|
|
58854
|
+
const attemptedKeys = new Set;
|
|
58855
|
+
let flakeTriageRan = false;
|
|
58856
|
+
const memo2 = {
|
|
58857
|
+
has: (key) => pendingKeys.has(key) || input.baseMemo?.has(key) === true,
|
|
58858
|
+
add: (key) => pendingKeys.add(key)
|
|
58859
|
+
};
|
|
58860
|
+
return {
|
|
58861
|
+
memo: memo2,
|
|
58862
|
+
get flakeTriageRan() {
|
|
58863
|
+
return flakeTriageRan;
|
|
58864
|
+
},
|
|
58865
|
+
candidates: (findings) => findings.filter((finding) => isCandidate({ finding, transactionInput: input, memo: memo2, attemptedKeys })),
|
|
58866
|
+
recordAttempt: (findings, ran) => {
|
|
58867
|
+
if (!ran)
|
|
58868
|
+
return;
|
|
58869
|
+
flakeTriageRan = true;
|
|
58870
|
+
for (const finding of findings)
|
|
58871
|
+
attemptedKeys.add(gateFindingKey(finding));
|
|
58872
|
+
},
|
|
58873
|
+
commit: () => {
|
|
58874
|
+
for (const key of pendingKeys)
|
|
58875
|
+
input.baseMemo?.add(key);
|
|
58876
|
+
}
|
|
58877
|
+
};
|
|
58878
|
+
}
|
|
58879
|
+
async function triageNbfGate(input) {
|
|
58880
|
+
const candidates = input.transaction.candidates(extractPhaseFindings(input.output));
|
|
58881
|
+
if (candidates.length === 0)
|
|
58882
|
+
return;
|
|
58883
|
+
const record2 = input.output;
|
|
58884
|
+
const rawOutput = typeof record2.rawOutput === "string" ? record2.rawOutput : "";
|
|
58885
|
+
try {
|
|
58886
|
+
const [, report] = await input.triage(candidates, {
|
|
58887
|
+
ctx: input.ctx,
|
|
58888
|
+
rawOutput,
|
|
58889
|
+
quarantineMemo: input.transaction.memo
|
|
58890
|
+
});
|
|
58891
|
+
const ran = report.flakeTriageRan ?? true;
|
|
58892
|
+
if (ran) {
|
|
58893
|
+
for (const key of report.quarantinedKeys)
|
|
58894
|
+
input.transaction.memo.add(key);
|
|
58895
|
+
}
|
|
58896
|
+
input.transaction.recordAttempt(candidates, ran);
|
|
58897
|
+
} catch (err) {
|
|
58898
|
+
getSafeLogger()?.warn("story-orchestrator", "NBF flake triage threw \u2014 keeping findings blocking", {
|
|
58899
|
+
storyId: input.ctx.storyId,
|
|
58900
|
+
gateName: input.gateName,
|
|
58901
|
+
error: err instanceof Error ? err.message : String(err)
|
|
58902
|
+
});
|
|
58903
|
+
}
|
|
58904
|
+
}
|
|
58905
|
+
var init_nbf_flake_triage = __esm(() => {
|
|
58906
|
+
init_logger2();
|
|
58907
|
+
init_phase_eval();
|
|
58908
|
+
});
|
|
58909
|
+
|
|
58910
|
+
// src/execution/non-blocking-fix.ts
|
|
58911
|
+
function actionableAdvisoryFindings(findings) {
|
|
58912
|
+
return findings.filter((f) => f.actionRequired !== false);
|
|
58913
|
+
}
|
|
58914
|
+
function shouldRunNonBlockingFix(cfg, advisoryCount) {
|
|
58915
|
+
return cfg?.enabled === true && advisoryCount > 0;
|
|
58916
|
+
}
|
|
58917
|
+
function nonBlockingExcludePhases() {
|
|
58918
|
+
return REVIEW_PHASE_KINDS;
|
|
58919
|
+
}
|
|
58920
|
+
function nonBlockingExtraPhases(cfg) {
|
|
58921
|
+
return (cfg.scope === "both" || cfg.scope === "triage") && cfg.verifierGuard ? ["verifier"] : [];
|
|
58922
|
+
}
|
|
58923
|
+
function createMeasureSourceDiff(args) {
|
|
58924
|
+
const packageDirRel = packageDirRelative(args.projectDir, args.packageDir);
|
|
58925
|
+
return async (workdir, fromRef) => {
|
|
58926
|
+
const resolved = await _nonBlockingFixDeps.resolveTestFilePatterns(args.config, args.projectDir, packageDirRel);
|
|
58927
|
+
const isTestFile3 = createTestFileClassifier(resolved);
|
|
58928
|
+
const proc = _nonBlockingFixDeps.spawn(["git", "diff", "--numstat", fromRef], {
|
|
58929
|
+
cwd: workdir,
|
|
58930
|
+
stdout: "pipe",
|
|
58931
|
+
stderr: "pipe"
|
|
58932
|
+
});
|
|
58933
|
+
const stdout = await Bun.readableStreamToText(proc.stdout);
|
|
58934
|
+
const stderr = await Bun.readableStreamToText(proc.stderr);
|
|
58935
|
+
const exitCode = await proc.exited;
|
|
58936
|
+
if (exitCode !== 0) {
|
|
58937
|
+
const detail = stderr.trim() || `exit ${exitCode}`;
|
|
58938
|
+
throw new Error(`[non-blocking-fix] git diff --numstat failed: ${detail}`);
|
|
58939
|
+
}
|
|
58940
|
+
let fileCount = 0;
|
|
58941
|
+
let sourceLineCount = 0;
|
|
58942
|
+
for (const line of stdout.trim().split(`
|
|
58943
|
+
`).filter(Boolean)) {
|
|
58944
|
+
const [addedStr, _deletedStr, filePath] = line.split("\t");
|
|
58945
|
+
if (!filePath || isTestFile3(filePath))
|
|
58946
|
+
continue;
|
|
58947
|
+
fileCount += 1;
|
|
58948
|
+
const added = Number.parseInt(addedStr ?? "", 10);
|
|
58949
|
+
if (Number.isFinite(added))
|
|
58950
|
+
sourceLineCount += added;
|
|
58951
|
+
}
|
|
58952
|
+
return { fileCount, sourceLineCount };
|
|
58953
|
+
};
|
|
58954
|
+
}
|
|
58955
|
+
async function runNonBlockingFix(args, overrides = {}) {
|
|
58956
|
+
const _deps = { ...DEFAULT_DEPS, ...overrides };
|
|
58957
|
+
const logger = getSafeLogger();
|
|
58958
|
+
if (!shouldRunNonBlockingFix(args.cfg, args.advisoryFindings.length)) {
|
|
58959
|
+
return { ran: false, kept: false, restored: false };
|
|
58960
|
+
}
|
|
58961
|
+
const phaseOutputsSnapshot = { ...args.phaseOutputs };
|
|
58962
|
+
const phaseCostsSnapshot = { ...args.phaseCosts };
|
|
58963
|
+
let restoreRef;
|
|
58964
|
+
try {
|
|
58965
|
+
restoreRef = await _deps.captureSnapshotRef(args.workdir, args.storyId);
|
|
58966
|
+
} catch (err) {
|
|
58967
|
+
logger?.warn("non-blocking-fix", "snapshot capture failed \u2014 skipping best-effort pass (no rollback point)", {
|
|
58968
|
+
storyId: args.storyId,
|
|
58969
|
+
error: err instanceof Error ? err.message : String(err)
|
|
58970
|
+
});
|
|
58971
|
+
return { ran: false, kept: false, restored: false };
|
|
58972
|
+
}
|
|
58973
|
+
const maxAttempts = 1 + args.cfg.regressionAttempts;
|
|
58974
|
+
const flakeTriage = createNbfFlakeTriageTransaction({
|
|
58975
|
+
baseMemo: args.quarantineMemo,
|
|
58976
|
+
baselineKeys: args.gateBaselineKeys ?? new Set
|
|
58977
|
+
});
|
|
58978
|
+
let exhausted = false;
|
|
58979
|
+
try {
|
|
58980
|
+
const result = await args.runRectify(maxAttempts, flakeTriage);
|
|
58981
|
+
exhausted = result.rectificationExhausted === true;
|
|
58982
|
+
} catch (err) {
|
|
58983
|
+
logger?.warn("non-blocking-fix", "best-effort pass threw \u2014 restoring", {
|
|
58984
|
+
storyId: args.storyId,
|
|
58985
|
+
error: err instanceof Error ? err.message : String(err)
|
|
58986
|
+
});
|
|
58987
|
+
exhausted = true;
|
|
58988
|
+
}
|
|
58989
|
+
if (!exhausted) {
|
|
58990
|
+
const gateVerdict = args.keptTreeRegressed?.(flakeTriage.memo);
|
|
58991
|
+
if (gateVerdict?.regressed) {
|
|
58992
|
+
logGateRegression({
|
|
58993
|
+
logger,
|
|
58994
|
+
storyId: args.storyId,
|
|
58995
|
+
message: "kept tree regressed the full-suite gate \u2014 restoring (ADR-024 \xA73)",
|
|
58996
|
+
verdict: gateVerdict,
|
|
58997
|
+
flakeTriageRan: flakeTriage.flakeTriageRan
|
|
58998
|
+
});
|
|
58999
|
+
return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
|
|
59000
|
+
}
|
|
59001
|
+
const cap = args.cfg.sourceDiffCap;
|
|
59002
|
+
if (cap) {
|
|
59003
|
+
let metrics;
|
|
59004
|
+
try {
|
|
59005
|
+
metrics = await _deps.measureSourceDiff(args.workdir, restoreRef);
|
|
59006
|
+
} catch (err) {
|
|
59007
|
+
logger?.warn("non-blocking-fix", "source-diff measurement threw \u2014 restoring", {
|
|
59008
|
+
storyId: args.storyId,
|
|
59009
|
+
error: err instanceof Error ? err.message : String(err)
|
|
59010
|
+
});
|
|
59011
|
+
return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
|
|
59012
|
+
}
|
|
59013
|
+
if (metrics.fileCount > cap.maxFiles || metrics.sourceLineCount > cap.maxLines) {
|
|
59014
|
+
logger?.info("non-blocking-fix", "source diff exceeded cap \u2014 restoring", {
|
|
59015
|
+
storyId: args.storyId,
|
|
59016
|
+
fileCount: metrics.fileCount,
|
|
59017
|
+
sourceLineCount: metrics.sourceLineCount,
|
|
59018
|
+
cap
|
|
59019
|
+
});
|
|
59020
|
+
return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
|
|
59021
|
+
}
|
|
59022
|
+
}
|
|
59023
|
+
flakeTriage.commit();
|
|
59024
|
+
logger?.info("non-blocking-fix", "best-effort fix kept", { storyId: args.storyId });
|
|
59025
|
+
return { ran: true, kept: true, restored: false };
|
|
59026
|
+
}
|
|
59027
|
+
const exhaustedGateVerdict = args.keptTreeRegressed?.(flakeTriage.memo);
|
|
59028
|
+
if (exhaustedGateVerdict?.regressed) {
|
|
59029
|
+
logGateRegression({
|
|
59030
|
+
logger,
|
|
59031
|
+
storyId: args.storyId,
|
|
59032
|
+
message: "best-effort fix exhausted with the full-suite gate red",
|
|
59033
|
+
verdict: exhaustedGateVerdict,
|
|
59034
|
+
flakeTriageRan: flakeTriage.flakeTriageRan
|
|
59035
|
+
});
|
|
59036
|
+
}
|
|
59037
|
+
return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
|
|
59038
|
+
}
|
|
59039
|
+
function logGateRegression(input) {
|
|
59040
|
+
const { logger, storyId, message, verdict, flakeTriageRan } = input;
|
|
59041
|
+
logger?.info("non-blocking-fix", message, {
|
|
59042
|
+
storyId,
|
|
59043
|
+
regressedKeys: verdict.regressedKeys.slice(0, MAX_LOGGED_REGRESSED_KEYS),
|
|
59044
|
+
regressedKeyCount: verdict.regressedKeys.length,
|
|
59045
|
+
baselineKeySize: verdict.baselineKeySize,
|
|
59046
|
+
keyless: verdict.keyless,
|
|
59047
|
+
memoExcludedKeyCount: verdict.memoExcludedKeys.length,
|
|
59048
|
+
flakeTriageRan
|
|
59049
|
+
});
|
|
59050
|
+
}
|
|
59051
|
+
async function restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger) {
|
|
59052
|
+
await _deps.rollbackToRef(args.workdir, restoreRef);
|
|
59053
|
+
for (const key of Object.keys(args.phaseOutputs))
|
|
59054
|
+
delete args.phaseOutputs[key];
|
|
59055
|
+
Object.assign(args.phaseOutputs, phaseOutputsSnapshot);
|
|
59056
|
+
for (const key of Object.keys(args.phaseCosts))
|
|
59057
|
+
delete args.phaseCosts[key];
|
|
59058
|
+
Object.assign(args.phaseCosts, phaseCostsSnapshot);
|
|
59059
|
+
logger?.info("non-blocking-fix", "best-effort fix exhausted \u2014 restored to adversarial-passed", {
|
|
59060
|
+
storyId: args.storyId
|
|
59061
|
+
});
|
|
59062
|
+
return { ran: true, kept: false, restored: true };
|
|
59063
|
+
}
|
|
59064
|
+
var REVIEW_PHASE_KINDS, MAX_LOGGED_REGRESSED_KEYS = 10, _nonBlockingFixDeps, DEFAULT_DEPS;
|
|
59065
|
+
var init_non_blocking_fix = __esm(() => {
|
|
59066
|
+
init_logger2();
|
|
59067
|
+
init_rollback();
|
|
59068
|
+
init_test_runners();
|
|
59069
|
+
init_bun_deps();
|
|
59070
|
+
init_paths3();
|
|
59071
|
+
init_nbf_flake_triage();
|
|
59072
|
+
REVIEW_PHASE_KINDS = ["semantic-review", "adversarial-review"];
|
|
59073
|
+
_nonBlockingFixDeps = {
|
|
59074
|
+
spawn: typedSpawn,
|
|
59075
|
+
resolveTestFilePatterns
|
|
59076
|
+
};
|
|
59077
|
+
DEFAULT_DEPS = {
|
|
59078
|
+
captureSnapshotRef,
|
|
59079
|
+
rollbackToRef,
|
|
59080
|
+
measureSourceDiff: async () => ({ fileCount: 0, sourceLineCount: 0 })
|
|
59081
|
+
};
|
|
59082
|
+
});
|
|
59083
|
+
|
|
58859
59084
|
// src/execution/story-orchestrator/phase-state.ts
|
|
58860
59085
|
function isSlot(value) {
|
|
58861
59086
|
return value !== null && typeof value === "object" && "op" in value && "input" in value && typeof value.op?.kind === "string";
|
|
@@ -59049,15 +59274,15 @@ var init_verification = __esm(() => {
|
|
|
59049
59274
|
});
|
|
59050
59275
|
|
|
59051
59276
|
// src/execution/story-orchestrator/flake-triage-seam.ts
|
|
59052
|
-
var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
|
|
59277
|
+
var productionTriageSeam = async (gateFindings, { ctx, rawOutput, quarantineMemo }) => {
|
|
59053
59278
|
const config2 = ctx.packageView.config;
|
|
59054
59279
|
const flakeDetection = config2.execution?.flakeDetection;
|
|
59055
59280
|
if (!flakeDetection?.enabled) {
|
|
59056
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59281
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
59057
59282
|
}
|
|
59058
59283
|
const framework = detectFramework(rawOutput);
|
|
59059
59284
|
if (framework === "unknown") {
|
|
59060
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59285
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
59061
59286
|
}
|
|
59062
59287
|
const workdir = ctx.runtime.workdir;
|
|
59063
59288
|
const storyWorkdir = ctx.story?.workdir;
|
|
@@ -59066,11 +59291,11 @@ var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
|
|
|
59066
59291
|
const { testCommand } = await resolveQualityTestCommands2(config2, workdir, storyWorkdir);
|
|
59067
59292
|
const baseCommand = testCommand ?? config2.quality?.commands?.test;
|
|
59068
59293
|
if (!baseCommand) {
|
|
59069
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59294
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
59070
59295
|
}
|
|
59071
59296
|
const diff = await resolveFlakeBaselineDiff(config2, workdir, storyWorkdir);
|
|
59072
59297
|
if (diff === null) {
|
|
59073
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59298
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
59074
59299
|
}
|
|
59075
59300
|
const result = await triageFlakyFindings({
|
|
59076
59301
|
findings: gateFindings,
|
|
@@ -59079,15 +59304,15 @@ var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
|
|
|
59079
59304
|
baseCommand,
|
|
59080
59305
|
cwd: ctx.packageDir,
|
|
59081
59306
|
framework,
|
|
59082
|
-
quarantineMemo: ctx.runtime.quarantineMemo
|
|
59307
|
+
quarantineMemo: quarantineMemo ?? ctx.runtime.quarantineMemo
|
|
59083
59308
|
});
|
|
59084
|
-
return [result.findings, { quarantinedKeys: result.quarantineReport.keys }];
|
|
59309
|
+
return [result.findings, { quarantinedKeys: result.quarantineReport.keys, flakeTriageRan: true }];
|
|
59085
59310
|
} catch (err) {
|
|
59086
59311
|
getSafeLogger()?.warn("story-orchestrator", "Flake triage seam failed resolving context \u2014 keeping findings blocking (no quarantine)", {
|
|
59087
59312
|
storyId: ctx.storyId,
|
|
59088
59313
|
error: errorMessage(err)
|
|
59089
59314
|
});
|
|
59090
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59315
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
59091
59316
|
}
|
|
59092
59317
|
};
|
|
59093
59318
|
var init_flake_triage_seam = __esm(() => {
|
|
@@ -59104,11 +59329,12 @@ function toReviewDecisionPayload(opName, output) {
|
|
|
59104
59329
|
const reviewer = opName === "semantic-review" ? "semantic" : opName === "adversarial-review" ? "adversarial" : null;
|
|
59105
59330
|
if (!reviewer)
|
|
59106
59331
|
return null;
|
|
59332
|
+
const unparsedPreview = typeof record2.unparsedPreview === "string" ? record2.unparsedPreview : undefined;
|
|
59107
59333
|
if (record2.failOpen === true) {
|
|
59108
|
-
return { reviewer, parsed: false, passed: true, failOpen: true, result: null };
|
|
59334
|
+
return { reviewer, parsed: false, passed: true, failOpen: true, result: null, unparsedPreview };
|
|
59109
59335
|
}
|
|
59110
59336
|
if (record2.looksLikeFail === true) {
|
|
59111
|
-
return { reviewer, parsed: false, passed: false, looksLikeFail: true, result: null };
|
|
59337
|
+
return { reviewer, parsed: false, passed: false, looksLikeFail: true, result: null, unparsedPreview };
|
|
59112
59338
|
}
|
|
59113
59339
|
if (typeof record2.passed !== "boolean" || !Array.isArray(record2.findings)) {
|
|
59114
59340
|
return null;
|
|
@@ -59130,7 +59356,8 @@ function toReviewDecisionPayload(opName, output) {
|
|
|
59130
59356
|
parsed: true,
|
|
59131
59357
|
passed: record2.passed,
|
|
59132
59358
|
result: { passed: record2.passed, findings: record2.findings },
|
|
59133
|
-
acDropped
|
|
59359
|
+
acDropped,
|
|
59360
|
+
...Array.isArray(record2.advisoryFindings) ? { advisoryFindings: record2.advisoryFindings } : {}
|
|
59134
59361
|
};
|
|
59135
59362
|
}
|
|
59136
59363
|
function emitReviewDecision(ctx, opName, output) {
|
|
@@ -59151,7 +59378,10 @@ function emitReviewDecision(ctx, opName, output) {
|
|
|
59151
59378
|
looksLikeFail: payload.parsed ? undefined : payload.looksLikeFail,
|
|
59152
59379
|
failOpen: payload.parsed ? false : payload.failOpen,
|
|
59153
59380
|
passed: payload.passed,
|
|
59154
|
-
result: payload.result
|
|
59381
|
+
result: payload.result,
|
|
59382
|
+
advisoryFindings: payload.parsed ? payload.advisoryFindings : undefined,
|
|
59383
|
+
acDropped: payload.parsed ? payload.acDropped : undefined,
|
|
59384
|
+
unparsedPreview: payload.parsed ? undefined : payload.unparsedPreview
|
|
59155
59385
|
});
|
|
59156
59386
|
}
|
|
59157
59387
|
function logUnifiedReviewPhaseStart(storyId, opName) {
|
|
@@ -59288,13 +59518,19 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
|
|
|
59288
59518
|
dispatchInput = await refreshReviewInputForDispatch(opName, dispatchInput);
|
|
59289
59519
|
let advIterationBefore = 0;
|
|
59290
59520
|
if (opName === "adversarial-review" && ctx.storyId) {
|
|
59291
|
-
const priorIterations =
|
|
59521
|
+
const priorIterations = getReviewIterations(ctx.runtime.adversarialIterations, ctx.storyId);
|
|
59292
59522
|
advIterationBefore = priorIterations.length;
|
|
59293
59523
|
dispatchInput = {
|
|
59294
59524
|
...dispatchInput,
|
|
59295
59525
|
priorAdversarialIterations: priorIterations
|
|
59296
59526
|
};
|
|
59297
59527
|
}
|
|
59528
|
+
if (opName === "semantic-review" && ctx.storyId) {
|
|
59529
|
+
dispatchInput = {
|
|
59530
|
+
...dispatchInput,
|
|
59531
|
+
priorSemanticIterations: getReviewIterations(ctx.runtime.semanticIterations, ctx.storyId)
|
|
59532
|
+
};
|
|
59533
|
+
}
|
|
59298
59534
|
if (isTddPhase) {
|
|
59299
59535
|
logger?.info("tdd", `-> Session: ${opName}`, { storyId: ctx.storyId, role: opName, ...progressData });
|
|
59300
59536
|
} else if (isThreeSession && opName === "full-suite-gate") {
|
|
@@ -59316,11 +59552,18 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
|
|
|
59316
59552
|
emitReviewDecision(ctx, opName, output);
|
|
59317
59553
|
if (opName === "adversarial-review" && ctx.storyId) {
|
|
59318
59554
|
const advOut = output;
|
|
59319
|
-
|
|
59555
|
+
recordReviewIteration(ctx.runtime.adversarialIterations, ctx.storyId, [
|
|
59320
59556
|
...advOut.normalizedFindings ?? [],
|
|
59321
59557
|
...advOut.advisoryFindings ?? []
|
|
59322
59558
|
]);
|
|
59323
59559
|
}
|
|
59560
|
+
if (opName === "semantic-review" && ctx.storyId) {
|
|
59561
|
+
const semOut = output;
|
|
59562
|
+
recordReviewIteration(ctx.runtime.semanticIterations, ctx.storyId, [
|
|
59563
|
+
...semOut.normalizedFindings ?? [],
|
|
59564
|
+
...semOut.advisoryFindings ?? []
|
|
59565
|
+
]);
|
|
59566
|
+
}
|
|
59324
59567
|
logUnifiedReviewPhaseResult(ctx.storyId, opName, output);
|
|
59325
59568
|
logDeterministicPhaseOutcome(ctx.storyId, opName, output, Date.now() - phaseStartedAt, isTddPhase, slot.op.stage, progressData);
|
|
59326
59569
|
outcome = derivePhaseOutcome(output);
|
|
@@ -59590,6 +59833,9 @@ function collectRectificationPhases(state) {
|
|
|
59590
59833
|
state.adversarialReview
|
|
59591
59834
|
].filter((phase) => phase !== undefined);
|
|
59592
59835
|
}
|
|
59836
|
+
function isQuarantinedOnlyGateFailure(phase, rawFindings, blockingFindings) {
|
|
59837
|
+
return phase.kind === "full-suite-gate" && rawFindings.length > 0 && blockingFindings.length === 0;
|
|
59838
|
+
}
|
|
59593
59839
|
async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides) {
|
|
59594
59840
|
const rectification2 = state.rectification;
|
|
59595
59841
|
const baseValidationPhases = collectRectificationPhases(state);
|
|
@@ -59656,9 +59902,21 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
|
|
|
59656
59902
|
if (shouldSkipPhaseForRectification({ phase, state, phaseOutputs, nbfPath }))
|
|
59657
59903
|
continue;
|
|
59658
59904
|
const output = phaseOutputs[phase.slot.op.name];
|
|
59905
|
+
const nbfFlakeTriage = overrides?.nbfFlakeTriage;
|
|
59906
|
+
if (nbfPath && phase.kind === "full-suite-gate" && nbfFlakeTriage) {
|
|
59907
|
+
await triageNbfGate({
|
|
59908
|
+
output,
|
|
59909
|
+
gateName: phase.slot.op.name,
|
|
59910
|
+
ctx,
|
|
59911
|
+
transaction: nbfFlakeTriage,
|
|
59912
|
+
triage: _storyOrchestratorDeps.triage
|
|
59913
|
+
});
|
|
59914
|
+
}
|
|
59659
59915
|
const phaseFindings = extractPhaseFindings(output);
|
|
59660
|
-
|
|
59661
|
-
|
|
59916
|
+
const blockingFindings = nbfPath ? phaseFindings.filter((finding) => !isQuarantinedFlake(finding, nbfFlakeTriage?.memo ?? ctx.runtime.quarantineMemo)) : phaseFindings;
|
|
59917
|
+
findings.push(...blockingFindings);
|
|
59918
|
+
const quarantinedOnly = nbfPath && isQuarantinedOnlyGateFailure(phase, phaseFindings, blockingFindings);
|
|
59919
|
+
if (!phasePassed(phase.slot.op.name, output, ctx.storyId) && !quarantinedOnly) {
|
|
59662
59920
|
getSafeLogger()?.warn("story-orchestrator", "Short-circuiting revalidation on phase failure", {
|
|
59663
59921
|
storyId: ctx.storyId,
|
|
59664
59922
|
phase: phase.slot.op.name
|
|
@@ -59716,6 +59974,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
|
|
|
59716
59974
|
}
|
|
59717
59975
|
var init_rectification = __esm(() => {
|
|
59718
59976
|
init_logger2();
|
|
59977
|
+
init_nbf_flake_triage();
|
|
59719
59978
|
init_phase_eval();
|
|
59720
59979
|
init_phase_eval();
|
|
59721
59980
|
init_run_phase();
|
|
@@ -59732,13 +59991,13 @@ class ExecutionPlan {
|
|
|
59732
59991
|
this.state = state;
|
|
59733
59992
|
this.isThreeSession = isThreeSession;
|
|
59734
59993
|
}
|
|
59735
|
-
describeGateRegressionNow(phaseOutputs, gateName,
|
|
59994
|
+
describeGateRegressionNow(phaseOutputs, gateName, options) {
|
|
59736
59995
|
return describeGateRegression({
|
|
59737
59996
|
gateOutput: gateName === undefined ? undefined : phaseOutputs[gateName],
|
|
59738
|
-
baselineKeys,
|
|
59997
|
+
baselineKeys: options.baselineKeys,
|
|
59739
59998
|
gateName,
|
|
59740
59999
|
storyId: this.ctx.storyId,
|
|
59741
|
-
quarantineMemo: this.ctx.runtime.quarantineMemo
|
|
60000
|
+
quarantineMemo: options.quarantineMemo ?? this.ctx.runtime.quarantineMemo
|
|
59742
60001
|
});
|
|
59743
60002
|
}
|
|
59744
60003
|
phaseNames() {
|
|
@@ -59886,15 +60145,21 @@ class ExecutionPlan {
|
|
|
59886
60145
|
cfg: advCfg,
|
|
59887
60146
|
phaseOutputs,
|
|
59888
60147
|
phaseCosts,
|
|
59889
|
-
|
|
60148
|
+
quarantineMemo: this.ctx.runtime.quarantineMemo,
|
|
60149
|
+
gateBaselineKeys: preRectGateFailureKeys,
|
|
60150
|
+
runRectify: (maxAttempts, nbfFlakeTriage) => runRectification(this.ctx, this.state, phaseCosts, phaseOutputs, {
|
|
59890
60151
|
initialFindings: advisoryFindings,
|
|
60152
|
+
nbfFlakeTriage,
|
|
59891
60153
|
strategies: this.state.nonBlockingFixStrategies ?? [],
|
|
59892
60154
|
excludePhaseKinds: nonBlockingExcludePhases(),
|
|
59893
60155
|
extraRevalidationKinds: nonBlockingExtraPhases(advCfg),
|
|
59894
60156
|
maxAttempts,
|
|
59895
60157
|
postValidate: this.state.nonBlockingFixPostValidate
|
|
59896
60158
|
}),
|
|
59897
|
-
keptTreeRegressed: () => this.describeGateRegressionNow(phaseOutputs, gateName,
|
|
60159
|
+
keptTreeRegressed: (quarantineMemo) => this.describeGateRegressionNow(phaseOutputs, gateName, {
|
|
60160
|
+
baselineKeys: preRectGateFailureKeys,
|
|
60161
|
+
quarantineMemo
|
|
60162
|
+
})
|
|
59898
60163
|
}, {
|
|
59899
60164
|
measureSourceDiff: createMeasureSourceDiff({
|
|
59900
60165
|
config: this.ctx.runtime.configLoader.current(),
|
|
@@ -59905,7 +60170,9 @@ class ExecutionPlan {
|
|
|
59905
60170
|
}
|
|
59906
60171
|
const verifierName = this.state.verifier?.slot.op.name;
|
|
59907
60172
|
const verifierExplicitlyPassed = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
|
|
59908
|
-
const gateRegressedDuringRect = this.describeGateRegressionNow(phaseOutputs, gateName,
|
|
60173
|
+
const gateRegressedDuringRect = this.describeGateRegressionNow(phaseOutputs, gateName, {
|
|
60174
|
+
baselineKeys: preRectGateFailureKeys
|
|
60175
|
+
}).regressed;
|
|
59909
60176
|
const verifierPassedSsot = verifierExplicitlyPassed && !gateRegressedDuringRect;
|
|
59910
60177
|
if (verifierExplicitlyPassed && gateRegressedDuringRect) {
|
|
59911
60178
|
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,7 +60323,9 @@ var init_story_orchestrator = __esm(() => {
|
|
|
60056
60323
|
init_execution_plan();
|
|
60057
60324
|
init_phase_eval();
|
|
60058
60325
|
init_rectification();
|
|
60326
|
+
init_nbf_flake_triage();
|
|
60059
60327
|
init_run_phase();
|
|
60328
|
+
init_review_decision();
|
|
60060
60329
|
init_types9();
|
|
60061
60330
|
});
|
|
60062
60331
|
|
|
@@ -60406,7 +60675,6 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
60406
60675
|
diff: prepared.diff,
|
|
60407
60676
|
excludePatterns: prepared.excludePatterns,
|
|
60408
60677
|
featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-semantic"),
|
|
60409
|
-
priorSemanticIterations: ctx.priorSemanticIterations,
|
|
60410
60678
|
resolvedTestPatterns,
|
|
60411
60679
|
blockingThreshold: ctx.config.review.blockingThreshold,
|
|
60412
60680
|
_refresh: {
|
|
@@ -60444,7 +60712,6 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
60444
60712
|
testGlobs: prepared.testGlobs,
|
|
60445
60713
|
refExcludePatterns: prepared.refExcludePatterns,
|
|
60446
60714
|
featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-adversarial"),
|
|
60447
|
-
priorAdversarialIterations: ctx.priorAdversarialIterations,
|
|
60448
60715
|
resolvedTestPatterns,
|
|
60449
60716
|
blockingThreshold: ctx.config.review.blockingThreshold,
|
|
60450
60717
|
_refresh: {
|
|
@@ -61946,7 +62213,7 @@ async function fanOutReporters(reporters, hook, invoke) {
|
|
|
61946
62213
|
}
|
|
61947
62214
|
}
|
|
61948
62215
|
}
|
|
61949
|
-
function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
62216
|
+
function wireReporters(bus, pluginRegistry, runId, startTime, projectKey) {
|
|
61950
62217
|
const logger = getSafeLogger();
|
|
61951
62218
|
const safe = (name, fn) => {
|
|
61952
62219
|
return fn().catch((err) => logger?.warn("reporters-subscriber", `Reporter "${name}" error`, { error: String(err) })).catch(() => {});
|
|
@@ -61996,7 +62263,8 @@ function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
|
61996
62263
|
runId,
|
|
61997
62264
|
feature: ev.feature,
|
|
61998
62265
|
totalStories: ev.totalStories,
|
|
61999
|
-
startTime: new Date(startTime).toISOString()
|
|
62266
|
+
startTime: new Date(startTime).toISOString(),
|
|
62267
|
+
project: projectKey
|
|
62000
62268
|
});
|
|
62001
62269
|
} catch (err) {
|
|
62002
62270
|
logger?.warn("plugins", `Reporter '${r.name}' onRunStart failed`, { error: err });
|
|
@@ -64843,6 +65111,7 @@ function getFinishAutoFlowConfig(ctx) {
|
|
|
64843
65111
|
quality: autoFlow.reviewers?.quality ?? null
|
|
64844
65112
|
},
|
|
64845
65113
|
escalate: { telegram: autoFlow.escalate?.telegram !== false },
|
|
65114
|
+
notify: { mode: autoFlow.notify?.mode ?? defaults.notify.mode },
|
|
64846
65115
|
timeouts: {
|
|
64847
65116
|
acceptanceMs: autoFlow.timeouts?.acceptanceMs ?? defaults.timeouts.acceptanceMs,
|
|
64848
65117
|
gateMs: autoFlow.timeouts?.gateMs ?? defaults.timeouts.gateMs,
|
|
@@ -64867,11 +65136,38 @@ var init_config2 = __esm(() => {
|
|
|
64867
65136
|
defaultAgent: null,
|
|
64868
65137
|
reviewers: { spec: null, quality: null },
|
|
64869
65138
|
escalate: { telegram: true },
|
|
65139
|
+
notify: { mode: "escalation" },
|
|
64870
65140
|
timeouts: { acceptanceMs: 600000, gateMs: 900000, flowMs: 5400000, stepMs: null }
|
|
64871
65141
|
};
|
|
64872
65142
|
});
|
|
64873
65143
|
|
|
65144
|
+
// src/plugins/builtin/nax-finish/output.ts
|
|
65145
|
+
function logTail(stream) {
|
|
65146
|
+
if (stream.length <= LOG_TAIL_CHARS)
|
|
65147
|
+
return stream;
|
|
65148
|
+
return `[\u2026${stream.length - LOG_TAIL_CHARS} chars truncated\u2026]
|
|
65149
|
+
${stream.slice(-LOG_TAIL_CHARS)}`;
|
|
65150
|
+
}
|
|
65151
|
+
function stderrTail(stderr) {
|
|
65152
|
+
const trimmed = stderr.trim();
|
|
65153
|
+
if (!trimmed)
|
|
65154
|
+
return "";
|
|
65155
|
+
const tail = trimmed.length > STDERR_TAIL_CHARS ? `\u2026${trimmed.slice(-STDERR_TAIL_CHARS)}` : trimmed;
|
|
65156
|
+
return tail.replace(/\s+/g, " ");
|
|
65157
|
+
}
|
|
65158
|
+
var STDERR_TAIL_CHARS = 400, LOG_TAIL_CHARS = 20000;
|
|
65159
|
+
|
|
64874
65160
|
// src/plugins/builtin/nax-finish/telegram.ts
|
|
65161
|
+
function buildTerminalMessage(options) {
|
|
65162
|
+
const lines = [`nax-finish ${options.status} ${options.feature}`];
|
|
65163
|
+
if (options.detail)
|
|
65164
|
+
lines.push(options.detail);
|
|
65165
|
+
if (options.url)
|
|
65166
|
+
lines.push(options.url);
|
|
65167
|
+
const message = lines.join(`
|
|
65168
|
+
`);
|
|
65169
|
+
return message.length <= TELEGRAM_MAX_MESSAGE_CHARS ? message : `${message.slice(0, TELEGRAM_MAX_MESSAGE_CHARS - 1)}\u2026`;
|
|
65170
|
+
}
|
|
64875
65171
|
function buildEscalationMessage(feature, reason, findings) {
|
|
64876
65172
|
const head = `nax-finish escalated ${feature}: ${reason}`;
|
|
64877
65173
|
if (findings.length === 0)
|
|
@@ -64908,19 +65204,6 @@ var init_telegram2 = __esm(() => {
|
|
|
64908
65204
|
|
|
64909
65205
|
// src/plugins/builtin/nax-finish/index.ts
|
|
64910
65206
|
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
65207
|
async function defaultRun2(cmd, opts) {
|
|
64925
65208
|
const proc = Bun.spawn(cmd, { cwd: opts.cwd, env: opts.env, stdout: "pipe", stderr: "pipe" });
|
|
64926
65209
|
let timedOut = false;
|
|
@@ -64951,6 +65234,11 @@ async function defaultReadResult(workdir) {
|
|
|
64951
65234
|
return null;
|
|
64952
65235
|
return JSON.parse(await f.text());
|
|
64953
65236
|
}
|
|
65237
|
+
async function defaultClearResult(workdir) {
|
|
65238
|
+
const file3 = Bun.file(path21.join(workdir, ".nax", "nax-finish-result.json"));
|
|
65239
|
+
if (await file3.exists())
|
|
65240
|
+
await file3.delete();
|
|
65241
|
+
}
|
|
64954
65242
|
function isFeatureBranch(b) {
|
|
64955
65243
|
return b !== "main" && b !== "master" && b.length > 0;
|
|
64956
65244
|
}
|
|
@@ -64994,13 +65282,133 @@ function buildFlowEnv(cfg) {
|
|
|
64994
65282
|
env2.NAX_FINISH_QUALITY_PROFILE = cfg.reviewers.quality;
|
|
64995
65283
|
return env2;
|
|
64996
65284
|
}
|
|
64997
|
-
|
|
65285
|
+
function missingResultOutcome(ctx, res, escalateTelegram) {
|
|
65286
|
+
ctx.logger.warn("nax-finish flow produced no result file", {
|
|
65287
|
+
exitCode: res.exitCode,
|
|
65288
|
+
stdout: logTail(res.stdout),
|
|
65289
|
+
stderr: logTail(res.stderr)
|
|
65290
|
+
});
|
|
65291
|
+
const tail = stderrTail(res.stderr);
|
|
65292
|
+
return {
|
|
65293
|
+
actionResult: {
|
|
65294
|
+
success: false,
|
|
65295
|
+
message: `nax-finish flow exited ${res.exitCode} (no result file)${tail ? `: ${tail}` : ""}`
|
|
65296
|
+
},
|
|
65297
|
+
escalateTelegram
|
|
65298
|
+
};
|
|
65299
|
+
}
|
|
65300
|
+
async function executeFinishFlow(options) {
|
|
65301
|
+
const { ctx, cfg, escalateTelegram } = options;
|
|
65302
|
+
const flowPath = await resolveFlowPath(ctx.workdir, cfg.flowPath);
|
|
65303
|
+
if (!flowPath) {
|
|
65304
|
+
return {
|
|
65305
|
+
actionResult: {
|
|
65306
|
+
success: false,
|
|
65307
|
+
message: `nax-finish: flow module "${cfg.flowPath}" not found in the nax install or ${ctx.workdir}`
|
|
65308
|
+
},
|
|
65309
|
+
escalateTelegram
|
|
65310
|
+
};
|
|
65311
|
+
}
|
|
65312
|
+
await _naxFinishDeps.clearResult(ctx.workdir);
|
|
65313
|
+
const input = {
|
|
65314
|
+
feature: ctx.feature,
|
|
65315
|
+
workdir: ctx.workdir,
|
|
65316
|
+
branch: ctx.branch,
|
|
65317
|
+
prdPath: ctx.prdPath,
|
|
65318
|
+
escalateTelegram,
|
|
65319
|
+
timeouts: { acceptanceMs: cfg.timeouts.acceptanceMs, gateMs: cfg.timeouts.gateMs }
|
|
65320
|
+
};
|
|
65321
|
+
const cmd = buildFlowArgv(flowPath, JSON.stringify(input), cfg.defaultAgent, cfg.timeouts.stepMs);
|
|
65322
|
+
const res = await _naxFinishDeps.run(cmd, {
|
|
65323
|
+
cwd: ctx.workdir,
|
|
65324
|
+
env: buildFlowEnv(cfg),
|
|
65325
|
+
timeoutMs: cfg.timeouts.flowMs
|
|
65326
|
+
});
|
|
65327
|
+
const result = await _naxFinishDeps.readResult(ctx.workdir);
|
|
65328
|
+
if (!result)
|
|
65329
|
+
return missingResultOutcome(ctx, res, escalateTelegram);
|
|
65330
|
+
return {
|
|
65331
|
+
actionResult: { success: true, message: `nax-finish: ${result.status}`, url: result.url },
|
|
65332
|
+
result,
|
|
65333
|
+
escalateTelegram
|
|
65334
|
+
};
|
|
65335
|
+
}
|
|
65336
|
+
async function settleFinishFlow(options) {
|
|
65337
|
+
const escalateTelegram = options.cfg.notify.mode !== "off" && options.cfg.escalate.telegram && options.creds !== null;
|
|
65338
|
+
try {
|
|
65339
|
+
return await executeFinishFlow({ ...options, escalateTelegram });
|
|
65340
|
+
} catch (error48) {
|
|
65341
|
+
options.ctx.logger.warn("nax-finish execute failed", { error: errorMessage(error48) });
|
|
65342
|
+
return {
|
|
65343
|
+
actionResult: { success: false, message: `nax-finish failed: ${errorMessage(error48)}` },
|
|
65344
|
+
escalateTelegram
|
|
65345
|
+
};
|
|
65346
|
+
}
|
|
65347
|
+
}
|
|
65348
|
+
async function notifyBestEffort(ctx, creds, message) {
|
|
65349
|
+
if (!creds) {
|
|
65350
|
+
ctx.logger.warn("nax-finish terminal notification skipped: Telegram credentials are unavailable");
|
|
65351
|
+
return;
|
|
65352
|
+
}
|
|
65353
|
+
try {
|
|
65354
|
+
if (!await _naxFinishDeps.notify(creds, message)) {
|
|
65355
|
+
ctx.logger.warn("nax-finish terminal notification was rejected", { feature: ctx.feature });
|
|
65356
|
+
}
|
|
65357
|
+
} catch (error48) {
|
|
65358
|
+
ctx.logger.warn("nax-finish terminal notification failed", { feature: ctx.feature, error: errorMessage(error48) });
|
|
65359
|
+
}
|
|
65360
|
+
}
|
|
65361
|
+
async function finalizeEscalation(ctx, outcome, creds) {
|
|
65362
|
+
const result = outcome.result;
|
|
65363
|
+
if (!result)
|
|
65364
|
+
return outcome.actionResult;
|
|
65365
|
+
const problems = [];
|
|
65366
|
+
let delivered = !outcome.escalateTelegram && !result.deliveryError;
|
|
65367
|
+
if (outcome.escalateTelegram && creds) {
|
|
65368
|
+
try {
|
|
65369
|
+
delivered = await _naxFinishDeps.notify(creds, buildEscalationMessage(result.feature, result.escalationReason ?? "", result.findings ?? []));
|
|
65370
|
+
if (!delivered)
|
|
65371
|
+
problems.push("Telegram rejected the message");
|
|
65372
|
+
} catch (error48) {
|
|
65373
|
+
problems.push(`Telegram failed: ${errorMessage(error48)}`);
|
|
65374
|
+
}
|
|
65375
|
+
}
|
|
65376
|
+
if (delivered)
|
|
65377
|
+
return outcome.actionResult;
|
|
65378
|
+
if (result.deliveryError)
|
|
65379
|
+
problems.push(`the flow could not post it: ${result.deliveryError}`);
|
|
65380
|
+
if (problems.length === 0)
|
|
65381
|
+
problems.push("no escalation channel was reachable");
|
|
65382
|
+
ctx.logger.warn("nax-finish escalation was not delivered", {
|
|
65383
|
+
feature: result.feature,
|
|
65384
|
+
reasons: problems,
|
|
65385
|
+
escalationReason: result.escalationReason
|
|
65386
|
+
});
|
|
65387
|
+
return {
|
|
65388
|
+
success: false,
|
|
65389
|
+
message: `nax-finish: escalated but undelivered \u2014 ${problems.join("; ")}`,
|
|
65390
|
+
url: result.url
|
|
65391
|
+
};
|
|
65392
|
+
}
|
|
65393
|
+
async function finalizeFinishOutcome(options) {
|
|
65394
|
+
const { ctx, cfg, creds, outcome } = options;
|
|
65395
|
+
if (outcome.result?.status === "escalated")
|
|
65396
|
+
return finalizeEscalation(ctx, outcome, creds);
|
|
65397
|
+
if (cfg.notify.mode === "always") {
|
|
65398
|
+
const status = outcome.result?.status ?? "failed";
|
|
65399
|
+
const detail = outcome.result ? undefined : outcome.actionResult.message;
|
|
65400
|
+
await notifyBestEffort(ctx, creds, buildTerminalMessage({ feature: ctx.feature, status, detail, url: outcome.actionResult.url }));
|
|
65401
|
+
}
|
|
65402
|
+
return outcome.actionResult;
|
|
65403
|
+
}
|
|
65404
|
+
var PLUGIN_NAME4 = "nax-finish", PLUGIN_VERSION4 = "0.1.0", PACKAGE_ROOT_SEARCH_DEPTH = 6, _naxFinishDeps, naxFinishAction, naxFinishPlugin;
|
|
64998
65405
|
var init_nax_finish = __esm(() => {
|
|
64999
65406
|
init_config2();
|
|
65000
65407
|
init_telegram2();
|
|
65001
65408
|
_naxFinishDeps = {
|
|
65002
65409
|
run: defaultRun2,
|
|
65003
65410
|
readResult: defaultReadResult,
|
|
65411
|
+
clearResult: defaultClearResult,
|
|
65004
65412
|
exists: (p) => Bun.file(p).exists(),
|
|
65005
65413
|
moduleDir: import.meta.dir,
|
|
65006
65414
|
notify: sendTelegramNotify
|
|
@@ -65018,76 +65426,10 @@ var init_nax_finish = __esm(() => {
|
|
|
65018
65426
|
return isFeatureBranch(ctx.branch);
|
|
65019
65427
|
},
|
|
65020
65428
|
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
|
-
}
|
|
65429
|
+
const cfg = getFinishAutoFlowConfig(ctx);
|
|
65430
|
+
const creds = telegramCreds(ctx.config);
|
|
65431
|
+
const outcome = await settleFinishFlow({ ctx, cfg, creds });
|
|
65432
|
+
return finalizeFinishOutcome({ ctx, cfg, creds, outcome });
|
|
65091
65433
|
}
|
|
65092
65434
|
};
|
|
65093
65435
|
naxFinishPlugin = {
|
|
@@ -66134,7 +66476,7 @@ class PluginRegistry {
|
|
|
66134
66476
|
sources;
|
|
66135
66477
|
builtinPostRunActions;
|
|
66136
66478
|
constructor(loadedPlugins, builtinPostRunActions = []) {
|
|
66137
|
-
this.builtinPostRunActions = builtinPostRunActions;
|
|
66479
|
+
this.builtinPostRunActions = builtinPostRunActions.map((registration) => ("action" in registration) ? registration : { pluginName: registration.name, action: registration });
|
|
66138
66480
|
if (loadedPlugins.length > 0 && "plugin" in loadedPlugins[0]) {
|
|
66139
66481
|
const typed = loadedPlugins;
|
|
66140
66482
|
this.plugins = typed.map((lp) => lp.plugin);
|
|
@@ -66173,7 +66515,13 @@ class PluginRegistry {
|
|
|
66173
66515
|
return this.plugins.filter((p) => p.provides.includes("reporter")).map((p) => p.extensions.reporter).filter((reporter) => reporter !== undefined);
|
|
66174
66516
|
}
|
|
66175
66517
|
getPostRunActions() {
|
|
66176
|
-
|
|
66518
|
+
return this.getPostRunActionRegistrations().map(({ action }) => action);
|
|
66519
|
+
}
|
|
66520
|
+
getPostRunActionRegistrations() {
|
|
66521
|
+
const pluginActions = this.plugins.flatMap((plugin) => {
|
|
66522
|
+
const action = plugin.extensions.postRunAction;
|
|
66523
|
+
return plugin.provides.includes("post-run-action") && action ? [{ pluginName: plugin.name, action }] : [];
|
|
66524
|
+
});
|
|
66177
66525
|
return [...pluginActions, ...this.builtinPostRunActions];
|
|
66178
66526
|
}
|
|
66179
66527
|
async teardownAll() {
|
|
@@ -66488,7 +66836,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
|
|
|
66488
66836
|
}
|
|
66489
66837
|
const autoPrAction2 = autoPrPlugin.extensions.postRunAction;
|
|
66490
66838
|
if (autoPrAction2) {
|
|
66491
|
-
builtinPostRunActions.push(autoPrAction2);
|
|
66839
|
+
builtinPostRunActions.push({ pluginName: autoPrPlugin.name, action: autoPrAction2 });
|
|
66492
66840
|
}
|
|
66493
66841
|
} else {
|
|
66494
66842
|
logger?.info("plugins", `Skipping disabled plugin: '${autoPrPlugin.name}' (built-in)`);
|
|
@@ -66500,7 +66848,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
|
|
|
66500
66848
|
}
|
|
66501
66849
|
const action = naxFinishPlugin.extensions.postRunAction;
|
|
66502
66850
|
if (action)
|
|
66503
|
-
builtinPostRunActions.push(action);
|
|
66851
|
+
builtinPostRunActions.push({ pluginName: naxFinishPlugin.name, action });
|
|
66504
66852
|
} else {
|
|
66505
66853
|
logger?.info("plugins", `Skipping disabled plugin: '${naxFinishPlugin.name}' (built-in)`);
|
|
66506
66854
|
}
|
|
@@ -66511,7 +66859,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
|
|
|
66511
66859
|
}
|
|
66512
66860
|
const autoRouteAction2 = autoRoutePlugin.extensions.postRunAction;
|
|
66513
66861
|
if (autoRouteAction2) {
|
|
66514
|
-
builtinPostRunActions.push(autoRouteAction2);
|
|
66862
|
+
builtinPostRunActions.push({ pluginName: autoRoutePlugin.name, action: autoRouteAction2 });
|
|
66515
66863
|
}
|
|
66516
66864
|
} else {
|
|
66517
66865
|
logger?.info("plugins", `Skipping disabled plugin: '${autoRoutePlugin.name}' (built-in)`);
|
|
@@ -66905,6 +67253,25 @@ var init_checkpoint = __esm(() => {
|
|
|
66905
67253
|
init_resume_cli();
|
|
66906
67254
|
});
|
|
66907
67255
|
|
|
67256
|
+
// src/hooks/types.ts
|
|
67257
|
+
var HOOK_EVENTS;
|
|
67258
|
+
var init_types10 = __esm(() => {
|
|
67259
|
+
HOOK_EVENTS = [
|
|
67260
|
+
"on-start",
|
|
67261
|
+
"on-story-start",
|
|
67262
|
+
"on-story-complete",
|
|
67263
|
+
"on-story-fail",
|
|
67264
|
+
"on-pause",
|
|
67265
|
+
"on-resume",
|
|
67266
|
+
"on-session-end",
|
|
67267
|
+
"on-all-stories-complete",
|
|
67268
|
+
"on-complete",
|
|
67269
|
+
"on-error",
|
|
67270
|
+
"on-final-regression-fail",
|
|
67271
|
+
"on-post-run-action"
|
|
67272
|
+
];
|
|
67273
|
+
});
|
|
67274
|
+
|
|
66908
67275
|
// src/hooks/runner.ts
|
|
66909
67276
|
import { join as join84 } from "path";
|
|
66910
67277
|
function createDrainDeadline2(deadlineMs) {
|
|
@@ -66966,6 +67333,12 @@ function buildEnv(ctx) {
|
|
|
66966
67333
|
env2.NAX_AGENT = escapeEnvValue(ctx.agent);
|
|
66967
67334
|
if (ctx.iteration !== undefined)
|
|
66968
67335
|
env2.NAX_ITERATION = String(ctx.iteration);
|
|
67336
|
+
if (ctx.pluginName)
|
|
67337
|
+
env2.NAX_PLUGIN_NAME = escapeEnvValue(ctx.pluginName);
|
|
67338
|
+
if (ctx.actionName)
|
|
67339
|
+
env2.NAX_ACTION_NAME = escapeEnvValue(ctx.actionName);
|
|
67340
|
+
if (ctx.url)
|
|
67341
|
+
env2.NAX_RESULT_URL = escapeEnvValue(ctx.url);
|
|
66969
67342
|
return env2;
|
|
66970
67343
|
}
|
|
66971
67344
|
function hasShellOperators(command) {
|
|
@@ -67096,9 +67469,11 @@ var init_runner5 = __esm(() => {
|
|
|
67096
67469
|
var exports_hooks = {};
|
|
67097
67470
|
__export(exports_hooks, {
|
|
67098
67471
|
loadHooksConfig: () => loadHooksConfig,
|
|
67099
|
-
fireHook: () => fireHook
|
|
67472
|
+
fireHook: () => fireHook,
|
|
67473
|
+
HOOK_EVENTS: () => HOOK_EVENTS
|
|
67100
67474
|
});
|
|
67101
67475
|
var init_hooks = __esm(() => {
|
|
67476
|
+
init_types10();
|
|
67102
67477
|
init_runner5();
|
|
67103
67478
|
});
|
|
67104
67479
|
|
|
@@ -69746,7 +70121,7 @@ function buildPreviewRouting(story, config2) {
|
|
|
69746
70121
|
|
|
69747
70122
|
// src/worktree/types.ts
|
|
69748
70123
|
var WorktreeDependencyPreparationError;
|
|
69749
|
-
var
|
|
70124
|
+
var init_types11 = __esm(() => {
|
|
69750
70125
|
WorktreeDependencyPreparationError = class WorktreeDependencyPreparationError extends Error {
|
|
69751
70126
|
mode;
|
|
69752
70127
|
failureCategory = "dependency-prep";
|
|
@@ -69816,7 +70191,7 @@ var PHASE_ONE_INHERIT_UNSUPPORTED_FILES, _worktreeDependencyDeps;
|
|
|
69816
70191
|
var init_dependencies = __esm(() => {
|
|
69817
70192
|
init_bun_deps();
|
|
69818
70193
|
init_command_argv();
|
|
69819
|
-
|
|
70194
|
+
init_types11();
|
|
69820
70195
|
PHASE_ONE_INHERIT_UNSUPPORTED_FILES = [
|
|
69821
70196
|
"package.json",
|
|
69822
70197
|
"bun.lock",
|
|
@@ -71033,6 +71408,20 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
71033
71408
|
// src/execution/iteration-runner.ts
|
|
71034
71409
|
import { existsSync as existsSync35 } from "fs";
|
|
71035
71410
|
import { join as join91 } from "path";
|
|
71411
|
+
function releaseHeavyPipelineContext(ctx) {
|
|
71412
|
+
ctx.agentResult = undefined;
|
|
71413
|
+
ctx.prompt = undefined;
|
|
71414
|
+
ctx.contextMarkdown = undefined;
|
|
71415
|
+
ctx.featureContextMarkdown = undefined;
|
|
71416
|
+
ctx.builtContext = undefined;
|
|
71417
|
+
ctx.contextBundle = undefined;
|
|
71418
|
+
ctx.constitution = undefined;
|
|
71419
|
+
ctx.acceptanceFailures = undefined;
|
|
71420
|
+
ctx.autofixPriorIterations = undefined;
|
|
71421
|
+
ctx.reviewFindings = undefined;
|
|
71422
|
+
ctx.selfVerification = undefined;
|
|
71423
|
+
ctx.tddIsolations = undefined;
|
|
71424
|
+
}
|
|
71036
71425
|
async function runIteration(ctx, prd, selection, iterations, totalCost2, allStoryMetrics) {
|
|
71037
71426
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
71038
71427
|
if (ctx.dryRun) {
|
|
@@ -71213,11 +71602,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost2, allStor
|
|
|
71213
71602
|
subStoryCount: pipelineResult.subStoryCount
|
|
71214
71603
|
};
|
|
71215
71604
|
}
|
|
71216
|
-
pipelineContext
|
|
71217
|
-
pipelineContext.prompt = undefined;
|
|
71218
|
-
pipelineContext.contextMarkdown = undefined;
|
|
71219
|
-
pipelineContext.builtContext = undefined;
|
|
71220
|
-
pipelineContext.constitution = undefined;
|
|
71605
|
+
releaseHeavyPipelineContext(pipelineContext);
|
|
71221
71606
|
return iterResult;
|
|
71222
71607
|
}
|
|
71223
71608
|
var _iterationRunnerDeps;
|
|
@@ -71691,7 +72076,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
71691
72076
|
_prevRunUnsubscribers = [];
|
|
71692
72077
|
const thisRunUnsubscribers = [
|
|
71693
72078
|
wireHooks(pipelineEventBus, ctx.hooks, ctx.workdir, ctx.feature),
|
|
71694
|
-
wireReporters(pipelineEventBus, ctx.pluginRegistry, ctx.runId, ctx.startTime),
|
|
72079
|
+
wireReporters(pipelineEventBus, ctx.pluginRegistry, ctx.runId, ctx.startTime, ctx.runtime.projectKey),
|
|
71695
72080
|
wireInteraction(pipelineEventBus, ctx.interactionChain, ctx.config),
|
|
71696
72081
|
wireEventsWriter(pipelineEventBus, ctx.feature, ctx.runId, ctx.workdir),
|
|
71697
72082
|
wireRegistry(pipelineEventBus, ctx.feature, ctx.runId, ctx.workdir, ctx.runtime.outputDir)
|
|
@@ -73463,8 +73848,63 @@ async function runSetupPhase(options) {
|
|
|
73463
73848
|
var exports_run_cleanup = {};
|
|
73464
73849
|
__export(exports_run_cleanup, {
|
|
73465
73850
|
cleanupRun: () => cleanupRun,
|
|
73466
|
-
buildPostRunContext: () => buildPostRunContext
|
|
73851
|
+
buildPostRunContext: () => buildPostRunContext,
|
|
73852
|
+
_runCleanupDeps: () => _runCleanupDeps
|
|
73467
73853
|
});
|
|
73854
|
+
async function settlePostRunAction(action, ctx) {
|
|
73855
|
+
try {
|
|
73856
|
+
if (!await action.shouldRun(ctx))
|
|
73857
|
+
return { status: "skipped", reason: "shouldRun=false" };
|
|
73858
|
+
return outcomeFromResult(await action.execute(ctx));
|
|
73859
|
+
} catch (error48) {
|
|
73860
|
+
return { status: "error", reason: errorMessage(error48) };
|
|
73861
|
+
}
|
|
73862
|
+
}
|
|
73863
|
+
function outcomeFromResult(result) {
|
|
73864
|
+
if (result.skipped)
|
|
73865
|
+
return { status: "skipped", reason: result.reason ?? result.message };
|
|
73866
|
+
if (!result.success)
|
|
73867
|
+
return { status: "failed", message: result.message, url: result.url };
|
|
73868
|
+
return { status: "succeeded", message: result.message, url: result.url };
|
|
73869
|
+
}
|
|
73870
|
+
function logPostRunOutcome(actionName, outcome) {
|
|
73871
|
+
const logger = getSafeLogger();
|
|
73872
|
+
if (outcome.status === "skipped") {
|
|
73873
|
+
const level = outcome.reason === "shouldRun=false" ? "debug" : "info";
|
|
73874
|
+
logger?.[level]("post-run", `[post-run] ${actionName}: skipped \u2014 ${outcome.reason}`);
|
|
73875
|
+
} else if (outcome.status === "failed") {
|
|
73876
|
+
logger?.warn("post-run", `[post-run] ${actionName}: failed \u2014 ${outcome.message}`);
|
|
73877
|
+
} else if (outcome.status === "error") {
|
|
73878
|
+
logger?.warn("post-run", `[post-run] ${actionName}: error \u2014 ${outcome.reason}`);
|
|
73879
|
+
} else {
|
|
73880
|
+
const suffix = outcome.url ? `${outcome.message} (${outcome.url})` : outcome.message;
|
|
73881
|
+
logger?.info("post-run", `[post-run] ${actionName}: ${suffix}`);
|
|
73882
|
+
}
|
|
73883
|
+
}
|
|
73884
|
+
function postRunHookContext(feature, registration, outcome) {
|
|
73885
|
+
const reason = outcome.status === "succeeded" || outcome.status === "failed" ? outcome.message : outcome.reason;
|
|
73886
|
+
return {
|
|
73887
|
+
event: "on-post-run-action",
|
|
73888
|
+
feature,
|
|
73889
|
+
pluginName: registration.pluginName,
|
|
73890
|
+
actionName: registration.action.name,
|
|
73891
|
+
status: outcome.status,
|
|
73892
|
+
reason,
|
|
73893
|
+
url: "url" in outcome ? outcome.url : undefined
|
|
73894
|
+
};
|
|
73895
|
+
}
|
|
73896
|
+
async function runPostRunActions(options, ctx) {
|
|
73897
|
+
const registrations = options.pluginRegistry.getPostRunActionRegistrations();
|
|
73898
|
+
for (const registration of registrations) {
|
|
73899
|
+
const outcome = await settlePostRunAction(registration.action, ctx);
|
|
73900
|
+
logPostRunOutcome(registration.action.name, outcome);
|
|
73901
|
+
try {
|
|
73902
|
+
await _runCleanupDeps.fireHook(options.hooks, "on-post-run-action", postRunHookContext(options.feature, registration, outcome), options.workdir);
|
|
73903
|
+
} catch (error48) {
|
|
73904
|
+
getSafeLogger()?.warn("hooks", `on-post-run-action hook failed for '${registration.pluginName}'`, { error: error48 });
|
|
73905
|
+
}
|
|
73906
|
+
}
|
|
73907
|
+
}
|
|
73468
73908
|
function buildPostRunContext(opts, durationMs, logger) {
|
|
73469
73909
|
const {
|
|
73470
73910
|
runId,
|
|
@@ -73537,7 +73977,6 @@ async function cleanupRun(options) {
|
|
|
73537
73977
|
}
|
|
73538
73978
|
}
|
|
73539
73979
|
}
|
|
73540
|
-
const actions = pluginRegistry.getPostRunActions();
|
|
73541
73980
|
const pluginLogger = {
|
|
73542
73981
|
debug: (msg, data) => logger?.debug("post-run", msg, data),
|
|
73543
73982
|
info: (msg, data) => logger?.info("post-run", msg, data),
|
|
@@ -73545,26 +73984,7 @@ async function cleanupRun(options) {
|
|
|
73545
73984
|
error: (msg, data) => logger?.error("post-run", msg, data)
|
|
73546
73985
|
};
|
|
73547
73986
|
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
|
-
}
|
|
73987
|
+
await runPostRunActions(options, ctx);
|
|
73568
73988
|
try {
|
|
73569
73989
|
await pluginRegistry.teardownAll();
|
|
73570
73990
|
} catch (error48) {
|
|
@@ -73581,11 +74001,14 @@ async function cleanupRun(options) {
|
|
|
73581
74001
|
disposeFeatureResolver(workdir);
|
|
73582
74002
|
await releaseLock(workdir);
|
|
73583
74003
|
}
|
|
74004
|
+
var _runCleanupDeps;
|
|
73584
74005
|
var init_run_cleanup = __esm(() => {
|
|
73585
74006
|
init_context();
|
|
74007
|
+
init_hooks();
|
|
73586
74008
|
init_logger2();
|
|
73587
74009
|
init_prd();
|
|
73588
74010
|
init_helpers();
|
|
74011
|
+
_runCleanupDeps = { fireHook };
|
|
73589
74012
|
});
|
|
73590
74013
|
|
|
73591
74014
|
// src/execution/runner.ts
|
|
@@ -73782,6 +74205,7 @@ async function run(options) {
|
|
|
73782
74205
|
prdPath,
|
|
73783
74206
|
branch,
|
|
73784
74207
|
version: NAX_VERSION,
|
|
74208
|
+
hooks,
|
|
73785
74209
|
runCompleted,
|
|
73786
74210
|
outputDir: runtime.outputDir,
|
|
73787
74211
|
globalDir: runtime.globalDir,
|
|
@@ -73835,17 +74259,20 @@ var exports_execution = {};
|
|
|
73835
74259
|
__export(exports_execution, {
|
|
73836
74260
|
writeExitSummary: () => writeExitSummary,
|
|
73837
74261
|
withIncreasingFailuresBail: () => withIncreasingFailuresBail,
|
|
74262
|
+
toReviewDecisionPayload: () => toReviewDecisionPayload,
|
|
73838
74263
|
synthesizeBackfillMetric: () => synthesizeBackfillMetric,
|
|
73839
74264
|
stopHeartbeat: () => stopHeartbeat,
|
|
73840
74265
|
startHeartbeat: () => startHeartbeat2,
|
|
73841
74266
|
runRectification: () => runRectification,
|
|
73842
74267
|
runPhase: () => runPhase,
|
|
74268
|
+
runNonBlockingFix: () => runNonBlockingFix,
|
|
73843
74269
|
runDeferredRegression: () => runDeferredRegression,
|
|
73844
74270
|
runCompletionPhase: () => runCompletionPhase,
|
|
73845
74271
|
run: () => run,
|
|
73846
74272
|
resolveMaxAttemptsOutcome: () => resolveMaxAttemptsOutcome,
|
|
73847
74273
|
resetCrashHandlers: () => resetCrashHandlers,
|
|
73848
74274
|
releaseLock: () => releaseLock,
|
|
74275
|
+
releaseHeavyPipelineContext: () => releaseHeavyPipelineContext,
|
|
73849
74276
|
refreshReviewInputForDispatch: () => refreshReviewInputForDispatch,
|
|
73850
74277
|
recordOscillations: () => recordOscillations,
|
|
73851
74278
|
readQueueFile: () => readQueueFile,
|
|
@@ -73878,6 +74305,7 @@ __export(exports_execution, {
|
|
|
73878
74305
|
describeGateRegression: () => describeGateRegression,
|
|
73879
74306
|
deriveTddFailureCategory: () => deriveTddFailureCategory,
|
|
73880
74307
|
decideStageAction: () => decideStageAction,
|
|
74308
|
+
createNbfFlakeTriageTransaction: () => createNbfFlakeTriageTransaction,
|
|
73881
74309
|
createCheckpointWriter: () => createCheckpointWriter,
|
|
73882
74310
|
countOscillationOutcomes: () => countOscillationOutcomes,
|
|
73883
74311
|
clearQueueFile: () => clearQueueFile,
|
|
@@ -73900,6 +74328,7 @@ __export(exports_execution, {
|
|
|
73900
74328
|
_runnerDeps: () => _runnerDeps,
|
|
73901
74329
|
_runnerCompletionDeps: () => _runnerCompletionDeps,
|
|
73902
74330
|
_runCompletionDeps: () => _runCompletionDeps,
|
|
74331
|
+
_runCleanupDeps: () => _runCleanupDeps,
|
|
73903
74332
|
_regressionDeps: () => _regressionDeps,
|
|
73904
74333
|
_postRunDeps: () => _postRunDeps,
|
|
73905
74334
|
_pidRegistryDeps: () => _pidRegistryDeps,
|
|
@@ -73917,6 +74346,7 @@ var init_execution2 = __esm(() => {
|
|
|
73917
74346
|
init_oscillation_breaker();
|
|
73918
74347
|
init_runner6();
|
|
73919
74348
|
init_progress();
|
|
74349
|
+
init_iteration_runner();
|
|
73920
74350
|
init_escalation();
|
|
73921
74351
|
init_queue_handler();
|
|
73922
74352
|
init_ensure_package_dirs();
|
|
@@ -73928,6 +74358,7 @@ var init_execution2 = __esm(() => {
|
|
|
73928
74358
|
init_story_orchestrator();
|
|
73929
74359
|
init_story_orchestrator_logging();
|
|
73930
74360
|
init_plan_inputs();
|
|
74361
|
+
init_non_blocking_fix();
|
|
73931
74362
|
init_build_plan_for_strategy();
|
|
73932
74363
|
init_checkpoint();
|
|
73933
74364
|
init_runner_completion();
|