@nathapp/nax 0.70.6 → 0.70.8
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 +276 -103
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -24693,7 +24693,7 @@ function parsePytestOutput(output) {
|
|
|
24693
24693
|
const failures = [];
|
|
24694
24694
|
for (const line of output.split(`
|
|
24695
24695
|
`)) {
|
|
24696
|
-
const m = line.match(/^FAILED\s+(\S+)(?:\s+-\s+(.*))?$/);
|
|
24696
|
+
const m = line.match(/^(?:\S+:\s+)?FAILED\s+(\S+)(?:\s+-\s+(.*))?$/);
|
|
24697
24697
|
if (m) {
|
|
24698
24698
|
const [, location, reason] = m;
|
|
24699
24699
|
const parts = location.split("::");
|
|
@@ -24705,6 +24705,28 @@ function parsePytestOutput(output) {
|
|
|
24705
24705
|
});
|
|
24706
24706
|
}
|
|
24707
24707
|
}
|
|
24708
|
+
const PLACEHOLDER_REASON = "Collection/import error";
|
|
24709
|
+
const errorByFile = new Map;
|
|
24710
|
+
for (const line of output.split(`
|
|
24711
|
+
`)) {
|
|
24712
|
+
const collecting = line.match(/^(?:\S+:\s+)?_*\s*ERROR\s+collecting\s+(\S+\.\w+)/);
|
|
24713
|
+
const summary = line.match(/^(?:\S+:\s+)?ERROR\s+(\S+\.\w+(?:::\S+)?)\s+-\s+(.*)/);
|
|
24714
|
+
const location = collecting?.[1] ?? summary?.[1];
|
|
24715
|
+
if (!location)
|
|
24716
|
+
continue;
|
|
24717
|
+
const reason = summary?.[2]?.trim();
|
|
24718
|
+
const file3 = location.split("::")[0] ?? location;
|
|
24719
|
+
const existing = errorByFile.get(file3);
|
|
24720
|
+
if (existing && existing.error !== PLACEHOLDER_REASON)
|
|
24721
|
+
continue;
|
|
24722
|
+
errorByFile.set(file3, {
|
|
24723
|
+
file: file3,
|
|
24724
|
+
testName: `collection error: ${file3}`,
|
|
24725
|
+
error: reason || PLACEHOLDER_REASON,
|
|
24726
|
+
stackTrace: []
|
|
24727
|
+
});
|
|
24728
|
+
}
|
|
24729
|
+
failures.push(...errorByFile.values());
|
|
24708
24730
|
const verboseStacks = parsePytestVerboseStacks(output);
|
|
24709
24731
|
for (const failure of failures) {
|
|
24710
24732
|
const leafName = failure.testName.split(" > ").pop() ?? failure.testName;
|
|
@@ -24808,6 +24830,10 @@ function parseCommonOutput(output) {
|
|
|
24808
24830
|
failed = Number.parseInt(failMatches[failMatches.length - 1][1], 10);
|
|
24809
24831
|
}
|
|
24810
24832
|
}
|
|
24833
|
+
const errorMatches = Array.from(output.matchAll(/(\d+)\s+errors?\b(?=[^\n]*\bin\s+[\d.]+\s*s\b)/gi));
|
|
24834
|
+
if (errorMatches.length > 0) {
|
|
24835
|
+
failed += Number.parseInt(errorMatches[errorMatches.length - 1][1], 10);
|
|
24836
|
+
}
|
|
24811
24837
|
return { passed, failed, failures: [] };
|
|
24812
24838
|
}
|
|
24813
24839
|
function formatFailureSummary(failures, maxChars = 2000) {
|
|
@@ -24860,9 +24886,10 @@ var init_parser = __esm(() => {
|
|
|
24860
24886
|
|
|
24861
24887
|
// src/test-runners/ac-parser.ts
|
|
24862
24888
|
function parseTestFailures(output) {
|
|
24863
|
-
const
|
|
24889
|
+
const clean = output.replace(ANSI_ESCAPE_PATTERN, "");
|
|
24890
|
+
const framework = detectFramework(clean);
|
|
24864
24891
|
const failedACs = [];
|
|
24865
|
-
const lines =
|
|
24892
|
+
const lines = clean.split(`
|
|
24866
24893
|
`);
|
|
24867
24894
|
for (const line of lines) {
|
|
24868
24895
|
if (framework === "bun" || framework === "unknown") {
|
|
@@ -24896,7 +24923,7 @@ function parseTestFailures(output) {
|
|
|
24896
24923
|
}
|
|
24897
24924
|
}
|
|
24898
24925
|
if (framework === "jest" || framework === "vitest" || framework === "unknown") {
|
|
24899
|
-
if (/[\u25CF\u00D7\u2715]/.test(line)) {
|
|
24926
|
+
if (/[\u25CF\u00D7\u2715]/.test(line) || /^\s*FAIL\s/.test(line)) {
|
|
24900
24927
|
const acMatch = line.match(/AC[-_]?(\d+)/i);
|
|
24901
24928
|
if (acMatch) {
|
|
24902
24929
|
const acId = `AC-${acMatch[1]}`;
|
|
@@ -24913,8 +24940,10 @@ function parseTestFailures(output) {
|
|
|
24913
24940
|
}
|
|
24914
24941
|
return failedACs;
|
|
24915
24942
|
}
|
|
24943
|
+
var ANSI_ESCAPE_PATTERN;
|
|
24916
24944
|
var init_ac_parser = __esm(() => {
|
|
24917
24945
|
init_detector2();
|
|
24946
|
+
ANSI_ESCAPE_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
|
|
24918
24947
|
});
|
|
24919
24948
|
|
|
24920
24949
|
// src/utils/git.ts
|
|
@@ -30246,11 +30275,7 @@ async function gitLsFiles2(workdir) {
|
|
|
30246
30275
|
return null;
|
|
30247
30276
|
}
|
|
30248
30277
|
}
|
|
30249
|
-
async function
|
|
30250
|
-
const files = await gitLsFiles2(workdir);
|
|
30251
|
-
if (files !== null) {
|
|
30252
|
-
return files.some((f) => isTestFileByPatterns(f, patterns));
|
|
30253
|
-
}
|
|
30278
|
+
async function hasTestFilesOnDisk(workdir, patterns) {
|
|
30254
30279
|
for (const pattern of patterns) {
|
|
30255
30280
|
const g = new Bun.Glob(pattern);
|
|
30256
30281
|
for await (const path3 of g.scan({ cwd: workdir, onlyFiles: true })) {
|
|
@@ -30261,6 +30286,13 @@ async function hasTestFiles(workdir, patterns) {
|
|
|
30261
30286
|
}
|
|
30262
30287
|
return false;
|
|
30263
30288
|
}
|
|
30289
|
+
async function hasTestFiles(workdir, patterns) {
|
|
30290
|
+
const files = await gitLsFiles2(workdir);
|
|
30291
|
+
if (files !== null) {
|
|
30292
|
+
return files.some((f) => isTestFileByPatterns(f, patterns));
|
|
30293
|
+
}
|
|
30294
|
+
return hasTestFilesOnDisk(workdir, patterns);
|
|
30295
|
+
}
|
|
30264
30296
|
async function isGreenfieldStory(_story, workdir, patterns) {
|
|
30265
30297
|
try {
|
|
30266
30298
|
return !await hasTestFiles(workdir, patterns ?? DEFAULT_TEST_FILE_PATTERNS);
|
|
@@ -30291,6 +30323,7 @@ var init_greenfield = __esm(() => {
|
|
|
30291
30323
|
"out",
|
|
30292
30324
|
"tmp",
|
|
30293
30325
|
"temp",
|
|
30326
|
+
".nax",
|
|
30294
30327
|
".git"
|
|
30295
30328
|
]);
|
|
30296
30329
|
});
|
|
@@ -32865,13 +32898,13 @@ var init_semantic_helpers = __esm(() => {
|
|
|
32865
32898
|
|
|
32866
32899
|
// src/review/semantic-evidence.ts
|
|
32867
32900
|
import { isAbsolute as isAbsolute8 } from "path";
|
|
32868
|
-
async function substantiateSemanticEvidence(findings, diffMode, workdir, storyId, blockingThreshold = "error") {
|
|
32901
|
+
async function substantiateSemanticEvidence(findings, diffMode, workdir, storyId, blockingThreshold = "error", repoRoot) {
|
|
32869
32902
|
if (diffMode !== "ref")
|
|
32870
32903
|
return findings;
|
|
32871
32904
|
return Promise.all(findings.map(async (finding) => {
|
|
32872
32905
|
if (!isBlockingSeverity(finding.severity, blockingThreshold))
|
|
32873
32906
|
return finding;
|
|
32874
|
-
const evidence = await checkFindingEvidence({ finding, workdir });
|
|
32907
|
+
const evidence = await checkFindingEvidence({ finding, workdir, repoRoot });
|
|
32875
32908
|
if (evidence.status !== "unmatched")
|
|
32876
32909
|
return finding;
|
|
32877
32910
|
return downgradeUnsubstantiatedFinding({ finding, storyId, ...evidence });
|
|
@@ -32883,7 +32916,8 @@ async function checkFindingEvidence(opts) {
|
|
|
32883
32916
|
const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
|
|
32884
32917
|
if (!observed)
|
|
32885
32918
|
return { status: "missing-observed", file: file3, line };
|
|
32886
|
-
const
|
|
32919
|
+
const roots = opts.repoRoot && opts.repoRoot !== opts.workdir ? [opts.repoRoot, opts.workdir] : [opts.workdir];
|
|
32920
|
+
const contents = await readSafeFile(roots, file3);
|
|
32887
32921
|
if (contents === null)
|
|
32888
32922
|
return { status: "unreadable", file: file3, line, observed };
|
|
32889
32923
|
return matchesEvidence(contents, observed, line) ? { status: "matched", file: file3, line, observed } : { status: "unmatched", file: file3, line, observed };
|
|
@@ -32912,13 +32946,13 @@ function downgradeUnsubstantiatedFinding(opts) {
|
|
|
32912
32946
|
});
|
|
32913
32947
|
return { ...opts.finding, severity: "unverifiable" };
|
|
32914
32948
|
}
|
|
32915
|
-
async function readSafeFile(
|
|
32916
|
-
const
|
|
32917
|
-
|
|
32918
|
-
|
|
32919
|
-
|
|
32920
|
-
|
|
32921
|
-
|
|
32949
|
+
async function readSafeFile(roots, file3) {
|
|
32950
|
+
for (const root of roots) {
|
|
32951
|
+
const validated = validateModulePath(file3, [root]);
|
|
32952
|
+
if (validated.valid && validated.absolutePath) {
|
|
32953
|
+
try {
|
|
32954
|
+
return await Bun.file(validated.absolutePath).text();
|
|
32955
|
+
} catch {}
|
|
32922
32956
|
}
|
|
32923
32957
|
}
|
|
32924
32958
|
if (isAbsolute8(file3)) {
|
|
@@ -32963,11 +32997,11 @@ function hasInspectionTrail(raw) {
|
|
|
32963
32997
|
return Array.isArray(files) && files.some((f) => typeof f === "string" && f.trim().length > 0);
|
|
32964
32998
|
}
|
|
32965
32999
|
async function substantiateAdversarialFindings(opts) {
|
|
32966
|
-
const { findings, workdir, storyId, blockingThreshold } = opts;
|
|
33000
|
+
const { findings, workdir, storyId, blockingThreshold, repoRoot } = opts;
|
|
32967
33001
|
return Promise.all(findings.map(async (finding) => {
|
|
32968
33002
|
if (!isBlockingSeverity(finding.severity, blockingThreshold))
|
|
32969
33003
|
return finding;
|
|
32970
|
-
const evidence = await checkFindingEvidence({ finding, workdir });
|
|
33004
|
+
const evidence = await checkFindingEvidence({ finding, workdir, repoRoot });
|
|
32971
33005
|
if (evidence.status !== "unmatched" && evidence.status !== "missing-observed")
|
|
32972
33006
|
return finding;
|
|
32973
33007
|
return downgradeUnsubstantiatedFinding({
|
|
@@ -33072,7 +33106,11 @@ async function requoteBlockingAdversarialFindings(findings, ctx) {
|
|
|
33072
33106
|
for (const [index, finding] of next.entries()) {
|
|
33073
33107
|
if (!isBlockingSeverity(finding.severity, threshold))
|
|
33074
33108
|
continue;
|
|
33075
|
-
const initialEvidence = await checkFindingEvidence({
|
|
33109
|
+
const initialEvidence = await checkFindingEvidence({
|
|
33110
|
+
finding,
|
|
33111
|
+
workdir: ctx.input.workdir,
|
|
33112
|
+
repoRoot: ctx.input.repoRoot
|
|
33113
|
+
});
|
|
33076
33114
|
if (initialEvidence.status !== "unmatched")
|
|
33077
33115
|
continue;
|
|
33078
33116
|
if (used >= maxRequotes)
|
|
@@ -33101,7 +33139,8 @@ async function requoteBlockingAdversarialFindings(findings, ctx) {
|
|
|
33101
33139
|
};
|
|
33102
33140
|
const requotedEvidence = await checkFindingEvidence({
|
|
33103
33141
|
finding: updatedFinding,
|
|
33104
|
-
workdir: ctx.input.workdir
|
|
33142
|
+
workdir: ctx.input.workdir,
|
|
33143
|
+
repoRoot: ctx.input.repoRoot
|
|
33105
33144
|
});
|
|
33106
33145
|
if (requotedEvidence.status === "matched") {
|
|
33107
33146
|
getSafeLogger()?.info("review", "Recovered adversarial finding via same-session requote", {
|
|
@@ -33329,6 +33368,7 @@ var init_adversarial_review = __esm(() => {
|
|
|
33329
33368
|
const substantiated = await substantiateAdversarialFindings({
|
|
33330
33369
|
findings,
|
|
33331
33370
|
workdir: input.workdir,
|
|
33371
|
+
repoRoot: input.repoRoot,
|
|
33332
33372
|
storyId: input.story.id,
|
|
33333
33373
|
blockingThreshold: threshold
|
|
33334
33374
|
});
|
|
@@ -34026,6 +34066,7 @@ async function runAdversarialReview(opts) {
|
|
|
34026
34066
|
try {
|
|
34027
34067
|
opResult = await _adversarialDeps.callOp(callCtx, adversarialReviewOp, {
|
|
34028
34068
|
workdir,
|
|
34069
|
+
repoRoot: projectDir ?? workdir,
|
|
34029
34070
|
story,
|
|
34030
34071
|
adversarialConfig,
|
|
34031
34072
|
mode: diffMode,
|
|
@@ -35548,6 +35589,10 @@ var init_decompose2 = __esm(() => {
|
|
|
35548
35589
|
});
|
|
35549
35590
|
|
|
35550
35591
|
// src/routing/classify.ts
|
|
35592
|
+
function isSecurityCriticalStory(title, tags = []) {
|
|
35593
|
+
const text = [title, ...tags ?? []].join(" ").toLowerCase();
|
|
35594
|
+
return SECURITY_KEYWORDS.some((kw) => text.includes(kw)) || PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw));
|
|
35595
|
+
}
|
|
35551
35596
|
function classifyComplexity(title, _description, acceptanceCriteria, tags = []) {
|
|
35552
35597
|
const text = [title, ...acceptanceCriteria ?? [], ...tags ?? []].join(" ").toLowerCase();
|
|
35553
35598
|
if (EXPERT_KEYWORDS.some((kw) => text.includes(kw)))
|
|
@@ -35565,10 +35610,7 @@ function determineTestStrategy(complexity, title, _description, tags = [], tddSt
|
|
|
35565
35610
|
return "tdd-simple";
|
|
35566
35611
|
if (tddStrategy === "off")
|
|
35567
35612
|
return "test-after";
|
|
35568
|
-
|
|
35569
|
-
const isSecurityCritical = SECURITY_KEYWORDS.some((kw) => text.includes(kw));
|
|
35570
|
-
const isPublicApi = PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw));
|
|
35571
|
-
if (isSecurityCritical || isPublicApi)
|
|
35613
|
+
if (isSecurityCriticalStory(title, tags))
|
|
35572
35614
|
return "three-session-tdd";
|
|
35573
35615
|
if (complexity === "expert")
|
|
35574
35616
|
return "three-session-tdd";
|
|
@@ -35956,6 +35998,7 @@ __export(exports_routing, {
|
|
|
35956
35998
|
routeTask: () => routeTask,
|
|
35957
35999
|
routeStory: () => routeStory,
|
|
35958
36000
|
resolveRouting: () => resolveRouting,
|
|
36001
|
+
isSecurityCriticalStory: () => isSecurityCriticalStory,
|
|
35959
36002
|
determineTestStrategy: () => determineTestStrategy,
|
|
35960
36003
|
complexityToModelTier: () => complexityToModelTier,
|
|
35961
36004
|
clearCache: () => clearCache,
|
|
@@ -36801,7 +36844,11 @@ async function requoteBlockingFindings(findings, ctx) {
|
|
|
36801
36844
|
for (const [index, finding] of next.entries()) {
|
|
36802
36845
|
if (!isBlockingSeverity(finding.severity, threshold))
|
|
36803
36846
|
continue;
|
|
36804
|
-
const initialEvidence = await checkFindingEvidence({
|
|
36847
|
+
const initialEvidence = await checkFindingEvidence({
|
|
36848
|
+
finding,
|
|
36849
|
+
workdir: ctx.input.workdir,
|
|
36850
|
+
repoRoot: ctx.input.repoRoot
|
|
36851
|
+
});
|
|
36805
36852
|
if (initialEvidence.status !== "unmatched")
|
|
36806
36853
|
continue;
|
|
36807
36854
|
if (used >= maxRequotes)
|
|
@@ -36831,7 +36878,8 @@ async function requoteBlockingFindings(findings, ctx) {
|
|
|
36831
36878
|
};
|
|
36832
36879
|
const requotedEvidence = await checkFindingEvidence({
|
|
36833
36880
|
finding: updatedFinding,
|
|
36834
|
-
workdir: ctx.input.workdir
|
|
36881
|
+
workdir: ctx.input.workdir,
|
|
36882
|
+
repoRoot: ctx.input.repoRoot
|
|
36835
36883
|
});
|
|
36836
36884
|
if (requotedEvidence.status === "matched") {
|
|
36837
36885
|
getSafeLogger()?.info("review", "Recovered semantic finding via same-session requote", {
|
|
@@ -36967,7 +37015,7 @@ var init_semantic_review = __esm(() => {
|
|
|
36967
37015
|
const threshold = input.blockingThreshold ?? "error";
|
|
36968
37016
|
const findings = parsed.findings;
|
|
36969
37017
|
const sanitized = sanitizeRefModeFindings(findings, input.mode, threshold);
|
|
36970
|
-
const substantiated = await substantiateSemanticEvidence(sanitized, input.mode, input.workdir, input.story.id, threshold);
|
|
37018
|
+
const substantiated = await substantiateSemanticEvidence(sanitized, input.mode, input.workdir, input.story.id, threshold, input.repoRoot);
|
|
36971
37019
|
const { accepted, dropped } = filterByAcGroundingMinimal(substantiated, input.story.acceptanceCriteria);
|
|
36972
37020
|
const blocking = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
36973
37021
|
const passed = parsed.passed && blocking.length === 0;
|
|
@@ -38975,8 +39023,13 @@ var init_greenfield_gate = __esm(() => {
|
|
|
38975
39023
|
config: greenfieldGateConfigSelector,
|
|
38976
39024
|
async execute(input, _ctx) {
|
|
38977
39025
|
const globs = input.resolvedTestPatterns.globs;
|
|
38978
|
-
|
|
38979
|
-
|
|
39026
|
+
let hasTests;
|
|
39027
|
+
try {
|
|
39028
|
+
hasTests = await hasTestFilesOnDisk(input.workdir, globs);
|
|
39029
|
+
} catch {
|
|
39030
|
+
return { success: true, hasPreExistingTests: true };
|
|
39031
|
+
}
|
|
39032
|
+
if (!hasTests) {
|
|
38980
39033
|
return { success: false, hasPreExistingTests: false, pauseReason: "greenfield-no-tests" };
|
|
38981
39034
|
}
|
|
38982
39035
|
return { success: true, hasPreExistingTests: true };
|
|
@@ -38984,6 +39037,33 @@ var init_greenfield_gate = __esm(() => {
|
|
|
38984
39037
|
};
|
|
38985
39038
|
});
|
|
38986
39039
|
|
|
39040
|
+
// src/operations/test-presence-gate.ts
|
|
39041
|
+
var testPresenceGateConfigSelector, testPresenceGateOp;
|
|
39042
|
+
var init_test_presence_gate = __esm(() => {
|
|
39043
|
+
init_config();
|
|
39044
|
+
init_greenfield();
|
|
39045
|
+
testPresenceGateConfigSelector = pickSelector("test-presence-gate", "execution");
|
|
39046
|
+
testPresenceGateOp = {
|
|
39047
|
+
kind: "deterministic",
|
|
39048
|
+
name: "test-presence-gate",
|
|
39049
|
+
stage: "verify",
|
|
39050
|
+
config: testPresenceGateConfigSelector,
|
|
39051
|
+
async execute(input, _ctx) {
|
|
39052
|
+
const globs = input.resolvedTestPatterns.globs;
|
|
39053
|
+
let hasTests;
|
|
39054
|
+
try {
|
|
39055
|
+
hasTests = await hasTestFilesOnDisk(input.workdir, globs);
|
|
39056
|
+
} catch {
|
|
39057
|
+
return { success: true, hasTests: true };
|
|
39058
|
+
}
|
|
39059
|
+
if (!hasTests) {
|
|
39060
|
+
return { success: false, hasTests: false, pauseReason: "no-tests-authored" };
|
|
39061
|
+
}
|
|
39062
|
+
return { success: true, hasTests: true };
|
|
39063
|
+
}
|
|
39064
|
+
};
|
|
39065
|
+
});
|
|
39066
|
+
|
|
38987
39067
|
// src/utils/command-argv.ts
|
|
38988
39068
|
function parseCommandToArgv(command) {
|
|
38989
39069
|
const safeEnv = buildAllowedEnv();
|
|
@@ -40667,6 +40747,7 @@ var init_operations = __esm(() => {
|
|
|
40667
40747
|
init_plan_critic_llm();
|
|
40668
40748
|
init_execution_gates();
|
|
40669
40749
|
init_greenfield_gate();
|
|
40750
|
+
init_test_presence_gate();
|
|
40670
40751
|
init_full_suite_gate();
|
|
40671
40752
|
init_full_suite_rectify();
|
|
40672
40753
|
init_full_suite_rectify_op();
|
|
@@ -42048,6 +42129,7 @@ async function runSemanticReview(opts) {
|
|
|
42048
42129
|
try {
|
|
42049
42130
|
opResult = await _semanticDeps.callOp(callCtx, semanticReviewOp, {
|
|
42050
42131
|
workdir,
|
|
42132
|
+
repoRoot: projectDir ?? workdir,
|
|
42051
42133
|
story,
|
|
42052
42134
|
semanticConfig,
|
|
42053
42135
|
mode: diffMode,
|
|
@@ -42602,6 +42684,7 @@ var init_runner2 = __esm(() => {
|
|
|
42602
42684
|
var init_review = __esm(() => {
|
|
42603
42685
|
init_semantic_helpers();
|
|
42604
42686
|
init_category_fix_target();
|
|
42687
|
+
init_finding_filters();
|
|
42605
42688
|
init_ac_quote_validator();
|
|
42606
42689
|
init_ac_structural_counterfactual();
|
|
42607
42690
|
init_adversarial();
|
|
@@ -42629,6 +42712,13 @@ UNRESOLVED: <brief explanation of which findings conflicted and why they cannot
|
|
|
42629
42712
|
|
|
42630
42713
|
Before emitting UNRESOLVED, confirm none of Exceptions 1\u2013${count} apply.
|
|
42631
42714
|
|
|
42715
|
+
**A missing-test or \`test-gap\` finding is never a false positive because a \`.nax/\` file exists.**
|
|
42716
|
+
\`.nax/\` is nax's own artifact directory; \`.nax-acceptance.test.ts\` is generated scaffolding for the
|
|
42717
|
+
acceptance gate \u2014 it is NOT source-tree test coverage. You may NOT cite any \`.nax/\`-resident file as
|
|
42718
|
+
evidence that an acceptance criterion is already tested, and you may NOT emit UNRESOLVED on that basis.
|
|
42719
|
+
The only valid response to a missing-test finding is to
|
|
42720
|
+
author a real test under the package's resolved test path.
|
|
42721
|
+
|
|
42632
42722
|
## Test-file edit exceptions
|
|
42633
42723
|
|
|
42634
42724
|
The "do not modify test files" rule has ${countWord} narrow escape valves. Each requires a
|
|
@@ -53992,7 +54082,7 @@ ${stderr}`;
|
|
|
53992
54082
|
errorExitCode = exitCode;
|
|
53993
54083
|
allFailedACs.push("AC-ERROR");
|
|
53994
54084
|
allFindings.push(acSentinelToFinding("AC-ERROR", output));
|
|
53995
|
-
failedPackages.push({ testPath, packageDir, testFramework, commandOverride });
|
|
54085
|
+
failedPackages.push({ testPath, packageDir, testFramework, commandOverride, output, failedACs: ["AC-ERROR"] });
|
|
53996
54086
|
continue;
|
|
53997
54087
|
}
|
|
53998
54088
|
for (const acId of actualFailures) {
|
|
@@ -54002,7 +54092,14 @@ ${stderr}`;
|
|
|
54002
54092
|
}
|
|
54003
54093
|
}
|
|
54004
54094
|
if (actualFailures.length > 0) {
|
|
54005
|
-
failedPackages.push({
|
|
54095
|
+
failedPackages.push({
|
|
54096
|
+
testPath,
|
|
54097
|
+
packageDir,
|
|
54098
|
+
testFramework,
|
|
54099
|
+
commandOverride,
|
|
54100
|
+
output,
|
|
54101
|
+
failedACs: actualFailures
|
|
54102
|
+
});
|
|
54006
54103
|
logger.error("acceptance", "Acceptance tests failed", {
|
|
54007
54104
|
storyId: ctx.story.id,
|
|
54008
54105
|
failedACs: actualFailures,
|
|
@@ -55441,6 +55538,7 @@ var init_types9 = __esm(() => {
|
|
|
55441
55538
|
"test-writer",
|
|
55442
55539
|
"greenfield-gate",
|
|
55443
55540
|
"implementer",
|
|
55541
|
+
"test-presence-gate",
|
|
55444
55542
|
"full-suite-gate",
|
|
55445
55543
|
"verifier",
|
|
55446
55544
|
"verify-scoped",
|
|
@@ -55453,6 +55551,7 @@ var init_types9 = __esm(() => {
|
|
|
55453
55551
|
"test-writer": "testWriter",
|
|
55454
55552
|
"greenfield-gate": "greenfieldGate",
|
|
55455
55553
|
implementer: "implementer",
|
|
55554
|
+
"test-presence-gate": "testPresenceGate",
|
|
55456
55555
|
"full-suite-gate": "fullSuiteGate",
|
|
55457
55556
|
verifier: "verifier",
|
|
55458
55557
|
"verify-scoped": "verifyScoped",
|
|
@@ -55595,6 +55694,8 @@ function collectOrderedPhases(state) {
|
|
|
55595
55694
|
return [state.greenfieldGate];
|
|
55596
55695
|
if (kind === "implementer" && state.implementer)
|
|
55597
55696
|
return [state.implementer];
|
|
55697
|
+
if (kind === "test-presence-gate" && state.testPresenceGate)
|
|
55698
|
+
return [state.testPresenceGate];
|
|
55598
55699
|
if (kind === "full-suite-gate" && state.fullSuiteGate)
|
|
55599
55700
|
return [state.fullSuiteGate];
|
|
55600
55701
|
if (kind === "verifier" && state.verifier)
|
|
@@ -56219,10 +56320,11 @@ class ExecutionPlan {
|
|
|
56219
56320
|
}
|
|
56220
56321
|
}
|
|
56221
56322
|
}
|
|
56323
|
+
const storyCurrentlyGreen = !rectResult.rectificationExhausted && Object.entries(phaseOutputs).every(([name, output]) => phasePassed(name, output, this.ctx.storyId));
|
|
56222
56324
|
const advCfg = this.state.adversarialReview ? this.state.nonBlockingFix : undefined;
|
|
56223
56325
|
const advisoryOut = phaseOutputs["adversarial-review"];
|
|
56224
56326
|
const advisoryFindings = advisoryOut?.advisoryFindings ?? [];
|
|
56225
|
-
if (advCfg && this.state.rectification && this.ctx.storyId && shouldRunNonBlockingFix(advCfg, advisoryFindings.length)) {
|
|
56327
|
+
if (advCfg && storyCurrentlyGreen && this.state.rectification && this.ctx.storyId && shouldRunNonBlockingFix(advCfg, advisoryFindings.length)) {
|
|
56226
56328
|
await _storyOrchestratorDeps.runNonBlockingFix({
|
|
56227
56329
|
workdir: this.ctx.packageDir,
|
|
56228
56330
|
storyId: this.ctx.storyId,
|
|
@@ -56333,6 +56435,10 @@ class StoryOrchestratorBuilder {
|
|
|
56333
56435
|
setPhase(this.state, "greenfield-gate", isSlot(value) ? value : { op: greenfieldGateOp, input: value });
|
|
56334
56436
|
return this;
|
|
56335
56437
|
}
|
|
56438
|
+
addTestPresenceGate(value) {
|
|
56439
|
+
setPhase(this.state, "test-presence-gate", isSlot(value) ? value : { op: testPresenceGateOp, input: value });
|
|
56440
|
+
return this;
|
|
56441
|
+
}
|
|
56336
56442
|
addVerifier(value) {
|
|
56337
56443
|
setPhase(this.state, "verifier", isSlot(value) ? value : { op: verifierOp, input: value });
|
|
56338
56444
|
return this;
|
|
@@ -56418,6 +56524,9 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
56418
56524
|
if (inputs.implementer) {
|
|
56419
56525
|
builder.addImplementer(inputs.implementer);
|
|
56420
56526
|
}
|
|
56527
|
+
if (!isThreeSession && inputs.testPresenceGate) {
|
|
56528
|
+
builder.addTestPresenceGate(inputs.testPresenceGate);
|
|
56529
|
+
}
|
|
56421
56530
|
const regressionMode = config2.execution?.regressionGate?.mode ?? "deferred";
|
|
56422
56531
|
if (inputs.fullSuiteGate && (isThreeSession || regressionMode === "per-story")) {
|
|
56423
56532
|
builder.addFullSuiteGate(inputs.fullSuiteGate);
|
|
@@ -56491,7 +56600,12 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
56491
56600
|
const nbStrategies = [];
|
|
56492
56601
|
if (nbf?.enabled && inputs.adversarialReview) {
|
|
56493
56602
|
const nbSink = makeDeclarationSink();
|
|
56494
|
-
if (
|
|
56603
|
+
if (!isThreeSession) {
|
|
56604
|
+
nbStrategies.push(makeAutofixImplementerStrategy(story, config2, nbSink, {
|
|
56605
|
+
includeAdversarialReview: true,
|
|
56606
|
+
promptSeverityFloor: "info"
|
|
56607
|
+
}));
|
|
56608
|
+
} else if (nbf.scope === "source") {
|
|
56495
56609
|
nbStrategies.push(makeAutofixImplementerStrategy(story, config2, nbSink, {
|
|
56496
56610
|
includeAdversarialReview: true,
|
|
56497
56611
|
promptSeverityFloor: "info"
|
|
@@ -56637,6 +56751,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
56637
56751
|
featureContextMarkdown: ctx.featureContextMarkdown,
|
|
56638
56752
|
constitution: ctx.constitution?.content
|
|
56639
56753
|
};
|
|
56754
|
+
const testPresenceGateInput = isSingleSessionTestOwningStrategy(ctx.routing.testStrategy) && resolvedTestPatterns ? { story, workdir: ctx.workdir, resolvedTestPatterns } : undefined;
|
|
56640
56755
|
const _regressionMode = ctx.config.execution?.regressionGate?.mode;
|
|
56641
56756
|
const fullSuiteGateInput = _isTdd || _regressionMode === "per-story" ? {
|
|
56642
56757
|
story,
|
|
@@ -56741,6 +56856,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
56741
56856
|
testWriter: testWriterInput,
|
|
56742
56857
|
greenfieldGate: greenfieldGateInput,
|
|
56743
56858
|
implementer: implementerInput,
|
|
56859
|
+
testPresenceGate: testPresenceGateInput,
|
|
56744
56860
|
fullSuiteGate: fullSuiteGateInput,
|
|
56745
56861
|
verifier: verifierInput,
|
|
56746
56862
|
verifyScoped: verifyScopedInput,
|
|
@@ -56798,6 +56914,11 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason, failure
|
|
|
56798
56914
|
case "review-incomplete":
|
|
56799
56915
|
case "greenfield-no-tests":
|
|
56800
56916
|
return { action: "escalate", reason: buildReason(failureCategory) };
|
|
56917
|
+
case "no-tests-authored":
|
|
56918
|
+
return {
|
|
56919
|
+
action: "escalate",
|
|
56920
|
+
reason: "No test files were authored for this story. You MUST write tests covering every acceptance criterion under the package's resolved test path before implementation is considered complete."
|
|
56921
|
+
};
|
|
56801
56922
|
case "dependency-prep":
|
|
56802
56923
|
return pauseFallback;
|
|
56803
56924
|
default:
|
|
@@ -56902,6 +57023,10 @@ function deriveTddFailureCategory(phaseOutputs, unfixedFindings, gateRegressedDu
|
|
|
56902
57023
|
if (greenfieldOutput?.success === false && greenfieldOutput?.pauseReason === "greenfield-no-tests") {
|
|
56903
57024
|
return "greenfield-no-tests";
|
|
56904
57025
|
}
|
|
57026
|
+
const testPresenceOutput = phaseOutputs[testPresenceGateOp.name];
|
|
57027
|
+
if (testPresenceOutput?.success === false && testPresenceOutput?.pauseReason === "no-tests-authored") {
|
|
57028
|
+
return "no-tests-authored";
|
|
57029
|
+
}
|
|
56905
57030
|
const verifierOutput = phaseOutputs[verifierOp.name];
|
|
56906
57031
|
if (verifierOutput?.success === false) {
|
|
56907
57032
|
if (verifierOutput.failureCategory) {
|
|
@@ -57941,13 +58066,21 @@ var init_routing2 = __esm(() => {
|
|
|
57941
58066
|
});
|
|
57942
58067
|
const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir, resolved?.globs);
|
|
57943
58068
|
if (isGreenfield) {
|
|
57944
|
-
|
|
57945
|
-
|
|
57946
|
-
|
|
57947
|
-
|
|
57948
|
-
|
|
57949
|
-
|
|
57950
|
-
|
|
58069
|
+
if (isSecurityCriticalStory(ctx.story.title, ctx.story.tags)) {
|
|
58070
|
+
logger.info("routing", "Greenfield + security-critical \u2014 keeping three-session strategy", {
|
|
58071
|
+
storyId: ctx.story.id,
|
|
58072
|
+
strategy: routing.testStrategy,
|
|
58073
|
+
scanDir: greenfieldScanDir
|
|
58074
|
+
});
|
|
58075
|
+
} else {
|
|
58076
|
+
logger.info("routing", "Greenfield detected \u2014 forcing tdd-simple strategy", {
|
|
58077
|
+
storyId: ctx.story.id,
|
|
58078
|
+
originalStrategy: routing.testStrategy,
|
|
58079
|
+
scanDir: greenfieldScanDir
|
|
58080
|
+
});
|
|
58081
|
+
routing.testStrategy = "tdd-simple";
|
|
58082
|
+
routing.reasoning = `${routing.reasoning} [GREENFIELD OVERRIDE: No test files exist, using tdd-simple (test-first, single-session) instead of three-session TDD]`;
|
|
58083
|
+
}
|
|
57951
58084
|
}
|
|
57952
58085
|
}
|
|
57953
58086
|
ctx.routing = routing;
|
|
@@ -61057,7 +61190,7 @@ var package_default;
|
|
|
61057
61190
|
var init_package = __esm(() => {
|
|
61058
61191
|
package_default = {
|
|
61059
61192
|
name: "@nathapp/nax",
|
|
61060
|
-
version: "0.70.
|
|
61193
|
+
version: "0.70.8",
|
|
61061
61194
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
61062
61195
|
type: "module",
|
|
61063
61196
|
bin: {
|
|
@@ -61157,8 +61290,8 @@ var init_version = __esm(() => {
|
|
|
61157
61290
|
NAX_VERSION = package_default.version;
|
|
61158
61291
|
NAX_COMMIT = (() => {
|
|
61159
61292
|
try {
|
|
61160
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
61161
|
-
return "
|
|
61293
|
+
if (/^[0-9a-f]{6,10}$/.test("54e59fa6"))
|
|
61294
|
+
return "54e59fa6";
|
|
61162
61295
|
} catch {}
|
|
61163
61296
|
try {
|
|
61164
61297
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -61756,12 +61889,12 @@ __export(exports_acceptance_loop, {
|
|
|
61756
61889
|
isTestLevelFailure: () => isTestLevelFailure,
|
|
61757
61890
|
isStubTestFile: () => isStubTestFile,
|
|
61758
61891
|
buildResult: () => buildResult,
|
|
61892
|
+
_runAcceptanceTestsOnceDeps: () => _runAcceptanceTestsOnceDeps,
|
|
61759
61893
|
_regenerateDeps: () => _regenerateDeps,
|
|
61760
61894
|
_acceptanceLoopDeps: () => _acceptanceLoopDeps,
|
|
61761
61895
|
_acceptanceFixCycleDeps: () => _acceptanceFixCycleDeps
|
|
61762
61896
|
});
|
|
61763
|
-
function resolveAcceptanceFixTarget(acceptanceTestPaths,
|
|
61764
|
-
const failedPackage = failedPackages?.[0];
|
|
61897
|
+
function resolveAcceptanceFixTarget(acceptanceTestPaths, failedPackage, config2) {
|
|
61765
61898
|
const matchedEntry = failedPackage ? acceptanceTestPaths?.find((entry) => entry.testPath === failedPackage.testPath || entry.packageDir === failedPackage.packageDir) : undefined;
|
|
61766
61899
|
const selectedPathEntry = matchedEntry ?? acceptanceTestPaths?.[0];
|
|
61767
61900
|
return {
|
|
@@ -61792,11 +61925,11 @@ function findingsForDiagnosis(failedACs, testOutput, diagnosis) {
|
|
|
61792
61925
|
{ ...f, fixTarget: "test" }
|
|
61793
61926
|
]);
|
|
61794
61927
|
}
|
|
61795
|
-
function buildFixCycleCtx(ctx, runtime, storyId) {
|
|
61928
|
+
function buildFixCycleCtx(ctx, runtime, storyId, packageDir) {
|
|
61796
61929
|
return {
|
|
61797
61930
|
runtime,
|
|
61798
|
-
packageView: runtime.packages.resolve(
|
|
61799
|
-
packageDir
|
|
61931
|
+
packageView: runtime.packages.resolve(packageDir),
|
|
61932
|
+
packageDir,
|
|
61800
61933
|
storyId,
|
|
61801
61934
|
featureName: ctx.feature,
|
|
61802
61935
|
agentName: ctx.agentManager?.getDefault() ?? "claude"
|
|
@@ -61830,9 +61963,10 @@ function buildAcceptanceContext(ctx, prd) {
|
|
|
61830
61963
|
abortSignal: ctx.abortSignal
|
|
61831
61964
|
};
|
|
61832
61965
|
}
|
|
61833
|
-
async function runAcceptanceTestsOnce(ctx, prd) {
|
|
61834
|
-
const
|
|
61835
|
-
const
|
|
61966
|
+
async function runAcceptanceTestsOnce(ctx, prd, packageFilter) {
|
|
61967
|
+
const baseCtx = packageFilter ? { ...ctx, acceptanceTestPaths: packageFilter } : ctx;
|
|
61968
|
+
const acceptanceContext = buildAcceptanceContext(baseCtx, prd);
|
|
61969
|
+
const { acceptanceStage: acceptanceStage2 } = await _runAcceptanceTestsOnceDeps.importAcceptanceStage();
|
|
61836
61970
|
const result = await acceptanceStage2.execute(acceptanceContext);
|
|
61837
61971
|
if (result.action !== "fail")
|
|
61838
61972
|
return { passed: true, failedACs: [], testOutput: "" };
|
|
@@ -61846,7 +61980,7 @@ async function runAcceptanceTestsOnce(ctx, prd) {
|
|
|
61846
61980
|
failedPackages: failures.failedPackages
|
|
61847
61981
|
};
|
|
61848
61982
|
}
|
|
61849
|
-
async function runAcceptanceFixCycle(ctx, prd, initialFailures, diagnosis, acceptanceTestPath, testCommand) {
|
|
61983
|
+
async function runAcceptanceFixCycle(ctx, prd, initialFailures, diagnosis, acceptanceTestPath, testCommand, fixTarget) {
|
|
61850
61984
|
const runtime = ctx.runtime;
|
|
61851
61985
|
if (!runtime) {
|
|
61852
61986
|
return { iterations: [], finalFindings: [], exitReason: "no-strategy" };
|
|
@@ -61854,7 +61988,7 @@ async function runAcceptanceFixCycle(ctx, prd, initialFailures, diagnosis, accep
|
|
|
61854
61988
|
let currentTestOutput = initialFailures.testOutput;
|
|
61855
61989
|
let currentFailedACs = initialFailures.failedACs;
|
|
61856
61990
|
const storyId = prd.userStories[0]?.id ?? "unknown";
|
|
61857
|
-
const cycleCtx = buildFixCycleCtx(ctx, runtime, storyId);
|
|
61991
|
+
const cycleCtx = buildFixCycleCtx(ctx, runtime, storyId, fixTarget?.packageDir ?? ctx.workdir);
|
|
61858
61992
|
const cycle = {
|
|
61859
61993
|
findings: findingsForDiagnosis(initialFailures.failedACs, initialFailures.testOutput, diagnosis),
|
|
61860
61994
|
iterations: [],
|
|
@@ -61892,7 +62026,8 @@ async function runAcceptanceFixCycle(ctx, prd, initialFailures, diagnosis, accep
|
|
|
61892
62026
|
}
|
|
61893
62027
|
],
|
|
61894
62028
|
validate: async (_ctx, _opts) => {
|
|
61895
|
-
const
|
|
62029
|
+
const packageFilter = fixTarget ? ctx.acceptanceTestPaths?.filter((entry) => entry.packageDir === fixTarget.packageDir) : undefined;
|
|
62030
|
+
const result = await runAcceptanceTestsOnce(ctx, prd, packageFilter);
|
|
61896
62031
|
if (result.passed)
|
|
61897
62032
|
return [];
|
|
61898
62033
|
currentTestOutput = result.testOutput;
|
|
@@ -61918,7 +62053,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
61918
62053
|
const storiesCompleted = ctx.storiesCompleted;
|
|
61919
62054
|
const prdDirty = false;
|
|
61920
62055
|
logger?.info("acceptance", "All stories complete, running acceptance validation");
|
|
61921
|
-
const { acceptanceStage: acceptanceStage2 } = await
|
|
62056
|
+
const { acceptanceStage: acceptanceStage2 } = await _runAcceptanceTestsOnceDeps.importAcceptanceStage();
|
|
61922
62057
|
while (acceptanceRetries < maxRetries) {
|
|
61923
62058
|
const firstStory = prd.userStories[0];
|
|
61924
62059
|
const acceptanceContext = buildAcceptanceContext(ctx, prd);
|
|
@@ -61981,40 +62116,55 @@ async function runAcceptanceLoop(ctx) {
|
|
|
61981
62116
|
logger?.error("acceptance", "Runtime not found for diagnosis", { storyId: firstStory?.id });
|
|
61982
62117
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
|
|
61983
62118
|
}
|
|
61984
|
-
const {
|
|
61985
|
-
const testEntries = ctx.acceptanceTestPaths ? await loadAcceptanceTestContent(ctx.acceptanceTestPaths.map((p) => p.testPath)) : [];
|
|
61986
|
-
const effectiveAcceptanceTestPath = acceptanceTestPath || testEntries[0]?.testPath || "";
|
|
61987
|
-
const selectedTestEntry = testEntries.find((entry) => entry.testPath === effectiveAcceptanceTestPath);
|
|
61988
|
-
const testFileContent = selectedTestEntry?.content ?? testEntries[0]?.content ?? "";
|
|
62119
|
+
const failedPkgs = failures.failedPackages && failures.failedPackages.length > 0 ? failures.failedPackages : [{ testPath: "", packageDir: ctx.workdir, output: failures.testOutput, failedACs: failures.failedACs }];
|
|
61989
62120
|
const strategy = ctx.config.acceptance.fix?.strategy ?? "diagnose-first";
|
|
61990
|
-
const
|
|
61991
|
-
|
|
61992
|
-
|
|
61993
|
-
|
|
61994
|
-
|
|
61995
|
-
|
|
61996
|
-
|
|
61997
|
-
|
|
61998
|
-
|
|
61999
|
-
|
|
62000
|
-
|
|
62001
|
-
|
|
62002
|
-
|
|
62003
|
-
|
|
62004
|
-
|
|
62005
|
-
|
|
62006
|
-
|
|
62007
|
-
|
|
62008
|
-
|
|
62009
|
-
|
|
62010
|
-
|
|
62011
|
-
|
|
62012
|
-
|
|
62013
|
-
|
|
62121
|
+
const testEntries = ctx.acceptanceTestPaths ? await _acceptanceLoopDeps.loadAcceptanceTestContent(ctx.acceptanceTestPaths.map((p) => p.testPath)) : [];
|
|
62122
|
+
const remainingFindings = [];
|
|
62123
|
+
let totalInternalIterations = 0;
|
|
62124
|
+
for (const pkg of failedPkgs) {
|
|
62125
|
+
const { acceptanceTestPath, testCommand } = resolveAcceptanceFixTarget(ctx.acceptanceTestPaths, pkg, ctx.config);
|
|
62126
|
+
const effectivePath = acceptanceTestPath || pkg.testPath || testEntries[0]?.testPath || "";
|
|
62127
|
+
const testFileContent = testEntries.find((entry) => entry.testPath === effectivePath)?.content ?? "";
|
|
62128
|
+
const pkgFailures = { failedACs: pkg.failedACs, testOutput: pkg.output };
|
|
62129
|
+
const diagnosis = await resolveAcceptanceDiagnosis({
|
|
62130
|
+
ctx,
|
|
62131
|
+
failures: pkgFailures,
|
|
62132
|
+
totalACs,
|
|
62133
|
+
strategy,
|
|
62134
|
+
semanticVerdicts,
|
|
62135
|
+
diagnosisOpts: {
|
|
62136
|
+
testOutput: pkg.output,
|
|
62137
|
+
testFileContent,
|
|
62138
|
+
acceptanceTestPath: effectivePath,
|
|
62139
|
+
workdir: pkg.packageDir,
|
|
62140
|
+
storyId: firstStory?.id
|
|
62141
|
+
}
|
|
62142
|
+
});
|
|
62143
|
+
logger?.info("acceptance.diagnosis", "Diagnosis resolved", {
|
|
62144
|
+
storyId: firstStory?.id,
|
|
62145
|
+
packageDir: pkg.packageDir,
|
|
62146
|
+
verdict: diagnosis.verdict,
|
|
62147
|
+
confidence: diagnosis.confidence,
|
|
62148
|
+
attempt: acceptanceRetries
|
|
62149
|
+
});
|
|
62150
|
+
const cycleResult = await runAcceptanceFixCycle(ctx, prd, pkgFailures, diagnosis, effectivePath, testCommand, {
|
|
62151
|
+
packageDir: pkg.packageDir,
|
|
62152
|
+
testPath: effectivePath
|
|
62153
|
+
});
|
|
62154
|
+
totalCost += cycleResult.costUsd ?? 0;
|
|
62155
|
+
totalInternalIterations += cycleResult.iterations.length;
|
|
62156
|
+
const pkgResolved = cycleResult.exitReason === "resolved" || cycleResult.finalFindings.length === 0;
|
|
62157
|
+
if (!pkgResolved)
|
|
62158
|
+
remainingFindings.push(...cycleResult.finalFindings);
|
|
62159
|
+
}
|
|
62160
|
+
const finalCheck = await runAcceptanceTestsOnce(ctx, prd);
|
|
62161
|
+
const success2 = finalCheck.passed && remainingFindings.length === 0;
|
|
62162
|
+
const failureMessages = !success2 ? finalCheck.failedACs.length > 0 ? finalCheck.failedACs : remainingFindings.length > 0 ? remainingFindings.map((f) => f.message) : ["acceptance validation failed (unknown cause)"] : undefined;
|
|
62163
|
+
return buildResult(success2, prd, totalCost, iterations, storiesCompleted, prdDirty, failureMessages, acceptanceRetries + totalInternalIterations);
|
|
62014
62164
|
}
|
|
62015
62165
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
62016
62166
|
}
|
|
62017
|
-
var _acceptanceLoopDeps, _acceptanceFixCycleDeps, MAX_STUB_REGENS = 2;
|
|
62167
|
+
var _acceptanceLoopDeps, _acceptanceFixCycleDeps, _runAcceptanceTestsOnceDeps, MAX_STUB_REGENS = 2;
|
|
62018
62168
|
var init_acceptance_loop = __esm(() => {
|
|
62019
62169
|
init_acceptance2();
|
|
62020
62170
|
init_findings();
|
|
@@ -62027,11 +62177,15 @@ var init_acceptance_loop = __esm(() => {
|
|
|
62027
62177
|
init_acceptance_helpers();
|
|
62028
62178
|
init_acceptance_helpers();
|
|
62029
62179
|
_acceptanceLoopDeps = {
|
|
62030
|
-
loadSemanticVerdicts
|
|
62180
|
+
loadSemanticVerdicts,
|
|
62181
|
+
loadAcceptanceTestContent
|
|
62031
62182
|
};
|
|
62032
62183
|
_acceptanceFixCycleDeps = {
|
|
62033
62184
|
runFixCycle
|
|
62034
62185
|
};
|
|
62186
|
+
_runAcceptanceTestsOnceDeps = {
|
|
62187
|
+
importAcceptanceStage: () => Promise.resolve().then(() => (init_acceptance3(), exports_acceptance2))
|
|
62188
|
+
};
|
|
62035
62189
|
});
|
|
62036
62190
|
|
|
62037
62191
|
// src/session/scratch-purge.ts
|
|
@@ -62100,6 +62254,23 @@ var init_verification = __esm(() => {
|
|
|
62100
62254
|
});
|
|
62101
62255
|
|
|
62102
62256
|
// src/execution/lifecycle/run-regression.ts
|
|
62257
|
+
function buildRegressionFindings(summary, rawOutput) {
|
|
62258
|
+
const structured = testSummaryToFindings(summary);
|
|
62259
|
+
if (structured.length > 0)
|
|
62260
|
+
return structured;
|
|
62261
|
+
return [
|
|
62262
|
+
{
|
|
62263
|
+
source: "test-runner",
|
|
62264
|
+
severity: "error",
|
|
62265
|
+
category: "failed-test",
|
|
62266
|
+
rule: "regression-suite",
|
|
62267
|
+
message: `Full test suite is failing but no individual test failures could be parsed from the output. Diagnose and fix the underlying failure. Raw test output:
|
|
62268
|
+
|
|
62269
|
+
${rawOutput.slice(0, SYNTHETIC_FINDING_OUTPUT_LIMIT)}`,
|
|
62270
|
+
fixTarget: "source"
|
|
62271
|
+
}
|
|
62272
|
+
];
|
|
62273
|
+
}
|
|
62103
62274
|
async function findResponsibleStory(testFile, workdir, passedStories) {
|
|
62104
62275
|
const logger = getSafeLogger();
|
|
62105
62276
|
for (let i = passedStories.length - 1;i >= 0; i--) {
|
|
@@ -62311,7 +62482,7 @@ async function runDeferredRegression(options) {
|
|
|
62311
62482
|
maxRectificationAttempts
|
|
62312
62483
|
});
|
|
62313
62484
|
const storyStartMs = Date.now();
|
|
62314
|
-
const initialFindings =
|
|
62485
|
+
const initialFindings = buildRegressionFindings(_regressionDeps.parseTestOutput(currentTestOutput), currentTestOutput);
|
|
62315
62486
|
const packageView = runtime.packages.repo();
|
|
62316
62487
|
const cycleCtx = {
|
|
62317
62488
|
runtime,
|
|
@@ -62332,7 +62503,7 @@ async function runDeferredRegression(options) {
|
|
|
62332
62503
|
if (verification.success)
|
|
62333
62504
|
return [];
|
|
62334
62505
|
if (verification.output)
|
|
62335
|
-
return
|
|
62506
|
+
return buildRegressionFindings(_regressionDeps.parseTestOutput(verification.output), verification.output);
|
|
62336
62507
|
return initialFindings;
|
|
62337
62508
|
}
|
|
62338
62509
|
};
|
|
@@ -62406,7 +62577,7 @@ async function runDeferredRegression(options) {
|
|
|
62406
62577
|
storyOutcomes: storyOutcomeAccum
|
|
62407
62578
|
};
|
|
62408
62579
|
}
|
|
62409
|
-
var _regressionDeps;
|
|
62580
|
+
var SYNTHETIC_FINDING_OUTPUT_LIMIT = 2000, _regressionDeps;
|
|
62410
62581
|
var init_run_regression = __esm(() => {
|
|
62411
62582
|
init_findings();
|
|
62412
62583
|
init_logger2();
|
|
@@ -64063,6 +64234,7 @@ function resolveMaxAttemptsOutcome(failureCategory) {
|
|
|
64063
64234
|
case "isolation-violation":
|
|
64064
64235
|
case "verifier-rejected":
|
|
64065
64236
|
case "greenfield-no-tests":
|
|
64237
|
+
case "no-tests-authored":
|
|
64066
64238
|
return "pause";
|
|
64067
64239
|
case "runtime-crash":
|
|
64068
64240
|
return "pause";
|
|
@@ -64098,7 +64270,7 @@ async function handleTierEscalation(ctx) {
|
|
|
64098
64270
|
const escalateRetryAsLite = ctx.pipelineResult.context.retryAsLite === true;
|
|
64099
64271
|
const escalateFailureCategory = ctx.pipelineResult.context.tddFailureCategory;
|
|
64100
64272
|
const escalateReviewFindings = ctx.pipelineResult.context.reviewFindings;
|
|
64101
|
-
const
|
|
64273
|
+
const escalateRetryAsTddSimple = escalateFailureCategory === "greenfield-no-tests";
|
|
64102
64274
|
const routingMode = ctx.config.routing.llm?.mode ?? "hybrid";
|
|
64103
64275
|
if (!escalationResult || !ctx.config.autoMode.escalation.enabled) {
|
|
64104
64276
|
return await handleNoTierAvailable(ctx, escalateFailureCategory);
|
|
@@ -64111,12 +64283,12 @@ async function handleTierEscalation(ctx) {
|
|
|
64111
64283
|
const escalatedTier = escalationResult.tier;
|
|
64112
64284
|
for (const s of storiesToEscalate) {
|
|
64113
64285
|
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
64114
|
-
const
|
|
64115
|
-
if (
|
|
64116
|
-
logger?.warn("escalation", "Switching strategy to
|
|
64286
|
+
const shouldSwitchToTddSimple = escalateRetryAsTddSimple && isThreeSessionStrategy(currentTestStrategy);
|
|
64287
|
+
if (shouldSwitchToTddSimple) {
|
|
64288
|
+
logger?.warn("escalation", "Switching strategy to tdd-simple (greenfield-no-tests fallback)", {
|
|
64117
64289
|
storyId: s.id,
|
|
64118
64290
|
fromStrategy: currentTestStrategy,
|
|
64119
|
-
toStrategy: "
|
|
64291
|
+
toStrategy: "tdd-simple"
|
|
64120
64292
|
});
|
|
64121
64293
|
} else {
|
|
64122
64294
|
logger?.warn("escalation", "Escalating story to next tier", {
|
|
@@ -64137,19 +64309,19 @@ async function handleTierEscalation(ctx) {
|
|
|
64137
64309
|
if (!shouldEscalate)
|
|
64138
64310
|
return s;
|
|
64139
64311
|
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
64140
|
-
const
|
|
64312
|
+
const shouldSwitchToTddSimple = escalateRetryAsTddSimple && isThreeSessionStrategy(currentTestStrategy);
|
|
64141
64313
|
const baseRouting = s.routing ?? { ...ctx.routing };
|
|
64142
64314
|
const updatedRouting = {
|
|
64143
64315
|
...baseRouting,
|
|
64144
|
-
modelTier:
|
|
64316
|
+
modelTier: shouldSwitchToTddSimple ? baseRouting.modelTier : escalatedTier,
|
|
64145
64317
|
...nextAgent !== undefined ? { agent: nextAgent } : {},
|
|
64146
64318
|
...escalateRetryAsLite ? { testStrategy: "three-session-tdd-lite" } : {},
|
|
64147
|
-
...
|
|
64319
|
+
...shouldSwitchToTddSimple ? { testStrategy: "tdd-simple" } : {}
|
|
64148
64320
|
};
|
|
64149
64321
|
const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
|
|
64150
64322
|
const isChangingTier = currentStoryTier !== escalatedTier;
|
|
64151
|
-
const shouldResetAttempts = isChangingTier ||
|
|
64152
|
-
const escalationRecord = isChangingTier ||
|
|
64323
|
+
const shouldResetAttempts = isChangingTier || shouldSwitchToTddSimple;
|
|
64324
|
+
const escalationRecord = isChangingTier || shouldSwitchToTddSimple ? buildEscalationRecord(currentStoryTier, shouldSwitchToTddSimple ? currentStoryTier : escalatedTier, ctx.pipelineResult.reason ?? "Escalated to next retry path", { fromAgent: s.routing?.agent, toAgent: nextAgent }) : undefined;
|
|
64153
64325
|
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost, verifiedPipelineReason, escalateFailureCategory);
|
|
64154
64326
|
return {
|
|
64155
64327
|
...s,
|
|
@@ -64188,6 +64360,7 @@ async function handleTierEscalation(ctx) {
|
|
|
64188
64360
|
var _tierEscalationDeps;
|
|
64189
64361
|
var init_tier_escalation = __esm(() => {
|
|
64190
64362
|
init_pipeline();
|
|
64363
|
+
init_config();
|
|
64191
64364
|
init_hooks();
|
|
64192
64365
|
init_logger2();
|
|
64193
64366
|
init_prd();
|
|
@@ -97923,7 +98096,7 @@ var FIELD_DESCRIPTIONS = {
|
|
|
97923
98096
|
"tdd.sessionTiers.verifier": "Model tier for verifier session",
|
|
97924
98097
|
"tdd.testWriterAllowedPaths": "Glob patterns for files test-writer can modify",
|
|
97925
98098
|
"tdd.rollbackOnFailure": "Rollback git changes when TDD fails",
|
|
97926
|
-
"tdd.greenfieldDetection": "Force
|
|
98099
|
+
"tdd.greenfieldDetection": "Force tdd-simple on projects with no test files",
|
|
97927
98100
|
constitution: "Constitution settings (core rules and constraints)",
|
|
97928
98101
|
"constitution.enabled": "Enable constitution loading and injection",
|
|
97929
98102
|
"constitution.path": "Path to constitution file (relative to nax/ directory)",
|