@nathapp/nax 0.70.8 → 0.71.1
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 +958 -111
- package/package.json +3 -2
package/dist/nax.js
CHANGED
|
@@ -16813,7 +16813,7 @@ var init_schemas_debate = __esm(() => {
|
|
|
16813
16813
|
});
|
|
16814
16814
|
|
|
16815
16815
|
// src/config/schemas-execution.ts
|
|
16816
|
-
var AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, WorktreeDependenciesConfigSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema;
|
|
16816
|
+
var AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, WorktreeDependenciesConfigSchema, FlakeDetectionConfigSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema;
|
|
16817
16817
|
var init_schemas_execution = __esm(() => {
|
|
16818
16818
|
init_zod();
|
|
16819
16819
|
init_schemas_model();
|
|
@@ -16879,6 +16879,12 @@ var init_schemas_execution = __esm(() => {
|
|
|
16879
16879
|
});
|
|
16880
16880
|
}
|
|
16881
16881
|
});
|
|
16882
|
+
FlakeDetectionConfigSchema = exports_external.object({
|
|
16883
|
+
enabled: exports_external.boolean().default(true),
|
|
16884
|
+
probeRuns: exports_external.number().int().min(1).max(20).default(2),
|
|
16885
|
+
maxProbesPerGate: exports_external.number().int().min(1).max(100).default(5),
|
|
16886
|
+
probeTimeoutSeconds: exports_external.number().int().min(5).max(600).default(60)
|
|
16887
|
+
});
|
|
16882
16888
|
ExecutionConfigSchema = exports_external.object({
|
|
16883
16889
|
maxIterations: exports_external.number().int().positive({ message: "maxIterations must be > 0" }),
|
|
16884
16890
|
iterationDelayMs: exports_external.number().int().nonnegative(),
|
|
@@ -16904,7 +16910,13 @@ var init_schemas_execution = __esm(() => {
|
|
|
16904
16910
|
mode: "off",
|
|
16905
16911
|
setupCommand: null
|
|
16906
16912
|
}),
|
|
16907
|
-
storyIsolation: exports_external.enum(["shared", "worktree"]).default("shared")
|
|
16913
|
+
storyIsolation: exports_external.enum(["shared", "worktree"]).default("shared"),
|
|
16914
|
+
flakeDetection: FlakeDetectionConfigSchema.default({
|
|
16915
|
+
enabled: true,
|
|
16916
|
+
probeRuns: 2,
|
|
16917
|
+
maxProbesPerGate: 5,
|
|
16918
|
+
probeTimeoutSeconds: 60
|
|
16919
|
+
})
|
|
16908
16920
|
});
|
|
16909
16921
|
QualityConfigSchema = exports_external.object({
|
|
16910
16922
|
requireTypecheck: exports_external.boolean().default(true),
|
|
@@ -17407,7 +17419,13 @@ var init_schemas3 = __esm(() => {
|
|
|
17407
17419
|
mode: "off",
|
|
17408
17420
|
setupCommand: null
|
|
17409
17421
|
},
|
|
17410
|
-
storyIsolation: "shared"
|
|
17422
|
+
storyIsolation: "shared",
|
|
17423
|
+
flakeDetection: {
|
|
17424
|
+
enabled: true,
|
|
17425
|
+
probeRuns: 2,
|
|
17426
|
+
maxProbesPerGate: 5,
|
|
17427
|
+
probeTimeoutSeconds: 60
|
|
17428
|
+
}
|
|
17411
17429
|
}),
|
|
17412
17430
|
quality: QualityConfigSchema.default({
|
|
17413
17431
|
requireTypecheck: true,
|
|
@@ -17738,6 +17756,22 @@ var init_schema = __esm(() => {
|
|
|
17738
17756
|
init_defaults();
|
|
17739
17757
|
});
|
|
17740
17758
|
|
|
17759
|
+
// src/review/severity.ts
|
|
17760
|
+
function isBlockingSeverity(sev, threshold = "error") {
|
|
17761
|
+
return (SEVERITY_RANK[sev] ?? 0) >= SEVERITY_RANK[threshold];
|
|
17762
|
+
}
|
|
17763
|
+
var SEVERITY_RANK;
|
|
17764
|
+
var init_severity = __esm(() => {
|
|
17765
|
+
SEVERITY_RANK = {
|
|
17766
|
+
info: 0,
|
|
17767
|
+
unverifiable: 0,
|
|
17768
|
+
low: 1,
|
|
17769
|
+
warning: 1,
|
|
17770
|
+
error: 2,
|
|
17771
|
+
critical: 3
|
|
17772
|
+
};
|
|
17773
|
+
});
|
|
17774
|
+
|
|
17741
17775
|
// src/log-format/types.ts
|
|
17742
17776
|
var EMOJI;
|
|
17743
17777
|
var init_types2 = __esm(() => {
|
|
@@ -18037,6 +18071,31 @@ function formatRunSummary(summary, options) {
|
|
|
18037
18071
|
return lines.join(`
|
|
18038
18072
|
`);
|
|
18039
18073
|
}
|
|
18074
|
+
function formatAdvisorySummary(findings, options) {
|
|
18075
|
+
if (findings.length === 0)
|
|
18076
|
+
return "";
|
|
18077
|
+
const { mode, useColor = true } = options;
|
|
18078
|
+
if (mode === "json") {
|
|
18079
|
+
return JSON.stringify(findings);
|
|
18080
|
+
}
|
|
18081
|
+
const c = useColor ? source_default : createNoopChalk();
|
|
18082
|
+
const sorted = [...findings].sort((a, b) => (SEVERITY_RANK[b.severity] ?? 0) - (SEVERITY_RANK[a.severity] ?? 0));
|
|
18083
|
+
const lines = [];
|
|
18084
|
+
lines.push("");
|
|
18085
|
+
lines.push(c.yellow("\u2500".repeat(60)));
|
|
18086
|
+
lines.push(c.bold(c.yellow(` ${EMOJI.warning} NON-BLOCKING REVIEW FINDINGS (${findings.length})`)));
|
|
18087
|
+
lines.push(c.yellow("\u2500".repeat(60)));
|
|
18088
|
+
for (const f of sorted) {
|
|
18089
|
+
const location = f.file ? `${f.file}${f.line ? `:${f.line}` : ""}` : undefined;
|
|
18090
|
+
const parts = [`[${f.severity}]`, f.storyId ?? "unknown", location, f.category].filter((v) => typeof v === "string" && v.length > 0);
|
|
18091
|
+
lines.push(` ${c.gray(parts.join(" \xB7 "))}`);
|
|
18092
|
+
lines.push(` ${f.issue}`);
|
|
18093
|
+
}
|
|
18094
|
+
lines.push(c.yellow("\u2500".repeat(60)));
|
|
18095
|
+
lines.push("");
|
|
18096
|
+
return lines.join(`
|
|
18097
|
+
`);
|
|
18098
|
+
}
|
|
18040
18099
|
function createNoopChalk() {
|
|
18041
18100
|
const noop = (s) => s;
|
|
18042
18101
|
return {
|
|
@@ -18054,6 +18113,7 @@ function createNoopChalk() {
|
|
|
18054
18113
|
var CONSUMED_META_KEYS;
|
|
18055
18114
|
var init_formatter = __esm(() => {
|
|
18056
18115
|
init_source();
|
|
18116
|
+
init_severity();
|
|
18057
18117
|
init_types2();
|
|
18058
18118
|
CONSUMED_META_KEYS = [
|
|
18059
18119
|
"agentName",
|
|
@@ -18397,7 +18457,11 @@ function mergePackageConfig(root, packageOverride) {
|
|
|
18397
18457
|
...root.execution.regressionGate,
|
|
18398
18458
|
...packageOverride.execution?.regressionGate
|
|
18399
18459
|
},
|
|
18400
|
-
verificationTimeoutSeconds: packageOverride.execution?.verificationTimeoutSeconds ?? root.execution.verificationTimeoutSeconds
|
|
18460
|
+
verificationTimeoutSeconds: packageOverride.execution?.verificationTimeoutSeconds ?? root.execution.verificationTimeoutSeconds,
|
|
18461
|
+
flakeDetection: {
|
|
18462
|
+
...root.execution.flakeDetection,
|
|
18463
|
+
...packageOverride.execution?.flakeDetection
|
|
18464
|
+
}
|
|
18401
18465
|
},
|
|
18402
18466
|
review: {
|
|
18403
18467
|
...root.review,
|
|
@@ -28560,7 +28624,10 @@ function getNextStory(prd, currentStoryId, maxRetries) {
|
|
|
28560
28624
|
return currentStory;
|
|
28561
28625
|
}
|
|
28562
28626
|
}
|
|
28563
|
-
|
|
28627
|
+
const eligible = prd.userStories.filter((s) => !s.passes && s.status !== "passed" && s.status !== "skipped" && s.status !== "blocked" && s.status !== "failed" && s.status !== "paused" && s.status !== "decomposed" && hasSatisfiedDependencies(s, storyIds, completedIds));
|
|
28628
|
+
if (eligible.length === 0)
|
|
28629
|
+
return null;
|
|
28630
|
+
return eligible.reduce((best, s) => (s.priority ?? 0) > (best.priority ?? 0) ? s : best);
|
|
28564
28631
|
}
|
|
28565
28632
|
function isComplete(prd) {
|
|
28566
28633
|
return prd.userStories.every((s) => s.passes || s.status === "passed" || s.status === "skipped");
|
|
@@ -28643,6 +28710,31 @@ function markStorySkipped(prd, storyId) {
|
|
|
28643
28710
|
story.status = "skipped";
|
|
28644
28711
|
}
|
|
28645
28712
|
}
|
|
28713
|
+
function resetStoryToPending(prd, storyId) {
|
|
28714
|
+
const story = prd.userStories.find((s) => s.id === storyId);
|
|
28715
|
+
if (!story)
|
|
28716
|
+
return;
|
|
28717
|
+
story.status = "pending";
|
|
28718
|
+
story.attempts = 0;
|
|
28719
|
+
story.failureCategory = undefined;
|
|
28720
|
+
story.failureStage = undefined;
|
|
28721
|
+
if (story.routing) {
|
|
28722
|
+
story.routing = {
|
|
28723
|
+
...story.routing,
|
|
28724
|
+
...story.routing.initialModelTier !== undefined && {
|
|
28725
|
+
modelTier: story.routing.initialModelTier
|
|
28726
|
+
},
|
|
28727
|
+
agent: story.routing.initialAgent
|
|
28728
|
+
};
|
|
28729
|
+
}
|
|
28730
|
+
story.escalations = [];
|
|
28731
|
+
}
|
|
28732
|
+
function setStoryPriority(prd, storyId, priority) {
|
|
28733
|
+
const story = prd.userStories.find((s) => s.id === storyId);
|
|
28734
|
+
if (story) {
|
|
28735
|
+
story.priority = priority;
|
|
28736
|
+
}
|
|
28737
|
+
}
|
|
28646
28738
|
function markStoryPaused(prd, storyId) {
|
|
28647
28739
|
const story = prd.userStories.find((s) => s.id === storyId);
|
|
28648
28740
|
if (story) {
|
|
@@ -32739,22 +32831,6 @@ var init_category_fix_target = __esm(() => {
|
|
|
32739
32831
|
init_ac_structural_counterfactual();
|
|
32740
32832
|
});
|
|
32741
32833
|
|
|
32742
|
-
// src/review/severity.ts
|
|
32743
|
-
function isBlockingSeverity(sev, threshold = "error") {
|
|
32744
|
-
return (SEVERITY_RANK[sev] ?? 0) >= SEVERITY_RANK[threshold];
|
|
32745
|
-
}
|
|
32746
|
-
var SEVERITY_RANK;
|
|
32747
|
-
var init_severity = __esm(() => {
|
|
32748
|
-
SEVERITY_RANK = {
|
|
32749
|
-
info: 0,
|
|
32750
|
-
unverifiable: 0,
|
|
32751
|
-
low: 1,
|
|
32752
|
-
warning: 1,
|
|
32753
|
-
error: 2,
|
|
32754
|
-
critical: 3
|
|
32755
|
-
};
|
|
32756
|
-
});
|
|
32757
|
-
|
|
32758
32834
|
// src/review/adversarial-helpers.ts
|
|
32759
32835
|
function validateAdversarialShape(parsed) {
|
|
32760
32836
|
if (typeof parsed !== "object" || parsed === null)
|
|
@@ -33849,15 +33925,36 @@ function createNoOpReviewAuditor() {
|
|
|
33849
33925
|
return {
|
|
33850
33926
|
recordDispatch() {},
|
|
33851
33927
|
recordDecision() {},
|
|
33852
|
-
async flush() {}
|
|
33928
|
+
async flush() {},
|
|
33929
|
+
getAdvisoryFindings() {
|
|
33930
|
+
return [];
|
|
33931
|
+
}
|
|
33853
33932
|
};
|
|
33854
33933
|
}
|
|
33934
|
+
function toAdvisorySummaryEntries(entry) {
|
|
33935
|
+
if (!entry.advisoryFindings || entry.advisoryFindings.length === 0)
|
|
33936
|
+
return [];
|
|
33937
|
+
return entry.advisoryFindings.map((raw) => {
|
|
33938
|
+
const f = raw;
|
|
33939
|
+
return {
|
|
33940
|
+
storyId: entry.storyId,
|
|
33941
|
+
featureName: entry.featureName,
|
|
33942
|
+
reviewer: entry.reviewer,
|
|
33943
|
+
severity: f.severity ?? "info",
|
|
33944
|
+
category: f.category,
|
|
33945
|
+
file: f.file,
|
|
33946
|
+
line: f.line,
|
|
33947
|
+
issue: f.issue ?? "(no description)"
|
|
33948
|
+
};
|
|
33949
|
+
});
|
|
33950
|
+
}
|
|
33855
33951
|
|
|
33856
33952
|
class ReviewAuditor {
|
|
33857
33953
|
_runId;
|
|
33858
33954
|
_outputDir;
|
|
33859
33955
|
_queue = Promise.resolve();
|
|
33860
33956
|
_dispatches = new Map;
|
|
33957
|
+
_advisoryFindings = [];
|
|
33861
33958
|
constructor(_runId, _outputDir) {
|
|
33862
33959
|
this._runId = _runId;
|
|
33863
33960
|
this._outputDir = _outputDir;
|
|
@@ -33866,6 +33963,7 @@ class ReviewAuditor {
|
|
|
33866
33963
|
this._dispatches.set(auditKey(entry.reviewer, entry.storyId), entry);
|
|
33867
33964
|
}
|
|
33868
33965
|
recordDecision(entry) {
|
|
33966
|
+
this._advisoryFindings.push(...toAdvisorySummaryEntries(entry));
|
|
33869
33967
|
const key = auditKey(entry.reviewer, entry.storyId);
|
|
33870
33968
|
const dispatch = this._dispatches.get(key);
|
|
33871
33969
|
this._dispatches.delete(key);
|
|
@@ -33894,6 +33992,9 @@ class ReviewAuditor {
|
|
|
33894
33992
|
async flush() {
|
|
33895
33993
|
await this._queue;
|
|
33896
33994
|
}
|
|
33995
|
+
getAdvisoryFindings() {
|
|
33996
|
+
return [...this._advisoryFindings];
|
|
33997
|
+
}
|
|
33897
33998
|
}
|
|
33898
33999
|
async function writeReviewAudit(entry) {
|
|
33899
34000
|
try {
|
|
@@ -34841,6 +34942,17 @@ var init_self_verification = __esm(() => {
|
|
|
34841
34942
|
});
|
|
34842
34943
|
|
|
34843
34944
|
// src/quality/index.ts
|
|
34945
|
+
var exports_quality = {};
|
|
34946
|
+
__export(exports_quality, {
|
|
34947
|
+
runQualityCommand: () => runQualityCommand,
|
|
34948
|
+
resolveSelfVerificationPromptInput: () => resolveSelfVerificationPromptInput,
|
|
34949
|
+
resolveQualityTestCommands: () => resolveQualityTestCommands,
|
|
34950
|
+
resolveDefaultQualityCommands: () => resolveDefaultQualityCommands,
|
|
34951
|
+
parseSelfVerificationMarker: () => parseSelfVerificationMarker,
|
|
34952
|
+
clearCommandDefaultsCache: () => clearCommandDefaultsCache,
|
|
34953
|
+
_commandResolverDeps: () => _commandResolverDeps,
|
|
34954
|
+
_commandDefaultsDeps: () => _commandDefaultsDeps
|
|
34955
|
+
});
|
|
34844
34956
|
var init_quality = __esm(() => {
|
|
34845
34957
|
init_runner();
|
|
34846
34958
|
init_command_resolver();
|
|
@@ -39527,7 +39639,8 @@ var init_full_suite_gate = __esm(() => {
|
|
|
39527
39639
|
status: "skipped",
|
|
39528
39640
|
estimatedCostUsd: 0,
|
|
39529
39641
|
attempts: 0,
|
|
39530
|
-
findings: []
|
|
39642
|
+
findings: [],
|
|
39643
|
+
rawOutput: ""
|
|
39531
39644
|
};
|
|
39532
39645
|
}
|
|
39533
39646
|
const gateCtx = await deps.resolveGateContext(input, ctx);
|
|
@@ -39546,7 +39659,15 @@ var init_full_suite_gate = __esm(() => {
|
|
|
39546
39659
|
});
|
|
39547
39660
|
const testResult = await deps.runTests(input, gateCtx);
|
|
39548
39661
|
if (testResult.passed) {
|
|
39549
|
-
return {
|
|
39662
|
+
return {
|
|
39663
|
+
success: true,
|
|
39664
|
+
passed: true,
|
|
39665
|
+
status: "passed",
|
|
39666
|
+
estimatedCostUsd: 0,
|
|
39667
|
+
attempts: 0,
|
|
39668
|
+
findings: [],
|
|
39669
|
+
rawOutput: testResult.output
|
|
39670
|
+
};
|
|
39550
39671
|
}
|
|
39551
39672
|
if (testResult.timedOut) {
|
|
39552
39673
|
const acceptOnTimeout = ctxConfig?.execution?.regressionGate?.acceptOnTimeout ?? true;
|
|
@@ -39560,7 +39681,8 @@ var init_full_suite_gate = __esm(() => {
|
|
|
39560
39681
|
status: "passed-on-timeout",
|
|
39561
39682
|
estimatedCostUsd: 0,
|
|
39562
39683
|
attempts: 0,
|
|
39563
|
-
findings: []
|
|
39684
|
+
findings: [],
|
|
39685
|
+
rawOutput: testResult.output
|
|
39564
39686
|
};
|
|
39565
39687
|
}
|
|
39566
39688
|
logger.warn("verify[regression]", "Full-suite timed out (failing)", {
|
|
@@ -39572,7 +39694,8 @@ var init_full_suite_gate = __esm(() => {
|
|
|
39572
39694
|
status: "timeout",
|
|
39573
39695
|
estimatedCostUsd: 0,
|
|
39574
39696
|
attempts: 0,
|
|
39575
|
-
findings: []
|
|
39697
|
+
findings: [],
|
|
39698
|
+
rawOutput: testResult.output
|
|
39576
39699
|
};
|
|
39577
39700
|
}
|
|
39578
39701
|
const findings = testSummaryToFindings(testResult.parsedSummary);
|
|
@@ -39597,10 +39720,19 @@ var init_full_suite_gate = __esm(() => {
|
|
|
39597
39720
|
status: "execution-failed",
|
|
39598
39721
|
estimatedCostUsd: 0,
|
|
39599
39722
|
attempts: 0,
|
|
39600
|
-
findings: [synth]
|
|
39723
|
+
findings: [synth],
|
|
39724
|
+
rawOutput: testResult.output
|
|
39601
39725
|
};
|
|
39602
39726
|
}
|
|
39603
|
-
return {
|
|
39727
|
+
return {
|
|
39728
|
+
success: false,
|
|
39729
|
+
passed: false,
|
|
39730
|
+
status: "failed",
|
|
39731
|
+
estimatedCostUsd: 0,
|
|
39732
|
+
attempts: 0,
|
|
39733
|
+
findings,
|
|
39734
|
+
rawOutput: testResult.output
|
|
39735
|
+
};
|
|
39604
39736
|
}
|
|
39605
39737
|
};
|
|
39606
39738
|
});
|
|
@@ -47347,6 +47479,190 @@ var init_session = __esm(() => {
|
|
|
47347
47479
|
init_types7();
|
|
47348
47480
|
});
|
|
47349
47481
|
|
|
47482
|
+
// src/verification/flake-probe.ts
|
|
47483
|
+
function escapeRegex2(input) {
|
|
47484
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47485
|
+
}
|
|
47486
|
+
function shellQuote(value) {
|
|
47487
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
47488
|
+
}
|
|
47489
|
+
function buildIsolationCommand(baseCommand, failure, framework) {
|
|
47490
|
+
const file3 = failure.file;
|
|
47491
|
+
const name = failure.testName;
|
|
47492
|
+
switch (framework) {
|
|
47493
|
+
case "pytest":
|
|
47494
|
+
return `${baseCommand} ${shellQuote(`${file3}::${name}`)}`;
|
|
47495
|
+
case "go":
|
|
47496
|
+
return `${baseCommand} -run ${shellQuote(`^${escapeRegex2(name)}$`)}`;
|
|
47497
|
+
case "bun":
|
|
47498
|
+
case "jest":
|
|
47499
|
+
case "vitest":
|
|
47500
|
+
return `${baseCommand} ${file3} -t ${shellQuote(escapeRegex2(name))}`;
|
|
47501
|
+
default:
|
|
47502
|
+
throw new NaxError(`[flake-probe] unsupported framework: ${framework}`, "FLAKE_PROBE_UNSUPPORTED_FRAMEWORK", {
|
|
47503
|
+
stage: "verify",
|
|
47504
|
+
framework
|
|
47505
|
+
});
|
|
47506
|
+
}
|
|
47507
|
+
}
|
|
47508
|
+
async function runFlakeProbe(input) {
|
|
47509
|
+
const { framework, baseCommand, failure, cwd, probeRuns, probeTimeoutSeconds } = input;
|
|
47510
|
+
if (failure.file === "unknown" || framework === "unknown") {
|
|
47511
|
+
return {
|
|
47512
|
+
verdict: "unprobeable",
|
|
47513
|
+
reason: failure.file === "unknown" && framework === "unknown" ? "unknown file and framework" : failure.file === "unknown" ? "unknown file" : "unknown framework"
|
|
47514
|
+
};
|
|
47515
|
+
}
|
|
47516
|
+
const command = buildIsolationCommand(baseCommand, failure, framework);
|
|
47517
|
+
let probePasses = 0;
|
|
47518
|
+
for (let i = 0;i < probeRuns; i += 1) {
|
|
47519
|
+
let result;
|
|
47520
|
+
try {
|
|
47521
|
+
result = await _flakeProbeDeps.execute(command, probeTimeoutSeconds, undefined, { cwd });
|
|
47522
|
+
} catch {
|
|
47523
|
+
continue;
|
|
47524
|
+
}
|
|
47525
|
+
if (result.success && result.countsTowardEscalation) {
|
|
47526
|
+
probePasses += 1;
|
|
47527
|
+
}
|
|
47528
|
+
}
|
|
47529
|
+
if (probePasses > 0) {
|
|
47530
|
+
return { verdict: "flaky", probeRuns, probePasses };
|
|
47531
|
+
}
|
|
47532
|
+
return { verdict: "consistent-failure", probeRuns };
|
|
47533
|
+
}
|
|
47534
|
+
var _flakeProbeDeps;
|
|
47535
|
+
var init_flake_probe = __esm(() => {
|
|
47536
|
+
init_errors();
|
|
47537
|
+
init_executor();
|
|
47538
|
+
_flakeProbeDeps = {
|
|
47539
|
+
execute: executeWithTimeout
|
|
47540
|
+
};
|
|
47541
|
+
});
|
|
47542
|
+
|
|
47543
|
+
// src/verification/flake-triage.ts
|
|
47544
|
+
function flakeMemoKey(failure) {
|
|
47545
|
+
return `${failure.file ?? ""}::${failure.rule ?? ""}`;
|
|
47546
|
+
}
|
|
47547
|
+
function createQuarantineMemo() {
|
|
47548
|
+
const keys = new Set;
|
|
47549
|
+
return {
|
|
47550
|
+
has: (key) => keys.has(key),
|
|
47551
|
+
add: (key) => {
|
|
47552
|
+
keys.add(key);
|
|
47553
|
+
}
|
|
47554
|
+
};
|
|
47555
|
+
}
|
|
47556
|
+
async function triageFlakyFindings(input) {
|
|
47557
|
+
const { findings, diff, flakeDetection, baseCommand, cwd, framework, quarantineMemo } = input;
|
|
47558
|
+
const logger = getSafeLogger();
|
|
47559
|
+
const result = [];
|
|
47560
|
+
const keys = [];
|
|
47561
|
+
const reasons = [];
|
|
47562
|
+
if (findings.length === 0) {
|
|
47563
|
+
return { findings: result, quarantineReport: { keys, reasons } };
|
|
47564
|
+
}
|
|
47565
|
+
if (!flakeDetection.enabled) {
|
|
47566
|
+
for (const f of findings)
|
|
47567
|
+
result.push({ ...f });
|
|
47568
|
+
return { findings: result, quarantineReport: { keys, reasons } };
|
|
47569
|
+
}
|
|
47570
|
+
const changedTestSet = new Set(diff.changedTestFiles.map(basename5));
|
|
47571
|
+
const mappedTestSet = new Set(diff.mappedTestFiles.map(basename5));
|
|
47572
|
+
const candidates = findings.filter((f) => isProbeCandidate(f, changedTestSet, mappedTestSet));
|
|
47573
|
+
if (candidates.length > flakeDetection.maxProbesPerGate) {
|
|
47574
|
+
logger?.info("flake-triage", `Skipping flake triage \u2014 ${candidates.length} candidates exceed maxProbesPerGate=${flakeDetection.maxProbesPerGate}`);
|
|
47575
|
+
reasons.push(`skipped: ${candidates.length} candidates exceed maxProbesPerGate=${flakeDetection.maxProbesPerGate}`);
|
|
47576
|
+
for (const f of findings)
|
|
47577
|
+
result.push({ ...f });
|
|
47578
|
+
return { findings: result, quarantineReport: { keys, reasons } };
|
|
47579
|
+
}
|
|
47580
|
+
for (const finding of findings) {
|
|
47581
|
+
const copy = { ...finding };
|
|
47582
|
+
if (copy.category !== "failed-test") {
|
|
47583
|
+
result.push(copy);
|
|
47584
|
+
continue;
|
|
47585
|
+
}
|
|
47586
|
+
const key = flakeMemoKey(copy);
|
|
47587
|
+
if (quarantineMemo.has(key)) {
|
|
47588
|
+
copy.category = "flaky-test";
|
|
47589
|
+
keys.push(key);
|
|
47590
|
+
reasons.push(`quarantined (memo): ${key}`);
|
|
47591
|
+
result.push(copy);
|
|
47592
|
+
continue;
|
|
47593
|
+
}
|
|
47594
|
+
if (!isProbeCandidate(copy, changedTestSet, mappedTestSet)) {
|
|
47595
|
+
result.push(copy);
|
|
47596
|
+
continue;
|
|
47597
|
+
}
|
|
47598
|
+
const failure = {
|
|
47599
|
+
file: copy.file ?? "unknown",
|
|
47600
|
+
testName: copy.rule ?? "",
|
|
47601
|
+
error: copy.message,
|
|
47602
|
+
stackTrace: []
|
|
47603
|
+
};
|
|
47604
|
+
const probeInput = {
|
|
47605
|
+
framework,
|
|
47606
|
+
baseCommand,
|
|
47607
|
+
failure,
|
|
47608
|
+
cwd,
|
|
47609
|
+
probeRuns: flakeDetection.probeRuns,
|
|
47610
|
+
probeTimeoutSeconds: flakeDetection.probeTimeoutSeconds
|
|
47611
|
+
};
|
|
47612
|
+
let verdict;
|
|
47613
|
+
try {
|
|
47614
|
+
verdict = await _flakeTriageDeps.runFlakeProbe({ failure, config: flakeDetection, probeInput });
|
|
47615
|
+
} catch (err) {
|
|
47616
|
+
logger?.warn("flake-triage", `Probe dependency threw for ${key} \u2014 keeping finding blocking`, {
|
|
47617
|
+
file: copy.file,
|
|
47618
|
+
testName: copy.rule,
|
|
47619
|
+
error: err instanceof Error ? err.message : String(err)
|
|
47620
|
+
});
|
|
47621
|
+
result.push(copy);
|
|
47622
|
+
continue;
|
|
47623
|
+
}
|
|
47624
|
+
if (verdict.verdict === "flaky") {
|
|
47625
|
+
copy.category = "flaky-test";
|
|
47626
|
+
copy.meta = { ...copy.meta ?? {}, probeRuns: verdict.probeRuns, probePasses: verdict.probePasses };
|
|
47627
|
+
quarantineMemo.add(key);
|
|
47628
|
+
keys.push(key);
|
|
47629
|
+
reasons.push(`quarantined: ${key}`);
|
|
47630
|
+
}
|
|
47631
|
+
result.push(copy);
|
|
47632
|
+
}
|
|
47633
|
+
return { findings: result, quarantineReport: { keys, reasons } };
|
|
47634
|
+
}
|
|
47635
|
+
function basename5(path7) {
|
|
47636
|
+
const i = path7.lastIndexOf("/");
|
|
47637
|
+
return i === -1 ? path7 : path7.slice(i + 1);
|
|
47638
|
+
}
|
|
47639
|
+
function isProbeCandidate(finding, changedTestSet, mappedTestSet) {
|
|
47640
|
+
if (finding.category !== "failed-test")
|
|
47641
|
+
return false;
|
|
47642
|
+
if (!finding.file || finding.file === "unknown")
|
|
47643
|
+
return false;
|
|
47644
|
+
if (!finding.rule)
|
|
47645
|
+
return false;
|
|
47646
|
+
const base = basename5(finding.file);
|
|
47647
|
+
if (changedTestSet.has(base) || changedTestSet.has(finding.file))
|
|
47648
|
+
return false;
|
|
47649
|
+
if (mappedTestSet.has(base) || mappedTestSet.has(finding.file))
|
|
47650
|
+
return false;
|
|
47651
|
+
return true;
|
|
47652
|
+
}
|
|
47653
|
+
var NULL_QUARANTINE_MEMO, _flakeTriageDeps;
|
|
47654
|
+
var init_flake_triage = __esm(() => {
|
|
47655
|
+
init_logger2();
|
|
47656
|
+
init_flake_probe();
|
|
47657
|
+
NULL_QUARANTINE_MEMO = {
|
|
47658
|
+
has: () => false,
|
|
47659
|
+
add: () => {}
|
|
47660
|
+
};
|
|
47661
|
+
_flakeTriageDeps = {
|
|
47662
|
+
runFlakeProbe: async (call) => runFlakeProbe(call.probeInput)
|
|
47663
|
+
};
|
|
47664
|
+
});
|
|
47665
|
+
|
|
47350
47666
|
// src/runtime/session-run-hop.ts
|
|
47351
47667
|
function createSessionRunHop(sessionManager, getAgentManager) {
|
|
47352
47668
|
return async (agentName, options) => {
|
|
@@ -47470,7 +47786,7 @@ __export(exports_runtime, {
|
|
|
47470
47786
|
CostAggregator: () => CostAggregator,
|
|
47471
47787
|
AgentStreamEventBus: () => AgentStreamEventBus
|
|
47472
47788
|
});
|
|
47473
|
-
import { basename as
|
|
47789
|
+
import { basename as basename6, join as join30 } from "path";
|
|
47474
47790
|
function createRuntime(config2, workdir, opts) {
|
|
47475
47791
|
const runId = crypto.randomUUID();
|
|
47476
47792
|
const controller = new AbortController;
|
|
@@ -47482,7 +47798,7 @@ function createRuntime(config2, workdir, opts) {
|
|
|
47482
47798
|
const configLoader = createConfigLoader(config2);
|
|
47483
47799
|
const dispatchEvents = new DispatchEventBus;
|
|
47484
47800
|
const agentStreamEvents = opts?.agentStreamEvents ?? new AgentStreamEventBus;
|
|
47485
|
-
const projectKey = config2.name?.trim() ||
|
|
47801
|
+
const projectKey = config2.name?.trim() || basename6(workdir);
|
|
47486
47802
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
47487
47803
|
const globalDir = globalOutputDir();
|
|
47488
47804
|
const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
|
|
@@ -47544,6 +47860,7 @@ function createRuntime(config2, workdir, opts) {
|
|
|
47544
47860
|
const offWatchdog = attachAgentIdleWatchdog(agentStreamEvents, watchdogControllerRegistry, config2);
|
|
47545
47861
|
const packages = createPackageRegistry(configLoader, workdir);
|
|
47546
47862
|
const logger = getLogger();
|
|
47863
|
+
const quarantineMemo = createQuarantineMemo();
|
|
47547
47864
|
let closed = false;
|
|
47548
47865
|
return {
|
|
47549
47866
|
runId,
|
|
@@ -47564,6 +47881,7 @@ function createRuntime(config2, workdir, opts) {
|
|
|
47564
47881
|
packages,
|
|
47565
47882
|
pidRegistry,
|
|
47566
47883
|
logger,
|
|
47884
|
+
quarantineMemo,
|
|
47567
47885
|
get signal() {
|
|
47568
47886
|
return controller.signal;
|
|
47569
47887
|
},
|
|
@@ -47612,6 +47930,7 @@ var init_runtime = __esm(() => {
|
|
|
47612
47930
|
init_logger2();
|
|
47613
47931
|
init_review_audit();
|
|
47614
47932
|
init_session();
|
|
47933
|
+
init_flake_triage();
|
|
47615
47934
|
init_agent_stream_events();
|
|
47616
47935
|
init_cost_aggregator();
|
|
47617
47936
|
init_dispatch_events();
|
|
@@ -55463,6 +55782,12 @@ async function runNonBlockingFix(args, overrides = {}) {
|
|
|
55463
55782
|
exhausted = true;
|
|
55464
55783
|
}
|
|
55465
55784
|
if (!exhausted) {
|
|
55785
|
+
if (args.keptTreeRegressed?.()) {
|
|
55786
|
+
logger?.info("non-blocking-fix", "kept tree regressed the full-suite gate \u2014 restoring (ADR-024 \xA73)", {
|
|
55787
|
+
storyId: args.storyId
|
|
55788
|
+
});
|
|
55789
|
+
return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
|
|
55790
|
+
}
|
|
55466
55791
|
const cap = args.cfg.sourceDiffCap;
|
|
55467
55792
|
if (cap) {
|
|
55468
55793
|
let metrics;
|
|
@@ -55638,6 +55963,8 @@ function gateFailureKeys(gateOutput) {
|
|
|
55638
55963
|
for (const f of extractPhaseFindings(gateOutput)) {
|
|
55639
55964
|
if (f.source !== "test-runner")
|
|
55640
55965
|
continue;
|
|
55966
|
+
if (f.category === "flaky-test")
|
|
55967
|
+
continue;
|
|
55641
55968
|
keys.add(`${f.file ?? ""}::${f.rule ?? ""}`);
|
|
55642
55969
|
}
|
|
55643
55970
|
return keys;
|
|
@@ -55775,6 +56102,85 @@ function logDeterministicPhaseOutcome(storyId, opName, output, durationMs, isTdd
|
|
|
55775
56102
|
var init_story_orchestrator_logging = __esm(() => {
|
|
55776
56103
|
init_logger2();
|
|
55777
56104
|
});
|
|
56105
|
+
// src/verification/flake-baseline-diff.ts
|
|
56106
|
+
async function resolveFlakeBaselineDiff(config2, workdir, storyWorkdir) {
|
|
56107
|
+
try {
|
|
56108
|
+
const resolved = await resolveTestFilePatterns(config2, workdir, storyWorkdir);
|
|
56109
|
+
const baseRef = await getMergeBase(workdir);
|
|
56110
|
+
const changedTestFiles = await getChangedTestFiles(workdir, workdir, baseRef, storyWorkdir, [...resolved.regex]);
|
|
56111
|
+
const changedNonTestFiles = await getChangedNonTestFiles(workdir, baseRef, storyWorkdir, [...resolved.regex], undefined, workdir);
|
|
56112
|
+
const mappedTestFiles = await mapSourceToTests(changedNonTestFiles, workdir, storyWorkdir, [...resolved.globs]);
|
|
56113
|
+
return { changedTestFiles, mappedTestFiles };
|
|
56114
|
+
} catch (err) {
|
|
56115
|
+
getSafeLogger()?.warn("flake-triage", "Baseline diff resolution failed \u2014 skipping triage this gate (fail closed)", {
|
|
56116
|
+
error: errorMessage(err)
|
|
56117
|
+
});
|
|
56118
|
+
return null;
|
|
56119
|
+
}
|
|
56120
|
+
}
|
|
56121
|
+
var init_flake_baseline_diff = __esm(() => {
|
|
56122
|
+
init_logger2();
|
|
56123
|
+
init_test_runners();
|
|
56124
|
+
init_git();
|
|
56125
|
+
init_smart_runner();
|
|
56126
|
+
});
|
|
56127
|
+
|
|
56128
|
+
// src/verification/index.ts
|
|
56129
|
+
var init_verification = __esm(() => {
|
|
56130
|
+
init_executor();
|
|
56131
|
+
init_runners();
|
|
56132
|
+
init_flake_probe();
|
|
56133
|
+
init_flake_triage();
|
|
56134
|
+
init_flake_baseline_diff();
|
|
56135
|
+
});
|
|
56136
|
+
|
|
56137
|
+
// src/execution/story-orchestrator/flake-triage-seam.ts
|
|
56138
|
+
var productionTriageSeam = async (gateFindings, { ctx, rawOutput }) => {
|
|
56139
|
+
const config2 = ctx.packageView.config;
|
|
56140
|
+
const flakeDetection = config2.execution?.flakeDetection;
|
|
56141
|
+
if (!flakeDetection?.enabled) {
|
|
56142
|
+
return [gateFindings, { quarantinedKeys: [] }];
|
|
56143
|
+
}
|
|
56144
|
+
const framework = detectFramework(rawOutput);
|
|
56145
|
+
if (framework === "unknown") {
|
|
56146
|
+
return [gateFindings, { quarantinedKeys: [] }];
|
|
56147
|
+
}
|
|
56148
|
+
const workdir = ctx.runtime.workdir;
|
|
56149
|
+
const storyWorkdir = ctx.story?.workdir;
|
|
56150
|
+
try {
|
|
56151
|
+
const { resolveQualityTestCommands: resolveQualityTestCommands2 } = await Promise.resolve().then(() => (init_quality(), exports_quality));
|
|
56152
|
+
const { testCommand } = await resolveQualityTestCommands2(config2, workdir, storyWorkdir);
|
|
56153
|
+
const baseCommand = testCommand ?? config2.quality?.commands?.test;
|
|
56154
|
+
if (!baseCommand) {
|
|
56155
|
+
return [gateFindings, { quarantinedKeys: [] }];
|
|
56156
|
+
}
|
|
56157
|
+
const diff = await resolveFlakeBaselineDiff(config2, workdir, storyWorkdir);
|
|
56158
|
+
if (diff === null) {
|
|
56159
|
+
return [gateFindings, { quarantinedKeys: [] }];
|
|
56160
|
+
}
|
|
56161
|
+
const result = await triageFlakyFindings({
|
|
56162
|
+
findings: gateFindings,
|
|
56163
|
+
diff,
|
|
56164
|
+
flakeDetection,
|
|
56165
|
+
baseCommand,
|
|
56166
|
+
cwd: ctx.packageDir,
|
|
56167
|
+
framework,
|
|
56168
|
+
quarantineMemo: ctx.runtime.quarantineMemo
|
|
56169
|
+
});
|
|
56170
|
+
return [result.findings, { quarantinedKeys: result.quarantineReport.keys }];
|
|
56171
|
+
} catch (err) {
|
|
56172
|
+
getSafeLogger()?.warn("story-orchestrator", "Flake triage seam failed resolving context \u2014 keeping findings blocking (no quarantine)", {
|
|
56173
|
+
storyId: ctx.storyId,
|
|
56174
|
+
error: errorMessage(err)
|
|
56175
|
+
});
|
|
56176
|
+
return [gateFindings, { quarantinedKeys: [] }];
|
|
56177
|
+
}
|
|
56178
|
+
};
|
|
56179
|
+
var init_flake_triage_seam = __esm(() => {
|
|
56180
|
+
init_logger2();
|
|
56181
|
+
init_test_runners();
|
|
56182
|
+
init_verification();
|
|
56183
|
+
});
|
|
55778
56184
|
|
|
55779
56185
|
// src/execution/story-orchestrator/review-decision.ts
|
|
55780
56186
|
function toReviewDecisionPayload(opName, output) {
|
|
@@ -56051,6 +56457,7 @@ var init_run_phase = __esm(() => {
|
|
|
56051
56457
|
init_git();
|
|
56052
56458
|
init_non_blocking_fix();
|
|
56053
56459
|
init_story_orchestrator_logging();
|
|
56460
|
+
init_flake_triage_seam();
|
|
56054
56461
|
init_review_decision();
|
|
56055
56462
|
init_types9();
|
|
56056
56463
|
_storyOrchestratorDeps = {
|
|
@@ -56059,7 +56466,8 @@ var init_run_phase = __esm(() => {
|
|
|
56059
56466
|
captureGitRef,
|
|
56060
56467
|
prepareSemanticReviewInput,
|
|
56061
56468
|
prepareAdversarialReviewInput,
|
|
56062
|
-
runNonBlockingFix
|
|
56469
|
+
runNonBlockingFix,
|
|
56470
|
+
triage: productionTriageSeam
|
|
56063
56471
|
};
|
|
56064
56472
|
});
|
|
56065
56473
|
|
|
@@ -56077,10 +56485,63 @@ function gatherRectificationFindings(phaseOutputs, phases, state) {
|
|
|
56077
56485
|
for (const phase of phases) {
|
|
56078
56486
|
if (shouldSkipPhaseForRectification(phase, state, phaseOutputs))
|
|
56079
56487
|
continue;
|
|
56080
|
-
|
|
56488
|
+
for (const f of extractPhaseFindings(phaseOutputs[phase.slot.op.name])) {
|
|
56489
|
+
if (f.category === "flaky-test")
|
|
56490
|
+
continue;
|
|
56491
|
+
findings.push(f);
|
|
56492
|
+
}
|
|
56081
56493
|
}
|
|
56082
56494
|
return findings;
|
|
56083
56495
|
}
|
|
56496
|
+
async function triageGateFindings(phaseOutputs, gateName, ctx) {
|
|
56497
|
+
if (!gateName)
|
|
56498
|
+
return { triaged: false, quarantinedKeys: [], skipped: true };
|
|
56499
|
+
const triage = _storyOrchestratorDeps.triage;
|
|
56500
|
+
if (typeof triage !== "function")
|
|
56501
|
+
return { triaged: false, quarantinedKeys: [], skipped: true };
|
|
56502
|
+
const output = phaseOutputs[gateName];
|
|
56503
|
+
if (output === null || output === undefined || typeof output !== "object") {
|
|
56504
|
+
return { triaged: false, quarantinedKeys: [], skipped: true };
|
|
56505
|
+
}
|
|
56506
|
+
const record2 = output;
|
|
56507
|
+
const findings = extractPhaseFindings(output);
|
|
56508
|
+
if (findings.length === 0) {
|
|
56509
|
+
return { triaged: false, quarantinedKeys: [], skipped: true };
|
|
56510
|
+
}
|
|
56511
|
+
const rawOutput = typeof record2.rawOutput === "string" ? record2.rawOutput : "";
|
|
56512
|
+
let triagedFindings;
|
|
56513
|
+
let quarantinedKeys;
|
|
56514
|
+
try {
|
|
56515
|
+
const [triaged, report] = await triage(findings, { ctx, rawOutput });
|
|
56516
|
+
triagedFindings = triaged;
|
|
56517
|
+
quarantinedKeys = report.quarantinedKeys;
|
|
56518
|
+
} catch (err) {
|
|
56519
|
+
getSafeLogger()?.warn("story-orchestrator", "Flake triage threw \u2014 keeping findings blocking (no quarantine)", {
|
|
56520
|
+
storyId: ctx.storyId,
|
|
56521
|
+
gateName,
|
|
56522
|
+
error: err instanceof Error ? err.message : String(err)
|
|
56523
|
+
});
|
|
56524
|
+
return { triaged: false, quarantinedKeys: [], skipped: true };
|
|
56525
|
+
}
|
|
56526
|
+
const allTestRunnersQuarantined = triagedFindings.every((f) => f.source !== "test-runner" || f.category === "flaky-test");
|
|
56527
|
+
const nextRecord = { ...record2, findings: triagedFindings };
|
|
56528
|
+
if (allTestRunnersQuarantined) {
|
|
56529
|
+
nextRecord.success = true;
|
|
56530
|
+
nextRecord.passed = true;
|
|
56531
|
+
}
|
|
56532
|
+
phaseOutputs[gateName] = nextRecord;
|
|
56533
|
+
if (quarantinedKeys.length > 0) {
|
|
56534
|
+
const logger = getSafeLogger();
|
|
56535
|
+
for (const key of quarantinedKeys) {
|
|
56536
|
+
logger?.warn("story-orchestrator", `Flake quarantined: ${key}`, {
|
|
56537
|
+
storyId: ctx.storyId,
|
|
56538
|
+
key,
|
|
56539
|
+
previousFailureCount: findings.length
|
|
56540
|
+
});
|
|
56541
|
+
}
|
|
56542
|
+
}
|
|
56543
|
+
return { triaged: true, quarantinedKeys, skipped: false };
|
|
56544
|
+
}
|
|
56084
56545
|
function collectRectificationPhases(state) {
|
|
56085
56546
|
return [
|
|
56086
56547
|
state.fullSuiteGate,
|
|
@@ -56093,16 +56554,32 @@ function collectRectificationPhases(state) {
|
|
|
56093
56554
|
].filter((phase) => phase !== undefined);
|
|
56094
56555
|
}
|
|
56095
56556
|
async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides) {
|
|
56096
|
-
const
|
|
56557
|
+
const rectification2 = state.rectification;
|
|
56097
56558
|
const baseValidationPhases = collectRectificationPhases(state);
|
|
56098
56559
|
const validationPhases = overrides?.excludePhaseKinds ? baseValidationPhases.filter((p) => !overrides.excludePhaseKinds?.includes(p.kind)) : baseValidationPhases;
|
|
56099
|
-
if (!
|
|
56560
|
+
if (!rectification2 || validationPhases.length === 0) {
|
|
56100
56561
|
return {};
|
|
56101
56562
|
}
|
|
56102
56563
|
if (ctx.runtime.signal?.aborted) {
|
|
56103
56564
|
return {};
|
|
56104
56565
|
}
|
|
56105
|
-
|
|
56566
|
+
let initialFindings;
|
|
56567
|
+
if (overrides?.initialFindings) {
|
|
56568
|
+
initialFindings = [...overrides.initialFindings];
|
|
56569
|
+
} else {
|
|
56570
|
+
const gateName = state.fullSuiteGate?.slot.op.name;
|
|
56571
|
+
if (overrides?.skipGateTriage !== true) {
|
|
56572
|
+
const triageReport = await triageGateFindings(phaseOutputs, gateName, ctx);
|
|
56573
|
+
if (triageReport.skipped && gateName !== undefined) {
|
|
56574
|
+
getSafeLogger()?.debug("story-orchestrator", "Gate triage skipped \u2014 passthrough to fix cycle", {
|
|
56575
|
+
storyId: ctx.storyId,
|
|
56576
|
+
gateName,
|
|
56577
|
+
triaged: triageReport.triaged
|
|
56578
|
+
});
|
|
56579
|
+
}
|
|
56580
|
+
}
|
|
56581
|
+
initialFindings = gatherRectificationFindings(phaseOutputs, validationPhases, state);
|
|
56582
|
+
}
|
|
56106
56583
|
if (initialFindings.length === 0) {
|
|
56107
56584
|
return {};
|
|
56108
56585
|
}
|
|
@@ -56117,8 +56594,8 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
|
|
|
56117
56594
|
const cycle = {
|
|
56118
56595
|
findings: [...initialFindings],
|
|
56119
56596
|
iterations: [],
|
|
56120
|
-
strategies: withIncreasingFailuresBail(overrides?.strategies ??
|
|
56121
|
-
config: { maxAttemptsTotal: overrides?.maxAttempts ??
|
|
56597
|
+
strategies: withIncreasingFailuresBail(overrides?.strategies ?? rectification2.strategies, rectification2.abortOnIncreasingFailures, rectification2.consecutiveIncreasesToBail ?? 1),
|
|
56598
|
+
config: { maxAttemptsTotal: overrides?.maxAttempts ?? rectification2.maxAttempts, validatorRetries: 1 },
|
|
56122
56599
|
validate: async (_validateCtx, opts) => {
|
|
56123
56600
|
if (ctx.runtime.signal?.aborted)
|
|
56124
56601
|
return { findings: [], shortCircuited: false };
|
|
@@ -56150,7 +56627,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
|
|
|
56150
56627
|
break;
|
|
56151
56628
|
}
|
|
56152
56629
|
}
|
|
56153
|
-
const postValidateFn = overrides?.postValidate ??
|
|
56630
|
+
const postValidateFn = overrides?.postValidate ?? rectification2.postValidate;
|
|
56154
56631
|
const validated = postValidateFn ? await postValidateFn(findings, _validateCtx) : findings;
|
|
56155
56632
|
return { findings: validated, shortCircuited };
|
|
56156
56633
|
}
|
|
@@ -56270,7 +56747,9 @@ class ExecutionPlan {
|
|
|
56270
56747
|
if (!resumeRectifyUsed) {
|
|
56271
56748
|
resumeRectifyUsed = true;
|
|
56272
56749
|
logger?.info("story-orchestrator", "Phase failed in post-rectification resume \u2014 invoking second rectification pass", { storyId: this.ctx.storyId, phase: name, source: "post-rectification-resume" });
|
|
56273
|
-
const secondRect = await runRectification(this.ctx, this.state, phaseCosts, phaseOutputs
|
|
56750
|
+
const secondRect = await runRectification(this.ctx, this.state, phaseCosts, phaseOutputs, {
|
|
56751
|
+
skipGateTriage: true
|
|
56752
|
+
});
|
|
56274
56753
|
if (secondRect.rectificationExhausted) {
|
|
56275
56754
|
logger?.warn("story-orchestrator", "Second rectification pass exhausted \u2014 terminal failure", {
|
|
56276
56755
|
storyId: this.ctx.storyId,
|
|
@@ -56339,7 +56818,8 @@ class ExecutionPlan {
|
|
|
56339
56818
|
extraRevalidationKinds: nonBlockingExtraPhases(advCfg),
|
|
56340
56819
|
maxAttempts,
|
|
56341
56820
|
postValidate: this.state.nonBlockingFixPostValidate
|
|
56342
|
-
})
|
|
56821
|
+
}),
|
|
56822
|
+
keptTreeRegressed: () => gateName !== undefined && gateRegressedAfterRectification(phaseOutputs[gateName], preRectGateFailureKeys, gateName, this.ctx.storyId)
|
|
56343
56823
|
}, {
|
|
56344
56824
|
measureSourceDiff: createMeasureSourceDiff({
|
|
56345
56825
|
config: this.ctx.runtime.configLoader.current(),
|
|
@@ -57874,6 +58354,27 @@ function parseQueueFile(content) {
|
|
|
57874
58354
|
}
|
|
57875
58355
|
} else if (upper === "SKIP") {
|
|
57876
58356
|
guidance.push(trimmed);
|
|
58357
|
+
} else if (upper.startsWith("RETRY ")) {
|
|
58358
|
+
const storyId = trimmed.substring(6).trim();
|
|
58359
|
+
if (storyId) {
|
|
58360
|
+
commands.push({ type: "RETRY", storyId });
|
|
58361
|
+
} else {
|
|
58362
|
+
guidance.push(trimmed);
|
|
58363
|
+
}
|
|
58364
|
+
} else if (upper === "RETRY") {
|
|
58365
|
+
guidance.push(trimmed);
|
|
58366
|
+
} else if (upper.startsWith("PRIORITY ")) {
|
|
58367
|
+
const rest = trimmed.substring(9).trim();
|
|
58368
|
+
const parts = rest.split(/\s+/);
|
|
58369
|
+
const storyId = parts[0];
|
|
58370
|
+
const value = parts[1] !== undefined ? Number(parts[1]) : Number.NaN;
|
|
58371
|
+
if (storyId && parts.length === 2 && Number.isFinite(value)) {
|
|
58372
|
+
commands.push({ type: "PRIORITY", storyId, value });
|
|
58373
|
+
} else {
|
|
58374
|
+
guidance.push(trimmed);
|
|
58375
|
+
}
|
|
58376
|
+
} else if (upper === "PRIORITY") {
|
|
58377
|
+
guidance.push(trimmed);
|
|
57877
58378
|
} else {
|
|
57878
58379
|
if (inPendingSection) {
|
|
57879
58380
|
guidance.push(trimmed);
|
|
@@ -57974,6 +58475,23 @@ var init_queue_check = __esm(() => {
|
|
|
57974
58475
|
await clearQueueFile(ctx.workdir);
|
|
57975
58476
|
return { action: "pause", reason: "User requested abort" };
|
|
57976
58477
|
}
|
|
58478
|
+
if (cmd.type === "RETRY") {
|
|
58479
|
+
logger.warn("queue", "Retrying story by user request", { storyId: cmd.storyId });
|
|
58480
|
+
resetStoryToPending(ctx.prd, cmd.storyId);
|
|
58481
|
+
const prdPath = ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}/nax/features/unknown/prd.json`;
|
|
58482
|
+
await savePRD(ctx.prd, prdPath);
|
|
58483
|
+
continue;
|
|
58484
|
+
}
|
|
58485
|
+
if (cmd.type === "PRIORITY") {
|
|
58486
|
+
logger.warn("queue", "Setting story priority by user request", {
|
|
58487
|
+
storyId: cmd.storyId,
|
|
58488
|
+
priority: cmd.value
|
|
58489
|
+
});
|
|
58490
|
+
setStoryPriority(ctx.prd, cmd.storyId, cmd.value);
|
|
58491
|
+
const prdPath = ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}/nax/features/unknown/prd.json`;
|
|
58492
|
+
await savePRD(ctx.prd, prdPath);
|
|
58493
|
+
continue;
|
|
58494
|
+
}
|
|
57977
58495
|
if (cmd.type === "SKIP") {
|
|
57978
58496
|
const isTargeted = ctx.stories.some((s) => s.id === cmd.storyId);
|
|
57979
58497
|
if (isTargeted) {
|
|
@@ -58163,6 +58681,7 @@ var init_pipeline = __esm(() => {
|
|
|
58163
58681
|
init_runner4();
|
|
58164
58682
|
init_events();
|
|
58165
58683
|
init_stages();
|
|
58684
|
+
init_queue_check();
|
|
58166
58685
|
init_execution_helpers();
|
|
58167
58686
|
init_event_bus();
|
|
58168
58687
|
});
|
|
@@ -58568,7 +59087,7 @@ __export(exports_init_context, {
|
|
|
58568
59087
|
generatePackageContextTemplate: () => generatePackageContextTemplate,
|
|
58569
59088
|
generateContextTemplate: () => generateContextTemplate
|
|
58570
59089
|
});
|
|
58571
|
-
import { basename as
|
|
59090
|
+
import { basename as basename10, join as join53 } from "path";
|
|
58572
59091
|
async function bunFileExists(path15) {
|
|
58573
59092
|
return Bun.file(path15).exists();
|
|
58574
59093
|
}
|
|
@@ -58663,7 +59182,7 @@ async function scanProject(projectRoot) {
|
|
|
58663
59182
|
const readmeSnippet = await readReadmeSnippet(projectRoot);
|
|
58664
59183
|
const entryPoints = await detectEntryPoints(projectRoot);
|
|
58665
59184
|
const configFiles = await detectConfigFiles(projectRoot);
|
|
58666
|
-
const projectName = packageManifest?.name ||
|
|
59185
|
+
const projectName = packageManifest?.name || basename10(projectRoot);
|
|
58667
59186
|
return {
|
|
58668
59187
|
projectName,
|
|
58669
59188
|
fileTree,
|
|
@@ -60800,11 +61319,11 @@ function getSafeLogger6() {
|
|
|
60800
61319
|
return getSafeLogger();
|
|
60801
61320
|
}
|
|
60802
61321
|
function extractPluginName(pluginPath) {
|
|
60803
|
-
const
|
|
60804
|
-
if (
|
|
61322
|
+
const basename12 = path18.basename(pluginPath);
|
|
61323
|
+
if (basename12 === "index.ts" || basename12 === "index.js" || basename12 === "index.mjs") {
|
|
60805
61324
|
return path18.basename(path18.dirname(pluginPath));
|
|
60806
61325
|
}
|
|
60807
|
-
return
|
|
61326
|
+
return basename12.replace(/\.(ts|js|mjs)$/, "");
|
|
60808
61327
|
}
|
|
60809
61328
|
async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins, isTestFileFn) {
|
|
60810
61329
|
const loadedPlugins = [];
|
|
@@ -61190,7 +61709,7 @@ var package_default;
|
|
|
61190
61709
|
var init_package = __esm(() => {
|
|
61191
61710
|
package_default = {
|
|
61192
61711
|
name: "@nathapp/nax",
|
|
61193
|
-
version: "0.
|
|
61712
|
+
version: "0.71.1",
|
|
61194
61713
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
61195
61714
|
type: "module",
|
|
61196
61715
|
bin: {
|
|
@@ -61201,7 +61720,7 @@ var init_package = __esm(() => {
|
|
|
61201
61720
|
dev: "bun run bin/nax.ts",
|
|
61202
61721
|
build: 'bun build bin/nax.ts --outdir dist --target bun --define "GIT_COMMIT=\\"$(git rev-parse --short HEAD)\\""',
|
|
61203
61722
|
typecheck: "bun x tsc --noEmit && bun x tsc --noEmit -p tsconfig.contracts.json",
|
|
61204
|
-
lint: "bun x biome check src/ bin/ && bun run check:no-real-global-nax && bun run check:alias-internals && bun run check:deep-relatives && bun run check:nax-error && bun run check:logger-storyid && bun run check:file-sizes",
|
|
61723
|
+
lint: "bun x biome check src/ bin/ && bun run check:no-real-global-nax && bun run check:alias-internals && bun run check:deep-relatives && bun run check:nax-error && bun run check:logger-storyid && bun run check:log-format-layering && bun run check:file-sizes",
|
|
61205
61724
|
"lint:json": "bun x biome check src/ bin/ --reporter json && bun run check:nax-error 1>&2 && bun run check:logger-storyid 1>&2",
|
|
61206
61725
|
"lint:fix": "bun x biome check --write src/ bin/",
|
|
61207
61726
|
"check:no-real-global-nax": "bun run scripts/check-no-real-global-nax.ts",
|
|
@@ -61212,6 +61731,7 @@ var init_package = __esm(() => {
|
|
|
61212
61731
|
"check:nax-error:update": "bun run scripts/check-nax-error.ts --update-baseline",
|
|
61213
61732
|
"check:logger-storyid": "bun run scripts/check-logger-storyid.ts",
|
|
61214
61733
|
"check:logger-storyid:update": "bun run scripts/check-logger-storyid.ts --update-baseline",
|
|
61734
|
+
"check:log-format-layering": "bun run scripts/check-log-format-layering.ts",
|
|
61215
61735
|
"check:file-sizes": "bun run scripts/check-file-sizes.ts",
|
|
61216
61736
|
"check:file-sizes:update": "bun run scripts/check-file-sizes.ts --update-baseline",
|
|
61217
61737
|
release: "bun scripts/release.ts",
|
|
@@ -61290,8 +61810,8 @@ var init_version = __esm(() => {
|
|
|
61290
61810
|
NAX_VERSION = package_default.version;
|
|
61291
61811
|
NAX_COMMIT = (() => {
|
|
61292
61812
|
try {
|
|
61293
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
61294
|
-
return "
|
|
61813
|
+
if (/^[0-9a-f]{6,10}$/.test("62eedccd"))
|
|
61814
|
+
return "62eedccd";
|
|
61295
61815
|
} catch {}
|
|
61296
61816
|
try {
|
|
61297
61817
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -62247,10 +62767,134 @@ var init_scratch_purge = __esm(() => {
|
|
|
62247
62767
|
now: () => Date.now()
|
|
62248
62768
|
};
|
|
62249
62769
|
});
|
|
62250
|
-
|
|
62251
|
-
|
|
62252
|
-
|
|
62253
|
-
|
|
62770
|
+
|
|
62771
|
+
// src/execution/lifecycle/backfill-story-metrics.ts
|
|
62772
|
+
function isExecutionFailure(story) {
|
|
62773
|
+
return story != null && (story.status === "failed" || story.status === "regression-failed") && (story.attempts ?? 0) > 0;
|
|
62774
|
+
}
|
|
62775
|
+
function synthesizeBackfillMetric(args) {
|
|
62776
|
+
const { storyId, story, totalCostUsd, config: config2, defaultAgent, timestamp } = args;
|
|
62777
|
+
if (story != null && isExecutionFailure(story)) {
|
|
62778
|
+
const tier = story.routing?.modelTier ?? "balanced";
|
|
62779
|
+
const agent = story.routing?.agent ?? defaultAgent;
|
|
62780
|
+
let modelUsed = agent;
|
|
62781
|
+
try {
|
|
62782
|
+
modelUsed = resolveModelForAgent(config2.models, agent, tier, defaultAgent).model;
|
|
62783
|
+
} catch {}
|
|
62784
|
+
const escalations = story.escalations ?? [];
|
|
62785
|
+
const finalTier = escalations.length > 0 ? escalations[escalations.length - 1].toTier : tier;
|
|
62786
|
+
const attempts = (story.priorFailures?.length ?? 0) + Math.max(1, story.attempts ?? 1);
|
|
62787
|
+
return {
|
|
62788
|
+
storyId,
|
|
62789
|
+
complexity: story.routing?.complexity ?? "medium",
|
|
62790
|
+
initialComplexity: story.routing?.initialComplexity ?? story.routing?.complexity,
|
|
62791
|
+
modelTier: tier,
|
|
62792
|
+
modelUsed,
|
|
62793
|
+
agentUsed: agent,
|
|
62794
|
+
attempts,
|
|
62795
|
+
finalTier,
|
|
62796
|
+
success: false,
|
|
62797
|
+
cost: totalCostUsd,
|
|
62798
|
+
durationMs: 0,
|
|
62799
|
+
firstPassSuccess: false,
|
|
62800
|
+
startedAt: timestamp,
|
|
62801
|
+
completedAt: timestamp,
|
|
62802
|
+
source: "execution-failed",
|
|
62803
|
+
runtimeCrashes: 0
|
|
62804
|
+
};
|
|
62805
|
+
}
|
|
62806
|
+
return {
|
|
62807
|
+
storyId,
|
|
62808
|
+
complexity: story?.routing?.complexity ?? "medium",
|
|
62809
|
+
modelTier: "balanced",
|
|
62810
|
+
modelUsed: defaultAgent,
|
|
62811
|
+
attempts: 0,
|
|
62812
|
+
finalTier: "balanced",
|
|
62813
|
+
success: story?.passes ?? true,
|
|
62814
|
+
cost: totalCostUsd,
|
|
62815
|
+
durationMs: 0,
|
|
62816
|
+
firstPassSuccess: story?.passes ?? true,
|
|
62817
|
+
startedAt: timestamp,
|
|
62818
|
+
completedAt: timestamp,
|
|
62819
|
+
source: "completion-phase",
|
|
62820
|
+
runtimeCrashes: 0
|
|
62821
|
+
};
|
|
62822
|
+
}
|
|
62823
|
+
var init_backfill_story_metrics = __esm(() => {
|
|
62824
|
+
init_config();
|
|
62825
|
+
});
|
|
62826
|
+
|
|
62827
|
+
// src/execution/lifecycle/run-regression-triage.ts
|
|
62828
|
+
async function runRegressionFlakeTriage(params) {
|
|
62829
|
+
const {
|
|
62830
|
+
regressionFindings,
|
|
62831
|
+
testSummary,
|
|
62832
|
+
rawOutput,
|
|
62833
|
+
config: config2,
|
|
62834
|
+
workdir,
|
|
62835
|
+
testCommand,
|
|
62836
|
+
quarantineMemo,
|
|
62837
|
+
triageFn,
|
|
62838
|
+
flakeDetection
|
|
62839
|
+
} = params;
|
|
62840
|
+
const logger = getSafeLogger();
|
|
62841
|
+
const baselineDiff = await resolveFlakeBaselineDiff(config2, workdir);
|
|
62842
|
+
if (baselineDiff === null) {
|
|
62843
|
+
const untriaged = regressionFindings.filter((f) => f.category === "failed-test");
|
|
62844
|
+
const testFilesInFailures2 = new Set;
|
|
62845
|
+
for (const finding of untriaged) {
|
|
62846
|
+
if (finding.file)
|
|
62847
|
+
testFilesInFailures2.add(finding.file);
|
|
62848
|
+
}
|
|
62849
|
+
return { shortCircuit: false, triagedFailedFindings: untriaged, testFilesInFailures: testFilesInFailures2, quarantineReport: undefined };
|
|
62850
|
+
}
|
|
62851
|
+
const triageResult = await triageFn({
|
|
62852
|
+
findings: regressionFindings,
|
|
62853
|
+
diff: baselineDiff,
|
|
62854
|
+
flakeDetection,
|
|
62855
|
+
baseCommand: testCommand,
|
|
62856
|
+
cwd: workdir,
|
|
62857
|
+
framework: detectFramework(rawOutput),
|
|
62858
|
+
quarantineMemo
|
|
62859
|
+
});
|
|
62860
|
+
const quarantineReport = triageResult.quarantineReport.keys.length > 0 ? triageResult.quarantineReport : undefined;
|
|
62861
|
+
const triagedFailedFindings = triageResult.findings.filter((f) => f.category === "failed-test");
|
|
62862
|
+
logger?.warn("regression", "Regression detected", {
|
|
62863
|
+
failedTests: testSummary.failed,
|
|
62864
|
+
passedTests: testSummary.passed,
|
|
62865
|
+
quarantined: triageResult.quarantineReport.keys.length
|
|
62866
|
+
});
|
|
62867
|
+
const testFilesInFailures = new Set;
|
|
62868
|
+
for (const finding of triagedFailedFindings) {
|
|
62869
|
+
if (finding.file)
|
|
62870
|
+
testFilesInFailures.add(finding.file);
|
|
62871
|
+
}
|
|
62872
|
+
if (testFilesInFailures.size === 0 && triagedFailedFindings.length === 0 && quarantineReport) {
|
|
62873
|
+
logger?.info("regression", "All regression failures quarantined as flaky \u2014 accepting as pass", {
|
|
62874
|
+
quarantined: quarantineReport.keys.length
|
|
62875
|
+
});
|
|
62876
|
+
return {
|
|
62877
|
+
shortCircuit: true,
|
|
62878
|
+
result: {
|
|
62879
|
+
success: true,
|
|
62880
|
+
failedTests: 0,
|
|
62881
|
+
failedTestFiles: [],
|
|
62882
|
+
passedTests: testSummary.passed,
|
|
62883
|
+
rectificationAttempts: 0,
|
|
62884
|
+
affectedStories: [],
|
|
62885
|
+
storyCosts: {},
|
|
62886
|
+
storyDurations: {},
|
|
62887
|
+
storyOutcomes: {},
|
|
62888
|
+
quarantineReport
|
|
62889
|
+
}
|
|
62890
|
+
};
|
|
62891
|
+
}
|
|
62892
|
+
return { shortCircuit: false, triagedFailedFindings, testFilesInFailures, quarantineReport };
|
|
62893
|
+
}
|
|
62894
|
+
var init_run_regression_triage = __esm(() => {
|
|
62895
|
+
init_logger2();
|
|
62896
|
+
init_test_runners();
|
|
62897
|
+
init_verification();
|
|
62254
62898
|
});
|
|
62255
62899
|
|
|
62256
62900
|
// src/execution/lifecycle/run-regression.ts
|
|
@@ -62405,18 +63049,25 @@ async function runDeferredRegression(options) {
|
|
|
62405
63049
|
storyOutcomes: {}
|
|
62406
63050
|
};
|
|
62407
63051
|
}
|
|
62408
|
-
const
|
|
62409
|
-
const
|
|
62410
|
-
|
|
62411
|
-
|
|
62412
|
-
|
|
63052
|
+
const regressionFindings = buildRegressionFindings(testSummary, fullSuiteResult.output);
|
|
63053
|
+
const quarantineMemo = options.quarantineMemo ?? NULL_QUARANTINE_MEMO;
|
|
63054
|
+
const triageOutcome = await runRegressionFlakeTriage({
|
|
63055
|
+
regressionFindings,
|
|
63056
|
+
testSummary,
|
|
63057
|
+
rawOutput: fullSuiteResult.output,
|
|
63058
|
+
config: config2,
|
|
63059
|
+
workdir,
|
|
63060
|
+
testCommand,
|
|
63061
|
+
quarantineMemo,
|
|
63062
|
+
triageFn: _regressionDeps.triageFlakyFindings,
|
|
63063
|
+
flakeDetection: config2.execution.flakeDetection
|
|
62413
63064
|
});
|
|
62414
|
-
|
|
62415
|
-
|
|
62416
|
-
if (failure.file) {
|
|
62417
|
-
testFilesInFailures.add(failure.file);
|
|
62418
|
-
}
|
|
63065
|
+
if (triageOutcome.shortCircuit) {
|
|
63066
|
+
return triageOutcome.result;
|
|
62419
63067
|
}
|
|
63068
|
+
const { testFilesInFailures, quarantineReport } = triageOutcome;
|
|
63069
|
+
const affectedStories = new Set;
|
|
63070
|
+
const affectedStoriesObjs = new Map;
|
|
62420
63071
|
if (testFilesInFailures.size === 0) {
|
|
62421
63072
|
logger?.warn("regression", "No test files found in failures (unmapped)");
|
|
62422
63073
|
for (const story of passedStories) {
|
|
@@ -62459,7 +63110,8 @@ async function runDeferredRegression(options) {
|
|
|
62459
63110
|
affectedStories: Array.from(affectedStories),
|
|
62460
63111
|
storyCosts: {},
|
|
62461
63112
|
storyDurations: {},
|
|
62462
|
-
storyOutcomes: {}
|
|
63113
|
+
storyOutcomes: {},
|
|
63114
|
+
...quarantineReport ? { quarantineReport } : {}
|
|
62463
63115
|
};
|
|
62464
63116
|
}
|
|
62465
63117
|
for (const storyId of affectedStories) {
|
|
@@ -62543,7 +63195,8 @@ async function runDeferredRegression(options) {
|
|
|
62543
63195
|
affectedStories: Array.from(affectedStories),
|
|
62544
63196
|
storyCosts: storyCostAccum,
|
|
62545
63197
|
storyDurations: storyDurationAccum,
|
|
62546
|
-
storyOutcomes: storyOutcomeAccum
|
|
63198
|
+
storyOutcomes: storyOutcomeAccum,
|
|
63199
|
+
...quarantineReport ? { quarantineReport } : {}
|
|
62547
63200
|
};
|
|
62548
63201
|
}
|
|
62549
63202
|
logger?.warn("regression", "Full suite still failing after story rectification \u2014 continuing", {
|
|
@@ -62574,7 +63227,8 @@ async function runDeferredRegression(options) {
|
|
|
62574
63227
|
affectedStories: Array.from(affectedStories),
|
|
62575
63228
|
storyCosts: storyCostAccum,
|
|
62576
63229
|
storyDurations: storyDurationAccum,
|
|
62577
|
-
storyOutcomes: storyOutcomeAccum
|
|
63230
|
+
storyOutcomes: storyOutcomeAccum,
|
|
63231
|
+
...quarantineReport ? { quarantineReport } : {}
|
|
62578
63232
|
};
|
|
62579
63233
|
}
|
|
62580
63234
|
var SYNTHETIC_FINDING_OUTPUT_LIMIT = 2000, _regressionDeps;
|
|
@@ -62587,10 +63241,12 @@ var init_run_regression = __esm(() => {
|
|
|
62587
63241
|
init_test_runners();
|
|
62588
63242
|
init_git();
|
|
62589
63243
|
init_verification();
|
|
63244
|
+
init_run_regression_triage();
|
|
62590
63245
|
_regressionDeps = {
|
|
62591
63246
|
runVerification: fullSuite,
|
|
62592
63247
|
runFixCycle: (cycle, ctx, name) => runFixCycle(cycle, ctx, name),
|
|
62593
|
-
parseTestOutput
|
|
63248
|
+
parseTestOutput,
|
|
63249
|
+
triageFlakyFindings
|
|
62594
63250
|
};
|
|
62595
63251
|
});
|
|
62596
63252
|
|
|
@@ -62614,8 +63270,10 @@ async function handleRunCompletion(options) {
|
|
|
62614
63270
|
workdir,
|
|
62615
63271
|
statusWriter,
|
|
62616
63272
|
config: config2,
|
|
62617
|
-
hooksConfig
|
|
63273
|
+
hooksConfig,
|
|
63274
|
+
exitReason
|
|
62618
63275
|
} = options;
|
|
63276
|
+
let regressionGateFailed = false;
|
|
62619
63277
|
const regressionMode = config2.execution.regressionGate?.mode;
|
|
62620
63278
|
if (options.skipRegression) {} else if ((regressionMode === "deferred" || regressionMode === "per-story") && config2.quality.commands.test) {
|
|
62621
63279
|
statusWriter.setPostRunPhase("regression", { status: "running" });
|
|
@@ -62625,6 +63283,7 @@ async function handleRunCompletion(options) {
|
|
|
62625
63283
|
prd,
|
|
62626
63284
|
workdir,
|
|
62627
63285
|
runtime: options.runtime,
|
|
63286
|
+
quarantineMemo: options.runtime.quarantineMemo,
|
|
62628
63287
|
storyMetrics: options.isSequential === false ? undefined : allStoryMetrics.map((m) => ({
|
|
62629
63288
|
storyId: m.storyId,
|
|
62630
63289
|
completedAt: m.completedAt,
|
|
@@ -62655,6 +63314,7 @@ async function handleRunCompletion(options) {
|
|
|
62655
63314
|
}
|
|
62656
63315
|
}
|
|
62657
63316
|
statusWriter.setRunStatus("failed");
|
|
63317
|
+
regressionGateFailed = true;
|
|
62658
63318
|
if (hooksConfig) {
|
|
62659
63319
|
await _runCompletionDeps.fireHook(hooksConfig, "on-final-regression-fail", {
|
|
62660
63320
|
event: "on-final-regression-fail",
|
|
@@ -62739,22 +63399,14 @@ async function handleRunCompletion(options) {
|
|
|
62739
63399
|
const existingIdx = existingIndex.get(storyId);
|
|
62740
63400
|
if (existingIdx === undefined) {
|
|
62741
63401
|
const story = prd.userStories.find((s) => s.id === storyId);
|
|
62742
|
-
allStoryMetrics.push({
|
|
63402
|
+
allStoryMetrics.push(synthesizeBackfillMetric({
|
|
62743
63403
|
storyId,
|
|
62744
|
-
|
|
62745
|
-
|
|
62746
|
-
|
|
62747
|
-
|
|
62748
|
-
|
|
62749
|
-
|
|
62750
|
-
cost: snap.totalCostUsd,
|
|
62751
|
-
durationMs: 0,
|
|
62752
|
-
firstPassSuccess: story?.passes ?? true,
|
|
62753
|
-
startedAt: completionCompletedAt,
|
|
62754
|
-
completedAt: completionCompletedAt,
|
|
62755
|
-
source: "completion-phase",
|
|
62756
|
-
runtimeCrashes: 0
|
|
62757
|
-
});
|
|
63404
|
+
story,
|
|
63405
|
+
totalCostUsd: snap.totalCostUsd,
|
|
63406
|
+
config: config2,
|
|
63407
|
+
defaultAgent,
|
|
63408
|
+
timestamp: completionCompletedAt
|
|
63409
|
+
}));
|
|
62758
63410
|
} else {
|
|
62759
63411
|
const existing = allStoryMetrics[existingIdx];
|
|
62760
63412
|
if (snap.totalCostUsd > (existing.cost ?? 0)) {
|
|
@@ -62856,7 +63508,7 @@ async function handleRunCompletion(options) {
|
|
|
62856
63508
|
});
|
|
62857
63509
|
statusWriter.setPrd(prd);
|
|
62858
63510
|
statusWriter.setCurrentStory(null);
|
|
62859
|
-
statusWriter.setRunStatus(isComplete(prd) ? "completed" : isStalled(prd) ? "stalled" : "running");
|
|
63511
|
+
statusWriter.setRunStatus(regressionGateFailed ? "failed" : exitReason === "cost-limit" ? "cost-limit" : isComplete(prd) ? "completed" : isStalled(prd) ? "stalled" : "running");
|
|
62860
63512
|
await statusWriter.update(reportedTotal, iterations);
|
|
62861
63513
|
return {
|
|
62862
63514
|
durationMs,
|
|
@@ -62884,6 +63536,7 @@ var init_run_completion = __esm(() => {
|
|
|
62884
63536
|
init_scratch_purge();
|
|
62885
63537
|
init_workspace();
|
|
62886
63538
|
init_smart_runner();
|
|
63539
|
+
init_backfill_story_metrics();
|
|
62887
63540
|
init_run_regression();
|
|
62888
63541
|
_runCompletionDeps = {
|
|
62889
63542
|
runDeferredRegression,
|
|
@@ -62896,7 +63549,8 @@ var init_run_completion = __esm(() => {
|
|
|
62896
63549
|
var exports_headless_formatter = {};
|
|
62897
63550
|
__export(exports_headless_formatter, {
|
|
62898
63551
|
outputRunHeader: () => outputRunHeader,
|
|
62899
|
-
outputRunFooter: () => outputRunFooter
|
|
63552
|
+
outputRunFooter: () => outputRunFooter,
|
|
63553
|
+
outputAdvisoryFindingsSummary: () => outputAdvisoryFindingsSummary
|
|
62900
63554
|
});
|
|
62901
63555
|
async function outputRunHeader(options) {
|
|
62902
63556
|
const { feature, totalStories, pendingStories, workdir, formatterMode } = options;
|
|
@@ -62934,10 +63588,19 @@ function outputRunFooter(options) {
|
|
|
62934
63588
|
});
|
|
62935
63589
|
console.log(summaryOutput);
|
|
62936
63590
|
}
|
|
63591
|
+
function outputAdvisoryFindingsSummary(findings, formatterMode) {
|
|
63592
|
+
if (findings.length === 0 || formatterMode === "json") {
|
|
63593
|
+
return;
|
|
63594
|
+
}
|
|
63595
|
+
const output = formatAdvisorySummary(findings, { mode: formatterMode, useColor: true });
|
|
63596
|
+
if (output) {
|
|
63597
|
+
console.log(output);
|
|
63598
|
+
}
|
|
63599
|
+
}
|
|
62937
63600
|
var init_headless_formatter = __esm(() => {
|
|
62938
|
-
init_source();
|
|
62939
63601
|
init_log_format();
|
|
62940
63602
|
init_version();
|
|
63603
|
+
init_source();
|
|
62941
63604
|
});
|
|
62942
63605
|
|
|
62943
63606
|
// src/execution/batching.ts
|
|
@@ -62982,10 +63645,10 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
|
|
|
62982
63645
|
|
|
62983
63646
|
// src/pipeline/subscribers/events-writer.ts
|
|
62984
63647
|
import { appendFile as appendFile4, mkdir as mkdir13 } from "fs/promises";
|
|
62985
|
-
import { basename as
|
|
63648
|
+
import { basename as basename14, join as join80 } from "path";
|
|
62986
63649
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
62987
63650
|
const logger = getSafeLogger();
|
|
62988
|
-
const project =
|
|
63651
|
+
const project = basename14(workdir);
|
|
62989
63652
|
const eventsDir = join80(getEventsRootDir(), project);
|
|
62990
63653
|
const eventsFile = join80(eventsDir, "events.jsonl");
|
|
62991
63654
|
let dirReady = false;
|
|
@@ -63168,10 +63831,10 @@ var init_interaction2 = __esm(() => {
|
|
|
63168
63831
|
|
|
63169
63832
|
// src/pipeline/subscribers/registry.ts
|
|
63170
63833
|
import { mkdir as mkdir14, writeFile as writeFile2 } from "fs/promises";
|
|
63171
|
-
import { basename as
|
|
63834
|
+
import { basename as basename15, join as join81 } from "path";
|
|
63172
63835
|
function wireRegistry(bus, feature, runId, workdir, outputDir) {
|
|
63173
63836
|
const logger = getSafeLogger();
|
|
63174
|
-
const project =
|
|
63837
|
+
const project = basename15(workdir);
|
|
63175
63838
|
const runDir = join81(getRunsDir(), `${project}-${feature}-${runId}`);
|
|
63176
63839
|
const metaFile = join81(runDir, "meta.json");
|
|
63177
63840
|
const unsub = bus.on("run:started", (_ev) => {
|
|
@@ -96308,9 +96971,9 @@ __export(exports_curator, {
|
|
|
96308
96971
|
});
|
|
96309
96972
|
import { readdirSync as readdirSync9 } from "fs";
|
|
96310
96973
|
import { unlink as unlink4 } from "fs/promises";
|
|
96311
|
-
import { basename as
|
|
96974
|
+
import { basename as basename16, join as join90 } from "path";
|
|
96312
96975
|
function getProjectKey(config2, projectDir) {
|
|
96313
|
-
return config2.name?.trim() ||
|
|
96976
|
+
return config2.name?.trim() || basename16(projectDir);
|
|
96314
96977
|
}
|
|
96315
96978
|
function listRunIds(runsDir) {
|
|
96316
96979
|
try {
|
|
@@ -96632,7 +97295,7 @@ var init_curator2 = __esm(() => {
|
|
|
96632
97295
|
init_source();
|
|
96633
97296
|
import { existsSync as existsSync37, mkdirSync as mkdirSync7 } from "fs";
|
|
96634
97297
|
import { homedir as homedir3 } from "os";
|
|
96635
|
-
import { basename as
|
|
97298
|
+
import { basename as basename17, join as join91 } from "path";
|
|
96636
97299
|
|
|
96637
97300
|
// node_modules/commander/esm.mjs
|
|
96638
97301
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -97203,10 +97866,10 @@ init_config();
|
|
|
97203
97866
|
init_logger2();
|
|
97204
97867
|
init_metrics();
|
|
97205
97868
|
init_runtime();
|
|
97206
|
-
import { basename as
|
|
97869
|
+
import { basename as basename7 } from "path";
|
|
97207
97870
|
async function resolveOutputDir(workdir) {
|
|
97208
97871
|
const config2 = await loadConfig(workdir).catch(() => null);
|
|
97209
|
-
const projectKey = config2?.name?.trim() ||
|
|
97872
|
+
const projectKey = config2?.name?.trim() || basename7(workdir);
|
|
97210
97873
|
return projectOutputDir(projectKey, config2?.outputDir);
|
|
97211
97874
|
}
|
|
97212
97875
|
async function displayCostMetrics(workdir) {
|
|
@@ -97321,7 +97984,7 @@ init_interaction();
|
|
|
97321
97984
|
init_prd();
|
|
97322
97985
|
init_runtime();
|
|
97323
97986
|
import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
|
|
97324
|
-
import { basename as
|
|
97987
|
+
import { basename as basename8, join as join44, resolve as resolve14 } from "path";
|
|
97325
97988
|
var _statusFeaturesDeps = {
|
|
97326
97989
|
projectOutputDir,
|
|
97327
97990
|
loadConfig
|
|
@@ -97348,7 +98011,7 @@ async function loadStatusFile(featureDir) {
|
|
|
97348
98011
|
}
|
|
97349
98012
|
async function loadProjectStatusFile(projectDir) {
|
|
97350
98013
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
97351
|
-
const projectKey = config2?.name?.trim() ||
|
|
98014
|
+
const projectKey = config2?.name?.trim() || basename8(projectDir);
|
|
97352
98015
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
97353
98016
|
const statusPath = join44(outputDir, "status.json");
|
|
97354
98017
|
if (!existsSync17(statusPath)) {
|
|
@@ -97403,6 +98066,8 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
97403
98066
|
pid: status.run.pid,
|
|
97404
98067
|
crashedAt: status.run.crashedAt
|
|
97405
98068
|
};
|
|
98069
|
+
} else if (status.run.status === "cost-limit") {
|
|
98070
|
+
summary.costLimitStopped = true;
|
|
97406
98071
|
}
|
|
97407
98072
|
}
|
|
97408
98073
|
const runsDir = join44(featureDir, "runs");
|
|
@@ -97417,7 +98082,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
97417
98082
|
}
|
|
97418
98083
|
async function displayAllFeatures(projectDir) {
|
|
97419
98084
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
97420
|
-
const projectKey = config2?.name?.trim() ||
|
|
98085
|
+
const projectKey = config2?.name?.trim() || basename8(projectDir);
|
|
97421
98086
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
97422
98087
|
const featuresDir = join44(outputDir, "features");
|
|
97423
98088
|
if (!existsSync17(featuresDir)) {
|
|
@@ -97458,6 +98123,14 @@ async function displayAllFeatures(projectDir) {
|
|
|
97458
98123
|
console.log(source_default.dim(` Signal: ${projectStatus.run.crashSignal}`));
|
|
97459
98124
|
}
|
|
97460
98125
|
console.log();
|
|
98126
|
+
} else if (projectStatus.run.status === "cost-limit") {
|
|
98127
|
+
console.log(source_default.magenta(`\uD83D\uDCB0 Cost Limit Reached:
|
|
98128
|
+
`));
|
|
98129
|
+
console.log(source_default.dim(` Feature: ${projectStatus.run.feature}`));
|
|
98130
|
+
console.log(source_default.dim(` Run ID: ${projectStatus.run.id}`));
|
|
98131
|
+
console.log(source_default.dim(` Spent: $${projectStatus.cost.spent.toFixed(4)}`));
|
|
98132
|
+
console.log(source_default.dim(` Limit: $${(projectStatus.cost.limit ?? 0).toFixed(4)}`));
|
|
98133
|
+
console.log();
|
|
97461
98134
|
}
|
|
97462
98135
|
}
|
|
97463
98136
|
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join44(featuresDir, name))));
|
|
@@ -97478,6 +98151,8 @@ async function displayAllFeatures(projectDir) {
|
|
|
97478
98151
|
status = source_default.yellow("\u26A1 Running");
|
|
97479
98152
|
} else if (summary.crashedRun) {
|
|
97480
98153
|
status = source_default.red("\uD83D\uDCA5 Crashed");
|
|
98154
|
+
} else if (summary.costLimitStopped) {
|
|
98155
|
+
status = source_default.magenta("\uD83D\uDCB0 Cost limit");
|
|
97481
98156
|
} else {
|
|
97482
98157
|
status = source_default.dim("\u2014");
|
|
97483
98158
|
}
|
|
@@ -97630,7 +98305,7 @@ async function displayFeatureStatus(options = {}) {
|
|
|
97630
98305
|
if (options.dir) {
|
|
97631
98306
|
const projectDir = resolve14(options.dir);
|
|
97632
98307
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
97633
|
-
const projectKey = config2?.name?.trim() ||
|
|
98308
|
+
const projectKey = config2?.name?.trim() || basename8(projectDir);
|
|
97634
98309
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
97635
98310
|
featureDir = join44(outputDir, "features", options.feature);
|
|
97636
98311
|
} else {
|
|
@@ -97652,12 +98327,12 @@ init_errors();
|
|
|
97652
98327
|
init_logger2();
|
|
97653
98328
|
init_runtime();
|
|
97654
98329
|
import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
|
|
97655
|
-
import { basename as
|
|
98330
|
+
import { basename as basename9, join as join45 } from "path";
|
|
97656
98331
|
async function resolveOutputDir2(workdir, override) {
|
|
97657
98332
|
if (override)
|
|
97658
98333
|
return override;
|
|
97659
98334
|
const config2 = await loadConfig(workdir).catch(() => null);
|
|
97660
|
-
const projectKey = config2?.name?.trim() ||
|
|
98335
|
+
const projectKey = config2?.name?.trim() || basename9(workdir);
|
|
97661
98336
|
return projectOutputDir(projectKey, config2?.outputDir);
|
|
97662
98337
|
}
|
|
97663
98338
|
async function parseRunLog(logPath) {
|
|
@@ -98044,7 +98719,7 @@ var FIELD_DESCRIPTIONS = {
|
|
|
98044
98719
|
execution: "Execution limits and timeouts",
|
|
98045
98720
|
"execution.maxIterations": "Max iterations per feature run (auto-calculated if not set)",
|
|
98046
98721
|
"execution.iterationDelayMs": "Delay between iterations in milliseconds",
|
|
98047
|
-
"execution.costLimit": "Max cost in USD before pausing execution",
|
|
98722
|
+
"execution.costLimit": "Max cost in USD before pausing execution (override per run with `nax run --max-cost`)",
|
|
98048
98723
|
"execution.sessionTimeoutSeconds": "Timeout per agent coding session in seconds",
|
|
98049
98724
|
"execution.verificationTimeoutSeconds": "Verification subprocess timeout in seconds",
|
|
98050
98725
|
"execution.maxStoriesPerFeature": "Max stories per feature (prevents memory exhaustion)",
|
|
@@ -98092,7 +98767,7 @@ var FIELD_DESCRIPTIONS = {
|
|
|
98092
98767
|
"tdd.autoApproveVerifier": "Auto-approve legitimate fixes in verifier session",
|
|
98093
98768
|
"tdd.sessionTiers": "Per-session model tier overrides",
|
|
98094
98769
|
"tdd.sessionTiers.testWriter": "Model tier for test-writer session",
|
|
98095
|
-
"tdd.sessionTiers.implementer": "
|
|
98770
|
+
"tdd.sessionTiers.implementer": "Ignored by design \u2014 implementer follows story.routing.modelTier + escalation, not this field. Kept optional so legacy configs still parse.",
|
|
98096
98771
|
"tdd.sessionTiers.verifier": "Model tier for verifier session",
|
|
98097
98772
|
"tdd.testWriterAllowedPaths": "Glob patterns for files test-writer can modify",
|
|
98098
98773
|
"tdd.rollbackOnFailure": "Rollback git changes when TDD fails",
|
|
@@ -98695,7 +99370,7 @@ async function contextInspectCommand(options) {
|
|
|
98695
99370
|
init_canonical_loader();
|
|
98696
99371
|
init_errors();
|
|
98697
99372
|
import { mkdir as mkdir11 } from "fs/promises";
|
|
98698
|
-
import { basename as
|
|
99373
|
+
import { basename as basename13, join as join68 } from "path";
|
|
98699
99374
|
var _rulesCLIDeps = {
|
|
98700
99375
|
readFile: async (path20) => Bun.file(path20).text(),
|
|
98701
99376
|
writeFile: async (path20, content) => {
|
|
@@ -98795,7 +99470,7 @@ async function collectMigrationSources(workdir) {
|
|
|
98795
99470
|
try {
|
|
98796
99471
|
const content = await _rulesCLIDeps.readFile(filePath);
|
|
98797
99472
|
if (content.trim()) {
|
|
98798
|
-
sources.push({ sourcePath: filePath, targetFileName:
|
|
99473
|
+
sources.push({ sourcePath: filePath, targetFileName: basename13(filePath), content });
|
|
98799
99474
|
}
|
|
98800
99475
|
} catch {}
|
|
98801
99476
|
}
|
|
@@ -99680,6 +100355,8 @@ function colorStatus(status) {
|
|
|
99680
100355
|
return source_default.red(status);
|
|
99681
100356
|
case "running":
|
|
99682
100357
|
return source_default.yellow(status);
|
|
100358
|
+
case "cost-limit":
|
|
100359
|
+
return source_default.magenta(status);
|
|
99683
100360
|
case "[unavailable]":
|
|
99684
100361
|
return source_default.dim(status);
|
|
99685
100362
|
default:
|
|
@@ -99970,17 +100647,27 @@ async function runCompletionPhase(options) {
|
|
|
99970
100647
|
sessionManager: options.sessionManager,
|
|
99971
100648
|
pluginProviderCache: options.pluginProviderCache,
|
|
99972
100649
|
deferredReview: options.deferredReview,
|
|
100650
|
+
exitReason: options.exitReason,
|
|
99973
100651
|
runtime: options.runtime,
|
|
99974
100652
|
abortSignal: options.abortSignal
|
|
99975
100653
|
});
|
|
99976
100654
|
const { durationMs, runCompletedAt, finalCounts, reportedTotal, pluginGateFailed } = completionResult;
|
|
99977
100655
|
if (options.featureDir) {
|
|
99978
|
-
const finalStatus = isComplete(options.prd)
|
|
100656
|
+
const finalStatus = pluginGateFailed ? "failed" : options.exitReason === "cost-limit" ? "cost-limit" : isComplete(options.prd) ? "completed" : "failed";
|
|
99979
100657
|
options.statusWriter.setRunStatus(finalStatus);
|
|
99980
100658
|
await options.statusWriter.writeFeatureStatus(options.featureDir, reportedTotal, options.iterations);
|
|
99981
100659
|
}
|
|
100660
|
+
const advisoryFindings = options.runtime?.reviewAuditor?.getAdvisoryFindings() ?? [];
|
|
100661
|
+
if (advisoryFindings.length > 0) {
|
|
100662
|
+
logger?.warn("review", `${advisoryFindings.length} non-blocking review finding(s) surfaced at run end`, {
|
|
100663
|
+
storyId: "_run",
|
|
100664
|
+
count: advisoryFindings.length,
|
|
100665
|
+
findings: advisoryFindings
|
|
100666
|
+
});
|
|
100667
|
+
}
|
|
99982
100668
|
if (options.headless && options.formatterMode !== "json") {
|
|
99983
|
-
const { outputRunFooter: outputRunFooter2 } = await Promise.resolve().then(() => (init_headless_formatter(), exports_headless_formatter));
|
|
100669
|
+
const { outputAdvisoryFindingsSummary: outputAdvisoryFindingsSummary2, outputRunFooter: outputRunFooter2 } = await Promise.resolve().then(() => (init_headless_formatter(), exports_headless_formatter));
|
|
100670
|
+
outputAdvisoryFindingsSummary2(advisoryFindings, options.formatterMode);
|
|
99984
100671
|
outputRunFooter2({
|
|
99985
100672
|
finalCounts: {
|
|
99986
100673
|
total: finalCounts.total,
|
|
@@ -100175,7 +100862,8 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
100175
100862
|
storiesCompleted,
|
|
100176
100863
|
totalCost,
|
|
100177
100864
|
allStoryMetrics,
|
|
100178
|
-
deferredReview: unifiedResult.deferredReview
|
|
100865
|
+
deferredReview: unifiedResult.deferredReview,
|
|
100866
|
+
exitReason: unifiedResult.exitReason
|
|
100179
100867
|
};
|
|
100180
100868
|
}
|
|
100181
100869
|
|
|
@@ -100344,6 +101032,7 @@ async function run(options) {
|
|
|
100344
101032
|
agentManager,
|
|
100345
101033
|
pluginProviderCache,
|
|
100346
101034
|
deferredReview: executionResult.deferredReview,
|
|
101035
|
+
exitReason: executionResult.exitReason,
|
|
100347
101036
|
runtime,
|
|
100348
101037
|
abortSignal: shutdownController.signal
|
|
100349
101038
|
});
|
|
@@ -100411,6 +101100,7 @@ init_pid_registry();
|
|
|
100411
101100
|
init_acceptance_loop();
|
|
100412
101101
|
init_headless_formatter();
|
|
100413
101102
|
init_run_completion();
|
|
101103
|
+
init_backfill_story_metrics();
|
|
100414
101104
|
init_run_cleanup();
|
|
100415
101105
|
init_run_setup();
|
|
100416
101106
|
init_run_regression();
|
|
@@ -100428,6 +101118,128 @@ init_logger2();
|
|
|
100428
101118
|
init_prd();
|
|
100429
101119
|
init_runtime();
|
|
100430
101120
|
|
|
101121
|
+
// src/schedule/parse.ts
|
|
101122
|
+
var ACCEPTED = "Accepted: relative (30m, 2h, 90s, 1h30m, 1d), time-of-day (17:00), or ISO datetime (2026-07-02T02:00).";
|
|
101123
|
+
var UNIT_MS = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
101124
|
+
var DURATION_RE = /^(\d+[smhd])+$/;
|
|
101125
|
+
var SEGMENT_RE = /(\d+)([smhd])/g;
|
|
101126
|
+
var TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
101127
|
+
var NAIVE_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/;
|
|
101128
|
+
var DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
101129
|
+
var OFFSET_RE = /T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})$/;
|
|
101130
|
+
function parseTimeOfDay(input, now2) {
|
|
101131
|
+
const m = TIME_RE.exec(input);
|
|
101132
|
+
if (!m)
|
|
101133
|
+
return null;
|
|
101134
|
+
const hours = Number(m[1]);
|
|
101135
|
+
const minutes = Number(m[2]);
|
|
101136
|
+
const target = new Date(now2.getFullYear(), now2.getMonth(), now2.getDate(), hours, minutes, 0, 0);
|
|
101137
|
+
if (target.getTime() <= now2.getTime()) {
|
|
101138
|
+
target.setDate(target.getDate() + 1);
|
|
101139
|
+
}
|
|
101140
|
+
return { ok: true, target };
|
|
101141
|
+
}
|
|
101142
|
+
function parseIso(input, now2) {
|
|
101143
|
+
if (DATE_ONLY_RE.test(input)) {
|
|
101144
|
+
return {
|
|
101145
|
+
ok: false,
|
|
101146
|
+
error: `Date-only value "${input}" has no time \u2014 specify a time, e.g. ${input}T02:00.`
|
|
101147
|
+
};
|
|
101148
|
+
}
|
|
101149
|
+
let target = null;
|
|
101150
|
+
const naive = NAIVE_RE.exec(input);
|
|
101151
|
+
if (naive) {
|
|
101152
|
+
const [, y, mo, d, h, mi, s] = naive;
|
|
101153
|
+
target = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s ?? "0"), 0);
|
|
101154
|
+
} else if (OFFSET_RE.test(input)) {
|
|
101155
|
+
const parsed = new Date(input);
|
|
101156
|
+
if (!Number.isNaN(parsed.getTime()))
|
|
101157
|
+
target = parsed;
|
|
101158
|
+
}
|
|
101159
|
+
if (!target || Number.isNaN(target.getTime())) {
|
|
101160
|
+
return { ok: false, error: `Unrecognized schedule "${input}". ${ACCEPTED}` };
|
|
101161
|
+
}
|
|
101162
|
+
if (target.getTime() <= now2.getTime()) {
|
|
101163
|
+
return { ok: false, error: `Scheduled time ${input} is in the past.` };
|
|
101164
|
+
}
|
|
101165
|
+
return { ok: true, target };
|
|
101166
|
+
}
|
|
101167
|
+
function parseRelative(input, now2) {
|
|
101168
|
+
if (!DURATION_RE.test(input))
|
|
101169
|
+
return null;
|
|
101170
|
+
let totalMs = 0;
|
|
101171
|
+
for (const [, n, unit] of input.matchAll(SEGMENT_RE)) {
|
|
101172
|
+
totalMs += Number(n) * UNIT_MS[unit];
|
|
101173
|
+
}
|
|
101174
|
+
if (totalMs <= 0)
|
|
101175
|
+
return { ok: false, error: `Duration must be positive. ${ACCEPTED}` };
|
|
101176
|
+
return { ok: true, target: new Date(now2.getTime() + totalMs) };
|
|
101177
|
+
}
|
|
101178
|
+
function resolveScheduleGate(input, now2) {
|
|
101179
|
+
if (input === undefined)
|
|
101180
|
+
return { ok: true, target: null };
|
|
101181
|
+
const parsed = parseSchedule(input, now2);
|
|
101182
|
+
if (!parsed.ok)
|
|
101183
|
+
return { ok: false, error: parsed.error };
|
|
101184
|
+
return { ok: true, target: parsed.target };
|
|
101185
|
+
}
|
|
101186
|
+
function parseSchedule(input, now2) {
|
|
101187
|
+
const trimmed = input.trim();
|
|
101188
|
+
if (trimmed === "")
|
|
101189
|
+
return { ok: false, error: `Empty schedule value. ${ACCEPTED}` };
|
|
101190
|
+
const relative16 = parseRelative(trimmed, now2);
|
|
101191
|
+
if (relative16)
|
|
101192
|
+
return relative16;
|
|
101193
|
+
const timeOfDay = parseTimeOfDay(trimmed, now2);
|
|
101194
|
+
if (timeOfDay)
|
|
101195
|
+
return timeOfDay;
|
|
101196
|
+
return parseIso(trimmed, now2);
|
|
101197
|
+
}
|
|
101198
|
+
// src/schedule/wait.ts
|
|
101199
|
+
init_logger2();
|
|
101200
|
+
init_bun_deps();
|
|
101201
|
+
function formatRemaining(ms) {
|
|
101202
|
+
const total = Math.max(0, Math.floor(ms / 1000));
|
|
101203
|
+
const h = Math.floor(total / 3600);
|
|
101204
|
+
const m = Math.floor(total % 3600 / 60);
|
|
101205
|
+
const s = total % 60;
|
|
101206
|
+
const pad4 = (n) => String(n).padStart(2, "0");
|
|
101207
|
+
return `${pad4(h)}:${pad4(m)}:${pad4(s)}`;
|
|
101208
|
+
}
|
|
101209
|
+
var TICK_MS = 1000;
|
|
101210
|
+
var DEFAULT_DEPS2 = {
|
|
101211
|
+
now: () => Date.now(),
|
|
101212
|
+
delay: (ms, signal) => cancellableDelay(ms, signal),
|
|
101213
|
+
render: (line) => {
|
|
101214
|
+
process.stdout.write(`\r${line}`);
|
|
101215
|
+
},
|
|
101216
|
+
log: (message, data) => getSafeLogger()?.info("schedule", message, data)
|
|
101217
|
+
};
|
|
101218
|
+
async function waitForSchedule(target, opts) {
|
|
101219
|
+
const deps = { ...DEFAULT_DEPS2, ...opts._deps };
|
|
101220
|
+
const targetMs = target.getTime();
|
|
101221
|
+
if (opts.headless) {
|
|
101222
|
+
deps.log("Scheduled run waiting", { label: opts.label, targetIso: target.toISOString() });
|
|
101223
|
+
}
|
|
101224
|
+
while (deps.now() < targetMs) {
|
|
101225
|
+
if (opts.signal.aborted)
|
|
101226
|
+
return "cancelled";
|
|
101227
|
+
const remaining = targetMs - deps.now();
|
|
101228
|
+
if (!opts.headless) {
|
|
101229
|
+
deps.render(`[WAIT] Scheduled run of "${opts.label}" \u2014 starting in ${formatRemaining(remaining)} (Ctrl-C to cancel)`);
|
|
101230
|
+
}
|
|
101231
|
+
const wait = Math.min(TICK_MS, remaining);
|
|
101232
|
+
try {
|
|
101233
|
+
await deps.delay(wait, opts.signal);
|
|
101234
|
+
} catch {
|
|
101235
|
+
return "cancelled";
|
|
101236
|
+
}
|
|
101237
|
+
}
|
|
101238
|
+
if (!opts.headless)
|
|
101239
|
+
deps.render(`
|
|
101240
|
+
`);
|
|
101241
|
+
return "fired";
|
|
101242
|
+
}
|
|
100431
101243
|
// node_modules/ink/build/render.js
|
|
100432
101244
|
import { Stream } from "stream";
|
|
100433
101245
|
import process14 from "process";
|
|
@@ -106591,6 +107403,12 @@ async function writeQueueCommand(queueFilePath, command) {
|
|
|
106591
107403
|
case "SKIP":
|
|
106592
107404
|
commandLine2 = `SKIP ${command.storyId}`;
|
|
106593
107405
|
break;
|
|
107406
|
+
case "RETRY":
|
|
107407
|
+
commandLine2 = `RETRY ${command.storyId}`;
|
|
107408
|
+
break;
|
|
107409
|
+
case "PRIORITY":
|
|
107410
|
+
commandLine2 = `PRIORITY ${command.storyId} ${command.value}`;
|
|
107411
|
+
break;
|
|
106594
107412
|
default: {
|
|
106595
107413
|
const _exhaustive = command;
|
|
106596
107414
|
throw new Error(`Unhandled queue command: ${_exhaustive}`);
|
|
@@ -108456,7 +109274,7 @@ program2.command("setup").description("Analyze repo and generate .nax/config.jso
|
|
|
108456
109274
|
});
|
|
108457
109275
|
process.exit(exitCode);
|
|
108458
109276
|
});
|
|
108459
|
-
program2.command("run").description("Run the orchestration loop for a feature").requiredOption("-f, --feature <name>", "Feature name").option("-a, --agent <name>", "Force a specific agent").option("-m, --max-iterations <n>", "Max iterations", "20").option("--dry-run", "Show plan without executing", false).option("--no-context", "Disable context builder (skip file context in prompts)").option("--no-batch", "Disable story batching (execute all stories individually)").option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)").option("--plan", "Run plan phase first before execution", false).option("--from <spec-path>", "Path to spec file (required when --plan is used)").option("--one-shot", "Skip interactive planning Q&A, use single LLM call (ACP only)", false).option("--force", "Force overwrite existing prd.json when using --plan", false).option("--headless", "Force headless mode (disable TUI, use pipe mode)", false).option("--verbose", "Enable verbose logging (debug level)", false).option("--quiet", "Quiet mode (warnings and errors only)", false).option("--silent", "Silent mode (errors only)", false).option("--json", "JSON mode (raw JSONL output to stdout)", false).option("-d, --dir <path>", "Working directory", process.cwd()).option("--skip-precheck", "Skip precheck validations (advanced users only)", false).option("--profile <name>", "Profile(s) to overlay (comma-separated or repeated; later overrides earlier)", collectProfile, []).action(async (options) => {
|
|
109277
|
+
program2.command("run").description("Run the orchestration loop for a feature").requiredOption("-f, --feature <name>", "Feature name").option("-a, --agent <name>", "Force a specific agent").option("-m, --max-iterations <n>", "Max iterations", "20").option("--max-cost <usd>", "Override cost limit (USD) for this run \u2014 aborts execution when exceeded").option("--dry-run", "Show plan without executing", false).option("--no-context", "Disable context builder (skip file context in prompts)").option("--no-batch", "Disable story batching (execute all stories individually)").option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)").option("--plan", "Run plan phase first before execution", false).option("--from <spec-path>", "Path to spec file (required when --plan is used)").option("--one-shot", "Skip interactive planning Q&A, use single LLM call (ACP only)", false).option("--force", "Force overwrite existing prd.json when using --plan", false).option("--headless", "Force headless mode (disable TUI, use pipe mode)", false).option("--verbose", "Enable verbose logging (debug level)", false).option("--quiet", "Quiet mode (warnings and errors only)", false).option("--silent", "Silent mode (errors only)", false).option("--json", "JSON mode (raw JSONL output to stdout)", false).option("-d, --dir <path>", "Working directory", process.cwd()).option("--skip-precheck", "Skip precheck validations (advanced users only)", false).option("--profile <name>", "Profile(s) to overlay (comma-separated or repeated; later overrides earlier)", collectProfile, []).option("--schedule <when>", "Defer run start until <when> (e.g. 30m, 1h30m, 17:00, 2026-07-02T02:00)").action(async (options) => {
|
|
108460
109278
|
let workdir;
|
|
108461
109279
|
try {
|
|
108462
109280
|
workdir = validateDirectory(options.dir);
|
|
@@ -108464,6 +109282,11 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
108464
109282
|
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
108465
109283
|
process.exit(1);
|
|
108466
109284
|
}
|
|
109285
|
+
const scheduleGate = resolveScheduleGate(options.schedule, new Date);
|
|
109286
|
+
if (!scheduleGate.ok) {
|
|
109287
|
+
console.error(source_default.red(`Invalid --schedule: ${scheduleGate.error}`));
|
|
109288
|
+
process.exit(1);
|
|
109289
|
+
}
|
|
108467
109290
|
if (options.plan && !options.from) {
|
|
108468
109291
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
108469
109292
|
process.exit(1);
|
|
@@ -108578,7 +109401,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
108578
109401
|
process.exit(1);
|
|
108579
109402
|
}
|
|
108580
109403
|
resetLogger();
|
|
108581
|
-
const projectKey = config2.name?.trim() ||
|
|
109404
|
+
const projectKey = config2.name?.trim() || basename17(workdir);
|
|
108582
109405
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
108583
109406
|
const runsDir = join91(outputDir, "features", options.feature, "runs");
|
|
108584
109407
|
mkdirSync7(runsDir, { recursive: true });
|
|
@@ -108601,6 +109424,14 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
108601
109424
|
config2.agent.default = options.agent;
|
|
108602
109425
|
}
|
|
108603
109426
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
109427
|
+
if (options.maxCost !== undefined) {
|
|
109428
|
+
const maxCost = Number(options.maxCost);
|
|
109429
|
+
if (!Number.isFinite(maxCost) || maxCost <= 0) {
|
|
109430
|
+
console.error(source_default.red("--max-cost must be a positive number"));
|
|
109431
|
+
process.exit(1);
|
|
109432
|
+
}
|
|
109433
|
+
config2.execution.costLimit = maxCost;
|
|
109434
|
+
}
|
|
108604
109435
|
const globalNaxDir = join91(homedir3(), ".nax");
|
|
108605
109436
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
108606
109437
|
const eventEmitter = new PipelineEventEmitter;
|
|
@@ -108635,6 +109466,22 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
108635
109466
|
process.exit(1);
|
|
108636
109467
|
}
|
|
108637
109468
|
}
|
|
109469
|
+
if (scheduleGate.target) {
|
|
109470
|
+
const scheduleController = new AbortController;
|
|
109471
|
+
const onSigint = () => scheduleController.abort();
|
|
109472
|
+
process.once("SIGINT", onSigint);
|
|
109473
|
+
const outcome = await waitForSchedule(scheduleGate.target, {
|
|
109474
|
+
label: options.feature,
|
|
109475
|
+
headless: useHeadless || formatterMode === "json",
|
|
109476
|
+
signal: scheduleController.signal
|
|
109477
|
+
});
|
|
109478
|
+
process.removeListener("SIGINT", onSigint);
|
|
109479
|
+
if (outcome === "cancelled") {
|
|
109480
|
+
console.log(source_default.dim(`
|
|
109481
|
+
Scheduled run cancelled.`));
|
|
109482
|
+
process.exit(0);
|
|
109483
|
+
}
|
|
109484
|
+
}
|
|
108638
109485
|
const result2 = await run({
|
|
108639
109486
|
prdPath,
|
|
108640
109487
|
workdir,
|
|
@@ -109105,7 +109952,7 @@ program2.command("unlock").description("Release stale lock from crashed nax proc
|
|
|
109105
109952
|
process.exit(1);
|
|
109106
109953
|
}
|
|
109107
109954
|
});
|
|
109108
|
-
var runs = program2.command("runs").description("Show all registered runs from the central registry (~/.nax/runs/)").option("--project <name>", "Filter by project name").option("--last <N>", "Limit to N most recent runs (default: 20)").option("--status <status>", "Filter by status (running|completed|failed|crashed)").action(async (options) => {
|
|
109955
|
+
var runs = program2.command("runs").description("Show all registered runs from the central registry (~/.nax/runs/)").option("--project <name>", "Filter by project name").option("--last <N>", "Limit to N most recent runs (default: 20)").option("--status <status>", "Filter by status (running|completed|failed|crashed|cost-limit)").action(async (options) => {
|
|
109109
109956
|
try {
|
|
109110
109957
|
await runsCommand({
|
|
109111
109958
|
project: options.project,
|