@nathapp/nax 0.70.6 → 0.70.7
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 +229 -99
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -24860,9 +24860,10 @@ var init_parser = __esm(() => {
|
|
|
24860
24860
|
|
|
24861
24861
|
// src/test-runners/ac-parser.ts
|
|
24862
24862
|
function parseTestFailures(output) {
|
|
24863
|
-
const
|
|
24863
|
+
const clean = output.replace(ANSI_ESCAPE_PATTERN, "");
|
|
24864
|
+
const framework = detectFramework(clean);
|
|
24864
24865
|
const failedACs = [];
|
|
24865
|
-
const lines =
|
|
24866
|
+
const lines = clean.split(`
|
|
24866
24867
|
`);
|
|
24867
24868
|
for (const line of lines) {
|
|
24868
24869
|
if (framework === "bun" || framework === "unknown") {
|
|
@@ -24896,7 +24897,7 @@ function parseTestFailures(output) {
|
|
|
24896
24897
|
}
|
|
24897
24898
|
}
|
|
24898
24899
|
if (framework === "jest" || framework === "vitest" || framework === "unknown") {
|
|
24899
|
-
if (/[\u25CF\u00D7\u2715]/.test(line)) {
|
|
24900
|
+
if (/[\u25CF\u00D7\u2715]/.test(line) || /^\s*FAIL\s/.test(line)) {
|
|
24900
24901
|
const acMatch = line.match(/AC[-_]?(\d+)/i);
|
|
24901
24902
|
if (acMatch) {
|
|
24902
24903
|
const acId = `AC-${acMatch[1]}`;
|
|
@@ -24913,8 +24914,10 @@ function parseTestFailures(output) {
|
|
|
24913
24914
|
}
|
|
24914
24915
|
return failedACs;
|
|
24915
24916
|
}
|
|
24917
|
+
var ANSI_ESCAPE_PATTERN;
|
|
24916
24918
|
var init_ac_parser = __esm(() => {
|
|
24917
24919
|
init_detector2();
|
|
24920
|
+
ANSI_ESCAPE_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
|
|
24918
24921
|
});
|
|
24919
24922
|
|
|
24920
24923
|
// src/utils/git.ts
|
|
@@ -30246,11 +30249,7 @@ async function gitLsFiles2(workdir) {
|
|
|
30246
30249
|
return null;
|
|
30247
30250
|
}
|
|
30248
30251
|
}
|
|
30249
|
-
async function
|
|
30250
|
-
const files = await gitLsFiles2(workdir);
|
|
30251
|
-
if (files !== null) {
|
|
30252
|
-
return files.some((f) => isTestFileByPatterns(f, patterns));
|
|
30253
|
-
}
|
|
30252
|
+
async function hasTestFilesOnDisk(workdir, patterns) {
|
|
30254
30253
|
for (const pattern of patterns) {
|
|
30255
30254
|
const g = new Bun.Glob(pattern);
|
|
30256
30255
|
for await (const path3 of g.scan({ cwd: workdir, onlyFiles: true })) {
|
|
@@ -30261,6 +30260,13 @@ async function hasTestFiles(workdir, patterns) {
|
|
|
30261
30260
|
}
|
|
30262
30261
|
return false;
|
|
30263
30262
|
}
|
|
30263
|
+
async function hasTestFiles(workdir, patterns) {
|
|
30264
|
+
const files = await gitLsFiles2(workdir);
|
|
30265
|
+
if (files !== null) {
|
|
30266
|
+
return files.some((f) => isTestFileByPatterns(f, patterns));
|
|
30267
|
+
}
|
|
30268
|
+
return hasTestFilesOnDisk(workdir, patterns);
|
|
30269
|
+
}
|
|
30264
30270
|
async function isGreenfieldStory(_story, workdir, patterns) {
|
|
30265
30271
|
try {
|
|
30266
30272
|
return !await hasTestFiles(workdir, patterns ?? DEFAULT_TEST_FILE_PATTERNS);
|
|
@@ -30291,6 +30297,7 @@ var init_greenfield = __esm(() => {
|
|
|
30291
30297
|
"out",
|
|
30292
30298
|
"tmp",
|
|
30293
30299
|
"temp",
|
|
30300
|
+
".nax",
|
|
30294
30301
|
".git"
|
|
30295
30302
|
]);
|
|
30296
30303
|
});
|
|
@@ -32865,13 +32872,13 @@ var init_semantic_helpers = __esm(() => {
|
|
|
32865
32872
|
|
|
32866
32873
|
// src/review/semantic-evidence.ts
|
|
32867
32874
|
import { isAbsolute as isAbsolute8 } from "path";
|
|
32868
|
-
async function substantiateSemanticEvidence(findings, diffMode, workdir, storyId, blockingThreshold = "error") {
|
|
32875
|
+
async function substantiateSemanticEvidence(findings, diffMode, workdir, storyId, blockingThreshold = "error", repoRoot) {
|
|
32869
32876
|
if (diffMode !== "ref")
|
|
32870
32877
|
return findings;
|
|
32871
32878
|
return Promise.all(findings.map(async (finding) => {
|
|
32872
32879
|
if (!isBlockingSeverity(finding.severity, blockingThreshold))
|
|
32873
32880
|
return finding;
|
|
32874
|
-
const evidence = await checkFindingEvidence({ finding, workdir });
|
|
32881
|
+
const evidence = await checkFindingEvidence({ finding, workdir, repoRoot });
|
|
32875
32882
|
if (evidence.status !== "unmatched")
|
|
32876
32883
|
return finding;
|
|
32877
32884
|
return downgradeUnsubstantiatedFinding({ finding, storyId, ...evidence });
|
|
@@ -32883,7 +32890,8 @@ async function checkFindingEvidence(opts) {
|
|
|
32883
32890
|
const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
|
|
32884
32891
|
if (!observed)
|
|
32885
32892
|
return { status: "missing-observed", file: file3, line };
|
|
32886
|
-
const
|
|
32893
|
+
const roots = opts.repoRoot && opts.repoRoot !== opts.workdir ? [opts.repoRoot, opts.workdir] : [opts.workdir];
|
|
32894
|
+
const contents = await readSafeFile(roots, file3);
|
|
32887
32895
|
if (contents === null)
|
|
32888
32896
|
return { status: "unreadable", file: file3, line, observed };
|
|
32889
32897
|
return matchesEvidence(contents, observed, line) ? { status: "matched", file: file3, line, observed } : { status: "unmatched", file: file3, line, observed };
|
|
@@ -32912,13 +32920,13 @@ function downgradeUnsubstantiatedFinding(opts) {
|
|
|
32912
32920
|
});
|
|
32913
32921
|
return { ...opts.finding, severity: "unverifiable" };
|
|
32914
32922
|
}
|
|
32915
|
-
async function readSafeFile(
|
|
32916
|
-
const
|
|
32917
|
-
|
|
32918
|
-
|
|
32919
|
-
|
|
32920
|
-
|
|
32921
|
-
|
|
32923
|
+
async function readSafeFile(roots, file3) {
|
|
32924
|
+
for (const root of roots) {
|
|
32925
|
+
const validated = validateModulePath(file3, [root]);
|
|
32926
|
+
if (validated.valid && validated.absolutePath) {
|
|
32927
|
+
try {
|
|
32928
|
+
return await Bun.file(validated.absolutePath).text();
|
|
32929
|
+
} catch {}
|
|
32922
32930
|
}
|
|
32923
32931
|
}
|
|
32924
32932
|
if (isAbsolute8(file3)) {
|
|
@@ -32963,11 +32971,11 @@ function hasInspectionTrail(raw) {
|
|
|
32963
32971
|
return Array.isArray(files) && files.some((f) => typeof f === "string" && f.trim().length > 0);
|
|
32964
32972
|
}
|
|
32965
32973
|
async function substantiateAdversarialFindings(opts) {
|
|
32966
|
-
const { findings, workdir, storyId, blockingThreshold } = opts;
|
|
32974
|
+
const { findings, workdir, storyId, blockingThreshold, repoRoot } = opts;
|
|
32967
32975
|
return Promise.all(findings.map(async (finding) => {
|
|
32968
32976
|
if (!isBlockingSeverity(finding.severity, blockingThreshold))
|
|
32969
32977
|
return finding;
|
|
32970
|
-
const evidence = await checkFindingEvidence({ finding, workdir });
|
|
32978
|
+
const evidence = await checkFindingEvidence({ finding, workdir, repoRoot });
|
|
32971
32979
|
if (evidence.status !== "unmatched" && evidence.status !== "missing-observed")
|
|
32972
32980
|
return finding;
|
|
32973
32981
|
return downgradeUnsubstantiatedFinding({
|
|
@@ -33072,7 +33080,11 @@ async function requoteBlockingAdversarialFindings(findings, ctx) {
|
|
|
33072
33080
|
for (const [index, finding] of next.entries()) {
|
|
33073
33081
|
if (!isBlockingSeverity(finding.severity, threshold))
|
|
33074
33082
|
continue;
|
|
33075
|
-
const initialEvidence = await checkFindingEvidence({
|
|
33083
|
+
const initialEvidence = await checkFindingEvidence({
|
|
33084
|
+
finding,
|
|
33085
|
+
workdir: ctx.input.workdir,
|
|
33086
|
+
repoRoot: ctx.input.repoRoot
|
|
33087
|
+
});
|
|
33076
33088
|
if (initialEvidence.status !== "unmatched")
|
|
33077
33089
|
continue;
|
|
33078
33090
|
if (used >= maxRequotes)
|
|
@@ -33101,7 +33113,8 @@ async function requoteBlockingAdversarialFindings(findings, ctx) {
|
|
|
33101
33113
|
};
|
|
33102
33114
|
const requotedEvidence = await checkFindingEvidence({
|
|
33103
33115
|
finding: updatedFinding,
|
|
33104
|
-
workdir: ctx.input.workdir
|
|
33116
|
+
workdir: ctx.input.workdir,
|
|
33117
|
+
repoRoot: ctx.input.repoRoot
|
|
33105
33118
|
});
|
|
33106
33119
|
if (requotedEvidence.status === "matched") {
|
|
33107
33120
|
getSafeLogger()?.info("review", "Recovered adversarial finding via same-session requote", {
|
|
@@ -33329,6 +33342,7 @@ var init_adversarial_review = __esm(() => {
|
|
|
33329
33342
|
const substantiated = await substantiateAdversarialFindings({
|
|
33330
33343
|
findings,
|
|
33331
33344
|
workdir: input.workdir,
|
|
33345
|
+
repoRoot: input.repoRoot,
|
|
33332
33346
|
storyId: input.story.id,
|
|
33333
33347
|
blockingThreshold: threshold
|
|
33334
33348
|
});
|
|
@@ -34026,6 +34040,7 @@ async function runAdversarialReview(opts) {
|
|
|
34026
34040
|
try {
|
|
34027
34041
|
opResult = await _adversarialDeps.callOp(callCtx, adversarialReviewOp, {
|
|
34028
34042
|
workdir,
|
|
34043
|
+
repoRoot: projectDir ?? workdir,
|
|
34029
34044
|
story,
|
|
34030
34045
|
adversarialConfig,
|
|
34031
34046
|
mode: diffMode,
|
|
@@ -35548,6 +35563,10 @@ var init_decompose2 = __esm(() => {
|
|
|
35548
35563
|
});
|
|
35549
35564
|
|
|
35550
35565
|
// src/routing/classify.ts
|
|
35566
|
+
function isSecurityCriticalStory(title, tags = []) {
|
|
35567
|
+
const text = [title, ...tags ?? []].join(" ").toLowerCase();
|
|
35568
|
+
return SECURITY_KEYWORDS.some((kw) => text.includes(kw)) || PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw));
|
|
35569
|
+
}
|
|
35551
35570
|
function classifyComplexity(title, _description, acceptanceCriteria, tags = []) {
|
|
35552
35571
|
const text = [title, ...acceptanceCriteria ?? [], ...tags ?? []].join(" ").toLowerCase();
|
|
35553
35572
|
if (EXPERT_KEYWORDS.some((kw) => text.includes(kw)))
|
|
@@ -35565,10 +35584,7 @@ function determineTestStrategy(complexity, title, _description, tags = [], tddSt
|
|
|
35565
35584
|
return "tdd-simple";
|
|
35566
35585
|
if (tddStrategy === "off")
|
|
35567
35586
|
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)
|
|
35587
|
+
if (isSecurityCriticalStory(title, tags))
|
|
35572
35588
|
return "three-session-tdd";
|
|
35573
35589
|
if (complexity === "expert")
|
|
35574
35590
|
return "three-session-tdd";
|
|
@@ -35956,6 +35972,7 @@ __export(exports_routing, {
|
|
|
35956
35972
|
routeTask: () => routeTask,
|
|
35957
35973
|
routeStory: () => routeStory,
|
|
35958
35974
|
resolveRouting: () => resolveRouting,
|
|
35975
|
+
isSecurityCriticalStory: () => isSecurityCriticalStory,
|
|
35959
35976
|
determineTestStrategy: () => determineTestStrategy,
|
|
35960
35977
|
complexityToModelTier: () => complexityToModelTier,
|
|
35961
35978
|
clearCache: () => clearCache,
|
|
@@ -36801,7 +36818,11 @@ async function requoteBlockingFindings(findings, ctx) {
|
|
|
36801
36818
|
for (const [index, finding] of next.entries()) {
|
|
36802
36819
|
if (!isBlockingSeverity(finding.severity, threshold))
|
|
36803
36820
|
continue;
|
|
36804
|
-
const initialEvidence = await checkFindingEvidence({
|
|
36821
|
+
const initialEvidence = await checkFindingEvidence({
|
|
36822
|
+
finding,
|
|
36823
|
+
workdir: ctx.input.workdir,
|
|
36824
|
+
repoRoot: ctx.input.repoRoot
|
|
36825
|
+
});
|
|
36805
36826
|
if (initialEvidence.status !== "unmatched")
|
|
36806
36827
|
continue;
|
|
36807
36828
|
if (used >= maxRequotes)
|
|
@@ -36831,7 +36852,8 @@ async function requoteBlockingFindings(findings, ctx) {
|
|
|
36831
36852
|
};
|
|
36832
36853
|
const requotedEvidence = await checkFindingEvidence({
|
|
36833
36854
|
finding: updatedFinding,
|
|
36834
|
-
workdir: ctx.input.workdir
|
|
36855
|
+
workdir: ctx.input.workdir,
|
|
36856
|
+
repoRoot: ctx.input.repoRoot
|
|
36835
36857
|
});
|
|
36836
36858
|
if (requotedEvidence.status === "matched") {
|
|
36837
36859
|
getSafeLogger()?.info("review", "Recovered semantic finding via same-session requote", {
|
|
@@ -36967,7 +36989,7 @@ var init_semantic_review = __esm(() => {
|
|
|
36967
36989
|
const threshold = input.blockingThreshold ?? "error";
|
|
36968
36990
|
const findings = parsed.findings;
|
|
36969
36991
|
const sanitized = sanitizeRefModeFindings(findings, input.mode, threshold);
|
|
36970
|
-
const substantiated = await substantiateSemanticEvidence(sanitized, input.mode, input.workdir, input.story.id, threshold);
|
|
36992
|
+
const substantiated = await substantiateSemanticEvidence(sanitized, input.mode, input.workdir, input.story.id, threshold, input.repoRoot);
|
|
36971
36993
|
const { accepted, dropped } = filterByAcGroundingMinimal(substantiated, input.story.acceptanceCriteria);
|
|
36972
36994
|
const blocking = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
36973
36995
|
const passed = parsed.passed && blocking.length === 0;
|
|
@@ -38975,8 +38997,13 @@ var init_greenfield_gate = __esm(() => {
|
|
|
38975
38997
|
config: greenfieldGateConfigSelector,
|
|
38976
38998
|
async execute(input, _ctx) {
|
|
38977
38999
|
const globs = input.resolvedTestPatterns.globs;
|
|
38978
|
-
|
|
38979
|
-
|
|
39000
|
+
let hasTests;
|
|
39001
|
+
try {
|
|
39002
|
+
hasTests = await hasTestFilesOnDisk(input.workdir, globs);
|
|
39003
|
+
} catch {
|
|
39004
|
+
return { success: true, hasPreExistingTests: true };
|
|
39005
|
+
}
|
|
39006
|
+
if (!hasTests) {
|
|
38980
39007
|
return { success: false, hasPreExistingTests: false, pauseReason: "greenfield-no-tests" };
|
|
38981
39008
|
}
|
|
38982
39009
|
return { success: true, hasPreExistingTests: true };
|
|
@@ -38984,6 +39011,33 @@ var init_greenfield_gate = __esm(() => {
|
|
|
38984
39011
|
};
|
|
38985
39012
|
});
|
|
38986
39013
|
|
|
39014
|
+
// src/operations/test-presence-gate.ts
|
|
39015
|
+
var testPresenceGateConfigSelector, testPresenceGateOp;
|
|
39016
|
+
var init_test_presence_gate = __esm(() => {
|
|
39017
|
+
init_config();
|
|
39018
|
+
init_greenfield();
|
|
39019
|
+
testPresenceGateConfigSelector = pickSelector("test-presence-gate", "execution");
|
|
39020
|
+
testPresenceGateOp = {
|
|
39021
|
+
kind: "deterministic",
|
|
39022
|
+
name: "test-presence-gate",
|
|
39023
|
+
stage: "verify",
|
|
39024
|
+
config: testPresenceGateConfigSelector,
|
|
39025
|
+
async execute(input, _ctx) {
|
|
39026
|
+
const globs = input.resolvedTestPatterns.globs;
|
|
39027
|
+
let hasTests;
|
|
39028
|
+
try {
|
|
39029
|
+
hasTests = await hasTestFilesOnDisk(input.workdir, globs);
|
|
39030
|
+
} catch {
|
|
39031
|
+
return { success: true, hasTests: true };
|
|
39032
|
+
}
|
|
39033
|
+
if (!hasTests) {
|
|
39034
|
+
return { success: false, hasTests: false, pauseReason: "no-tests-authored" };
|
|
39035
|
+
}
|
|
39036
|
+
return { success: true, hasTests: true };
|
|
39037
|
+
}
|
|
39038
|
+
};
|
|
39039
|
+
});
|
|
39040
|
+
|
|
38987
39041
|
// src/utils/command-argv.ts
|
|
38988
39042
|
function parseCommandToArgv(command) {
|
|
38989
39043
|
const safeEnv = buildAllowedEnv();
|
|
@@ -40667,6 +40721,7 @@ var init_operations = __esm(() => {
|
|
|
40667
40721
|
init_plan_critic_llm();
|
|
40668
40722
|
init_execution_gates();
|
|
40669
40723
|
init_greenfield_gate();
|
|
40724
|
+
init_test_presence_gate();
|
|
40670
40725
|
init_full_suite_gate();
|
|
40671
40726
|
init_full_suite_rectify();
|
|
40672
40727
|
init_full_suite_rectify_op();
|
|
@@ -42048,6 +42103,7 @@ async function runSemanticReview(opts) {
|
|
|
42048
42103
|
try {
|
|
42049
42104
|
opResult = await _semanticDeps.callOp(callCtx, semanticReviewOp, {
|
|
42050
42105
|
workdir,
|
|
42106
|
+
repoRoot: projectDir ?? workdir,
|
|
42051
42107
|
story,
|
|
42052
42108
|
semanticConfig,
|
|
42053
42109
|
mode: diffMode,
|
|
@@ -42602,6 +42658,7 @@ var init_runner2 = __esm(() => {
|
|
|
42602
42658
|
var init_review = __esm(() => {
|
|
42603
42659
|
init_semantic_helpers();
|
|
42604
42660
|
init_category_fix_target();
|
|
42661
|
+
init_finding_filters();
|
|
42605
42662
|
init_ac_quote_validator();
|
|
42606
42663
|
init_ac_structural_counterfactual();
|
|
42607
42664
|
init_adversarial();
|
|
@@ -42629,6 +42686,13 @@ UNRESOLVED: <brief explanation of which findings conflicted and why they cannot
|
|
|
42629
42686
|
|
|
42630
42687
|
Before emitting UNRESOLVED, confirm none of Exceptions 1\u2013${count} apply.
|
|
42631
42688
|
|
|
42689
|
+
**A missing-test or \`test-gap\` finding is never a false positive because a \`.nax/\` file exists.**
|
|
42690
|
+
\`.nax/\` is nax's own artifact directory; \`.nax-acceptance.test.ts\` is generated scaffolding for the
|
|
42691
|
+
acceptance gate \u2014 it is NOT source-tree test coverage. You may NOT cite any \`.nax/\`-resident file as
|
|
42692
|
+
evidence that an acceptance criterion is already tested, and you may NOT emit UNRESOLVED on that basis.
|
|
42693
|
+
The only valid response to a missing-test finding is to
|
|
42694
|
+
author a real test under the package's resolved test path.
|
|
42695
|
+
|
|
42632
42696
|
## Test-file edit exceptions
|
|
42633
42697
|
|
|
42634
42698
|
The "do not modify test files" rule has ${countWord} narrow escape valves. Each requires a
|
|
@@ -53992,7 +54056,7 @@ ${stderr}`;
|
|
|
53992
54056
|
errorExitCode = exitCode;
|
|
53993
54057
|
allFailedACs.push("AC-ERROR");
|
|
53994
54058
|
allFindings.push(acSentinelToFinding("AC-ERROR", output));
|
|
53995
|
-
failedPackages.push({ testPath, packageDir, testFramework, commandOverride });
|
|
54059
|
+
failedPackages.push({ testPath, packageDir, testFramework, commandOverride, output, failedACs: ["AC-ERROR"] });
|
|
53996
54060
|
continue;
|
|
53997
54061
|
}
|
|
53998
54062
|
for (const acId of actualFailures) {
|
|
@@ -54002,7 +54066,14 @@ ${stderr}`;
|
|
|
54002
54066
|
}
|
|
54003
54067
|
}
|
|
54004
54068
|
if (actualFailures.length > 0) {
|
|
54005
|
-
failedPackages.push({
|
|
54069
|
+
failedPackages.push({
|
|
54070
|
+
testPath,
|
|
54071
|
+
packageDir,
|
|
54072
|
+
testFramework,
|
|
54073
|
+
commandOverride,
|
|
54074
|
+
output,
|
|
54075
|
+
failedACs: actualFailures
|
|
54076
|
+
});
|
|
54006
54077
|
logger.error("acceptance", "Acceptance tests failed", {
|
|
54007
54078
|
storyId: ctx.story.id,
|
|
54008
54079
|
failedACs: actualFailures,
|
|
@@ -55441,6 +55512,7 @@ var init_types9 = __esm(() => {
|
|
|
55441
55512
|
"test-writer",
|
|
55442
55513
|
"greenfield-gate",
|
|
55443
55514
|
"implementer",
|
|
55515
|
+
"test-presence-gate",
|
|
55444
55516
|
"full-suite-gate",
|
|
55445
55517
|
"verifier",
|
|
55446
55518
|
"verify-scoped",
|
|
@@ -55453,6 +55525,7 @@ var init_types9 = __esm(() => {
|
|
|
55453
55525
|
"test-writer": "testWriter",
|
|
55454
55526
|
"greenfield-gate": "greenfieldGate",
|
|
55455
55527
|
implementer: "implementer",
|
|
55528
|
+
"test-presence-gate": "testPresenceGate",
|
|
55456
55529
|
"full-suite-gate": "fullSuiteGate",
|
|
55457
55530
|
verifier: "verifier",
|
|
55458
55531
|
"verify-scoped": "verifyScoped",
|
|
@@ -55595,6 +55668,8 @@ function collectOrderedPhases(state) {
|
|
|
55595
55668
|
return [state.greenfieldGate];
|
|
55596
55669
|
if (kind === "implementer" && state.implementer)
|
|
55597
55670
|
return [state.implementer];
|
|
55671
|
+
if (kind === "test-presence-gate" && state.testPresenceGate)
|
|
55672
|
+
return [state.testPresenceGate];
|
|
55598
55673
|
if (kind === "full-suite-gate" && state.fullSuiteGate)
|
|
55599
55674
|
return [state.fullSuiteGate];
|
|
55600
55675
|
if (kind === "verifier" && state.verifier)
|
|
@@ -56219,10 +56294,11 @@ class ExecutionPlan {
|
|
|
56219
56294
|
}
|
|
56220
56295
|
}
|
|
56221
56296
|
}
|
|
56297
|
+
const storyCurrentlyGreen = !rectResult.rectificationExhausted && Object.entries(phaseOutputs).every(([name, output]) => phasePassed(name, output, this.ctx.storyId));
|
|
56222
56298
|
const advCfg = this.state.adversarialReview ? this.state.nonBlockingFix : undefined;
|
|
56223
56299
|
const advisoryOut = phaseOutputs["adversarial-review"];
|
|
56224
56300
|
const advisoryFindings = advisoryOut?.advisoryFindings ?? [];
|
|
56225
|
-
if (advCfg && this.state.rectification && this.ctx.storyId && shouldRunNonBlockingFix(advCfg, advisoryFindings.length)) {
|
|
56301
|
+
if (advCfg && storyCurrentlyGreen && this.state.rectification && this.ctx.storyId && shouldRunNonBlockingFix(advCfg, advisoryFindings.length)) {
|
|
56226
56302
|
await _storyOrchestratorDeps.runNonBlockingFix({
|
|
56227
56303
|
workdir: this.ctx.packageDir,
|
|
56228
56304
|
storyId: this.ctx.storyId,
|
|
@@ -56333,6 +56409,10 @@ class StoryOrchestratorBuilder {
|
|
|
56333
56409
|
setPhase(this.state, "greenfield-gate", isSlot(value) ? value : { op: greenfieldGateOp, input: value });
|
|
56334
56410
|
return this;
|
|
56335
56411
|
}
|
|
56412
|
+
addTestPresenceGate(value) {
|
|
56413
|
+
setPhase(this.state, "test-presence-gate", isSlot(value) ? value : { op: testPresenceGateOp, input: value });
|
|
56414
|
+
return this;
|
|
56415
|
+
}
|
|
56336
56416
|
addVerifier(value) {
|
|
56337
56417
|
setPhase(this.state, "verifier", isSlot(value) ? value : { op: verifierOp, input: value });
|
|
56338
56418
|
return this;
|
|
@@ -56418,6 +56498,9 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
56418
56498
|
if (inputs.implementer) {
|
|
56419
56499
|
builder.addImplementer(inputs.implementer);
|
|
56420
56500
|
}
|
|
56501
|
+
if (!isThreeSession && inputs.testPresenceGate) {
|
|
56502
|
+
builder.addTestPresenceGate(inputs.testPresenceGate);
|
|
56503
|
+
}
|
|
56421
56504
|
const regressionMode = config2.execution?.regressionGate?.mode ?? "deferred";
|
|
56422
56505
|
if (inputs.fullSuiteGate && (isThreeSession || regressionMode === "per-story")) {
|
|
56423
56506
|
builder.addFullSuiteGate(inputs.fullSuiteGate);
|
|
@@ -56491,7 +56574,12 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
56491
56574
|
const nbStrategies = [];
|
|
56492
56575
|
if (nbf?.enabled && inputs.adversarialReview) {
|
|
56493
56576
|
const nbSink = makeDeclarationSink();
|
|
56494
|
-
if (
|
|
56577
|
+
if (!isThreeSession) {
|
|
56578
|
+
nbStrategies.push(makeAutofixImplementerStrategy(story, config2, nbSink, {
|
|
56579
|
+
includeAdversarialReview: true,
|
|
56580
|
+
promptSeverityFloor: "info"
|
|
56581
|
+
}));
|
|
56582
|
+
} else if (nbf.scope === "source") {
|
|
56495
56583
|
nbStrategies.push(makeAutofixImplementerStrategy(story, config2, nbSink, {
|
|
56496
56584
|
includeAdversarialReview: true,
|
|
56497
56585
|
promptSeverityFloor: "info"
|
|
@@ -56637,6 +56725,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
56637
56725
|
featureContextMarkdown: ctx.featureContextMarkdown,
|
|
56638
56726
|
constitution: ctx.constitution?.content
|
|
56639
56727
|
};
|
|
56728
|
+
const testPresenceGateInput = isSingleSessionTestOwningStrategy(ctx.routing.testStrategy) && resolvedTestPatterns ? { story, workdir: ctx.workdir, resolvedTestPatterns } : undefined;
|
|
56640
56729
|
const _regressionMode = ctx.config.execution?.regressionGate?.mode;
|
|
56641
56730
|
const fullSuiteGateInput = _isTdd || _regressionMode === "per-story" ? {
|
|
56642
56731
|
story,
|
|
@@ -56741,6 +56830,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
56741
56830
|
testWriter: testWriterInput,
|
|
56742
56831
|
greenfieldGate: greenfieldGateInput,
|
|
56743
56832
|
implementer: implementerInput,
|
|
56833
|
+
testPresenceGate: testPresenceGateInput,
|
|
56744
56834
|
fullSuiteGate: fullSuiteGateInput,
|
|
56745
56835
|
verifier: verifierInput,
|
|
56746
56836
|
verifyScoped: verifyScopedInput,
|
|
@@ -56798,6 +56888,11 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason, failure
|
|
|
56798
56888
|
case "review-incomplete":
|
|
56799
56889
|
case "greenfield-no-tests":
|
|
56800
56890
|
return { action: "escalate", reason: buildReason(failureCategory) };
|
|
56891
|
+
case "no-tests-authored":
|
|
56892
|
+
return {
|
|
56893
|
+
action: "escalate",
|
|
56894
|
+
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."
|
|
56895
|
+
};
|
|
56801
56896
|
case "dependency-prep":
|
|
56802
56897
|
return pauseFallback;
|
|
56803
56898
|
default:
|
|
@@ -56902,6 +56997,10 @@ function deriveTddFailureCategory(phaseOutputs, unfixedFindings, gateRegressedDu
|
|
|
56902
56997
|
if (greenfieldOutput?.success === false && greenfieldOutput?.pauseReason === "greenfield-no-tests") {
|
|
56903
56998
|
return "greenfield-no-tests";
|
|
56904
56999
|
}
|
|
57000
|
+
const testPresenceOutput = phaseOutputs[testPresenceGateOp.name];
|
|
57001
|
+
if (testPresenceOutput?.success === false && testPresenceOutput?.pauseReason === "no-tests-authored") {
|
|
57002
|
+
return "no-tests-authored";
|
|
57003
|
+
}
|
|
56905
57004
|
const verifierOutput = phaseOutputs[verifierOp.name];
|
|
56906
57005
|
if (verifierOutput?.success === false) {
|
|
56907
57006
|
if (verifierOutput.failureCategory) {
|
|
@@ -57941,13 +58040,21 @@ var init_routing2 = __esm(() => {
|
|
|
57941
58040
|
});
|
|
57942
58041
|
const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir, resolved?.globs);
|
|
57943
58042
|
if (isGreenfield) {
|
|
57944
|
-
|
|
57945
|
-
|
|
57946
|
-
|
|
57947
|
-
|
|
57948
|
-
|
|
57949
|
-
|
|
57950
|
-
|
|
58043
|
+
if (isSecurityCriticalStory(ctx.story.title, ctx.story.tags)) {
|
|
58044
|
+
logger.info("routing", "Greenfield + security-critical \u2014 keeping three-session strategy", {
|
|
58045
|
+
storyId: ctx.story.id,
|
|
58046
|
+
strategy: routing.testStrategy,
|
|
58047
|
+
scanDir: greenfieldScanDir
|
|
58048
|
+
});
|
|
58049
|
+
} else {
|
|
58050
|
+
logger.info("routing", "Greenfield detected \u2014 forcing tdd-simple strategy", {
|
|
58051
|
+
storyId: ctx.story.id,
|
|
58052
|
+
originalStrategy: routing.testStrategy,
|
|
58053
|
+
scanDir: greenfieldScanDir
|
|
58054
|
+
});
|
|
58055
|
+
routing.testStrategy = "tdd-simple";
|
|
58056
|
+
routing.reasoning = `${routing.reasoning} [GREENFIELD OVERRIDE: No test files exist, using tdd-simple (test-first, single-session) instead of three-session TDD]`;
|
|
58057
|
+
}
|
|
57951
58058
|
}
|
|
57952
58059
|
}
|
|
57953
58060
|
ctx.routing = routing;
|
|
@@ -61057,7 +61164,7 @@ var package_default;
|
|
|
61057
61164
|
var init_package = __esm(() => {
|
|
61058
61165
|
package_default = {
|
|
61059
61166
|
name: "@nathapp/nax",
|
|
61060
|
-
version: "0.70.
|
|
61167
|
+
version: "0.70.7",
|
|
61061
61168
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
61062
61169
|
type: "module",
|
|
61063
61170
|
bin: {
|
|
@@ -61157,8 +61264,8 @@ var init_version = __esm(() => {
|
|
|
61157
61264
|
NAX_VERSION = package_default.version;
|
|
61158
61265
|
NAX_COMMIT = (() => {
|
|
61159
61266
|
try {
|
|
61160
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
61161
|
-
return "
|
|
61267
|
+
if (/^[0-9a-f]{6,10}$/.test("20a6d5ac"))
|
|
61268
|
+
return "20a6d5ac";
|
|
61162
61269
|
} catch {}
|
|
61163
61270
|
try {
|
|
61164
61271
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -61756,12 +61863,12 @@ __export(exports_acceptance_loop, {
|
|
|
61756
61863
|
isTestLevelFailure: () => isTestLevelFailure,
|
|
61757
61864
|
isStubTestFile: () => isStubTestFile,
|
|
61758
61865
|
buildResult: () => buildResult,
|
|
61866
|
+
_runAcceptanceTestsOnceDeps: () => _runAcceptanceTestsOnceDeps,
|
|
61759
61867
|
_regenerateDeps: () => _regenerateDeps,
|
|
61760
61868
|
_acceptanceLoopDeps: () => _acceptanceLoopDeps,
|
|
61761
61869
|
_acceptanceFixCycleDeps: () => _acceptanceFixCycleDeps
|
|
61762
61870
|
});
|
|
61763
|
-
function resolveAcceptanceFixTarget(acceptanceTestPaths,
|
|
61764
|
-
const failedPackage = failedPackages?.[0];
|
|
61871
|
+
function resolveAcceptanceFixTarget(acceptanceTestPaths, failedPackage, config2) {
|
|
61765
61872
|
const matchedEntry = failedPackage ? acceptanceTestPaths?.find((entry) => entry.testPath === failedPackage.testPath || entry.packageDir === failedPackage.packageDir) : undefined;
|
|
61766
61873
|
const selectedPathEntry = matchedEntry ?? acceptanceTestPaths?.[0];
|
|
61767
61874
|
return {
|
|
@@ -61792,11 +61899,11 @@ function findingsForDiagnosis(failedACs, testOutput, diagnosis) {
|
|
|
61792
61899
|
{ ...f, fixTarget: "test" }
|
|
61793
61900
|
]);
|
|
61794
61901
|
}
|
|
61795
|
-
function buildFixCycleCtx(ctx, runtime, storyId) {
|
|
61902
|
+
function buildFixCycleCtx(ctx, runtime, storyId, packageDir) {
|
|
61796
61903
|
return {
|
|
61797
61904
|
runtime,
|
|
61798
|
-
packageView: runtime.packages.resolve(
|
|
61799
|
-
packageDir
|
|
61905
|
+
packageView: runtime.packages.resolve(packageDir),
|
|
61906
|
+
packageDir,
|
|
61800
61907
|
storyId,
|
|
61801
61908
|
featureName: ctx.feature,
|
|
61802
61909
|
agentName: ctx.agentManager?.getDefault() ?? "claude"
|
|
@@ -61830,9 +61937,10 @@ function buildAcceptanceContext(ctx, prd) {
|
|
|
61830
61937
|
abortSignal: ctx.abortSignal
|
|
61831
61938
|
};
|
|
61832
61939
|
}
|
|
61833
|
-
async function runAcceptanceTestsOnce(ctx, prd) {
|
|
61834
|
-
const
|
|
61835
|
-
const
|
|
61940
|
+
async function runAcceptanceTestsOnce(ctx, prd, packageFilter) {
|
|
61941
|
+
const baseCtx = packageFilter ? { ...ctx, acceptanceTestPaths: packageFilter } : ctx;
|
|
61942
|
+
const acceptanceContext = buildAcceptanceContext(baseCtx, prd);
|
|
61943
|
+
const { acceptanceStage: acceptanceStage2 } = await _runAcceptanceTestsOnceDeps.importAcceptanceStage();
|
|
61836
61944
|
const result = await acceptanceStage2.execute(acceptanceContext);
|
|
61837
61945
|
if (result.action !== "fail")
|
|
61838
61946
|
return { passed: true, failedACs: [], testOutput: "" };
|
|
@@ -61846,7 +61954,7 @@ async function runAcceptanceTestsOnce(ctx, prd) {
|
|
|
61846
61954
|
failedPackages: failures.failedPackages
|
|
61847
61955
|
};
|
|
61848
61956
|
}
|
|
61849
|
-
async function runAcceptanceFixCycle(ctx, prd, initialFailures, diagnosis, acceptanceTestPath, testCommand) {
|
|
61957
|
+
async function runAcceptanceFixCycle(ctx, prd, initialFailures, diagnosis, acceptanceTestPath, testCommand, fixTarget) {
|
|
61850
61958
|
const runtime = ctx.runtime;
|
|
61851
61959
|
if (!runtime) {
|
|
61852
61960
|
return { iterations: [], finalFindings: [], exitReason: "no-strategy" };
|
|
@@ -61854,7 +61962,7 @@ async function runAcceptanceFixCycle(ctx, prd, initialFailures, diagnosis, accep
|
|
|
61854
61962
|
let currentTestOutput = initialFailures.testOutput;
|
|
61855
61963
|
let currentFailedACs = initialFailures.failedACs;
|
|
61856
61964
|
const storyId = prd.userStories[0]?.id ?? "unknown";
|
|
61857
|
-
const cycleCtx = buildFixCycleCtx(ctx, runtime, storyId);
|
|
61965
|
+
const cycleCtx = buildFixCycleCtx(ctx, runtime, storyId, fixTarget?.packageDir ?? ctx.workdir);
|
|
61858
61966
|
const cycle = {
|
|
61859
61967
|
findings: findingsForDiagnosis(initialFailures.failedACs, initialFailures.testOutput, diagnosis),
|
|
61860
61968
|
iterations: [],
|
|
@@ -61892,7 +62000,8 @@ async function runAcceptanceFixCycle(ctx, prd, initialFailures, diagnosis, accep
|
|
|
61892
62000
|
}
|
|
61893
62001
|
],
|
|
61894
62002
|
validate: async (_ctx, _opts) => {
|
|
61895
|
-
const
|
|
62003
|
+
const packageFilter = fixTarget ? ctx.acceptanceTestPaths?.filter((entry) => entry.packageDir === fixTarget.packageDir) : undefined;
|
|
62004
|
+
const result = await runAcceptanceTestsOnce(ctx, prd, packageFilter);
|
|
61896
62005
|
if (result.passed)
|
|
61897
62006
|
return [];
|
|
61898
62007
|
currentTestOutput = result.testOutput;
|
|
@@ -61918,7 +62027,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
61918
62027
|
const storiesCompleted = ctx.storiesCompleted;
|
|
61919
62028
|
const prdDirty = false;
|
|
61920
62029
|
logger?.info("acceptance", "All stories complete, running acceptance validation");
|
|
61921
|
-
const { acceptanceStage: acceptanceStage2 } = await
|
|
62030
|
+
const { acceptanceStage: acceptanceStage2 } = await _runAcceptanceTestsOnceDeps.importAcceptanceStage();
|
|
61922
62031
|
while (acceptanceRetries < maxRetries) {
|
|
61923
62032
|
const firstStory = prd.userStories[0];
|
|
61924
62033
|
const acceptanceContext = buildAcceptanceContext(ctx, prd);
|
|
@@ -61981,40 +62090,55 @@ async function runAcceptanceLoop(ctx) {
|
|
|
61981
62090
|
logger?.error("acceptance", "Runtime not found for diagnosis", { storyId: firstStory?.id });
|
|
61982
62091
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
|
|
61983
62092
|
}
|
|
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 ?? "";
|
|
62093
|
+
const failedPkgs = failures.failedPackages && failures.failedPackages.length > 0 ? failures.failedPackages : [{ testPath: "", packageDir: ctx.workdir, output: failures.testOutput, failedACs: failures.failedACs }];
|
|
61989
62094
|
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
|
-
|
|
62095
|
+
const testEntries = ctx.acceptanceTestPaths ? await _acceptanceLoopDeps.loadAcceptanceTestContent(ctx.acceptanceTestPaths.map((p) => p.testPath)) : [];
|
|
62096
|
+
const remainingFindings = [];
|
|
62097
|
+
let totalInternalIterations = 0;
|
|
62098
|
+
for (const pkg of failedPkgs) {
|
|
62099
|
+
const { acceptanceTestPath, testCommand } = resolveAcceptanceFixTarget(ctx.acceptanceTestPaths, pkg, ctx.config);
|
|
62100
|
+
const effectivePath = acceptanceTestPath || pkg.testPath || testEntries[0]?.testPath || "";
|
|
62101
|
+
const testFileContent = testEntries.find((entry) => entry.testPath === effectivePath)?.content ?? "";
|
|
62102
|
+
const pkgFailures = { failedACs: pkg.failedACs, testOutput: pkg.output };
|
|
62103
|
+
const diagnosis = await resolveAcceptanceDiagnosis({
|
|
62104
|
+
ctx,
|
|
62105
|
+
failures: pkgFailures,
|
|
62106
|
+
totalACs,
|
|
62107
|
+
strategy,
|
|
62108
|
+
semanticVerdicts,
|
|
62109
|
+
diagnosisOpts: {
|
|
62110
|
+
testOutput: pkg.output,
|
|
62111
|
+
testFileContent,
|
|
62112
|
+
acceptanceTestPath: effectivePath,
|
|
62113
|
+
workdir: pkg.packageDir,
|
|
62114
|
+
storyId: firstStory?.id
|
|
62115
|
+
}
|
|
62116
|
+
});
|
|
62117
|
+
logger?.info("acceptance.diagnosis", "Diagnosis resolved", {
|
|
62118
|
+
storyId: firstStory?.id,
|
|
62119
|
+
packageDir: pkg.packageDir,
|
|
62120
|
+
verdict: diagnosis.verdict,
|
|
62121
|
+
confidence: diagnosis.confidence,
|
|
62122
|
+
attempt: acceptanceRetries
|
|
62123
|
+
});
|
|
62124
|
+
const cycleResult = await runAcceptanceFixCycle(ctx, prd, pkgFailures, diagnosis, effectivePath, testCommand, {
|
|
62125
|
+
packageDir: pkg.packageDir,
|
|
62126
|
+
testPath: effectivePath
|
|
62127
|
+
});
|
|
62128
|
+
totalCost += cycleResult.costUsd ?? 0;
|
|
62129
|
+
totalInternalIterations += cycleResult.iterations.length;
|
|
62130
|
+
const pkgResolved = cycleResult.exitReason === "resolved" || cycleResult.finalFindings.length === 0;
|
|
62131
|
+
if (!pkgResolved)
|
|
62132
|
+
remainingFindings.push(...cycleResult.finalFindings);
|
|
62133
|
+
}
|
|
62134
|
+
const finalCheck = await runAcceptanceTestsOnce(ctx, prd);
|
|
62135
|
+
const success2 = finalCheck.passed && remainingFindings.length === 0;
|
|
62136
|
+
const failureMessages = !success2 ? finalCheck.failedACs.length > 0 ? finalCheck.failedACs : remainingFindings.length > 0 ? remainingFindings.map((f) => f.message) : ["acceptance validation failed (unknown cause)"] : undefined;
|
|
62137
|
+
return buildResult(success2, prd, totalCost, iterations, storiesCompleted, prdDirty, failureMessages, acceptanceRetries + totalInternalIterations);
|
|
62014
62138
|
}
|
|
62015
62139
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
62016
62140
|
}
|
|
62017
|
-
var _acceptanceLoopDeps, _acceptanceFixCycleDeps, MAX_STUB_REGENS = 2;
|
|
62141
|
+
var _acceptanceLoopDeps, _acceptanceFixCycleDeps, _runAcceptanceTestsOnceDeps, MAX_STUB_REGENS = 2;
|
|
62018
62142
|
var init_acceptance_loop = __esm(() => {
|
|
62019
62143
|
init_acceptance2();
|
|
62020
62144
|
init_findings();
|
|
@@ -62027,11 +62151,15 @@ var init_acceptance_loop = __esm(() => {
|
|
|
62027
62151
|
init_acceptance_helpers();
|
|
62028
62152
|
init_acceptance_helpers();
|
|
62029
62153
|
_acceptanceLoopDeps = {
|
|
62030
|
-
loadSemanticVerdicts
|
|
62154
|
+
loadSemanticVerdicts,
|
|
62155
|
+
loadAcceptanceTestContent
|
|
62031
62156
|
};
|
|
62032
62157
|
_acceptanceFixCycleDeps = {
|
|
62033
62158
|
runFixCycle
|
|
62034
62159
|
};
|
|
62160
|
+
_runAcceptanceTestsOnceDeps = {
|
|
62161
|
+
importAcceptanceStage: () => Promise.resolve().then(() => (init_acceptance3(), exports_acceptance2))
|
|
62162
|
+
};
|
|
62035
62163
|
});
|
|
62036
62164
|
|
|
62037
62165
|
// src/session/scratch-purge.ts
|
|
@@ -64063,6 +64191,7 @@ function resolveMaxAttemptsOutcome(failureCategory) {
|
|
|
64063
64191
|
case "isolation-violation":
|
|
64064
64192
|
case "verifier-rejected":
|
|
64065
64193
|
case "greenfield-no-tests":
|
|
64194
|
+
case "no-tests-authored":
|
|
64066
64195
|
return "pause";
|
|
64067
64196
|
case "runtime-crash":
|
|
64068
64197
|
return "pause";
|
|
@@ -64098,7 +64227,7 @@ async function handleTierEscalation(ctx) {
|
|
|
64098
64227
|
const escalateRetryAsLite = ctx.pipelineResult.context.retryAsLite === true;
|
|
64099
64228
|
const escalateFailureCategory = ctx.pipelineResult.context.tddFailureCategory;
|
|
64100
64229
|
const escalateReviewFindings = ctx.pipelineResult.context.reviewFindings;
|
|
64101
|
-
const
|
|
64230
|
+
const escalateRetryAsTddSimple = escalateFailureCategory === "greenfield-no-tests";
|
|
64102
64231
|
const routingMode = ctx.config.routing.llm?.mode ?? "hybrid";
|
|
64103
64232
|
if (!escalationResult || !ctx.config.autoMode.escalation.enabled) {
|
|
64104
64233
|
return await handleNoTierAvailable(ctx, escalateFailureCategory);
|
|
@@ -64111,12 +64240,12 @@ async function handleTierEscalation(ctx) {
|
|
|
64111
64240
|
const escalatedTier = escalationResult.tier;
|
|
64112
64241
|
for (const s of storiesToEscalate) {
|
|
64113
64242
|
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
64114
|
-
const
|
|
64115
|
-
if (
|
|
64116
|
-
logger?.warn("escalation", "Switching strategy to
|
|
64243
|
+
const shouldSwitchToTddSimple = escalateRetryAsTddSimple && isThreeSessionStrategy(currentTestStrategy);
|
|
64244
|
+
if (shouldSwitchToTddSimple) {
|
|
64245
|
+
logger?.warn("escalation", "Switching strategy to tdd-simple (greenfield-no-tests fallback)", {
|
|
64117
64246
|
storyId: s.id,
|
|
64118
64247
|
fromStrategy: currentTestStrategy,
|
|
64119
|
-
toStrategy: "
|
|
64248
|
+
toStrategy: "tdd-simple"
|
|
64120
64249
|
});
|
|
64121
64250
|
} else {
|
|
64122
64251
|
logger?.warn("escalation", "Escalating story to next tier", {
|
|
@@ -64137,19 +64266,19 @@ async function handleTierEscalation(ctx) {
|
|
|
64137
64266
|
if (!shouldEscalate)
|
|
64138
64267
|
return s;
|
|
64139
64268
|
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
64140
|
-
const
|
|
64269
|
+
const shouldSwitchToTddSimple = escalateRetryAsTddSimple && isThreeSessionStrategy(currentTestStrategy);
|
|
64141
64270
|
const baseRouting = s.routing ?? { ...ctx.routing };
|
|
64142
64271
|
const updatedRouting = {
|
|
64143
64272
|
...baseRouting,
|
|
64144
|
-
modelTier:
|
|
64273
|
+
modelTier: shouldSwitchToTddSimple ? baseRouting.modelTier : escalatedTier,
|
|
64145
64274
|
...nextAgent !== undefined ? { agent: nextAgent } : {},
|
|
64146
64275
|
...escalateRetryAsLite ? { testStrategy: "three-session-tdd-lite" } : {},
|
|
64147
|
-
...
|
|
64276
|
+
...shouldSwitchToTddSimple ? { testStrategy: "tdd-simple" } : {}
|
|
64148
64277
|
};
|
|
64149
64278
|
const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
|
|
64150
64279
|
const isChangingTier = currentStoryTier !== escalatedTier;
|
|
64151
|
-
const shouldResetAttempts = isChangingTier ||
|
|
64152
|
-
const escalationRecord = isChangingTier ||
|
|
64280
|
+
const shouldResetAttempts = isChangingTier || shouldSwitchToTddSimple;
|
|
64281
|
+
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
64282
|
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost, verifiedPipelineReason, escalateFailureCategory);
|
|
64154
64283
|
return {
|
|
64155
64284
|
...s,
|
|
@@ -64188,6 +64317,7 @@ async function handleTierEscalation(ctx) {
|
|
|
64188
64317
|
var _tierEscalationDeps;
|
|
64189
64318
|
var init_tier_escalation = __esm(() => {
|
|
64190
64319
|
init_pipeline();
|
|
64320
|
+
init_config();
|
|
64191
64321
|
init_hooks();
|
|
64192
64322
|
init_logger2();
|
|
64193
64323
|
init_prd();
|
|
@@ -97923,7 +98053,7 @@ var FIELD_DESCRIPTIONS = {
|
|
|
97923
98053
|
"tdd.sessionTiers.verifier": "Model tier for verifier session",
|
|
97924
98054
|
"tdd.testWriterAllowedPaths": "Glob patterns for files test-writer can modify",
|
|
97925
98055
|
"tdd.rollbackOnFailure": "Rollback git changes when TDD fails",
|
|
97926
|
-
"tdd.greenfieldDetection": "Force
|
|
98056
|
+
"tdd.greenfieldDetection": "Force tdd-simple on projects with no test files",
|
|
97927
98057
|
constitution: "Constitution settings (core rules and constraints)",
|
|
97928
98058
|
"constitution.enabled": "Enable constitution loading and injection",
|
|
97929
98059
|
"constitution.path": "Path to constitution file (relative to nax/ directory)",
|