@nathapp/nax 0.75.3 → 0.75.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nax.js +1050 -360
- package/flows/nax-finish/nax-finish.flow.ts +155 -16
- package/flows/nax-finish/steps/acceptance.ts +28 -5
- package/flows/nax-finish/steps/forge.ts +55 -6
- package/flows/nax-finish/steps/git.ts +61 -27
- package/flows/nax-finish/types.ts +14 -0
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -17317,7 +17317,11 @@ var init_schemas_reporters = __esm(() => {
|
|
|
17317
17317
|
maxBatchSize: exports_external.number().int().positive().default(64),
|
|
17318
17318
|
flushIntervalMs: exports_external.number().int().positive().default(5000),
|
|
17319
17319
|
maxQueueSize: exports_external.number().int().positive().default(2048),
|
|
17320
|
-
phases: exports_external.array(exports_external.string()).optional()
|
|
17320
|
+
phases: exports_external.array(exports_external.string()).optional(),
|
|
17321
|
+
logs: exports_external.object({
|
|
17322
|
+
enabled: exports_external.boolean().default(false),
|
|
17323
|
+
level: exports_external.enum(["silent", "error", "warn", "info", "debug"]).default("info")
|
|
17324
|
+
}).default({ enabled: false, level: "info" })
|
|
17321
17325
|
}).default({
|
|
17322
17326
|
enabled: false,
|
|
17323
17327
|
headers: {},
|
|
@@ -17327,7 +17331,8 @@ var init_schemas_reporters = __esm(() => {
|
|
|
17327
17331
|
heartbeatIntervalMs: 1e4,
|
|
17328
17332
|
maxBatchSize: 64,
|
|
17329
17333
|
flushIntervalMs: 5000,
|
|
17330
|
-
maxQueueSize: 2048
|
|
17334
|
+
maxQueueSize: 2048,
|
|
17335
|
+
logs: { enabled: false, level: "info" }
|
|
17331
17336
|
});
|
|
17332
17337
|
ReportersConfigSchema = exports_external.object({
|
|
17333
17338
|
webhook: WebhookReporterConfigSchema,
|
|
@@ -17347,7 +17352,8 @@ var init_schemas_reporters = __esm(() => {
|
|
|
17347
17352
|
heartbeatIntervalMs: 1e4,
|
|
17348
17353
|
maxBatchSize: 64,
|
|
17349
17354
|
flushIntervalMs: 5000,
|
|
17350
|
-
maxQueueSize: 2048
|
|
17355
|
+
maxQueueSize: 2048,
|
|
17356
|
+
logs: { enabled: false, level: "info" }
|
|
17351
17357
|
}
|
|
17352
17358
|
});
|
|
17353
17359
|
});
|
|
@@ -17817,6 +17823,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17817
17823
|
quality: exports_external.string().nullable().default(null)
|
|
17818
17824
|
}).default({ spec: null, quality: null }),
|
|
17819
17825
|
escalate: exports_external.object({ telegram: exports_external.boolean().default(true) }).default({ telegram: true }),
|
|
17826
|
+
notify: exports_external.object({ mode: exports_external.enum(["escalation", "always", "off"]).default("escalation") }).default({ mode: "escalation" }),
|
|
17820
17827
|
timeouts: exports_external.object({
|
|
17821
17828
|
acceptanceMs: exports_external.number().int().positive().default(600000),
|
|
17822
17829
|
gateMs: exports_external.number().int().positive().default(900000),
|
|
@@ -17829,6 +17836,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17829
17836
|
defaultAgent: null,
|
|
17830
17837
|
reviewers: { spec: null, quality: null },
|
|
17831
17838
|
escalate: { telegram: true },
|
|
17839
|
+
notify: { mode: "escalation" },
|
|
17832
17840
|
timeouts: { acceptanceMs: 600000, gateMs: 900000, flowMs: 5400000, stepMs: null }
|
|
17833
17841
|
})
|
|
17834
17842
|
}).default({
|
|
@@ -17838,6 +17846,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17838
17846
|
defaultAgent: null,
|
|
17839
17847
|
reviewers: { spec: null, quality: null },
|
|
17840
17848
|
escalate: { telegram: true },
|
|
17849
|
+
notify: { mode: "escalation" },
|
|
17841
17850
|
timeouts: { acceptanceMs: 600000, gateMs: 900000, flowMs: 5400000, stepMs: null }
|
|
17842
17851
|
}
|
|
17843
17852
|
}),
|
|
@@ -18388,6 +18397,30 @@ var init_redact = __esm(() => {
|
|
|
18388
18397
|
];
|
|
18389
18398
|
});
|
|
18390
18399
|
|
|
18400
|
+
// src/logger/sink-registry.ts
|
|
18401
|
+
class SinkRegistry {
|
|
18402
|
+
sinks = [];
|
|
18403
|
+
add(sink) {
|
|
18404
|
+
this.sinks.push(sink);
|
|
18405
|
+
return () => {
|
|
18406
|
+
const idx = this.sinks.indexOf(sink);
|
|
18407
|
+
if (idx !== -1) {
|
|
18408
|
+
this.sinks.splice(idx, 1);
|
|
18409
|
+
}
|
|
18410
|
+
};
|
|
18411
|
+
}
|
|
18412
|
+
dispatch(entry) {
|
|
18413
|
+
for (const sink of this.sinks) {
|
|
18414
|
+
try {
|
|
18415
|
+
sink({ ...entry });
|
|
18416
|
+
} catch (error48) {
|
|
18417
|
+
process.stderr.write(`[logger] Sink threw: ${error48}
|
|
18418
|
+
`);
|
|
18419
|
+
}
|
|
18420
|
+
}
|
|
18421
|
+
}
|
|
18422
|
+
}
|
|
18423
|
+
|
|
18391
18424
|
// src/logger/logger.ts
|
|
18392
18425
|
import { mkdirSync } from "fs";
|
|
18393
18426
|
import { appendFile } from "fs/promises";
|
|
@@ -18400,6 +18433,7 @@ class Logger {
|
|
|
18400
18433
|
suppressConsole;
|
|
18401
18434
|
writeQueueTail = Promise.resolve();
|
|
18402
18435
|
pendingLines = [];
|
|
18436
|
+
sinkRegistry = new SinkRegistry;
|
|
18403
18437
|
constructor(options) {
|
|
18404
18438
|
this.level = options.level;
|
|
18405
18439
|
this.filePath = options.filePath;
|
|
@@ -18443,10 +18477,11 @@ class Logger {
|
|
|
18443
18477
|
...sessionRole && { sessionRole },
|
|
18444
18478
|
...strippedData && { data: strippedData }
|
|
18445
18479
|
};
|
|
18480
|
+
const entry = redactEntry(rawEntry);
|
|
18481
|
+
this.sinkRegistry.dispatch(entry);
|
|
18446
18482
|
const consoleEnabled = this.shouldLog(level) && !this.suppressConsole;
|
|
18447
18483
|
if (!consoleEnabled && !this.filePath)
|
|
18448
18484
|
return;
|
|
18449
|
-
const entry = redactEntry(rawEntry);
|
|
18450
18485
|
if (consoleEnabled) {
|
|
18451
18486
|
let consoleOutput = null;
|
|
18452
18487
|
if (this.formatterMode) {
|
|
@@ -18530,8 +18565,19 @@ ${JSON.stringify(entry.data, null, 2)}`;
|
|
|
18530
18565
|
debug: (stage, message, data) => this.log("debug", stage, message, data, storyId)
|
|
18531
18566
|
};
|
|
18532
18567
|
}
|
|
18568
|
+
addSink(sink) {
|
|
18569
|
+
return this.sinkRegistry.add(sink);
|
|
18570
|
+
}
|
|
18533
18571
|
close() {}
|
|
18534
18572
|
}
|
|
18573
|
+
function addSink(sink) {
|
|
18574
|
+
if (!instance) {
|
|
18575
|
+
throw new NaxError("Logger not initialized. Call initLogger() before addSink().", "LOGGER_NOT_INITIALIZED", {
|
|
18576
|
+
stage: "logger"
|
|
18577
|
+
});
|
|
18578
|
+
}
|
|
18579
|
+
return instance.addSink(sink);
|
|
18580
|
+
}
|
|
18535
18581
|
function initLogger(options = { level: "silent" }) {
|
|
18536
18582
|
if (instance) {
|
|
18537
18583
|
throw new Error("Logger already initialized. Call getLogger() to access existing instance.");
|
|
@@ -18560,6 +18606,7 @@ function resetLogger() {
|
|
|
18560
18606
|
}
|
|
18561
18607
|
var LOG_LEVEL_PRIORITY, MAX_BATCH_BYTES, instance = null, noopLogger;
|
|
18562
18608
|
var init_logger = __esm(() => {
|
|
18609
|
+
init_errors();
|
|
18563
18610
|
init_log_format();
|
|
18564
18611
|
init_formatters();
|
|
18565
18612
|
init_redact();
|
|
@@ -42579,7 +42626,7 @@ var package_default;
|
|
|
42579
42626
|
var init_package = __esm(() => {
|
|
42580
42627
|
package_default = {
|
|
42581
42628
|
name: "@nathapp/nax",
|
|
42582
|
-
version: "0.75.
|
|
42629
|
+
version: "0.75.5",
|
|
42583
42630
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
42584
42631
|
type: "module",
|
|
42585
42632
|
bin: {
|
|
@@ -42683,8 +42730,8 @@ var init_version = __esm(() => {
|
|
|
42683
42730
|
NAX_VERSION = package_default.version;
|
|
42684
42731
|
NAX_COMMIT = (() => {
|
|
42685
42732
|
try {
|
|
42686
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
42687
|
-
return "
|
|
42733
|
+
if (/^[0-9a-f]{6,10}$/.test("c8f74c5f"))
|
|
42734
|
+
return "c8f74c5f";
|
|
42688
42735
|
} catch {}
|
|
42689
42736
|
try {
|
|
42690
42737
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -57527,10 +57574,13 @@ ${stderr}` };
|
|
|
57527
57574
|
|
|
57528
57575
|
// src/context/engine/effectiveness.ts
|
|
57529
57576
|
function tokenize2(text) {
|
|
57530
|
-
|
|
57531
|
-
|
|
57532
|
-
|
|
57533
|
-
|
|
57577
|
+
const terms = new Set;
|
|
57578
|
+
for (const match of text.matchAll(TOKEN_PATTERN)) {
|
|
57579
|
+
const term = match[0].toLowerCase();
|
|
57580
|
+
if (term.length >= MIN_TOKEN_LEN2 && !STOPWORDS2.has(term))
|
|
57581
|
+
terms.add(term);
|
|
57582
|
+
}
|
|
57583
|
+
return terms;
|
|
57534
57584
|
}
|
|
57535
57585
|
function sharedTermCount2(a, b) {
|
|
57536
57586
|
let count = 0;
|
|
@@ -57540,34 +57590,35 @@ function sharedTermCount2(a, b) {
|
|
|
57540
57590
|
}
|
|
57541
57591
|
return count;
|
|
57542
57592
|
}
|
|
57543
|
-
function
|
|
57544
|
-
const
|
|
57545
|
-
|
|
57546
|
-
|
|
57593
|
+
function buildEvidenceTerms(agentOutput, diffText, findingMessages) {
|
|
57594
|
+
const diffTerms = diffText ? _effectivenessDeps.tokenize(diffText) : undefined;
|
|
57595
|
+
const outputTerms = agentOutput ? _effectivenessDeps.tokenize(agentOutput) : undefined;
|
|
57596
|
+
let combined;
|
|
57597
|
+
if (diffTerms || outputTerms) {
|
|
57598
|
+
combined = new Set(diffTerms);
|
|
57599
|
+
for (const term of outputTerms ?? [])
|
|
57600
|
+
combined.add(term);
|
|
57547
57601
|
}
|
|
57548
|
-
|
|
57549
|
-
|
|
57550
|
-
|
|
57551
|
-
|
|
57552
|
-
|
|
57553
|
-
|
|
57554
|
-
|
|
57602
|
+
return {
|
|
57603
|
+
findings: findingMessages.map((message) => ({ message, terms: _effectivenessDeps.tokenize(message) })),
|
|
57604
|
+
diff: diffTerms,
|
|
57605
|
+
combined
|
|
57606
|
+
};
|
|
57607
|
+
}
|
|
57608
|
+
function classifyWithTerms(chunkSummary, evidence) {
|
|
57609
|
+
const summaryTerms = _effectivenessDeps.tokenize(chunkSummary);
|
|
57610
|
+
if (summaryTerms.size < MIN_SIGNIFICANT_TERMS)
|
|
57611
|
+
return { signal: "unknown" };
|
|
57612
|
+
for (const finding of evidence.findings) {
|
|
57613
|
+
if (sharedTermCount2(summaryTerms, finding.terms) >= MIN_SIGNIFICANT_TERMS) {
|
|
57614
|
+
return { signal: "contradicted", evidence: finding.message.slice(0, 200) };
|
|
57555
57615
|
}
|
|
57556
57616
|
}
|
|
57557
|
-
if (
|
|
57558
|
-
|
|
57559
|
-
if (sharedTermCount2(summaryTerms, diffTerms) >= MIN_SIGNIFICANT_TERMS) {
|
|
57560
|
-
return {
|
|
57561
|
-
signal: "followed",
|
|
57562
|
-
evidence: "terms found in diff"
|
|
57563
|
-
};
|
|
57564
|
-
}
|
|
57617
|
+
if (evidence.diff && sharedTermCount2(summaryTerms, evidence.diff) >= MIN_SIGNIFICANT_TERMS) {
|
|
57618
|
+
return { signal: "followed", evidence: "terms found in diff" };
|
|
57565
57619
|
}
|
|
57566
|
-
if (
|
|
57567
|
-
|
|
57568
|
-
if (sharedTermCount2(summaryTerms, combinedTerms) < MIN_SIGNIFICANT_TERMS) {
|
|
57569
|
-
return { signal: "ignored" };
|
|
57570
|
-
}
|
|
57620
|
+
if (evidence.combined && sharedTermCount2(summaryTerms, evidence.combined) < MIN_SIGNIFICANT_TERMS) {
|
|
57621
|
+
return { signal: "ignored" };
|
|
57571
57622
|
}
|
|
57572
57623
|
return { signal: "unknown" };
|
|
57573
57624
|
}
|
|
@@ -57577,16 +57628,18 @@ async function annotateManifestEffectiveness(projectDir, featureId, storyId, {
|
|
|
57577
57628
|
findingMessages
|
|
57578
57629
|
}) {
|
|
57579
57630
|
const stored = await loadContextManifests(projectDir, storyId, featureId);
|
|
57631
|
+
let evidenceTerms;
|
|
57580
57632
|
for (const item of stored) {
|
|
57581
57633
|
const { manifest } = item;
|
|
57582
57634
|
if (!manifest.chunkSummaries || manifest.includedChunks.length === 0)
|
|
57583
57635
|
continue;
|
|
57636
|
+
evidenceTerms ??= buildEvidenceTerms(agentOutput, diffText, findingMessages);
|
|
57584
57637
|
const effectiveness = {};
|
|
57585
57638
|
for (const id of manifest.includedChunks) {
|
|
57586
57639
|
const summary = manifest.chunkSummaries[id];
|
|
57587
57640
|
if (!summary)
|
|
57588
57641
|
continue;
|
|
57589
|
-
effectiveness[id] =
|
|
57642
|
+
effectiveness[id] = classifyWithTerms(summary, evidenceTerms);
|
|
57590
57643
|
}
|
|
57591
57644
|
if (Object.keys(effectiveness).length === 0)
|
|
57592
57645
|
continue;
|
|
@@ -57604,12 +57657,13 @@ async function annotateManifestEffectiveness(projectDir, featureId, storyId, {
|
|
|
57604
57657
|
}
|
|
57605
57658
|
}
|
|
57606
57659
|
}
|
|
57607
|
-
var _effectivenessDeps, MIN_SIGNIFICANT_TERMS = 3, STOPWORDS2, MIN_TOKEN_LEN2 = 4;
|
|
57660
|
+
var _effectivenessDeps, MIN_SIGNIFICANT_TERMS = 3, STOPWORDS2, MIN_TOKEN_LEN2 = 4, TOKEN_PATTERN;
|
|
57608
57661
|
var init_effectiveness = __esm(() => {
|
|
57609
57662
|
init_logger2();
|
|
57610
57663
|
init_manifest_store();
|
|
57611
57664
|
_effectivenessDeps = {
|
|
57612
|
-
getLogger
|
|
57665
|
+
getLogger,
|
|
57666
|
+
tokenize: tokenize2
|
|
57613
57667
|
};
|
|
57614
57668
|
STOPWORDS2 = new Set([
|
|
57615
57669
|
"the",
|
|
@@ -57651,6 +57705,7 @@ var init_effectiveness = __esm(() => {
|
|
|
57651
57705
|
"you",
|
|
57652
57706
|
"your"
|
|
57653
57707
|
]);
|
|
57708
|
+
TOKEN_PATTERN = /[^\s_\-./:,;()\[\]{}'"!?]+/g;
|
|
57654
57709
|
});
|
|
57655
57710
|
|
|
57656
57711
|
// src/execution/progress.ts
|
|
@@ -57667,19 +57722,61 @@ async function appendProgress(featureDir, storyId, status, message) {
|
|
|
57667
57722
|
var init_progress = () => {};
|
|
57668
57723
|
|
|
57669
57724
|
// src/pipeline/stages/completion.ts
|
|
57725
|
+
function logHighMemoryCheckpoint(logger, ctx) {
|
|
57726
|
+
const usage = process.memoryUsage();
|
|
57727
|
+
if (usage.heapUsed < HIGH_MEMORY_TELEMETRY_BYTES && usage.rss < HIGH_MEMORY_TELEMETRY_BYTES)
|
|
57728
|
+
return;
|
|
57729
|
+
logger.debug("completion.memory", "High memory at completion boundary", {
|
|
57730
|
+
storyId: ctx.story.id,
|
|
57731
|
+
heapUsedBytes: usage.heapUsed,
|
|
57732
|
+
rssBytes: usage.rss,
|
|
57733
|
+
externalBytes: usage.external,
|
|
57734
|
+
arrayBuffersBytes: usage.arrayBuffers,
|
|
57735
|
+
agentOutputChars: ctx.agentResult?.output.length ?? 0
|
|
57736
|
+
});
|
|
57737
|
+
}
|
|
57738
|
+
async function readTextStreamPrefix(stream, maxChars) {
|
|
57739
|
+
const reader = stream.getReader();
|
|
57740
|
+
const decoder = new TextDecoder;
|
|
57741
|
+
let output = "";
|
|
57742
|
+
try {
|
|
57743
|
+
while (true) {
|
|
57744
|
+
const { done, value } = await reader.read();
|
|
57745
|
+
if (done)
|
|
57746
|
+
break;
|
|
57747
|
+
if (output.length >= maxChars)
|
|
57748
|
+
continue;
|
|
57749
|
+
const decoded = decoder.decode(value, { stream: true });
|
|
57750
|
+
output += decoded.slice(0, maxChars - output.length);
|
|
57751
|
+
}
|
|
57752
|
+
if (output.length < maxChars) {
|
|
57753
|
+
output += decoder.decode().slice(0, maxChars - output.length);
|
|
57754
|
+
}
|
|
57755
|
+
return output;
|
|
57756
|
+
} finally {
|
|
57757
|
+
reader.releaseLock();
|
|
57758
|
+
}
|
|
57759
|
+
}
|
|
57670
57760
|
async function getDiffText(workdir, baseRef) {
|
|
57671
57761
|
if (!baseRef)
|
|
57672
57762
|
return "";
|
|
57673
57763
|
try {
|
|
57674
|
-
const proc =
|
|
57675
|
-
|
|
57676
|
-
|
|
57677
|
-
|
|
57764
|
+
const proc = _completionDeps.spawn(["git", "diff", `${baseRef}..HEAD`], {
|
|
57765
|
+
cwd: workdir,
|
|
57766
|
+
stdout: "pipe",
|
|
57767
|
+
stderr: "pipe"
|
|
57768
|
+
});
|
|
57769
|
+
const [output] = await Promise.all([
|
|
57770
|
+
readTextStreamPrefix(proc.stdout, MAX_EFFECTIVENESS_DIFF_CHARS),
|
|
57771
|
+
readTextStreamPrefix(proc.stderr, 0),
|
|
57772
|
+
proc.exited
|
|
57773
|
+
]);
|
|
57774
|
+
return output;
|
|
57678
57775
|
} catch {
|
|
57679
57776
|
return "";
|
|
57680
57777
|
}
|
|
57681
57778
|
}
|
|
57682
|
-
var completionStage, _completionDeps;
|
|
57779
|
+
var MAX_EFFECTIVENESS_DIFF_CHARS = 8000, HIGH_MEMORY_TELEMETRY_BYTES, completionStage, _completionDeps;
|
|
57683
57780
|
var init_completion = __esm(() => {
|
|
57684
57781
|
init_semantic_verdict();
|
|
57685
57782
|
init_effectiveness();
|
|
@@ -57689,6 +57786,7 @@ var init_completion = __esm(() => {
|
|
|
57689
57786
|
init_metrics();
|
|
57690
57787
|
init_prd();
|
|
57691
57788
|
init_event_bus();
|
|
57789
|
+
HIGH_MEMORY_TELEMETRY_BYTES = 512 * 1024 * 1024;
|
|
57692
57790
|
completionStage = {
|
|
57693
57791
|
name: "completion",
|
|
57694
57792
|
enabled: () => true,
|
|
@@ -57758,6 +57856,7 @@ var init_completion = __esm(() => {
|
|
|
57758
57856
|
if (persistPrd) {
|
|
57759
57857
|
await _completionDeps.savePRD(ctx.prd, prdPath);
|
|
57760
57858
|
}
|
|
57859
|
+
logHighMemoryCheckpoint(logger, ctx);
|
|
57761
57860
|
const updatedCounts = countStories(ctx.prd);
|
|
57762
57861
|
logger.info("completion", "Progress update", {
|
|
57763
57862
|
storyId: ctx.story.id,
|
|
@@ -57773,7 +57872,9 @@ var init_completion = __esm(() => {
|
|
|
57773
57872
|
checkReviewGate,
|
|
57774
57873
|
persistSemanticVerdict,
|
|
57775
57874
|
savePRD,
|
|
57776
|
-
getDiffText
|
|
57875
|
+
getDiffText,
|
|
57876
|
+
readTextStreamPrefix,
|
|
57877
|
+
spawn: Bun.spawn
|
|
57777
57878
|
};
|
|
57778
57879
|
});
|
|
57779
57880
|
|
|
@@ -58476,6 +58577,253 @@ var init_paths3 = __esm(() => {
|
|
|
58476
58577
|
init_paths();
|
|
58477
58578
|
});
|
|
58478
58579
|
|
|
58580
|
+
// src/execution/story-orchestrator/types.ts
|
|
58581
|
+
var EXHAUSTED_EXIT_REASONS, TDD_OP_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES, STRICT_VERDICT_PHASE_NAMES;
|
|
58582
|
+
var init_types9 = __esm(() => {
|
|
58583
|
+
EXHAUSTED_EXIT_REASONS = new Set([
|
|
58584
|
+
"max-attempts-total",
|
|
58585
|
+
"max-attempts-per-strategy",
|
|
58586
|
+
"bail-when",
|
|
58587
|
+
"no-strategy",
|
|
58588
|
+
"agent-gave-up",
|
|
58589
|
+
"validate-short-circuit"
|
|
58590
|
+
]);
|
|
58591
|
+
TDD_OP_NAMES = new Set(["test-writer", "implementer", "verifier"]);
|
|
58592
|
+
CANONICAL_ORDER = [
|
|
58593
|
+
"test-writer",
|
|
58594
|
+
"greenfield-gate",
|
|
58595
|
+
"implementer",
|
|
58596
|
+
"test-presence-gate",
|
|
58597
|
+
"full-suite-gate",
|
|
58598
|
+
"mutation-check",
|
|
58599
|
+
"verifier",
|
|
58600
|
+
"verify-scoped",
|
|
58601
|
+
"lint-check",
|
|
58602
|
+
"typecheck-check",
|
|
58603
|
+
"semantic-review",
|
|
58604
|
+
"adversarial-review"
|
|
58605
|
+
];
|
|
58606
|
+
PHASE_KIND_TO_STATE_KEY = {
|
|
58607
|
+
"test-writer": "testWriter",
|
|
58608
|
+
"greenfield-gate": "greenfieldGate",
|
|
58609
|
+
implementer: "implementer",
|
|
58610
|
+
"test-presence-gate": "testPresenceGate",
|
|
58611
|
+
"full-suite-gate": "fullSuiteGate",
|
|
58612
|
+
"mutation-check": "mutationCheck",
|
|
58613
|
+
verifier: "verifier",
|
|
58614
|
+
"verify-scoped": "verifyScoped",
|
|
58615
|
+
"lint-check": "lintCheck",
|
|
58616
|
+
"typecheck-check": "typecheckCheck",
|
|
58617
|
+
"semantic-review": "semanticReview",
|
|
58618
|
+
"adversarial-review": "adversarialReview"
|
|
58619
|
+
};
|
|
58620
|
+
STRATEGY_TO_REVALIDATION_PHASES = {
|
|
58621
|
+
"mechanical-lintfix": ["lint-check"],
|
|
58622
|
+
"mechanical-formatfix": ["lint-check"],
|
|
58623
|
+
"autofix-implementer": ["lint-check", "typecheck-check", "full-suite-gate", "semantic-review", "adversarial-review"],
|
|
58624
|
+
"autofix-test-writer": ["lint-check", "typecheck-check", "full-suite-gate", "adversarial-review"],
|
|
58625
|
+
"full-suite-rectify": [
|
|
58626
|
+
"lint-check",
|
|
58627
|
+
"typecheck-check",
|
|
58628
|
+
"full-suite-gate",
|
|
58629
|
+
"verifier",
|
|
58630
|
+
"verify-scoped",
|
|
58631
|
+
"semantic-review",
|
|
58632
|
+
"adversarial-review"
|
|
58633
|
+
]
|
|
58634
|
+
};
|
|
58635
|
+
STRICT_VERDICT_PHASE_NAMES = new Set([
|
|
58636
|
+
"full-suite-gate",
|
|
58637
|
+
"verify-scoped",
|
|
58638
|
+
"lint-check",
|
|
58639
|
+
"typecheck-check",
|
|
58640
|
+
"verifier"
|
|
58641
|
+
]);
|
|
58642
|
+
});
|
|
58643
|
+
|
|
58644
|
+
// src/execution/story-orchestrator/phase-eval.ts
|
|
58645
|
+
function phaseExplicitlyPassed(output) {
|
|
58646
|
+
if (output === null || output === undefined || typeof output !== "object")
|
|
58647
|
+
return false;
|
|
58648
|
+
const r = output;
|
|
58649
|
+
return r.success === true || r.passed === true;
|
|
58650
|
+
}
|
|
58651
|
+
function phasePassed(opName, output, storyId) {
|
|
58652
|
+
const strictVerdictPhase = STRICT_VERDICT_PHASE_NAMES.has(opName);
|
|
58653
|
+
if (output === null || output === undefined) {
|
|
58654
|
+
getSafeLogger()?.warn("story-orchestrator", strictVerdictPhase ? "Strict phase produced no output \u2014 treating as fail" : "Phase produced no output \u2014 treating as pass", {
|
|
58655
|
+
storyId,
|
|
58656
|
+
phase: opName
|
|
58657
|
+
});
|
|
58658
|
+
return !strictVerdictPhase;
|
|
58659
|
+
}
|
|
58660
|
+
if (typeof output !== "object") {
|
|
58661
|
+
if (!strictVerdictPhase)
|
|
58662
|
+
return true;
|
|
58663
|
+
getSafeLogger()?.warn("story-orchestrator", "Strict phase produced non-object output \u2014 treating as fail", {
|
|
58664
|
+
storyId,
|
|
58665
|
+
phase: opName
|
|
58666
|
+
});
|
|
58667
|
+
return false;
|
|
58668
|
+
}
|
|
58669
|
+
const r = output;
|
|
58670
|
+
if ("success" in r)
|
|
58671
|
+
return r.success !== false;
|
|
58672
|
+
if ("passed" in r)
|
|
58673
|
+
return r.passed !== false;
|
|
58674
|
+
getSafeLogger()?.warn("story-orchestrator", strictVerdictPhase ? "Strict phase output has neither 'success' nor 'passed' \u2014 treating as fail" : "Phase output has neither 'success' nor 'passed' \u2014 treating as pass", {
|
|
58675
|
+
storyId,
|
|
58676
|
+
phase: opName
|
|
58677
|
+
});
|
|
58678
|
+
return !strictVerdictPhase;
|
|
58679
|
+
}
|
|
58680
|
+
function isFinding(value) {
|
|
58681
|
+
return typeof value === "object" && value !== null && typeof value.source === "string" && value.source.length > 0;
|
|
58682
|
+
}
|
|
58683
|
+
function extractPhaseFindings(output) {
|
|
58684
|
+
if (output === null || output === undefined || typeof output !== "object") {
|
|
58685
|
+
return [];
|
|
58686
|
+
}
|
|
58687
|
+
const record2 = output;
|
|
58688
|
+
const rawArray = Array.isArray(record2.normalizedFindings) && record2.normalizedFindings.length > 0 ? record2.normalizedFindings : Array.isArray(record2.findings) ? record2.findings : [];
|
|
58689
|
+
const findings = rawArray.filter(isFinding);
|
|
58690
|
+
const success2 = "success" in record2 ? record2.success === true : ("passed" in record2) ? record2.passed === true : findings.length === 0;
|
|
58691
|
+
return success2 ? [] : findings;
|
|
58692
|
+
}
|
|
58693
|
+
function gateFailureKeys(gateOutput) {
|
|
58694
|
+
const keys = new Set;
|
|
58695
|
+
for (const f of extractPhaseFindings(gateOutput)) {
|
|
58696
|
+
if (f.source !== "test-runner")
|
|
58697
|
+
continue;
|
|
58698
|
+
if (f.category === "flaky-test")
|
|
58699
|
+
continue;
|
|
58700
|
+
keys.add(gateFindingKey(f));
|
|
58701
|
+
}
|
|
58702
|
+
return keys;
|
|
58703
|
+
}
|
|
58704
|
+
function gateFindingKey(finding) {
|
|
58705
|
+
return `${finding.file ?? ""}::${finding.rule ?? ""}`;
|
|
58706
|
+
}
|
|
58707
|
+
function isQuarantinedFlake(finding, quarantineMemo) {
|
|
58708
|
+
if (finding.source !== "test-runner")
|
|
58709
|
+
return false;
|
|
58710
|
+
const key = gateFindingKey(finding);
|
|
58711
|
+
return key !== KEYLESS_GATE_FAILURE_KEY && quarantineMemo?.has(key) === true;
|
|
58712
|
+
}
|
|
58713
|
+
function describeGateRegression(input) {
|
|
58714
|
+
const { gateOutput, baselineKeys, gateName, storyId, quarantineMemo } = input;
|
|
58715
|
+
const notRegressed = {
|
|
58716
|
+
regressed: false,
|
|
58717
|
+
regressedKeys: [],
|
|
58718
|
+
memoExcludedKeys: [],
|
|
58719
|
+
baselineKeySize: baselineKeys.size,
|
|
58720
|
+
keyless: false
|
|
58721
|
+
};
|
|
58722
|
+
if (gateName === undefined || phasePassed(gateName, gateOutput, storyId))
|
|
58723
|
+
return notRegressed;
|
|
58724
|
+
const allKeys = gateFailureKeys(gateOutput);
|
|
58725
|
+
const memoExcludedKeys = quarantineMemo ? [...allKeys].filter((k) => quarantineMemo.has(k)) : [];
|
|
58726
|
+
const excluded = new Set(memoExcludedKeys);
|
|
58727
|
+
const regressedKeys = [...allKeys].filter((k) => !baselineKeys.has(k) && !excluded.has(k));
|
|
58728
|
+
const keyless = allKeys.size === 0 || allKeys.has(KEYLESS_GATE_FAILURE_KEY);
|
|
58729
|
+
return {
|
|
58730
|
+
regressed: regressedKeys.length > 0 || keyless,
|
|
58731
|
+
regressedKeys,
|
|
58732
|
+
memoExcludedKeys,
|
|
58733
|
+
baselineKeySize: baselineKeys.size,
|
|
58734
|
+
keyless
|
|
58735
|
+
};
|
|
58736
|
+
}
|
|
58737
|
+
function phasesToRevalidate(strategiesRun, allPhases) {
|
|
58738
|
+
if (!strategiesRun || strategiesRun.length === 0)
|
|
58739
|
+
return allPhases;
|
|
58740
|
+
const unknown2 = strategiesRun.some((name) => STRATEGY_TO_REVALIDATION_PHASES[name] === undefined);
|
|
58741
|
+
if (unknown2)
|
|
58742
|
+
return allPhases;
|
|
58743
|
+
const needed = new Set;
|
|
58744
|
+
for (const name of strategiesRun) {
|
|
58745
|
+
for (const kind of STRATEGY_TO_REVALIDATION_PHASES[name] ?? []) {
|
|
58746
|
+
needed.add(kind);
|
|
58747
|
+
}
|
|
58748
|
+
}
|
|
58749
|
+
return allPhases.filter((p) => needed.has(p.kind));
|
|
58750
|
+
}
|
|
58751
|
+
function orderGateLast(phases) {
|
|
58752
|
+
const rest = phases.filter((p) => p.kind !== "full-suite-gate");
|
|
58753
|
+
const gates = phases.filter((p) => p.kind === "full-suite-gate");
|
|
58754
|
+
return [...rest, ...gates];
|
|
58755
|
+
}
|
|
58756
|
+
var KEYLESS_GATE_FAILURE_KEY = "::";
|
|
58757
|
+
var init_phase_eval = __esm(() => {
|
|
58758
|
+
init_logger2();
|
|
58759
|
+
init_types9();
|
|
58760
|
+
});
|
|
58761
|
+
|
|
58762
|
+
// src/execution/story-orchestrator/nbf-flake-triage.ts
|
|
58763
|
+
function isCandidate(input) {
|
|
58764
|
+
if (input.finding.source !== "test-runner" || input.finding.category !== "failed-test")
|
|
58765
|
+
return false;
|
|
58766
|
+
const key = gateFindingKey(input.finding);
|
|
58767
|
+
return !input.transactionInput.baselineKeys.has(key) && !input.memo.has(key) && !input.attemptedKeys.has(key);
|
|
58768
|
+
}
|
|
58769
|
+
function createNbfFlakeTriageTransaction(input) {
|
|
58770
|
+
const pendingKeys = new Set;
|
|
58771
|
+
const attemptedKeys = new Set;
|
|
58772
|
+
let flakeTriageRan = false;
|
|
58773
|
+
const memo2 = {
|
|
58774
|
+
has: (key) => pendingKeys.has(key) || input.baseMemo?.has(key) === true,
|
|
58775
|
+
add: (key) => pendingKeys.add(key)
|
|
58776
|
+
};
|
|
58777
|
+
return {
|
|
58778
|
+
memo: memo2,
|
|
58779
|
+
get flakeTriageRan() {
|
|
58780
|
+
return flakeTriageRan;
|
|
58781
|
+
},
|
|
58782
|
+
candidates: (findings) => findings.filter((finding) => isCandidate({ finding, transactionInput: input, memo: memo2, attemptedKeys })),
|
|
58783
|
+
recordAttempt: (findings, ran) => {
|
|
58784
|
+
if (!ran)
|
|
58785
|
+
return;
|
|
58786
|
+
flakeTriageRan = true;
|
|
58787
|
+
for (const finding of findings)
|
|
58788
|
+
attemptedKeys.add(gateFindingKey(finding));
|
|
58789
|
+
},
|
|
58790
|
+
commit: () => {
|
|
58791
|
+
for (const key of pendingKeys)
|
|
58792
|
+
input.baseMemo?.add(key);
|
|
58793
|
+
}
|
|
58794
|
+
};
|
|
58795
|
+
}
|
|
58796
|
+
async function triageNbfGate(input) {
|
|
58797
|
+
const candidates = input.transaction.candidates(extractPhaseFindings(input.output));
|
|
58798
|
+
if (candidates.length === 0)
|
|
58799
|
+
return;
|
|
58800
|
+
const record2 = input.output;
|
|
58801
|
+
const rawOutput = typeof record2.rawOutput === "string" ? record2.rawOutput : "";
|
|
58802
|
+
try {
|
|
58803
|
+
const [, report] = await input.triage(candidates, {
|
|
58804
|
+
ctx: input.ctx,
|
|
58805
|
+
rawOutput,
|
|
58806
|
+
quarantineMemo: input.transaction.memo
|
|
58807
|
+
});
|
|
58808
|
+
const ran = report.flakeTriageRan ?? true;
|
|
58809
|
+
if (ran) {
|
|
58810
|
+
for (const key of report.quarantinedKeys)
|
|
58811
|
+
input.transaction.memo.add(key);
|
|
58812
|
+
}
|
|
58813
|
+
input.transaction.recordAttempt(candidates, ran);
|
|
58814
|
+
} catch (err) {
|
|
58815
|
+
getSafeLogger()?.warn("story-orchestrator", "NBF flake triage threw \u2014 keeping findings blocking", {
|
|
58816
|
+
storyId: input.ctx.storyId,
|
|
58817
|
+
gateName: input.gateName,
|
|
58818
|
+
error: err instanceof Error ? err.message : String(err)
|
|
58819
|
+
});
|
|
58820
|
+
}
|
|
58821
|
+
}
|
|
58822
|
+
var init_nbf_flake_triage = __esm(() => {
|
|
58823
|
+
init_logger2();
|
|
58824
|
+
init_phase_eval();
|
|
58825
|
+
});
|
|
58826
|
+
|
|
58479
58827
|
// src/execution/non-blocking-fix.ts
|
|
58480
58828
|
function actionableAdvisoryFindings(findings) {
|
|
58481
58829
|
return findings.filter((f) => f.actionRequired !== false);
|
|
@@ -58540,9 +58888,13 @@ async function runNonBlockingFix(args, overrides = {}) {
|
|
|
58540
58888
|
return { ran: false, kept: false, restored: false };
|
|
58541
58889
|
}
|
|
58542
58890
|
const maxAttempts = 1 + args.cfg.regressionAttempts;
|
|
58891
|
+
const flakeTriage = createNbfFlakeTriageTransaction({
|
|
58892
|
+
baseMemo: args.quarantineMemo,
|
|
58893
|
+
baselineKeys: args.gateBaselineKeys ?? new Set
|
|
58894
|
+
});
|
|
58543
58895
|
let exhausted = false;
|
|
58544
58896
|
try {
|
|
58545
|
-
const result = await args.runRectify(maxAttempts);
|
|
58897
|
+
const result = await args.runRectify(maxAttempts, flakeTriage);
|
|
58546
58898
|
exhausted = result.rectificationExhausted === true;
|
|
58547
58899
|
} catch (err) {
|
|
58548
58900
|
logger?.warn("non-blocking-fix", "best-effort pass threw \u2014 restoring", {
|
|
@@ -58552,16 +58904,14 @@ async function runNonBlockingFix(args, overrides = {}) {
|
|
|
58552
58904
|
exhausted = true;
|
|
58553
58905
|
}
|
|
58554
58906
|
if (!exhausted) {
|
|
58555
|
-
const gateVerdict = args.keptTreeRegressed?.();
|
|
58907
|
+
const gateVerdict = args.keptTreeRegressed?.(flakeTriage.memo);
|
|
58556
58908
|
if (gateVerdict?.regressed) {
|
|
58557
|
-
|
|
58909
|
+
logGateRegression({
|
|
58910
|
+
logger,
|
|
58558
58911
|
storyId: args.storyId,
|
|
58559
|
-
|
|
58560
|
-
|
|
58561
|
-
|
|
58562
|
-
keyless: gateVerdict.keyless,
|
|
58563
|
-
memoExcludedKeyCount: gateVerdict.memoExcludedKeys.length,
|
|
58564
|
-
flakeTriageRan: false
|
|
58912
|
+
message: "kept tree regressed the full-suite gate \u2014 restoring (ADR-024 \xA73)",
|
|
58913
|
+
verdict: gateVerdict,
|
|
58914
|
+
flakeTriageRan: flakeTriage.flakeTriageRan
|
|
58565
58915
|
});
|
|
58566
58916
|
return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
|
|
58567
58917
|
}
|
|
@@ -58587,11 +58937,34 @@ async function runNonBlockingFix(args, overrides = {}) {
|
|
|
58587
58937
|
return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
|
|
58588
58938
|
}
|
|
58589
58939
|
}
|
|
58940
|
+
flakeTriage.commit();
|
|
58590
58941
|
logger?.info("non-blocking-fix", "best-effort fix kept", { storyId: args.storyId });
|
|
58591
58942
|
return { ran: true, kept: true, restored: false };
|
|
58592
58943
|
}
|
|
58944
|
+
const exhaustedGateVerdict = args.keptTreeRegressed?.(flakeTriage.memo);
|
|
58945
|
+
if (exhaustedGateVerdict?.regressed) {
|
|
58946
|
+
logGateRegression({
|
|
58947
|
+
logger,
|
|
58948
|
+
storyId: args.storyId,
|
|
58949
|
+
message: "best-effort fix exhausted with the full-suite gate red",
|
|
58950
|
+
verdict: exhaustedGateVerdict,
|
|
58951
|
+
flakeTriageRan: flakeTriage.flakeTriageRan
|
|
58952
|
+
});
|
|
58953
|
+
}
|
|
58593
58954
|
return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
|
|
58594
58955
|
}
|
|
58956
|
+
function logGateRegression(input) {
|
|
58957
|
+
const { logger, storyId, message, verdict, flakeTriageRan } = input;
|
|
58958
|
+
logger?.info("non-blocking-fix", message, {
|
|
58959
|
+
storyId,
|
|
58960
|
+
regressedKeys: verdict.regressedKeys.slice(0, MAX_LOGGED_REGRESSED_KEYS),
|
|
58961
|
+
regressedKeyCount: verdict.regressedKeys.length,
|
|
58962
|
+
baselineKeySize: verdict.baselineKeySize,
|
|
58963
|
+
keyless: verdict.keyless,
|
|
58964
|
+
memoExcludedKeyCount: verdict.memoExcludedKeys.length,
|
|
58965
|
+
flakeTriageRan
|
|
58966
|
+
});
|
|
58967
|
+
}
|
|
58595
58968
|
async function restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger) {
|
|
58596
58969
|
await _deps.rollbackToRef(args.workdir, restoreRef);
|
|
58597
58970
|
for (const key of Object.keys(args.phaseOutputs))
|
|
@@ -58612,6 +58985,7 @@ var init_non_blocking_fix = __esm(() => {
|
|
|
58612
58985
|
init_test_runners();
|
|
58613
58986
|
init_bun_deps();
|
|
58614
58987
|
init_paths3();
|
|
58988
|
+
init_nbf_flake_triage();
|
|
58615
58989
|
REVIEW_PHASE_KINDS = ["semantic-review", "adversarial-review"];
|
|
58616
58990
|
_nonBlockingFixDeps = {
|
|
58617
58991
|
spawn: typedSpawn,
|
|
@@ -58624,179 +58998,6 @@ var init_non_blocking_fix = __esm(() => {
|
|
|
58624
58998
|
};
|
|
58625
58999
|
});
|
|
58626
59000
|
|
|
58627
|
-
// src/execution/story-orchestrator/types.ts
|
|
58628
|
-
var EXHAUSTED_EXIT_REASONS, TDD_OP_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES, STRICT_VERDICT_PHASE_NAMES;
|
|
58629
|
-
var init_types9 = __esm(() => {
|
|
58630
|
-
EXHAUSTED_EXIT_REASONS = new Set([
|
|
58631
|
-
"max-attempts-total",
|
|
58632
|
-
"max-attempts-per-strategy",
|
|
58633
|
-
"bail-when",
|
|
58634
|
-
"no-strategy",
|
|
58635
|
-
"agent-gave-up",
|
|
58636
|
-
"validate-short-circuit"
|
|
58637
|
-
]);
|
|
58638
|
-
TDD_OP_NAMES = new Set(["test-writer", "implementer", "verifier"]);
|
|
58639
|
-
CANONICAL_ORDER = [
|
|
58640
|
-
"test-writer",
|
|
58641
|
-
"greenfield-gate",
|
|
58642
|
-
"implementer",
|
|
58643
|
-
"test-presence-gate",
|
|
58644
|
-
"full-suite-gate",
|
|
58645
|
-
"mutation-check",
|
|
58646
|
-
"verifier",
|
|
58647
|
-
"verify-scoped",
|
|
58648
|
-
"lint-check",
|
|
58649
|
-
"typecheck-check",
|
|
58650
|
-
"semantic-review",
|
|
58651
|
-
"adversarial-review"
|
|
58652
|
-
];
|
|
58653
|
-
PHASE_KIND_TO_STATE_KEY = {
|
|
58654
|
-
"test-writer": "testWriter",
|
|
58655
|
-
"greenfield-gate": "greenfieldGate",
|
|
58656
|
-
implementer: "implementer",
|
|
58657
|
-
"test-presence-gate": "testPresenceGate",
|
|
58658
|
-
"full-suite-gate": "fullSuiteGate",
|
|
58659
|
-
"mutation-check": "mutationCheck",
|
|
58660
|
-
verifier: "verifier",
|
|
58661
|
-
"verify-scoped": "verifyScoped",
|
|
58662
|
-
"lint-check": "lintCheck",
|
|
58663
|
-
"typecheck-check": "typecheckCheck",
|
|
58664
|
-
"semantic-review": "semanticReview",
|
|
58665
|
-
"adversarial-review": "adversarialReview"
|
|
58666
|
-
};
|
|
58667
|
-
STRATEGY_TO_REVALIDATION_PHASES = {
|
|
58668
|
-
"mechanical-lintfix": ["lint-check"],
|
|
58669
|
-
"mechanical-formatfix": ["lint-check"],
|
|
58670
|
-
"autofix-implementer": ["lint-check", "typecheck-check", "full-suite-gate", "semantic-review", "adversarial-review"],
|
|
58671
|
-
"autofix-test-writer": ["lint-check", "typecheck-check", "full-suite-gate", "adversarial-review"],
|
|
58672
|
-
"full-suite-rectify": [
|
|
58673
|
-
"lint-check",
|
|
58674
|
-
"typecheck-check",
|
|
58675
|
-
"full-suite-gate",
|
|
58676
|
-
"verifier",
|
|
58677
|
-
"verify-scoped",
|
|
58678
|
-
"semantic-review",
|
|
58679
|
-
"adversarial-review"
|
|
58680
|
-
]
|
|
58681
|
-
};
|
|
58682
|
-
STRICT_VERDICT_PHASE_NAMES = new Set([
|
|
58683
|
-
"full-suite-gate",
|
|
58684
|
-
"verify-scoped",
|
|
58685
|
-
"lint-check",
|
|
58686
|
-
"typecheck-check",
|
|
58687
|
-
"verifier"
|
|
58688
|
-
]);
|
|
58689
|
-
});
|
|
58690
|
-
|
|
58691
|
-
// src/execution/story-orchestrator/phase-eval.ts
|
|
58692
|
-
function phaseExplicitlyPassed(output) {
|
|
58693
|
-
if (output === null || output === undefined || typeof output !== "object")
|
|
58694
|
-
return false;
|
|
58695
|
-
const r = output;
|
|
58696
|
-
return r.success === true || r.passed === true;
|
|
58697
|
-
}
|
|
58698
|
-
function phasePassed(opName, output, storyId) {
|
|
58699
|
-
const strictVerdictPhase = STRICT_VERDICT_PHASE_NAMES.has(opName);
|
|
58700
|
-
if (output === null || output === undefined) {
|
|
58701
|
-
getSafeLogger()?.warn("story-orchestrator", strictVerdictPhase ? "Strict phase produced no output \u2014 treating as fail" : "Phase produced no output \u2014 treating as pass", {
|
|
58702
|
-
storyId,
|
|
58703
|
-
phase: opName
|
|
58704
|
-
});
|
|
58705
|
-
return !strictVerdictPhase;
|
|
58706
|
-
}
|
|
58707
|
-
if (typeof output !== "object") {
|
|
58708
|
-
if (!strictVerdictPhase)
|
|
58709
|
-
return true;
|
|
58710
|
-
getSafeLogger()?.warn("story-orchestrator", "Strict phase produced non-object output \u2014 treating as fail", {
|
|
58711
|
-
storyId,
|
|
58712
|
-
phase: opName
|
|
58713
|
-
});
|
|
58714
|
-
return false;
|
|
58715
|
-
}
|
|
58716
|
-
const r = output;
|
|
58717
|
-
if ("success" in r)
|
|
58718
|
-
return r.success !== false;
|
|
58719
|
-
if ("passed" in r)
|
|
58720
|
-
return r.passed !== false;
|
|
58721
|
-
getSafeLogger()?.warn("story-orchestrator", strictVerdictPhase ? "Strict phase output has neither 'success' nor 'passed' \u2014 treating as fail" : "Phase output has neither 'success' nor 'passed' \u2014 treating as pass", {
|
|
58722
|
-
storyId,
|
|
58723
|
-
phase: opName
|
|
58724
|
-
});
|
|
58725
|
-
return !strictVerdictPhase;
|
|
58726
|
-
}
|
|
58727
|
-
function isFinding(value) {
|
|
58728
|
-
return typeof value === "object" && value !== null && typeof value.source === "string" && value.source.length > 0;
|
|
58729
|
-
}
|
|
58730
|
-
function extractPhaseFindings(output) {
|
|
58731
|
-
if (output === null || output === undefined || typeof output !== "object") {
|
|
58732
|
-
return [];
|
|
58733
|
-
}
|
|
58734
|
-
const record2 = output;
|
|
58735
|
-
const rawArray = Array.isArray(record2.normalizedFindings) && record2.normalizedFindings.length > 0 ? record2.normalizedFindings : Array.isArray(record2.findings) ? record2.findings : [];
|
|
58736
|
-
const findings = rawArray.filter(isFinding);
|
|
58737
|
-
const success2 = "success" in record2 ? record2.success === true : ("passed" in record2) ? record2.passed === true : findings.length === 0;
|
|
58738
|
-
return success2 ? [] : findings;
|
|
58739
|
-
}
|
|
58740
|
-
function gateFailureKeys(gateOutput) {
|
|
58741
|
-
const keys = new Set;
|
|
58742
|
-
for (const f of extractPhaseFindings(gateOutput)) {
|
|
58743
|
-
if (f.source !== "test-runner")
|
|
58744
|
-
continue;
|
|
58745
|
-
if (f.category === "flaky-test")
|
|
58746
|
-
continue;
|
|
58747
|
-
keys.add(`${f.file ?? ""}::${f.rule ?? ""}`);
|
|
58748
|
-
}
|
|
58749
|
-
return keys;
|
|
58750
|
-
}
|
|
58751
|
-
function describeGateRegression(input) {
|
|
58752
|
-
const { gateOutput, baselineKeys, gateName, storyId, quarantineMemo } = input;
|
|
58753
|
-
const notRegressed = {
|
|
58754
|
-
regressed: false,
|
|
58755
|
-
regressedKeys: [],
|
|
58756
|
-
memoExcludedKeys: [],
|
|
58757
|
-
baselineKeySize: baselineKeys.size,
|
|
58758
|
-
keyless: false
|
|
58759
|
-
};
|
|
58760
|
-
if (gateName === undefined || phasePassed(gateName, gateOutput, storyId))
|
|
58761
|
-
return notRegressed;
|
|
58762
|
-
const allKeys = gateFailureKeys(gateOutput);
|
|
58763
|
-
const memoExcludedKeys = quarantineMemo ? [...allKeys].filter((k) => quarantineMemo.has(k)) : [];
|
|
58764
|
-
const excluded = new Set(memoExcludedKeys);
|
|
58765
|
-
const regressedKeys = [...allKeys].filter((k) => !baselineKeys.has(k) && !excluded.has(k));
|
|
58766
|
-
const keyless = allKeys.size === 0 || allKeys.has(KEYLESS_GATE_FAILURE_KEY);
|
|
58767
|
-
return {
|
|
58768
|
-
regressed: regressedKeys.length > 0 || keyless,
|
|
58769
|
-
regressedKeys,
|
|
58770
|
-
memoExcludedKeys,
|
|
58771
|
-
baselineKeySize: baselineKeys.size,
|
|
58772
|
-
keyless
|
|
58773
|
-
};
|
|
58774
|
-
}
|
|
58775
|
-
function phasesToRevalidate(strategiesRun, allPhases) {
|
|
58776
|
-
if (!strategiesRun || strategiesRun.length === 0)
|
|
58777
|
-
return allPhases;
|
|
58778
|
-
const unknown2 = strategiesRun.some((name) => STRATEGY_TO_REVALIDATION_PHASES[name] === undefined);
|
|
58779
|
-
if (unknown2)
|
|
58780
|
-
return allPhases;
|
|
58781
|
-
const needed = new Set;
|
|
58782
|
-
for (const name of strategiesRun) {
|
|
58783
|
-
for (const kind of STRATEGY_TO_REVALIDATION_PHASES[name] ?? []) {
|
|
58784
|
-
needed.add(kind);
|
|
58785
|
-
}
|
|
58786
|
-
}
|
|
58787
|
-
return allPhases.filter((p) => needed.has(p.kind));
|
|
58788
|
-
}
|
|
58789
|
-
function orderGateLast(phases) {
|
|
58790
|
-
const rest = phases.filter((p) => p.kind !== "full-suite-gate");
|
|
58791
|
-
const gates = phases.filter((p) => p.kind === "full-suite-gate");
|
|
58792
|
-
return [...rest, ...gates];
|
|
58793
|
-
}
|
|
58794
|
-
var KEYLESS_GATE_FAILURE_KEY = "::";
|
|
58795
|
-
var init_phase_eval = __esm(() => {
|
|
58796
|
-
init_logger2();
|
|
58797
|
-
init_types9();
|
|
58798
|
-
});
|
|
58799
|
-
|
|
58800
59001
|
// src/execution/story-orchestrator/phase-state.ts
|
|
58801
59002
|
function isSlot(value) {
|
|
58802
59003
|
return value !== null && typeof value === "object" && "op" in value && "input" in value && typeof value.op?.kind === "string";
|
|
@@ -58990,15 +59191,15 @@ var init_verification = __esm(() => {
|
|
|
58990
59191
|
});
|
|
58991
59192
|
|
|
58992
59193
|
// src/execution/story-orchestrator/flake-triage-seam.ts
|
|
58993
|
-
var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
|
|
59194
|
+
var productionTriageSeam = async (gateFindings, { ctx, rawOutput, quarantineMemo }) => {
|
|
58994
59195
|
const config2 = ctx.packageView.config;
|
|
58995
59196
|
const flakeDetection = config2.execution?.flakeDetection;
|
|
58996
59197
|
if (!flakeDetection?.enabled) {
|
|
58997
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59198
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
58998
59199
|
}
|
|
58999
59200
|
const framework = detectFramework(rawOutput);
|
|
59000
59201
|
if (framework === "unknown") {
|
|
59001
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59202
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
59002
59203
|
}
|
|
59003
59204
|
const workdir = ctx.runtime.workdir;
|
|
59004
59205
|
const storyWorkdir = ctx.story?.workdir;
|
|
@@ -59007,11 +59208,11 @@ var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
|
|
|
59007
59208
|
const { testCommand } = await resolveQualityTestCommands2(config2, workdir, storyWorkdir);
|
|
59008
59209
|
const baseCommand = testCommand ?? config2.quality?.commands?.test;
|
|
59009
59210
|
if (!baseCommand) {
|
|
59010
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59211
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
59011
59212
|
}
|
|
59012
59213
|
const diff = await resolveFlakeBaselineDiff(config2, workdir, storyWorkdir);
|
|
59013
59214
|
if (diff === null) {
|
|
59014
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59215
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
59015
59216
|
}
|
|
59016
59217
|
const result = await triageFlakyFindings({
|
|
59017
59218
|
findings: gateFindings,
|
|
@@ -59020,15 +59221,15 @@ var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
|
|
|
59020
59221
|
baseCommand,
|
|
59021
59222
|
cwd: ctx.packageDir,
|
|
59022
59223
|
framework,
|
|
59023
|
-
quarantineMemo: ctx.runtime.quarantineMemo
|
|
59224
|
+
quarantineMemo: quarantineMemo ?? ctx.runtime.quarantineMemo
|
|
59024
59225
|
});
|
|
59025
|
-
return [result.findings, { quarantinedKeys: result.quarantineReport.keys }];
|
|
59226
|
+
return [result.findings, { quarantinedKeys: result.quarantineReport.keys, flakeTriageRan: true }];
|
|
59026
59227
|
} catch (err) {
|
|
59027
59228
|
getSafeLogger()?.warn("story-orchestrator", "Flake triage seam failed resolving context \u2014 keeping findings blocking (no quarantine)", {
|
|
59028
59229
|
storyId: ctx.storyId,
|
|
59029
59230
|
error: errorMessage(err)
|
|
59030
59231
|
});
|
|
59031
|
-
return [gateFindings, { quarantinedKeys: [] }];
|
|
59232
|
+
return [gateFindings, { quarantinedKeys: [], flakeTriageRan: false }];
|
|
59032
59233
|
}
|
|
59033
59234
|
};
|
|
59034
59235
|
var init_flake_triage_seam = __esm(() => {
|
|
@@ -59447,9 +59648,12 @@ var init_run_phase = __esm(() => {
|
|
|
59447
59648
|
});
|
|
59448
59649
|
|
|
59449
59650
|
// src/execution/story-orchestrator/rectification.ts
|
|
59450
|
-
function shouldSkipPhaseForRectification(
|
|
59651
|
+
function shouldSkipPhaseForRectification(input) {
|
|
59652
|
+
const { phase, state, phaseOutputs, nbfPath } = input;
|
|
59451
59653
|
if (phase.kind !== "full-suite-gate")
|
|
59452
59654
|
return false;
|
|
59655
|
+
if (nbfPath)
|
|
59656
|
+
return false;
|
|
59453
59657
|
const verifierName = state.verifier?.slot.op.name;
|
|
59454
59658
|
if (!verifierName)
|
|
59455
59659
|
return false;
|
|
@@ -59458,7 +59662,7 @@ function shouldSkipPhaseForRectification(phase, state, phaseOutputs) {
|
|
|
59458
59662
|
function gatherRectificationFindings(phaseOutputs, phases, state) {
|
|
59459
59663
|
const findings = [];
|
|
59460
59664
|
for (const phase of phases) {
|
|
59461
|
-
if (shouldSkipPhaseForRectification(phase, state, phaseOutputs))
|
|
59665
|
+
if (shouldSkipPhaseForRectification({ phase, state, phaseOutputs }))
|
|
59462
59666
|
continue;
|
|
59463
59667
|
for (const f of extractPhaseFindings(phaseOutputs[phase.slot.op.name])) {
|
|
59464
59668
|
if (f.category === "flaky-test")
|
|
@@ -59528,6 +59732,9 @@ function collectRectificationPhases(state) {
|
|
|
59528
59732
|
state.adversarialReview
|
|
59529
59733
|
].filter((phase) => phase !== undefined);
|
|
59530
59734
|
}
|
|
59735
|
+
function isQuarantinedOnlyGateFailure(phase, rawFindings, blockingFindings) {
|
|
59736
|
+
return phase.kind === "full-suite-gate" && rawFindings.length > 0 && blockingFindings.length === 0;
|
|
59737
|
+
}
|
|
59531
59738
|
async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides) {
|
|
59532
59739
|
const rectification2 = state.rectification;
|
|
59533
59740
|
const baseValidationPhases = collectRectificationPhases(state);
|
|
@@ -59539,7 +59746,9 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
|
|
|
59539
59746
|
return {};
|
|
59540
59747
|
}
|
|
59541
59748
|
let initialFindings;
|
|
59749
|
+
let nbfPath = false;
|
|
59542
59750
|
if (overrides?.initialFindings) {
|
|
59751
|
+
nbfPath = true;
|
|
59543
59752
|
initialFindings = [...overrides.initialFindings];
|
|
59544
59753
|
} else {
|
|
59545
59754
|
const gateName = state.fullSuiteGate?.slot.op.name;
|
|
@@ -59589,11 +59798,24 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
|
|
|
59589
59798
|
let shortCircuited = false;
|
|
59590
59799
|
for (const phase of phases) {
|
|
59591
59800
|
await runPhase(ctx, phase.slot, phaseCosts, phaseOutputs);
|
|
59592
|
-
if (shouldSkipPhaseForRectification(phase, state, phaseOutputs))
|
|
59801
|
+
if (shouldSkipPhaseForRectification({ phase, state, phaseOutputs, nbfPath }))
|
|
59593
59802
|
continue;
|
|
59594
59803
|
const output = phaseOutputs[phase.slot.op.name];
|
|
59595
|
-
|
|
59596
|
-
if (
|
|
59804
|
+
const nbfFlakeTriage = overrides?.nbfFlakeTriage;
|
|
59805
|
+
if (nbfPath && phase.kind === "full-suite-gate" && nbfFlakeTriage) {
|
|
59806
|
+
await triageNbfGate({
|
|
59807
|
+
output,
|
|
59808
|
+
gateName: phase.slot.op.name,
|
|
59809
|
+
ctx,
|
|
59810
|
+
transaction: nbfFlakeTriage,
|
|
59811
|
+
triage: _storyOrchestratorDeps.triage
|
|
59812
|
+
});
|
|
59813
|
+
}
|
|
59814
|
+
const phaseFindings = extractPhaseFindings(output);
|
|
59815
|
+
const blockingFindings = nbfPath ? phaseFindings.filter((finding) => !isQuarantinedFlake(finding, nbfFlakeTriage?.memo ?? ctx.runtime.quarantineMemo)) : phaseFindings;
|
|
59816
|
+
findings.push(...blockingFindings);
|
|
59817
|
+
const quarantinedOnly = nbfPath && isQuarantinedOnlyGateFailure(phase, phaseFindings, blockingFindings);
|
|
59818
|
+
if (!phasePassed(phase.slot.op.name, output, ctx.storyId) && !quarantinedOnly) {
|
|
59597
59819
|
getSafeLogger()?.warn("story-orchestrator", "Short-circuiting revalidation on phase failure", {
|
|
59598
59820
|
storyId: ctx.storyId,
|
|
59599
59821
|
phase: phase.slot.op.name
|
|
@@ -59651,6 +59873,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
|
|
|
59651
59873
|
}
|
|
59652
59874
|
var init_rectification = __esm(() => {
|
|
59653
59875
|
init_logger2();
|
|
59876
|
+
init_nbf_flake_triage();
|
|
59654
59877
|
init_phase_eval();
|
|
59655
59878
|
init_phase_eval();
|
|
59656
59879
|
init_run_phase();
|
|
@@ -59667,13 +59890,13 @@ class ExecutionPlan {
|
|
|
59667
59890
|
this.state = state;
|
|
59668
59891
|
this.isThreeSession = isThreeSession;
|
|
59669
59892
|
}
|
|
59670
|
-
describeGateRegressionNow(phaseOutputs, gateName,
|
|
59893
|
+
describeGateRegressionNow(phaseOutputs, gateName, options) {
|
|
59671
59894
|
return describeGateRegression({
|
|
59672
59895
|
gateOutput: gateName === undefined ? undefined : phaseOutputs[gateName],
|
|
59673
|
-
baselineKeys,
|
|
59896
|
+
baselineKeys: options.baselineKeys,
|
|
59674
59897
|
gateName,
|
|
59675
59898
|
storyId: this.ctx.storyId,
|
|
59676
|
-
quarantineMemo: this.ctx.runtime.quarantineMemo
|
|
59899
|
+
quarantineMemo: options.quarantineMemo ?? this.ctx.runtime.quarantineMemo
|
|
59677
59900
|
});
|
|
59678
59901
|
}
|
|
59679
59902
|
phaseNames() {
|
|
@@ -59821,15 +60044,21 @@ class ExecutionPlan {
|
|
|
59821
60044
|
cfg: advCfg,
|
|
59822
60045
|
phaseOutputs,
|
|
59823
60046
|
phaseCosts,
|
|
59824
|
-
|
|
60047
|
+
quarantineMemo: this.ctx.runtime.quarantineMemo,
|
|
60048
|
+
gateBaselineKeys: preRectGateFailureKeys,
|
|
60049
|
+
runRectify: (maxAttempts, nbfFlakeTriage) => runRectification(this.ctx, this.state, phaseCosts, phaseOutputs, {
|
|
59825
60050
|
initialFindings: advisoryFindings,
|
|
60051
|
+
nbfFlakeTriage,
|
|
59826
60052
|
strategies: this.state.nonBlockingFixStrategies ?? [],
|
|
59827
60053
|
excludePhaseKinds: nonBlockingExcludePhases(),
|
|
59828
60054
|
extraRevalidationKinds: nonBlockingExtraPhases(advCfg),
|
|
59829
60055
|
maxAttempts,
|
|
59830
60056
|
postValidate: this.state.nonBlockingFixPostValidate
|
|
59831
60057
|
}),
|
|
59832
|
-
keptTreeRegressed: () => this.describeGateRegressionNow(phaseOutputs, gateName,
|
|
60058
|
+
keptTreeRegressed: (quarantineMemo) => this.describeGateRegressionNow(phaseOutputs, gateName, {
|
|
60059
|
+
baselineKeys: preRectGateFailureKeys,
|
|
60060
|
+
quarantineMemo
|
|
60061
|
+
})
|
|
59833
60062
|
}, {
|
|
59834
60063
|
measureSourceDiff: createMeasureSourceDiff({
|
|
59835
60064
|
config: this.ctx.runtime.configLoader.current(),
|
|
@@ -59840,7 +60069,9 @@ class ExecutionPlan {
|
|
|
59840
60069
|
}
|
|
59841
60070
|
const verifierName = this.state.verifier?.slot.op.name;
|
|
59842
60071
|
const verifierExplicitlyPassed = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
|
|
59843
|
-
const gateRegressedDuringRect = this.describeGateRegressionNow(phaseOutputs, gateName,
|
|
60072
|
+
const gateRegressedDuringRect = this.describeGateRegressionNow(phaseOutputs, gateName, {
|
|
60073
|
+
baselineKeys: preRectGateFailureKeys
|
|
60074
|
+
}).regressed;
|
|
59844
60075
|
const verifierPassedSsot = verifierExplicitlyPassed && !gateRegressedDuringRect;
|
|
59845
60076
|
if (verifierExplicitlyPassed && gateRegressedDuringRect) {
|
|
59846
60077
|
logger?.warn("story-orchestrator", "Gate regressed during rectification after verifier passed \u2014 verifier verdict is stale, failing story", { storyId: this.ctx.storyId, packageDir: this.ctx.packageDir });
|
|
@@ -59991,6 +60222,7 @@ var init_story_orchestrator = __esm(() => {
|
|
|
59991
60222
|
init_execution_plan();
|
|
59992
60223
|
init_phase_eval();
|
|
59993
60224
|
init_rectification();
|
|
60225
|
+
init_nbf_flake_triage();
|
|
59994
60226
|
init_run_phase();
|
|
59995
60227
|
init_types9();
|
|
59996
60228
|
});
|
|
@@ -64778,6 +65010,7 @@ function getFinishAutoFlowConfig(ctx) {
|
|
|
64778
65010
|
quality: autoFlow.reviewers?.quality ?? null
|
|
64779
65011
|
},
|
|
64780
65012
|
escalate: { telegram: autoFlow.escalate?.telegram !== false },
|
|
65013
|
+
notify: { mode: autoFlow.notify?.mode ?? defaults.notify.mode },
|
|
64781
65014
|
timeouts: {
|
|
64782
65015
|
acceptanceMs: autoFlow.timeouts?.acceptanceMs ?? defaults.timeouts.acceptanceMs,
|
|
64783
65016
|
gateMs: autoFlow.timeouts?.gateMs ?? defaults.timeouts.gateMs,
|
|
@@ -64802,39 +65035,74 @@ var init_config2 = __esm(() => {
|
|
|
64802
65035
|
defaultAgent: null,
|
|
64803
65036
|
reviewers: { spec: null, quality: null },
|
|
64804
65037
|
escalate: { telegram: true },
|
|
65038
|
+
notify: { mode: "escalation" },
|
|
64805
65039
|
timeouts: { acceptanceMs: 600000, gateMs: 900000, flowMs: 5400000, stepMs: null }
|
|
64806
65040
|
};
|
|
64807
65041
|
});
|
|
64808
65042
|
|
|
65043
|
+
// src/plugins/builtin/nax-finish/output.ts
|
|
65044
|
+
function logTail(stream) {
|
|
65045
|
+
if (stream.length <= LOG_TAIL_CHARS)
|
|
65046
|
+
return stream;
|
|
65047
|
+
return `[\u2026${stream.length - LOG_TAIL_CHARS} chars truncated\u2026]
|
|
65048
|
+
${stream.slice(-LOG_TAIL_CHARS)}`;
|
|
65049
|
+
}
|
|
65050
|
+
function stderrTail(stderr) {
|
|
65051
|
+
const trimmed = stderr.trim();
|
|
65052
|
+
if (!trimmed)
|
|
65053
|
+
return "";
|
|
65054
|
+
const tail = trimmed.length > STDERR_TAIL_CHARS ? `\u2026${trimmed.slice(-STDERR_TAIL_CHARS)}` : trimmed;
|
|
65055
|
+
return tail.replace(/\s+/g, " ");
|
|
65056
|
+
}
|
|
65057
|
+
var STDERR_TAIL_CHARS = 400, LOG_TAIL_CHARS = 20000;
|
|
65058
|
+
|
|
64809
65059
|
// src/plugins/builtin/nax-finish/telegram.ts
|
|
65060
|
+
function buildTerminalMessage(options) {
|
|
65061
|
+
const lines = [`nax-finish ${options.status} ${options.feature}`];
|
|
65062
|
+
if (options.detail)
|
|
65063
|
+
lines.push(options.detail);
|
|
65064
|
+
if (options.url)
|
|
65065
|
+
lines.push(options.url);
|
|
65066
|
+
const message = lines.join(`
|
|
65067
|
+
`);
|
|
65068
|
+
return message.length <= TELEGRAM_MAX_MESSAGE_CHARS ? message : `${message.slice(0, TELEGRAM_MAX_MESSAGE_CHARS - 1)}\u2026`;
|
|
65069
|
+
}
|
|
65070
|
+
function buildEscalationMessage(feature, reason, findings) {
|
|
65071
|
+
const head = `nax-finish escalated ${feature}: ${reason}`;
|
|
65072
|
+
if (findings.length === 0)
|
|
65073
|
+
return head;
|
|
65074
|
+
const footerReserve = `
|
|
65075
|
+
\u2026and ${findings.length} more`.length;
|
|
65076
|
+
const lines = [];
|
|
65077
|
+
let used = head.length;
|
|
65078
|
+
for (const f of findings) {
|
|
65079
|
+
const title = f.title.length > MAX_FINDING_TITLE_CHARS ? `${f.title.slice(0, MAX_FINDING_TITLE_CHARS)}\u2026` : f.title;
|
|
65080
|
+
const line = `
|
|
65081
|
+
- [${f.severity}] ${title}`;
|
|
65082
|
+
if (used + line.length + footerReserve > TELEGRAM_MAX_MESSAGE_CHARS)
|
|
65083
|
+
break;
|
|
65084
|
+
lines.push(line);
|
|
65085
|
+
used += line.length;
|
|
65086
|
+
}
|
|
65087
|
+
const omitted = findings.length - lines.length;
|
|
65088
|
+
return `${head}${lines.join("")}${omitted > 0 ? `
|
|
65089
|
+
\u2026and ${omitted} more` : ""}`;
|
|
65090
|
+
}
|
|
64810
65091
|
async function sendTelegramNotify(cfg, text) {
|
|
64811
65092
|
const res = await _telegramDeps.fetch(`https://api.telegram.org/bot${cfg.token}/sendMessage`, {
|
|
64812
65093
|
method: "POST",
|
|
64813
65094
|
headers: { "content-type": "application/json" },
|
|
64814
|
-
body: JSON.stringify({ chat_id: cfg.chatId, text
|
|
65095
|
+
body: JSON.stringify({ chat_id: cfg.chatId, text })
|
|
64815
65096
|
});
|
|
64816
65097
|
return res.ok;
|
|
64817
65098
|
}
|
|
64818
|
-
var _telegramDeps;
|
|
65099
|
+
var TELEGRAM_MAX_MESSAGE_CHARS = 4096, MAX_FINDING_TITLE_CHARS = 120, _telegramDeps;
|
|
64819
65100
|
var init_telegram2 = __esm(() => {
|
|
64820
65101
|
_telegramDeps = { fetch: (...a) => fetch(...a) };
|
|
64821
65102
|
});
|
|
64822
65103
|
|
|
64823
65104
|
// src/plugins/builtin/nax-finish/index.ts
|
|
64824
65105
|
import * as path21 from "path";
|
|
64825
|
-
function logTail(stream) {
|
|
64826
|
-
if (stream.length <= LOG_TAIL_CHARS)
|
|
64827
|
-
return stream;
|
|
64828
|
-
return `[\u2026${stream.length - LOG_TAIL_CHARS} chars truncated\u2026]
|
|
64829
|
-
${stream.slice(-LOG_TAIL_CHARS)}`;
|
|
64830
|
-
}
|
|
64831
|
-
function stderrTail(stderr) {
|
|
64832
|
-
const trimmed = stderr.trim();
|
|
64833
|
-
if (!trimmed)
|
|
64834
|
-
return "";
|
|
64835
|
-
const tail = trimmed.length > STDERR_TAIL_CHARS ? `\u2026${trimmed.slice(-STDERR_TAIL_CHARS)}` : trimmed;
|
|
64836
|
-
return tail.replace(/\s+/g, " ");
|
|
64837
|
-
}
|
|
64838
65106
|
async function defaultRun2(cmd, opts) {
|
|
64839
65107
|
const proc = Bun.spawn(cmd, { cwd: opts.cwd, env: opts.env, stdout: "pipe", stderr: "pipe" });
|
|
64840
65108
|
let timedOut = false;
|
|
@@ -64865,6 +65133,11 @@ async function defaultReadResult(workdir) {
|
|
|
64865
65133
|
return null;
|
|
64866
65134
|
return JSON.parse(await f.text());
|
|
64867
65135
|
}
|
|
65136
|
+
async function defaultClearResult(workdir) {
|
|
65137
|
+
const file3 = Bun.file(path21.join(workdir, ".nax", "nax-finish-result.json"));
|
|
65138
|
+
if (await file3.exists())
|
|
65139
|
+
await file3.delete();
|
|
65140
|
+
}
|
|
64868
65141
|
function isFeatureBranch(b) {
|
|
64869
65142
|
return b !== "main" && b !== "master" && b.length > 0;
|
|
64870
65143
|
}
|
|
@@ -64908,13 +65181,133 @@ function buildFlowEnv(cfg) {
|
|
|
64908
65181
|
env2.NAX_FINISH_QUALITY_PROFILE = cfg.reviewers.quality;
|
|
64909
65182
|
return env2;
|
|
64910
65183
|
}
|
|
64911
|
-
|
|
65184
|
+
function missingResultOutcome(ctx, res, escalateTelegram) {
|
|
65185
|
+
ctx.logger.warn("nax-finish flow produced no result file", {
|
|
65186
|
+
exitCode: res.exitCode,
|
|
65187
|
+
stdout: logTail(res.stdout),
|
|
65188
|
+
stderr: logTail(res.stderr)
|
|
65189
|
+
});
|
|
65190
|
+
const tail = stderrTail(res.stderr);
|
|
65191
|
+
return {
|
|
65192
|
+
actionResult: {
|
|
65193
|
+
success: false,
|
|
65194
|
+
message: `nax-finish flow exited ${res.exitCode} (no result file)${tail ? `: ${tail}` : ""}`
|
|
65195
|
+
},
|
|
65196
|
+
escalateTelegram
|
|
65197
|
+
};
|
|
65198
|
+
}
|
|
65199
|
+
async function executeFinishFlow(options) {
|
|
65200
|
+
const { ctx, cfg, escalateTelegram } = options;
|
|
65201
|
+
const flowPath = await resolveFlowPath(ctx.workdir, cfg.flowPath);
|
|
65202
|
+
if (!flowPath) {
|
|
65203
|
+
return {
|
|
65204
|
+
actionResult: {
|
|
65205
|
+
success: false,
|
|
65206
|
+
message: `nax-finish: flow module "${cfg.flowPath}" not found in the nax install or ${ctx.workdir}`
|
|
65207
|
+
},
|
|
65208
|
+
escalateTelegram
|
|
65209
|
+
};
|
|
65210
|
+
}
|
|
65211
|
+
await _naxFinishDeps.clearResult(ctx.workdir);
|
|
65212
|
+
const input = {
|
|
65213
|
+
feature: ctx.feature,
|
|
65214
|
+
workdir: ctx.workdir,
|
|
65215
|
+
branch: ctx.branch,
|
|
65216
|
+
prdPath: ctx.prdPath,
|
|
65217
|
+
escalateTelegram,
|
|
65218
|
+
timeouts: { acceptanceMs: cfg.timeouts.acceptanceMs, gateMs: cfg.timeouts.gateMs }
|
|
65219
|
+
};
|
|
65220
|
+
const cmd = buildFlowArgv(flowPath, JSON.stringify(input), cfg.defaultAgent, cfg.timeouts.stepMs);
|
|
65221
|
+
const res = await _naxFinishDeps.run(cmd, {
|
|
65222
|
+
cwd: ctx.workdir,
|
|
65223
|
+
env: buildFlowEnv(cfg),
|
|
65224
|
+
timeoutMs: cfg.timeouts.flowMs
|
|
65225
|
+
});
|
|
65226
|
+
const result = await _naxFinishDeps.readResult(ctx.workdir);
|
|
65227
|
+
if (!result)
|
|
65228
|
+
return missingResultOutcome(ctx, res, escalateTelegram);
|
|
65229
|
+
return {
|
|
65230
|
+
actionResult: { success: true, message: `nax-finish: ${result.status}`, url: result.url },
|
|
65231
|
+
result,
|
|
65232
|
+
escalateTelegram
|
|
65233
|
+
};
|
|
65234
|
+
}
|
|
65235
|
+
async function settleFinishFlow(options) {
|
|
65236
|
+
const escalateTelegram = options.cfg.notify.mode !== "off" && options.cfg.escalate.telegram && options.creds !== null;
|
|
65237
|
+
try {
|
|
65238
|
+
return await executeFinishFlow({ ...options, escalateTelegram });
|
|
65239
|
+
} catch (error48) {
|
|
65240
|
+
options.ctx.logger.warn("nax-finish execute failed", { error: errorMessage(error48) });
|
|
65241
|
+
return {
|
|
65242
|
+
actionResult: { success: false, message: `nax-finish failed: ${errorMessage(error48)}` },
|
|
65243
|
+
escalateTelegram
|
|
65244
|
+
};
|
|
65245
|
+
}
|
|
65246
|
+
}
|
|
65247
|
+
async function notifyBestEffort(ctx, creds, message) {
|
|
65248
|
+
if (!creds) {
|
|
65249
|
+
ctx.logger.warn("nax-finish terminal notification skipped: Telegram credentials are unavailable");
|
|
65250
|
+
return;
|
|
65251
|
+
}
|
|
65252
|
+
try {
|
|
65253
|
+
if (!await _naxFinishDeps.notify(creds, message)) {
|
|
65254
|
+
ctx.logger.warn("nax-finish terminal notification was rejected", { feature: ctx.feature });
|
|
65255
|
+
}
|
|
65256
|
+
} catch (error48) {
|
|
65257
|
+
ctx.logger.warn("nax-finish terminal notification failed", { feature: ctx.feature, error: errorMessage(error48) });
|
|
65258
|
+
}
|
|
65259
|
+
}
|
|
65260
|
+
async function finalizeEscalation(ctx, outcome, creds) {
|
|
65261
|
+
const result = outcome.result;
|
|
65262
|
+
if (!result)
|
|
65263
|
+
return outcome.actionResult;
|
|
65264
|
+
const problems = [];
|
|
65265
|
+
let delivered = !outcome.escalateTelegram && !result.deliveryError;
|
|
65266
|
+
if (outcome.escalateTelegram && creds) {
|
|
65267
|
+
try {
|
|
65268
|
+
delivered = await _naxFinishDeps.notify(creds, buildEscalationMessage(result.feature, result.escalationReason ?? "", result.findings ?? []));
|
|
65269
|
+
if (!delivered)
|
|
65270
|
+
problems.push("Telegram rejected the message");
|
|
65271
|
+
} catch (error48) {
|
|
65272
|
+
problems.push(`Telegram failed: ${errorMessage(error48)}`);
|
|
65273
|
+
}
|
|
65274
|
+
}
|
|
65275
|
+
if (delivered)
|
|
65276
|
+
return outcome.actionResult;
|
|
65277
|
+
if (result.deliveryError)
|
|
65278
|
+
problems.push(`the flow could not post it: ${result.deliveryError}`);
|
|
65279
|
+
if (problems.length === 0)
|
|
65280
|
+
problems.push("no escalation channel was reachable");
|
|
65281
|
+
ctx.logger.warn("nax-finish escalation was not delivered", {
|
|
65282
|
+
feature: result.feature,
|
|
65283
|
+
reasons: problems,
|
|
65284
|
+
escalationReason: result.escalationReason
|
|
65285
|
+
});
|
|
65286
|
+
return {
|
|
65287
|
+
success: false,
|
|
65288
|
+
message: `nax-finish: escalated but undelivered \u2014 ${problems.join("; ")}`,
|
|
65289
|
+
url: result.url
|
|
65290
|
+
};
|
|
65291
|
+
}
|
|
65292
|
+
async function finalizeFinishOutcome(options) {
|
|
65293
|
+
const { ctx, cfg, creds, outcome } = options;
|
|
65294
|
+
if (outcome.result?.status === "escalated")
|
|
65295
|
+
return finalizeEscalation(ctx, outcome, creds);
|
|
65296
|
+
if (cfg.notify.mode === "always") {
|
|
65297
|
+
const status = outcome.result?.status ?? "failed";
|
|
65298
|
+
const detail = outcome.result ? undefined : outcome.actionResult.message;
|
|
65299
|
+
await notifyBestEffort(ctx, creds, buildTerminalMessage({ feature: ctx.feature, status, detail, url: outcome.actionResult.url }));
|
|
65300
|
+
}
|
|
65301
|
+
return outcome.actionResult;
|
|
65302
|
+
}
|
|
65303
|
+
var PLUGIN_NAME4 = "nax-finish", PLUGIN_VERSION4 = "0.1.0", PACKAGE_ROOT_SEARCH_DEPTH = 6, _naxFinishDeps, naxFinishAction, naxFinishPlugin;
|
|
64912
65304
|
var init_nax_finish = __esm(() => {
|
|
64913
65305
|
init_config2();
|
|
64914
65306
|
init_telegram2();
|
|
64915
65307
|
_naxFinishDeps = {
|
|
64916
65308
|
run: defaultRun2,
|
|
64917
65309
|
readResult: defaultReadResult,
|
|
65310
|
+
clearResult: defaultClearResult,
|
|
64918
65311
|
exists: (p) => Bun.file(p).exists(),
|
|
64919
65312
|
moduleDir: import.meta.dir,
|
|
64920
65313
|
notify: sendTelegramNotify
|
|
@@ -64932,52 +65325,10 @@ var init_nax_finish = __esm(() => {
|
|
|
64932
65325
|
return isFeatureBranch(ctx.branch);
|
|
64933
65326
|
},
|
|
64934
65327
|
async execute(ctx) {
|
|
64935
|
-
|
|
64936
|
-
|
|
64937
|
-
|
|
64938
|
-
|
|
64939
|
-
return {
|
|
64940
|
-
success: false,
|
|
64941
|
-
message: `nax-finish: flow module "${cfg.flowPath}" not found in the nax install or ${ctx.workdir}`
|
|
64942
|
-
};
|
|
64943
|
-
}
|
|
64944
|
-
const creds = telegramCreds(ctx.config);
|
|
64945
|
-
const escalateTelegram = cfg.escalate.telegram && creds !== null;
|
|
64946
|
-
const input = {
|
|
64947
|
-
feature: ctx.feature,
|
|
64948
|
-
workdir: ctx.workdir,
|
|
64949
|
-
branch: ctx.branch,
|
|
64950
|
-
prdPath: ctx.prdPath,
|
|
64951
|
-
escalateTelegram,
|
|
64952
|
-
timeouts: { acceptanceMs: cfg.timeouts.acceptanceMs, gateMs: cfg.timeouts.gateMs }
|
|
64953
|
-
};
|
|
64954
|
-
const cmd = buildFlowArgv(flowPath, JSON.stringify(input), cfg.defaultAgent, cfg.timeouts.stepMs);
|
|
64955
|
-
const res = await _naxFinishDeps.run(cmd, {
|
|
64956
|
-
cwd: ctx.workdir,
|
|
64957
|
-
env: buildFlowEnv(cfg),
|
|
64958
|
-
timeoutMs: cfg.timeouts.flowMs
|
|
64959
|
-
});
|
|
64960
|
-
const result = await _naxFinishDeps.readResult(ctx.workdir);
|
|
64961
|
-
if (!result) {
|
|
64962
|
-
ctx.logger.warn("nax-finish flow produced no result file", {
|
|
64963
|
-
exitCode: res.exitCode,
|
|
64964
|
-
stdout: logTail(res.stdout),
|
|
64965
|
-
stderr: logTail(res.stderr)
|
|
64966
|
-
});
|
|
64967
|
-
const tail = stderrTail(res.stderr);
|
|
64968
|
-
return {
|
|
64969
|
-
success: false,
|
|
64970
|
-
message: `nax-finish flow exited ${res.exitCode} (no result file)${tail ? `: ${tail}` : ""}`
|
|
64971
|
-
};
|
|
64972
|
-
}
|
|
64973
|
-
if (result.status === "escalated" && escalateTelegram && creds) {
|
|
64974
|
-
await _naxFinishDeps.notify(creds, `nax-finish escalated *${result.feature}*: ${result.escalationReason ?? ""}`);
|
|
64975
|
-
}
|
|
64976
|
-
return { success: true, message: `nax-finish: ${result.status}`, url: result.url };
|
|
64977
|
-
} catch (err) {
|
|
64978
|
-
ctx.logger.warn("nax-finish execute failed", { error: String(err) });
|
|
64979
|
-
return { success: false, message: `nax-finish failed: ${String(err)}` };
|
|
64980
|
-
}
|
|
65328
|
+
const cfg = getFinishAutoFlowConfig(ctx);
|
|
65329
|
+
const creds = telegramCreds(ctx.config);
|
|
65330
|
+
const outcome = await settleFinishFlow({ ctx, cfg, creds });
|
|
65331
|
+
return finalizeFinishOutcome({ ctx, cfg, creds, outcome });
|
|
64981
65332
|
}
|
|
64982
65333
|
};
|
|
64983
65334
|
naxFinishPlugin = {
|
|
@@ -65114,6 +65465,7 @@ var init_batch_queue = __esm(() => {
|
|
|
65114
65465
|
});
|
|
65115
65466
|
|
|
65116
65467
|
// src/plugins/builtin/otel-reporter/otlp.ts
|
|
65468
|
+
import { hostname as hostname3 } from "os";
|
|
65117
65469
|
function attr(key, value) {
|
|
65118
65470
|
return typeof value === "number" ? { key, value: { doubleValue: value } } : { key, value: { stringValue: value } };
|
|
65119
65471
|
}
|
|
@@ -65133,8 +65485,25 @@ function buildHistogramPoint(values, bounds, attributes, timeUnixNano) {
|
|
|
65133
65485
|
function buildCounterPoint(count, attributes, timeUnixNano) {
|
|
65134
65486
|
return { attributes, timeUnixNano, asInt: String(count) };
|
|
65135
65487
|
}
|
|
65136
|
-
function buildResourceAttributes(
|
|
65137
|
-
|
|
65488
|
+
function buildResourceAttributes(input) {
|
|
65489
|
+
const attrs = [
|
|
65490
|
+
attr("service.name", input.serviceName),
|
|
65491
|
+
attr("nax.run_id", input.runId),
|
|
65492
|
+
attr("nax.version", NAX_VERSION),
|
|
65493
|
+
attr("process.pid", process.pid)
|
|
65494
|
+
];
|
|
65495
|
+
try {
|
|
65496
|
+
attrs.push(attr("host.name", hostname3()));
|
|
65497
|
+
} catch {}
|
|
65498
|
+
if (input.feature !== undefined)
|
|
65499
|
+
attrs.push(attr("nax.feature", input.feature));
|
|
65500
|
+
if (input.project !== undefined)
|
|
65501
|
+
attrs.push(attr("nax.project", input.project));
|
|
65502
|
+
if (input.git?.branch !== undefined)
|
|
65503
|
+
attrs.push(attr("nax.git.branch", input.git.branch));
|
|
65504
|
+
if (input.git?.sha !== undefined)
|
|
65505
|
+
attrs.push(attr("nax.git.sha", input.git.sha));
|
|
65506
|
+
return attrs;
|
|
65138
65507
|
}
|
|
65139
65508
|
function buildTracesPayload(p) {
|
|
65140
65509
|
const span = {
|
|
@@ -65160,7 +65529,18 @@ function buildTracesPayload(p) {
|
|
|
65160
65529
|
return {
|
|
65161
65530
|
resourceSpans: [
|
|
65162
65531
|
{
|
|
65163
|
-
resource: {
|
|
65532
|
+
resource: {
|
|
65533
|
+
attributes: buildResourceAttributes({
|
|
65534
|
+
serviceName: p.serviceName,
|
|
65535
|
+
runId: p.runId,
|
|
65536
|
+
feature: p.feature,
|
|
65537
|
+
project: p.project,
|
|
65538
|
+
git: {
|
|
65539
|
+
branch: p.gitBranch,
|
|
65540
|
+
sha: p.gitSha
|
|
65541
|
+
}
|
|
65542
|
+
})
|
|
65543
|
+
},
|
|
65164
65544
|
scopeSpans: [{ scope: { name: "nax" }, spans: [span, ...p.extraSpans ?? []] }]
|
|
65165
65545
|
}
|
|
65166
65546
|
]
|
|
@@ -65187,7 +65567,18 @@ function buildMetricsPayload(p) {
|
|
|
65187
65567
|
return {
|
|
65188
65568
|
resourceMetrics: [
|
|
65189
65569
|
{
|
|
65190
|
-
resource: {
|
|
65570
|
+
resource: {
|
|
65571
|
+
attributes: buildResourceAttributes({
|
|
65572
|
+
serviceName: p.serviceName,
|
|
65573
|
+
runId: p.runId,
|
|
65574
|
+
feature: p.feature,
|
|
65575
|
+
project: p.project,
|
|
65576
|
+
git: {
|
|
65577
|
+
branch: p.gitBranch,
|
|
65578
|
+
sha: p.gitSha
|
|
65579
|
+
}
|
|
65580
|
+
})
|
|
65581
|
+
},
|
|
65191
65582
|
scopeMetrics: [
|
|
65192
65583
|
{
|
|
65193
65584
|
scope: { name: "nax" },
|
|
@@ -65198,6 +65589,9 @@ function buildMetricsPayload(p) {
|
|
|
65198
65589
|
]
|
|
65199
65590
|
};
|
|
65200
65591
|
}
|
|
65592
|
+
var init_otlp = __esm(() => {
|
|
65593
|
+
init_version();
|
|
65594
|
+
});
|
|
65201
65595
|
|
|
65202
65596
|
// src/plugins/builtin/otel-reporter/heartbeat.ts
|
|
65203
65597
|
function startHeartbeat(opts) {
|
|
@@ -65250,7 +65644,12 @@ function buildHeartbeatMetricsPayload(p) {
|
|
|
65250
65644
|
return {
|
|
65251
65645
|
resourceMetrics: [
|
|
65252
65646
|
{
|
|
65253
|
-
resource: {
|
|
65647
|
+
resource: {
|
|
65648
|
+
attributes: buildResourceAttributes({
|
|
65649
|
+
serviceName: p.serviceName,
|
|
65650
|
+
runId: p.snapshot.attributes.runId
|
|
65651
|
+
})
|
|
65652
|
+
},
|
|
65254
65653
|
scopeMetrics: [
|
|
65255
65654
|
{
|
|
65256
65655
|
scope: { name: "nax" },
|
|
@@ -65268,6 +65667,7 @@ function buildHeartbeatMetricsPayload(p) {
|
|
|
65268
65667
|
var STAGE2 = "otel-reporter-heartbeat";
|
|
65269
65668
|
var init_heartbeat = __esm(() => {
|
|
65270
65669
|
init_logger2();
|
|
65670
|
+
init_otlp();
|
|
65271
65671
|
});
|
|
65272
65672
|
|
|
65273
65673
|
// src/plugins/builtin/otel-reporter/ids.ts
|
|
@@ -65281,6 +65681,78 @@ function randomHex(bytes) {
|
|
|
65281
65681
|
}
|
|
65282
65682
|
var newTraceId = () => randomHex(16), newSpanId = () => randomHex(8);
|
|
65283
65683
|
|
|
65684
|
+
// src/plugins/builtin/otel-reporter/logs.ts
|
|
65685
|
+
function entryTimestampMs(entry) {
|
|
65686
|
+
return new Date(entry.timestamp).getTime();
|
|
65687
|
+
}
|
|
65688
|
+
function toLogRecord(entry) {
|
|
65689
|
+
const timeUnixNano = msToUnixNano(entryTimestampMs(entry));
|
|
65690
|
+
const { number: severityNumber, text: severityText } = SEVERITY[entry.level];
|
|
65691
|
+
const attributes = [attr("nax.stage", entry.stage)];
|
|
65692
|
+
if (entry.storyId !== undefined)
|
|
65693
|
+
attributes.push(attr("nax.story_id", entry.storyId));
|
|
65694
|
+
if (entry.sessionRole !== undefined)
|
|
65695
|
+
attributes.push(attr("nax.session_role", entry.sessionRole));
|
|
65696
|
+
const data = entry.data ?? {};
|
|
65697
|
+
const nonScalars = {};
|
|
65698
|
+
for (const [key, value] of Object.entries(data)) {
|
|
65699
|
+
if (typeof value === "string") {
|
|
65700
|
+
attributes.push(attr(`nax.data.${key}`, truncate3(value)));
|
|
65701
|
+
} else if (typeof value === "number") {
|
|
65702
|
+
if (Number.isFinite(value)) {
|
|
65703
|
+
attributes.push(attr(`nax.data.${key}`, value));
|
|
65704
|
+
} else {
|
|
65705
|
+
nonScalars[key] = value;
|
|
65706
|
+
}
|
|
65707
|
+
} else if (typeof value === "boolean") {
|
|
65708
|
+
attributes.push(attr(`nax.data.${key}`, String(value)));
|
|
65709
|
+
} else {
|
|
65710
|
+
nonScalars[key] = value;
|
|
65711
|
+
}
|
|
65712
|
+
}
|
|
65713
|
+
if (Object.keys(nonScalars).length > 0) {
|
|
65714
|
+
attributes.push(attr("nax.data_json", truncate3(JSON.stringify(nonScalars))));
|
|
65715
|
+
}
|
|
65716
|
+
return {
|
|
65717
|
+
body: { stringValue: entry.message },
|
|
65718
|
+
timeUnixNano,
|
|
65719
|
+
severityNumber,
|
|
65720
|
+
severityText,
|
|
65721
|
+
attributes
|
|
65722
|
+
};
|
|
65723
|
+
}
|
|
65724
|
+
function buildLogsPayload(entries, resource) {
|
|
65725
|
+
const logRecords = entries.map(toLogRecord);
|
|
65726
|
+
return {
|
|
65727
|
+
resourceLogs: [
|
|
65728
|
+
{
|
|
65729
|
+
resource: {
|
|
65730
|
+
attributes: buildResourceAttributes(resource)
|
|
65731
|
+
},
|
|
65732
|
+
scopeLogs: [{ scope: { name: "nax" }, logRecords }]
|
|
65733
|
+
}
|
|
65734
|
+
]
|
|
65735
|
+
};
|
|
65736
|
+
}
|
|
65737
|
+
function truncate3(value) {
|
|
65738
|
+
if (value.length <= DATA_JSON_MAX)
|
|
65739
|
+
return value;
|
|
65740
|
+
const marker = TRUNCATION_MARKER;
|
|
65741
|
+
const keep = DATA_JSON_MAX - marker.length;
|
|
65742
|
+
return `${value.slice(0, keep)}${marker}`;
|
|
65743
|
+
}
|
|
65744
|
+
var SEVERITY, DATA_JSON_MAX = 2048, TRUNCATION_MARKER = "...[truncated]";
|
|
65745
|
+
var init_logs = __esm(() => {
|
|
65746
|
+
init_otlp();
|
|
65747
|
+
SEVERITY = {
|
|
65748
|
+
silent: { number: 0, text: "SILENT" },
|
|
65749
|
+
error: { number: 17, text: "ERROR" },
|
|
65750
|
+
warn: { number: 13, text: "WARN" },
|
|
65751
|
+
info: { number: 9, text: "INFO" },
|
|
65752
|
+
debug: { number: 5, text: "DEBUG" }
|
|
65753
|
+
};
|
|
65754
|
+
});
|
|
65755
|
+
|
|
65284
65756
|
// src/plugins/builtin/otel-reporter/span-tree.ts
|
|
65285
65757
|
function createSpanTree(traceId, runSpanId) {
|
|
65286
65758
|
const storySpanIds = new Map;
|
|
@@ -65364,7 +65836,8 @@ function createPhaseMetricsAggregator() {
|
|
|
65364
65836
|
function recordEscalation(toTier, count) {
|
|
65365
65837
|
bumpCounter(escalations, [attr("to_tier", toTier)], count);
|
|
65366
65838
|
}
|
|
65367
|
-
function buildMetricsPayload2(
|
|
65839
|
+
function buildMetricsPayload2(input) {
|
|
65840
|
+
const { serviceName, runId, timeUnixNano, feature, project, gitBranch, gitSha } = input;
|
|
65368
65841
|
const groups = [...phaseGroups.values()];
|
|
65369
65842
|
const counterMetric = (name, source) => ({
|
|
65370
65843
|
name,
|
|
@@ -65400,7 +65873,15 @@ function createPhaseMetricsAggregator() {
|
|
|
65400
65873
|
return {
|
|
65401
65874
|
resourceMetrics: [
|
|
65402
65875
|
{
|
|
65403
|
-
resource: {
|
|
65876
|
+
resource: {
|
|
65877
|
+
attributes: buildResourceAttributes({
|
|
65878
|
+
serviceName,
|
|
65879
|
+
runId,
|
|
65880
|
+
feature,
|
|
65881
|
+
project,
|
|
65882
|
+
git: { branch: gitBranch, sha: gitSha }
|
|
65883
|
+
})
|
|
65884
|
+
},
|
|
65404
65885
|
scopeMetrics: [{ scope: { name: "nax" }, metrics }]
|
|
65405
65886
|
}
|
|
65406
65887
|
]
|
|
@@ -65410,6 +65891,7 @@ function createPhaseMetricsAggregator() {
|
|
|
65410
65891
|
}
|
|
65411
65892
|
var PHASE_DURATION_BOUNDS, PHASE_COST_BOUNDS;
|
|
65412
65893
|
var init_span_tree = __esm(() => {
|
|
65894
|
+
init_otlp();
|
|
65413
65895
|
PHASE_DURATION_BOUNDS = [100, 500, 1000, 5000, 15000, 60000, 300000, 900000];
|
|
65414
65896
|
PHASE_COST_BOUNDS = [0.001, 0.01, 0.05, 0.1, 0.5, 1, 5];
|
|
65415
65897
|
});
|
|
@@ -65484,11 +65966,11 @@ function reviewSpanEvents(details, timeUnixNano, verbose) {
|
|
|
65484
65966
|
return { timeUnixNano, name: "review.finding", attributes };
|
|
65485
65967
|
});
|
|
65486
65968
|
}
|
|
65487
|
-
function createOtelReporterPlugin(cfg, deps) {
|
|
65969
|
+
function createOtelReporterPlugin(cfg, deps, workdir) {
|
|
65488
65970
|
const states = new Map;
|
|
65489
65971
|
const base = cfg.endpoint?.replace(/\/$/, "");
|
|
65490
65972
|
let tornDown = false;
|
|
65491
|
-
const
|
|
65973
|
+
const makeSendSpanBatch = (resourceAttrs) => async (batch) => {
|
|
65492
65974
|
if (!base || batch.length === 0)
|
|
65493
65975
|
return true;
|
|
65494
65976
|
const { resolved, missing } = interpolateHeaders(cfg.headers);
|
|
@@ -65499,21 +65981,55 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
65499
65981
|
const payload = {
|
|
65500
65982
|
resourceSpans: [
|
|
65501
65983
|
{
|
|
65502
|
-
resource: { attributes:
|
|
65984
|
+
resource: { attributes: resourceAttrs },
|
|
65503
65985
|
scopeSpans: [{ scope: { name: "nax" }, spans: batch }]
|
|
65504
65986
|
}
|
|
65505
65987
|
]
|
|
65506
65988
|
};
|
|
65507
|
-
return postJson(`${base}/v1/traces`, payload, {
|
|
65989
|
+
return postJson(`${base}/v1/traces`, payload, {
|
|
65990
|
+
headers: resolved,
|
|
65991
|
+
timeoutMs: cfg.timeoutMs,
|
|
65992
|
+
stage: STAGE3,
|
|
65993
|
+
deps
|
|
65994
|
+
});
|
|
65508
65995
|
};
|
|
65509
|
-
const makeSpanQueue = () => createBatchQueue({
|
|
65996
|
+
const makeSpanQueue = (resourceAttrs) => createBatchQueue({
|
|
65510
65997
|
maxBatchSize: cfg.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE,
|
|
65511
65998
|
flushIntervalMs: cfg.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
65512
65999
|
maxQueueSize: cfg.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
|
|
65513
|
-
send:
|
|
66000
|
+
send: makeSendSpanBatch(resourceAttrs)
|
|
66001
|
+
});
|
|
66002
|
+
const makeSendLogsBatch = (resource) => async (batch) => {
|
|
66003
|
+
if (!base || batch.length === 0)
|
|
66004
|
+
return true;
|
|
66005
|
+
const { resolved, missing } = interpolateHeaders(cfg.headers);
|
|
66006
|
+
if (missing.length > 0) {
|
|
66007
|
+
getSafeLogger()?.warn(STAGE3, "Skipping OTLP export \u2014 unresolved env vars", { missing });
|
|
66008
|
+
return true;
|
|
66009
|
+
}
|
|
66010
|
+
const payload = buildLogsPayload(batch, {
|
|
66011
|
+
serviceName: resource.serviceName,
|
|
66012
|
+
runId: resource.runId,
|
|
66013
|
+
feature: resource.feature,
|
|
66014
|
+
project: resource.project,
|
|
66015
|
+
git: { branch: resource.gitBranch, sha: resource.gitSha }
|
|
66016
|
+
});
|
|
66017
|
+
return postJson(`${base}/v1/logs`, payload, {
|
|
66018
|
+
headers: resolved,
|
|
66019
|
+
timeoutMs: cfg.timeoutMs,
|
|
66020
|
+
stage: STAGE3,
|
|
66021
|
+
deps
|
|
66022
|
+
});
|
|
66023
|
+
};
|
|
66024
|
+
const makeLogsQueue = (resource) => createBatchQueue({
|
|
66025
|
+
maxBatchSize: cfg.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE,
|
|
66026
|
+
flushIntervalMs: cfg.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
66027
|
+
maxQueueSize: cfg.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
|
|
66028
|
+
send: makeSendLogsBatch(resource)
|
|
65514
66029
|
});
|
|
65515
66030
|
const buildOrphanState = (startMs) => {
|
|
65516
66031
|
const identity = rootSpanIdentity();
|
|
66032
|
+
const orphanAttrs = buildResourceAttributes({ serviceName: cfg.serviceName, runId: "orphan" });
|
|
65517
66033
|
return {
|
|
65518
66034
|
...identity,
|
|
65519
66035
|
startMs,
|
|
@@ -65521,7 +66037,7 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
65521
66037
|
project: "",
|
|
65522
66038
|
events: [],
|
|
65523
66039
|
spanTree: createSpanTree(identity.traceId, identity.spanId),
|
|
65524
|
-
spanQueue: makeSpanQueue(),
|
|
66040
|
+
spanQueue: makeSpanQueue(orphanAttrs),
|
|
65525
66041
|
metrics: createPhaseMetricsAggregator(),
|
|
65526
66042
|
storyBounds: new Map,
|
|
65527
66043
|
costUsd: 0,
|
|
@@ -65561,6 +66077,9 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
65561
66077
|
startUnixNano,
|
|
65562
66078
|
endUnixNano,
|
|
65563
66079
|
feature: st.feature,
|
|
66080
|
+
project: st.project,
|
|
66081
|
+
gitBranch: st.gitBranch,
|
|
66082
|
+
gitSha: st.gitSha,
|
|
65564
66083
|
runId: e.runId,
|
|
65565
66084
|
storySummary: e.storySummary,
|
|
65566
66085
|
totalCost: e.totalCost,
|
|
@@ -65570,11 +66089,23 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
65570
66089
|
serviceName: cfg.serviceName,
|
|
65571
66090
|
runId: e.runId,
|
|
65572
66091
|
timeUnixNano: endUnixNano,
|
|
66092
|
+
feature: st.feature,
|
|
66093
|
+
project: st.project,
|
|
66094
|
+
gitBranch: st.gitBranch,
|
|
66095
|
+
gitSha: st.gitSha,
|
|
65573
66096
|
storySummary: e.storySummary,
|
|
65574
66097
|
totalCost: e.totalCost,
|
|
65575
66098
|
totalDurationMs: e.totalDurationMs
|
|
65576
66099
|
});
|
|
65577
|
-
const aggMetrics = st.metrics.buildMetricsPayload(
|
|
66100
|
+
const aggMetrics = st.metrics.buildMetricsPayload({
|
|
66101
|
+
serviceName: cfg.serviceName,
|
|
66102
|
+
runId: e.runId,
|
|
66103
|
+
timeUnixNano: endUnixNano,
|
|
66104
|
+
feature: st.feature,
|
|
66105
|
+
project: st.project,
|
|
66106
|
+
gitBranch: st.gitBranch,
|
|
66107
|
+
gitSha: st.gitSha
|
|
66108
|
+
});
|
|
65578
66109
|
metrics.resourceMetrics[0].scopeMetrics[0].metrics.push(...aggMetrics.resourceMetrics[0].scopeMetrics[0].metrics);
|
|
65579
66110
|
const opts = { headers: resolved, timeoutMs: cfg.timeoutMs, stage: STAGE3, deps };
|
|
65580
66111
|
await postJson(`${base}/v1/traces`, traces, opts);
|
|
@@ -65585,14 +66116,41 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
65585
66116
|
async onRunStart(event) {
|
|
65586
66117
|
const identity = rootSpanIdentity();
|
|
65587
66118
|
const runId = event.runId;
|
|
66119
|
+
let gitBranch;
|
|
66120
|
+
let gitSha;
|
|
66121
|
+
if (base && workdir) {
|
|
66122
|
+
const [branchResult, shaResult] = await Promise.all([
|
|
66123
|
+
gitWithTimeout(["rev-parse", "--abbrev-ref", "HEAD"], workdir).catch(() => null),
|
|
66124
|
+
gitWithTimeout(["rev-parse", "HEAD"], workdir).catch(() => null)
|
|
66125
|
+
]);
|
|
66126
|
+
if (branchResult?.exitCode === 0) {
|
|
66127
|
+
const branch = branchResult.stdout.trim();
|
|
66128
|
+
if (branch && branch !== "HEAD")
|
|
66129
|
+
gitBranch = branch;
|
|
66130
|
+
}
|
|
66131
|
+
if (shaResult?.exitCode === 0) {
|
|
66132
|
+
const sha = shaResult.stdout.trim();
|
|
66133
|
+
if (sha)
|
|
66134
|
+
gitSha = sha;
|
|
66135
|
+
}
|
|
66136
|
+
}
|
|
66137
|
+
const resourceAttrs = buildResourceAttributes({
|
|
66138
|
+
serviceName: cfg.serviceName,
|
|
66139
|
+
runId,
|
|
66140
|
+
feature: event.feature,
|
|
66141
|
+
project: event.project,
|
|
66142
|
+
git: { branch: gitBranch, sha: gitSha }
|
|
66143
|
+
});
|
|
65588
66144
|
const state = {
|
|
65589
66145
|
...identity,
|
|
65590
66146
|
startMs: Date.parse(event.startTime),
|
|
65591
66147
|
feature: event.feature,
|
|
65592
66148
|
project: event.project ?? "",
|
|
66149
|
+
gitBranch,
|
|
66150
|
+
gitSha,
|
|
65593
66151
|
events: [],
|
|
65594
66152
|
spanTree: createSpanTree(identity.traceId, identity.spanId),
|
|
65595
|
-
spanQueue: makeSpanQueue(),
|
|
66153
|
+
spanQueue: makeSpanQueue(resourceAttrs),
|
|
65596
66154
|
metrics: createPhaseMetricsAggregator(),
|
|
65597
66155
|
storyBounds: new Map,
|
|
65598
66156
|
costUsd: 0,
|
|
@@ -65603,6 +66161,27 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
65603
66161
|
})
|
|
65604
66162
|
};
|
|
65605
66163
|
states.set(runId, state);
|
|
66164
|
+
if (cfg.logs?.enabled) {
|
|
66165
|
+
const logsQueue = makeLogsQueue({
|
|
66166
|
+
serviceName: cfg.serviceName,
|
|
66167
|
+
runId,
|
|
66168
|
+
feature: event.feature,
|
|
66169
|
+
project: event.project ?? "",
|
|
66170
|
+
gitBranch,
|
|
66171
|
+
gitSha
|
|
66172
|
+
});
|
|
66173
|
+
const floorKey = cfg.logs.level;
|
|
66174
|
+
const sank = (entry) => {
|
|
66175
|
+
if (REENTRY_STAGES.has(entry.stage))
|
|
66176
|
+
return;
|
|
66177
|
+
if (LOG_PRIORITY[entry.level] > LOG_PRIORITY[floorKey])
|
|
66178
|
+
return;
|
|
66179
|
+
logsQueue.enqueue(entry);
|
|
66180
|
+
};
|
|
66181
|
+
const addSinkFn = deps?.addSink ?? addSink;
|
|
66182
|
+
state.logsQueue = logsQueue;
|
|
66183
|
+
state.logUnsubscribe = addSinkFn(sank);
|
|
66184
|
+
}
|
|
65606
66185
|
},
|
|
65607
66186
|
async onStoryComplete(event) {
|
|
65608
66187
|
const st = states.get(event.runId);
|
|
@@ -65674,6 +66253,11 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
65674
66253
|
states.delete(event.runId);
|
|
65675
66254
|
await st.spanQueue.flushNow();
|
|
65676
66255
|
st.spanQueue.teardown();
|
|
66256
|
+
if (st.logsQueue) {
|
|
66257
|
+
await st.logsQueue.flushNow();
|
|
66258
|
+
st.logsQueue.teardown();
|
|
66259
|
+
st.logUnsubscribe?.();
|
|
66260
|
+
}
|
|
65677
66261
|
await flush(st, startMs + event.totalDurationMs, event);
|
|
65678
66262
|
}
|
|
65679
66263
|
};
|
|
@@ -65691,6 +66275,11 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
65691
66275
|
st.heartbeat.stop();
|
|
65692
66276
|
await st.spanQueue.flushNow();
|
|
65693
66277
|
st.spanQueue.teardown();
|
|
66278
|
+
if (st.logsQueue) {
|
|
66279
|
+
await st.logsQueue.flushNow();
|
|
66280
|
+
st.logsQueue.teardown();
|
|
66281
|
+
st.logUnsubscribe?.();
|
|
66282
|
+
}
|
|
65694
66283
|
const endMs = Date.now();
|
|
65695
66284
|
await flush(st, endMs, {
|
|
65696
66285
|
runId,
|
|
@@ -65703,14 +66292,25 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
65703
66292
|
extensions: { reporter }
|
|
65704
66293
|
};
|
|
65705
66294
|
}
|
|
65706
|
-
var STAGE3 = "otel-reporter", DEFAULT_MAX_BATCH_SIZE = 64, DEFAULT_FLUSH_INTERVAL_MS = 5000, DEFAULT_MAX_QUEUE_SIZE = 2048;
|
|
66295
|
+
var STAGE3 = "otel-reporter", REENTRY_STAGE = "otel-batch-queue", REENTRY_STAGES, DEFAULT_MAX_BATCH_SIZE = 64, DEFAULT_FLUSH_INTERVAL_MS = 5000, DEFAULT_MAX_QUEUE_SIZE = 2048, LOG_PRIORITY;
|
|
65707
66296
|
var init_otel_reporter = __esm(() => {
|
|
65708
66297
|
init_logger2();
|
|
66298
|
+
init_git();
|
|
65709
66299
|
init_reporter_shared();
|
|
65710
66300
|
init_batch_queue();
|
|
65711
66301
|
init_heartbeat();
|
|
66302
|
+
init_logs();
|
|
66303
|
+
init_otlp();
|
|
65712
66304
|
init_span_tree();
|
|
65713
66305
|
init_traceparent();
|
|
66306
|
+
REENTRY_STAGES = new Set([STAGE3, REENTRY_STAGE]);
|
|
66307
|
+
LOG_PRIORITY = {
|
|
66308
|
+
silent: -1,
|
|
66309
|
+
error: 0,
|
|
66310
|
+
warn: 1,
|
|
66311
|
+
info: 2,
|
|
66312
|
+
debug: 3
|
|
66313
|
+
};
|
|
65714
66314
|
});
|
|
65715
66315
|
|
|
65716
66316
|
// src/plugins/builtin/webhook-reporter/index.ts
|
|
@@ -65775,7 +66375,7 @@ class PluginRegistry {
|
|
|
65775
66375
|
sources;
|
|
65776
66376
|
builtinPostRunActions;
|
|
65777
66377
|
constructor(loadedPlugins, builtinPostRunActions = []) {
|
|
65778
|
-
this.builtinPostRunActions = builtinPostRunActions;
|
|
66378
|
+
this.builtinPostRunActions = builtinPostRunActions.map((registration) => ("action" in registration) ? registration : { pluginName: registration.name, action: registration });
|
|
65779
66379
|
if (loadedPlugins.length > 0 && "plugin" in loadedPlugins[0]) {
|
|
65780
66380
|
const typed = loadedPlugins;
|
|
65781
66381
|
this.plugins = typed.map((lp) => lp.plugin);
|
|
@@ -65814,7 +66414,13 @@ class PluginRegistry {
|
|
|
65814
66414
|
return this.plugins.filter((p) => p.provides.includes("reporter")).map((p) => p.extensions.reporter).filter((reporter) => reporter !== undefined);
|
|
65815
66415
|
}
|
|
65816
66416
|
getPostRunActions() {
|
|
65817
|
-
|
|
66417
|
+
return this.getPostRunActionRegistrations().map(({ action }) => action);
|
|
66418
|
+
}
|
|
66419
|
+
getPostRunActionRegistrations() {
|
|
66420
|
+
const pluginActions = this.plugins.flatMap((plugin) => {
|
|
66421
|
+
const action = plugin.extensions.postRunAction;
|
|
66422
|
+
return plugin.provides.includes("post-run-action") && action ? [{ pluginName: plugin.name, action }] : [];
|
|
66423
|
+
});
|
|
65818
66424
|
return [...pluginActions, ...this.builtinPostRunActions];
|
|
65819
66425
|
}
|
|
65820
66426
|
async teardownAll() {
|
|
@@ -66129,7 +66735,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
|
|
|
66129
66735
|
}
|
|
66130
66736
|
const autoPrAction2 = autoPrPlugin.extensions.postRunAction;
|
|
66131
66737
|
if (autoPrAction2) {
|
|
66132
|
-
builtinPostRunActions.push(autoPrAction2);
|
|
66738
|
+
builtinPostRunActions.push({ pluginName: autoPrPlugin.name, action: autoPrAction2 });
|
|
66133
66739
|
}
|
|
66134
66740
|
} else {
|
|
66135
66741
|
logger?.info("plugins", `Skipping disabled plugin: '${autoPrPlugin.name}' (built-in)`);
|
|
@@ -66141,7 +66747,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
|
|
|
66141
66747
|
}
|
|
66142
66748
|
const action = naxFinishPlugin.extensions.postRunAction;
|
|
66143
66749
|
if (action)
|
|
66144
|
-
builtinPostRunActions.push(action);
|
|
66750
|
+
builtinPostRunActions.push({ pluginName: naxFinishPlugin.name, action });
|
|
66145
66751
|
} else {
|
|
66146
66752
|
logger?.info("plugins", `Skipping disabled plugin: '${naxFinishPlugin.name}' (built-in)`);
|
|
66147
66753
|
}
|
|
@@ -66152,7 +66758,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
|
|
|
66152
66758
|
}
|
|
66153
66759
|
const autoRouteAction2 = autoRoutePlugin.extensions.postRunAction;
|
|
66154
66760
|
if (autoRouteAction2) {
|
|
66155
|
-
builtinPostRunActions.push(autoRouteAction2);
|
|
66761
|
+
builtinPostRunActions.push({ pluginName: autoRoutePlugin.name, action: autoRouteAction2 });
|
|
66156
66762
|
}
|
|
66157
66763
|
} else {
|
|
66158
66764
|
logger?.info("plugins", `Skipping disabled plugin: '${autoRoutePlugin.name}' (built-in)`);
|
|
@@ -66166,7 +66772,7 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
|
|
|
66166
66772
|
{
|
|
66167
66773
|
name: "otel-reporter",
|
|
66168
66774
|
enabled: reporters.otel.enabled,
|
|
66169
|
-
make: () => createOtelReporterPlugin(reporters.otel)
|
|
66775
|
+
make: () => createOtelReporterPlugin(reporters.otel, undefined, effectiveProjectRoot)
|
|
66170
66776
|
}
|
|
66171
66777
|
] : [];
|
|
66172
66778
|
for (const { name, enabled: reporterEnabled, make } of reporterFactories) {
|
|
@@ -66546,6 +67152,25 @@ var init_checkpoint = __esm(() => {
|
|
|
66546
67152
|
init_resume_cli();
|
|
66547
67153
|
});
|
|
66548
67154
|
|
|
67155
|
+
// src/hooks/types.ts
|
|
67156
|
+
var HOOK_EVENTS;
|
|
67157
|
+
var init_types10 = __esm(() => {
|
|
67158
|
+
HOOK_EVENTS = [
|
|
67159
|
+
"on-start",
|
|
67160
|
+
"on-story-start",
|
|
67161
|
+
"on-story-complete",
|
|
67162
|
+
"on-story-fail",
|
|
67163
|
+
"on-pause",
|
|
67164
|
+
"on-resume",
|
|
67165
|
+
"on-session-end",
|
|
67166
|
+
"on-all-stories-complete",
|
|
67167
|
+
"on-complete",
|
|
67168
|
+
"on-error",
|
|
67169
|
+
"on-final-regression-fail",
|
|
67170
|
+
"on-post-run-action"
|
|
67171
|
+
];
|
|
67172
|
+
});
|
|
67173
|
+
|
|
66549
67174
|
// src/hooks/runner.ts
|
|
66550
67175
|
import { join as join84 } from "path";
|
|
66551
67176
|
function createDrainDeadline2(deadlineMs) {
|
|
@@ -66607,6 +67232,12 @@ function buildEnv(ctx) {
|
|
|
66607
67232
|
env2.NAX_AGENT = escapeEnvValue(ctx.agent);
|
|
66608
67233
|
if (ctx.iteration !== undefined)
|
|
66609
67234
|
env2.NAX_ITERATION = String(ctx.iteration);
|
|
67235
|
+
if (ctx.pluginName)
|
|
67236
|
+
env2.NAX_PLUGIN_NAME = escapeEnvValue(ctx.pluginName);
|
|
67237
|
+
if (ctx.actionName)
|
|
67238
|
+
env2.NAX_ACTION_NAME = escapeEnvValue(ctx.actionName);
|
|
67239
|
+
if (ctx.url)
|
|
67240
|
+
env2.NAX_RESULT_URL = escapeEnvValue(ctx.url);
|
|
66610
67241
|
return env2;
|
|
66611
67242
|
}
|
|
66612
67243
|
function hasShellOperators(command) {
|
|
@@ -66737,9 +67368,11 @@ var init_runner5 = __esm(() => {
|
|
|
66737
67368
|
var exports_hooks = {};
|
|
66738
67369
|
__export(exports_hooks, {
|
|
66739
67370
|
loadHooksConfig: () => loadHooksConfig,
|
|
66740
|
-
fireHook: () => fireHook
|
|
67371
|
+
fireHook: () => fireHook,
|
|
67372
|
+
HOOK_EVENTS: () => HOOK_EVENTS
|
|
66741
67373
|
});
|
|
66742
67374
|
var init_hooks = __esm(() => {
|
|
67375
|
+
init_types10();
|
|
66743
67376
|
init_runner5();
|
|
66744
67377
|
});
|
|
66745
67378
|
|
|
@@ -69387,7 +70020,7 @@ function buildPreviewRouting(story, config2) {
|
|
|
69387
70020
|
|
|
69388
70021
|
// src/worktree/types.ts
|
|
69389
70022
|
var WorktreeDependencyPreparationError;
|
|
69390
|
-
var
|
|
70023
|
+
var init_types11 = __esm(() => {
|
|
69391
70024
|
WorktreeDependencyPreparationError = class WorktreeDependencyPreparationError extends Error {
|
|
69392
70025
|
mode;
|
|
69393
70026
|
failureCategory = "dependency-prep";
|
|
@@ -69457,7 +70090,7 @@ var PHASE_ONE_INHERIT_UNSUPPORTED_FILES, _worktreeDependencyDeps;
|
|
|
69457
70090
|
var init_dependencies = __esm(() => {
|
|
69458
70091
|
init_bun_deps();
|
|
69459
70092
|
init_command_argv();
|
|
69460
|
-
|
|
70093
|
+
init_types11();
|
|
69461
70094
|
PHASE_ONE_INHERIT_UNSUPPORTED_FILES = [
|
|
69462
70095
|
"package.json",
|
|
69463
70096
|
"bun.lock",
|
|
@@ -70674,6 +71307,22 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
70674
71307
|
// src/execution/iteration-runner.ts
|
|
70675
71308
|
import { existsSync as existsSync35 } from "fs";
|
|
70676
71309
|
import { join as join91 } from "path";
|
|
71310
|
+
function releaseHeavyPipelineContext(ctx) {
|
|
71311
|
+
ctx.agentResult = undefined;
|
|
71312
|
+
ctx.prompt = undefined;
|
|
71313
|
+
ctx.contextMarkdown = undefined;
|
|
71314
|
+
ctx.featureContextMarkdown = undefined;
|
|
71315
|
+
ctx.builtContext = undefined;
|
|
71316
|
+
ctx.contextBundle = undefined;
|
|
71317
|
+
ctx.constitution = undefined;
|
|
71318
|
+
ctx.acceptanceFailures = undefined;
|
|
71319
|
+
ctx.autofixPriorIterations = undefined;
|
|
71320
|
+
ctx.priorSemanticIterations = undefined;
|
|
71321
|
+
ctx.priorAdversarialIterations = undefined;
|
|
71322
|
+
ctx.reviewFindings = undefined;
|
|
71323
|
+
ctx.selfVerification = undefined;
|
|
71324
|
+
ctx.tddIsolations = undefined;
|
|
71325
|
+
}
|
|
70677
71326
|
async function runIteration(ctx, prd, selection, iterations, totalCost2, allStoryMetrics) {
|
|
70678
71327
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
70679
71328
|
if (ctx.dryRun) {
|
|
@@ -70854,11 +71503,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost2, allStor
|
|
|
70854
71503
|
subStoryCount: pipelineResult.subStoryCount
|
|
70855
71504
|
};
|
|
70856
71505
|
}
|
|
70857
|
-
pipelineContext
|
|
70858
|
-
pipelineContext.prompt = undefined;
|
|
70859
|
-
pipelineContext.contextMarkdown = undefined;
|
|
70860
|
-
pipelineContext.builtContext = undefined;
|
|
70861
|
-
pipelineContext.constitution = undefined;
|
|
71506
|
+
releaseHeavyPipelineContext(pipelineContext);
|
|
70862
71507
|
return iterResult;
|
|
70863
71508
|
}
|
|
70864
71509
|
var _iterationRunnerDeps;
|
|
@@ -73104,8 +73749,63 @@ async function runSetupPhase(options) {
|
|
|
73104
73749
|
var exports_run_cleanup = {};
|
|
73105
73750
|
__export(exports_run_cleanup, {
|
|
73106
73751
|
cleanupRun: () => cleanupRun,
|
|
73107
|
-
buildPostRunContext: () => buildPostRunContext
|
|
73752
|
+
buildPostRunContext: () => buildPostRunContext,
|
|
73753
|
+
_runCleanupDeps: () => _runCleanupDeps
|
|
73108
73754
|
});
|
|
73755
|
+
async function settlePostRunAction(action, ctx) {
|
|
73756
|
+
try {
|
|
73757
|
+
if (!await action.shouldRun(ctx))
|
|
73758
|
+
return { status: "skipped", reason: "shouldRun=false" };
|
|
73759
|
+
return outcomeFromResult(await action.execute(ctx));
|
|
73760
|
+
} catch (error48) {
|
|
73761
|
+
return { status: "error", reason: errorMessage(error48) };
|
|
73762
|
+
}
|
|
73763
|
+
}
|
|
73764
|
+
function outcomeFromResult(result) {
|
|
73765
|
+
if (result.skipped)
|
|
73766
|
+
return { status: "skipped", reason: result.reason ?? result.message };
|
|
73767
|
+
if (!result.success)
|
|
73768
|
+
return { status: "failed", message: result.message, url: result.url };
|
|
73769
|
+
return { status: "succeeded", message: result.message, url: result.url };
|
|
73770
|
+
}
|
|
73771
|
+
function logPostRunOutcome(actionName, outcome) {
|
|
73772
|
+
const logger = getSafeLogger();
|
|
73773
|
+
if (outcome.status === "skipped") {
|
|
73774
|
+
const level = outcome.reason === "shouldRun=false" ? "debug" : "info";
|
|
73775
|
+
logger?.[level]("post-run", `[post-run] ${actionName}: skipped \u2014 ${outcome.reason}`);
|
|
73776
|
+
} else if (outcome.status === "failed") {
|
|
73777
|
+
logger?.warn("post-run", `[post-run] ${actionName}: failed \u2014 ${outcome.message}`);
|
|
73778
|
+
} else if (outcome.status === "error") {
|
|
73779
|
+
logger?.warn("post-run", `[post-run] ${actionName}: error \u2014 ${outcome.reason}`);
|
|
73780
|
+
} else {
|
|
73781
|
+
const suffix = outcome.url ? `${outcome.message} (${outcome.url})` : outcome.message;
|
|
73782
|
+
logger?.info("post-run", `[post-run] ${actionName}: ${suffix}`);
|
|
73783
|
+
}
|
|
73784
|
+
}
|
|
73785
|
+
function postRunHookContext(feature, registration, outcome) {
|
|
73786
|
+
const reason = outcome.status === "succeeded" || outcome.status === "failed" ? outcome.message : outcome.reason;
|
|
73787
|
+
return {
|
|
73788
|
+
event: "on-post-run-action",
|
|
73789
|
+
feature,
|
|
73790
|
+
pluginName: registration.pluginName,
|
|
73791
|
+
actionName: registration.action.name,
|
|
73792
|
+
status: outcome.status,
|
|
73793
|
+
reason,
|
|
73794
|
+
url: "url" in outcome ? outcome.url : undefined
|
|
73795
|
+
};
|
|
73796
|
+
}
|
|
73797
|
+
async function runPostRunActions(options, ctx) {
|
|
73798
|
+
const registrations = options.pluginRegistry.getPostRunActionRegistrations();
|
|
73799
|
+
for (const registration of registrations) {
|
|
73800
|
+
const outcome = await settlePostRunAction(registration.action, ctx);
|
|
73801
|
+
logPostRunOutcome(registration.action.name, outcome);
|
|
73802
|
+
try {
|
|
73803
|
+
await _runCleanupDeps.fireHook(options.hooks, "on-post-run-action", postRunHookContext(options.feature, registration, outcome), options.workdir);
|
|
73804
|
+
} catch (error48) {
|
|
73805
|
+
getSafeLogger()?.warn("hooks", `on-post-run-action hook failed for '${registration.pluginName}'`, { error: error48 });
|
|
73806
|
+
}
|
|
73807
|
+
}
|
|
73808
|
+
}
|
|
73109
73809
|
function buildPostRunContext(opts, durationMs, logger) {
|
|
73110
73810
|
const {
|
|
73111
73811
|
runId,
|
|
@@ -73178,7 +73878,6 @@ async function cleanupRun(options) {
|
|
|
73178
73878
|
}
|
|
73179
73879
|
}
|
|
73180
73880
|
}
|
|
73181
|
-
const actions = pluginRegistry.getPostRunActions();
|
|
73182
73881
|
const pluginLogger = {
|
|
73183
73882
|
debug: (msg, data) => logger?.debug("post-run", msg, data),
|
|
73184
73883
|
info: (msg, data) => logger?.info("post-run", msg, data),
|
|
@@ -73186,26 +73885,7 @@ async function cleanupRun(options) {
|
|
|
73186
73885
|
error: (msg, data) => logger?.error("post-run", msg, data)
|
|
73187
73886
|
};
|
|
73188
73887
|
const ctx = buildPostRunContext(options, durationMs, pluginLogger);
|
|
73189
|
-
|
|
73190
|
-
try {
|
|
73191
|
-
const shouldRun = await action.shouldRun(ctx);
|
|
73192
|
-
if (!shouldRun) {
|
|
73193
|
-
logger?.debug("post-run", `[post-run] ${action.name}: shouldRun=false, skipping`);
|
|
73194
|
-
continue;
|
|
73195
|
-
}
|
|
73196
|
-
const result = await action.execute(ctx);
|
|
73197
|
-
if (result.skipped) {
|
|
73198
|
-
logger?.info("post-run", `[post-run] ${action.name}: skipped \u2014 ${result.reason}`);
|
|
73199
|
-
} else if (!result.success) {
|
|
73200
|
-
logger?.warn("post-run", `[post-run] ${action.name}: failed \u2014 ${result.message}`);
|
|
73201
|
-
} else {
|
|
73202
|
-
const msg = result.url ? `[post-run] ${action.name}: ${result.message} (${result.url})` : `[post-run] ${action.name}: ${result.message}`;
|
|
73203
|
-
logger?.info("post-run", msg);
|
|
73204
|
-
}
|
|
73205
|
-
} catch (error48) {
|
|
73206
|
-
logger?.warn("post-run", `[post-run] ${action.name}: error \u2014 ${error48}`);
|
|
73207
|
-
}
|
|
73208
|
-
}
|
|
73888
|
+
await runPostRunActions(options, ctx);
|
|
73209
73889
|
try {
|
|
73210
73890
|
await pluginRegistry.teardownAll();
|
|
73211
73891
|
} catch (error48) {
|
|
@@ -73222,11 +73902,14 @@ async function cleanupRun(options) {
|
|
|
73222
73902
|
disposeFeatureResolver(workdir);
|
|
73223
73903
|
await releaseLock(workdir);
|
|
73224
73904
|
}
|
|
73905
|
+
var _runCleanupDeps;
|
|
73225
73906
|
var init_run_cleanup = __esm(() => {
|
|
73226
73907
|
init_context();
|
|
73908
|
+
init_hooks();
|
|
73227
73909
|
init_logger2();
|
|
73228
73910
|
init_prd();
|
|
73229
73911
|
init_helpers();
|
|
73912
|
+
_runCleanupDeps = { fireHook };
|
|
73230
73913
|
});
|
|
73231
73914
|
|
|
73232
73915
|
// src/execution/runner.ts
|
|
@@ -73423,6 +74106,7 @@ async function run(options) {
|
|
|
73423
74106
|
prdPath,
|
|
73424
74107
|
branch,
|
|
73425
74108
|
version: NAX_VERSION,
|
|
74109
|
+
hooks,
|
|
73426
74110
|
runCompleted,
|
|
73427
74111
|
outputDir: runtime.outputDir,
|
|
73428
74112
|
globalDir: runtime.globalDir,
|
|
@@ -73481,12 +74165,14 @@ __export(exports_execution, {
|
|
|
73481
74165
|
startHeartbeat: () => startHeartbeat2,
|
|
73482
74166
|
runRectification: () => runRectification,
|
|
73483
74167
|
runPhase: () => runPhase,
|
|
74168
|
+
runNonBlockingFix: () => runNonBlockingFix,
|
|
73484
74169
|
runDeferredRegression: () => runDeferredRegression,
|
|
73485
74170
|
runCompletionPhase: () => runCompletionPhase,
|
|
73486
74171
|
run: () => run,
|
|
73487
74172
|
resolveMaxAttemptsOutcome: () => resolveMaxAttemptsOutcome,
|
|
73488
74173
|
resetCrashHandlers: () => resetCrashHandlers,
|
|
73489
74174
|
releaseLock: () => releaseLock,
|
|
74175
|
+
releaseHeavyPipelineContext: () => releaseHeavyPipelineContext,
|
|
73490
74176
|
refreshReviewInputForDispatch: () => refreshReviewInputForDispatch,
|
|
73491
74177
|
recordOscillations: () => recordOscillations,
|
|
73492
74178
|
readQueueFile: () => readQueueFile,
|
|
@@ -73519,6 +74205,7 @@ __export(exports_execution, {
|
|
|
73519
74205
|
describeGateRegression: () => describeGateRegression,
|
|
73520
74206
|
deriveTddFailureCategory: () => deriveTddFailureCategory,
|
|
73521
74207
|
decideStageAction: () => decideStageAction,
|
|
74208
|
+
createNbfFlakeTriageTransaction: () => createNbfFlakeTriageTransaction,
|
|
73522
74209
|
createCheckpointWriter: () => createCheckpointWriter,
|
|
73523
74210
|
countOscillationOutcomes: () => countOscillationOutcomes,
|
|
73524
74211
|
clearQueueFile: () => clearQueueFile,
|
|
@@ -73541,6 +74228,7 @@ __export(exports_execution, {
|
|
|
73541
74228
|
_runnerDeps: () => _runnerDeps,
|
|
73542
74229
|
_runnerCompletionDeps: () => _runnerCompletionDeps,
|
|
73543
74230
|
_runCompletionDeps: () => _runCompletionDeps,
|
|
74231
|
+
_runCleanupDeps: () => _runCleanupDeps,
|
|
73544
74232
|
_regressionDeps: () => _regressionDeps,
|
|
73545
74233
|
_postRunDeps: () => _postRunDeps,
|
|
73546
74234
|
_pidRegistryDeps: () => _pidRegistryDeps,
|
|
@@ -73558,6 +74246,7 @@ var init_execution2 = __esm(() => {
|
|
|
73558
74246
|
init_oscillation_breaker();
|
|
73559
74247
|
init_runner6();
|
|
73560
74248
|
init_progress();
|
|
74249
|
+
init_iteration_runner();
|
|
73561
74250
|
init_escalation();
|
|
73562
74251
|
init_queue_handler();
|
|
73563
74252
|
init_ensure_package_dirs();
|
|
@@ -73569,6 +74258,7 @@ var init_execution2 = __esm(() => {
|
|
|
73569
74258
|
init_story_orchestrator();
|
|
73570
74259
|
init_story_orchestrator_logging();
|
|
73571
74260
|
init_plan_inputs();
|
|
74261
|
+
init_non_blocking_fix();
|
|
73572
74262
|
init_build_plan_for_strategy();
|
|
73573
74263
|
init_checkpoint();
|
|
73574
74264
|
init_runner_completion();
|