@nathapp/nax 0.59.2 → 0.59.3
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 +526 -296
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -18060,7 +18060,7 @@ function isLegacyFlatModels(val) {
|
|
|
18060
18060
|
}
|
|
18061
18061
|
return false;
|
|
18062
18062
|
}
|
|
18063
|
-
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, PerAgentModelMapSchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, SemanticReviewConfigSchema, ReviewDialogueConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceFixConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PromptAuditConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, DebaterSchema, toObject = (val) => val === undefined || val === null ? {} : val, RESOLVER_TYPES, makeResolverSchema = (defaultType) => exports_external.preprocess(toObject, exports_external.object({
|
|
18063
|
+
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, PerAgentModelMapSchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, SemanticReviewConfigSchema, ReviewDialogueConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceFixConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PromptAuditConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, DebaterPersonaEnum, DebaterSchema, toObject = (val) => val === undefined || val === null ? {} : val, RESOLVER_TYPES, makeResolverSchema = (defaultType) => exports_external.preprocess(toObject, exports_external.object({
|
|
18064
18064
|
type: exports_external.enum(RESOLVER_TYPES).default(defaultType),
|
|
18065
18065
|
agent: exports_external.string().min(1).optional(),
|
|
18066
18066
|
tieBreaker: exports_external.string().min(1).optional(),
|
|
@@ -18072,7 +18072,8 @@ var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, PerAgentModelMapSchema
|
|
|
18072
18072
|
rounds: exports_external.number().int().min(1).default(defaults.rounds),
|
|
18073
18073
|
mode: exports_external.enum(["panel", "hybrid"]).default("panel"),
|
|
18074
18074
|
debaters: exports_external.array(DebaterSchema).min(2, "debaters must have at least 2 entries").optional(),
|
|
18075
|
-
timeoutSeconds: exports_external.number().int().positive().default(600)
|
|
18075
|
+
timeoutSeconds: exports_external.number().int().positive().default(600),
|
|
18076
|
+
autoPersona: exports_external.boolean().default(false)
|
|
18076
18077
|
})), DebateConfigSchema, NaxConfigSchema;
|
|
18077
18078
|
var init_schemas3 = __esm(() => {
|
|
18078
18079
|
init_zod();
|
|
@@ -18431,9 +18432,11 @@ var init_schemas3 = __esm(() => {
|
|
|
18431
18432
|
GenerateConfigSchema = exports_external.object({
|
|
18432
18433
|
agents: exports_external.array(exports_external.enum(VALID_AGENT_TYPES)).optional()
|
|
18433
18434
|
});
|
|
18435
|
+
DebaterPersonaEnum = exports_external.enum(["challenger", "pragmatist", "completionist", "security", "testability"]);
|
|
18434
18436
|
DebaterSchema = exports_external.object({
|
|
18435
18437
|
agent: exports_external.string().min(1, "debater.agent must be non-empty"),
|
|
18436
|
-
model: exports_external.string().min(1, "debater.model must be non-empty").optional()
|
|
18438
|
+
model: exports_external.string().min(1, "debater.model must be non-empty").optional(),
|
|
18439
|
+
persona: DebaterPersonaEnum.optional()
|
|
18437
18440
|
});
|
|
18438
18441
|
RESOLVER_TYPES = ["synthesis", "majority-fail-closed", "majority-fail-open", "custom"];
|
|
18439
18442
|
DebateConfigSchema = exports_external.preprocess(toObject, exports_external.object({
|
|
@@ -18722,7 +18725,8 @@ var init_schemas3 = __esm(() => {
|
|
|
18722
18725
|
sessionMode: "stateful",
|
|
18723
18726
|
rounds: 3,
|
|
18724
18727
|
mode: "panel",
|
|
18725
|
-
timeoutSeconds: 600
|
|
18728
|
+
timeoutSeconds: 600,
|
|
18729
|
+
autoPersona: false
|
|
18726
18730
|
},
|
|
18727
18731
|
review: {
|
|
18728
18732
|
enabled: true,
|
|
@@ -18730,7 +18734,8 @@ var init_schemas3 = __esm(() => {
|
|
|
18730
18734
|
sessionMode: "one-shot",
|
|
18731
18735
|
rounds: 2,
|
|
18732
18736
|
mode: "panel",
|
|
18733
|
-
timeoutSeconds: 600
|
|
18737
|
+
timeoutSeconds: 600,
|
|
18738
|
+
autoPersona: false
|
|
18734
18739
|
},
|
|
18735
18740
|
acceptance: {
|
|
18736
18741
|
enabled: false,
|
|
@@ -18738,7 +18743,8 @@ var init_schemas3 = __esm(() => {
|
|
|
18738
18743
|
sessionMode: "one-shot",
|
|
18739
18744
|
rounds: 1,
|
|
18740
18745
|
mode: "panel",
|
|
18741
|
-
timeoutSeconds: 600
|
|
18746
|
+
timeoutSeconds: 600,
|
|
18747
|
+
autoPersona: false
|
|
18742
18748
|
},
|
|
18743
18749
|
rectification: {
|
|
18744
18750
|
enabled: false,
|
|
@@ -18746,7 +18752,8 @@ var init_schemas3 = __esm(() => {
|
|
|
18746
18752
|
sessionMode: "one-shot",
|
|
18747
18753
|
rounds: 1,
|
|
18748
18754
|
mode: "panel",
|
|
18749
|
-
timeoutSeconds: 600
|
|
18755
|
+
timeoutSeconds: 600,
|
|
18756
|
+
autoPersona: false
|
|
18750
18757
|
},
|
|
18751
18758
|
escalation: {
|
|
18752
18759
|
enabled: false,
|
|
@@ -18754,7 +18761,8 @@ var init_schemas3 = __esm(() => {
|
|
|
18754
18761
|
sessionMode: "one-shot",
|
|
18755
18762
|
rounds: 1,
|
|
18756
18763
|
mode: "panel",
|
|
18757
|
-
timeoutSeconds: 600
|
|
18764
|
+
timeoutSeconds: 600,
|
|
18765
|
+
autoPersona: false
|
|
18758
18766
|
}
|
|
18759
18767
|
}
|
|
18760
18768
|
})),
|
|
@@ -21365,28 +21373,85 @@ var init_config = __esm(() => {
|
|
|
21365
21373
|
init_profile();
|
|
21366
21374
|
});
|
|
21367
21375
|
|
|
21368
|
-
// src/
|
|
21369
|
-
function
|
|
21370
|
-
const
|
|
21371
|
-
|
|
21372
|
-
|
|
21373
|
-
|
|
21374
|
-
|
|
21375
|
-
|
|
21376
|
-
|
|
21377
|
-
|
|
21378
|
-
|
|
21376
|
+
// src/utils/llm-json.ts
|
|
21377
|
+
function extractJsonFromMarkdown(text) {
|
|
21378
|
+
const match = text.match(/```(?:json)?\s*\n([\s\S]*?)\n?\s*```/);
|
|
21379
|
+
if (match) {
|
|
21380
|
+
return match[1] ?? text;
|
|
21381
|
+
}
|
|
21382
|
+
return text;
|
|
21383
|
+
}
|
|
21384
|
+
function stripTrailingCommas(text) {
|
|
21385
|
+
return text.replace(/,\s*([}\]])/g, "$1");
|
|
21386
|
+
}
|
|
21387
|
+
function extractJsonObject(text) {
|
|
21388
|
+
const objStart = text.indexOf("{");
|
|
21389
|
+
const arrStart = text.indexOf("[");
|
|
21390
|
+
let start;
|
|
21391
|
+
let closeChar;
|
|
21392
|
+
if (objStart === -1 && arrStart === -1)
|
|
21393
|
+
return null;
|
|
21394
|
+
if (objStart === -1) {
|
|
21395
|
+
start = arrStart;
|
|
21396
|
+
closeChar = "]";
|
|
21397
|
+
} else if (arrStart === -1) {
|
|
21398
|
+
start = objStart;
|
|
21399
|
+
closeChar = "}";
|
|
21400
|
+
} else if (objStart < arrStart) {
|
|
21401
|
+
start = objStart;
|
|
21402
|
+
closeChar = "}";
|
|
21403
|
+
} else {
|
|
21404
|
+
start = arrStart;
|
|
21405
|
+
closeChar = "]";
|
|
21406
|
+
}
|
|
21407
|
+
const end = text.lastIndexOf(closeChar);
|
|
21408
|
+
if (end <= start)
|
|
21409
|
+
return null;
|
|
21410
|
+
return text.slice(start, end + 1);
|
|
21411
|
+
}
|
|
21412
|
+
function wrapJsonPrompt(prompt) {
|
|
21413
|
+
return `IMPORTANT: Your entire response must be a single JSON object or array. Do not explain your reasoning. Do not use markdown formatting. Output ONLY the JSON.
|
|
21379
21414
|
|
|
21380
|
-
|
|
21381
|
-
${proposalsSection}
|
|
21415
|
+
${prompt.trim()}
|
|
21382
21416
|
|
|
21383
|
-
|
|
21417
|
+
YOUR RESPONSE MUST START WITH { OR [ AND END WITH } OR ]. No other text.`;
|
|
21384
21418
|
}
|
|
21385
|
-
function
|
|
21386
|
-
const
|
|
21419
|
+
function parseLLMJson(text) {
|
|
21420
|
+
const trimmed = text.trim();
|
|
21421
|
+
try {
|
|
21422
|
+
return JSON.parse(trimmed);
|
|
21423
|
+
} catch {}
|
|
21424
|
+
const fromFence = extractJsonFromMarkdown(trimmed);
|
|
21425
|
+
if (fromFence !== trimmed) {
|
|
21426
|
+
try {
|
|
21427
|
+
return JSON.parse(stripTrailingCommas(fromFence));
|
|
21428
|
+
} catch {}
|
|
21429
|
+
}
|
|
21430
|
+
const bareJson = extractJsonObject(trimmed);
|
|
21431
|
+
if (bareJson) {
|
|
21432
|
+
try {
|
|
21433
|
+
return JSON.parse(stripTrailingCommas(bareJson));
|
|
21434
|
+
} catch {}
|
|
21435
|
+
}
|
|
21436
|
+
throw new SyntaxError("[llm-json] Failed to parse LLM response as JSON");
|
|
21437
|
+
}
|
|
21438
|
+
function tryParseLLMJson(text) {
|
|
21439
|
+
try {
|
|
21440
|
+
return parseLLMJson(text);
|
|
21441
|
+
} catch {
|
|
21442
|
+
return null;
|
|
21443
|
+
}
|
|
21444
|
+
}
|
|
21445
|
+
|
|
21446
|
+
// src/debate/resolvers.ts
|
|
21447
|
+
function buildProposalsSection(proposals) {
|
|
21448
|
+
return proposals.map((p, i) => `### Proposal ${i + 1}
|
|
21387
21449
|
${p}`).join(`
|
|
21388
21450
|
|
|
21389
21451
|
`);
|
|
21452
|
+
}
|
|
21453
|
+
function buildSynthesisPrompt(proposals, critiques) {
|
|
21454
|
+
const proposalsSection = buildProposalsSection(proposals);
|
|
21390
21455
|
const critiquesSection = critiques.length > 0 ? `
|
|
21391
21456
|
|
|
21392
21457
|
## Critiques
|
|
@@ -21402,10 +21467,7 @@ ${proposalsSection}${critiquesSection}
|
|
|
21402
21467
|
Please synthesize these into the best possible unified response, incorporating the strongest elements from each proposal.`;
|
|
21403
21468
|
}
|
|
21404
21469
|
function buildJudgePrompt(proposals, critiques) {
|
|
21405
|
-
const proposalsSection = proposals
|
|
21406
|
-
${p}`).join(`
|
|
21407
|
-
|
|
21408
|
-
`);
|
|
21470
|
+
const proposalsSection = buildProposalsSection(proposals);
|
|
21409
21471
|
const critiquesSection = critiques.length > 0 ? `
|
|
21410
21472
|
|
|
21411
21473
|
## Critiques
|
|
@@ -21420,28 +21482,6 @@ ${proposalsSection}${critiquesSection}
|
|
|
21420
21482
|
|
|
21421
21483
|
As the judge, provide your final verdict with clear reasoning, selecting or synthesizing the best approach.`;
|
|
21422
21484
|
}
|
|
21423
|
-
function buildRebuttalContext(prompt, proposals, rebuttalOutputs, currentDebaterIndex) {
|
|
21424
|
-
const proposalsSection = proposals.map((p, i) => `### Proposal ${i + 1} (${p.debater.agent})
|
|
21425
|
-
${p.output}`).join(`
|
|
21426
|
-
|
|
21427
|
-
`);
|
|
21428
|
-
const rebuttalsSection = rebuttalOutputs.length > 0 ? `
|
|
21429
|
-
|
|
21430
|
-
## Previous Rebuttals
|
|
21431
|
-
${rebuttalOutputs.map((r, i) => `${i + 1}. ${r}`).join(`
|
|
21432
|
-
|
|
21433
|
-
`)}` : "";
|
|
21434
|
-
const debaterNumber = currentDebaterIndex + 1;
|
|
21435
|
-
return `${prompt}
|
|
21436
|
-
|
|
21437
|
-
## Proposals
|
|
21438
|
-
${proposalsSection}${rebuttalsSection}
|
|
21439
|
-
|
|
21440
|
-
## Your Task
|
|
21441
|
-
You are debater ${debaterNumber}. Provide your rebuttal to the proposals and previous rebuttals above.`;
|
|
21442
|
-
}
|
|
21443
|
-
|
|
21444
|
-
// src/debate/resolvers.ts
|
|
21445
21485
|
function stripMarkdownFence(text) {
|
|
21446
21486
|
const match = text.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/);
|
|
21447
21487
|
return match ? match[1] ?? text : text;
|
|
@@ -21491,7 +21531,6 @@ async function judgeResolver(proposals, critiques, resolverConfig, opts) {
|
|
|
21491
21531
|
return adapter.complete(prompt, opts.completeOptions);
|
|
21492
21532
|
}
|
|
21493
21533
|
var DEFAULT_FALLBACK_AGENT = "claude";
|
|
21494
|
-
var init_resolvers = () => {};
|
|
21495
21534
|
|
|
21496
21535
|
// src/debate/session-helpers.ts
|
|
21497
21536
|
function resolveDebaterModel(debater, config2) {
|
|
@@ -21581,21 +21620,13 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21581
21620
|
let passCount = 0;
|
|
21582
21621
|
let failCount = 0;
|
|
21583
21622
|
for (const proposal of proposalOutputs) {
|
|
21584
|
-
|
|
21585
|
-
|
|
21586
|
-
|
|
21587
|
-
|
|
21588
|
-
|
|
21589
|
-
|
|
21590
|
-
|
|
21591
|
-
else
|
|
21592
|
-
failCount++;
|
|
21593
|
-
} catch {
|
|
21594
|
-
if (failOpen)
|
|
21595
|
-
passCount++;
|
|
21596
|
-
else
|
|
21597
|
-
failCount++;
|
|
21598
|
-
}
|
|
21623
|
+
const parsed = tryParseLLMJson(proposal);
|
|
21624
|
+
if (parsed !== null && typeof parsed.passed === "boolean" && parsed.passed)
|
|
21625
|
+
passCount++;
|
|
21626
|
+
else if (failOpen)
|
|
21627
|
+
passCount++;
|
|
21628
|
+
else
|
|
21629
|
+
failCount++;
|
|
21599
21630
|
}
|
|
21600
21631
|
debateCtx.majorityVote = { passed: rawOutcome === "passed", passCount, failCount };
|
|
21601
21632
|
}
|
|
@@ -21694,7 +21725,6 @@ var init_session_helpers = __esm(() => {
|
|
|
21694
21725
|
init_registry();
|
|
21695
21726
|
init_config();
|
|
21696
21727
|
init_logger2();
|
|
21697
|
-
init_resolvers();
|
|
21698
21728
|
_debateSessionDeps = {
|
|
21699
21729
|
getAgent: (name, config2) => config2 ? createAgentRegistry(config2).getAgent(name) : getAgent(name),
|
|
21700
21730
|
getSafeLogger,
|
|
@@ -21728,6 +21758,171 @@ async function allSettledBounded(tasks, limit) {
|
|
|
21728
21758
|
return results;
|
|
21729
21759
|
}
|
|
21730
21760
|
|
|
21761
|
+
// src/debate/personas.ts
|
|
21762
|
+
function resolvePersonas(debaters, stage, autoPersona) {
|
|
21763
|
+
if (!autoPersona)
|
|
21764
|
+
return debaters;
|
|
21765
|
+
const rotation = stage === "plan" ? PLAN_ROTATION : REVIEW_ROTATION;
|
|
21766
|
+
let rotationIndex = 0;
|
|
21767
|
+
return debaters.map((d) => {
|
|
21768
|
+
if (d.persona)
|
|
21769
|
+
return d;
|
|
21770
|
+
const assigned = rotation[rotationIndex % rotation.length];
|
|
21771
|
+
rotationIndex++;
|
|
21772
|
+
return { ...d, persona: assigned };
|
|
21773
|
+
});
|
|
21774
|
+
}
|
|
21775
|
+
var PERSONA_FRAGMENTS, PLAN_ROTATION, REVIEW_ROTATION;
|
|
21776
|
+
var init_personas = __esm(() => {
|
|
21777
|
+
PERSONA_FRAGMENTS = {
|
|
21778
|
+
challenger: {
|
|
21779
|
+
identity: "You are the challenger \u2014 your job is to stress-test proposals and find weaknesses.",
|
|
21780
|
+
lens: "Question every assumption. Look for missing edge cases, unhandled error states, " + "and scenarios where the proposed approach could break under real-world conditions. " + "If a proposal lacks justification for a design choice, call it out."
|
|
21781
|
+
},
|
|
21782
|
+
pragmatist: {
|
|
21783
|
+
identity: "You are the pragmatist \u2014 your job is to find the simplest path that satisfies the spec.",
|
|
21784
|
+
lens: "Favour minimal scope, fewest files changed, and lowest complexity. " + "Challenge any proposal that adds abstraction, configuration, or code beyond what the spec requires. " + "If something can be done in 5 lines instead of 50, advocate for the 5-line version."
|
|
21785
|
+
},
|
|
21786
|
+
completionist: {
|
|
21787
|
+
identity: "You are the completionist \u2014 your job is to ensure nothing is missed.",
|
|
21788
|
+
lens: "Verify every acceptance criterion is addressed. Check that edge cases have tests, " + "that error messages are user-friendly, and that the implementation handles all status/state variants. " + "If the spec is ambiguous, flag it and propose the safer interpretation."
|
|
21789
|
+
},
|
|
21790
|
+
security: {
|
|
21791
|
+
identity: "You are the security reviewer \u2014 your job is to surface risks before they ship.",
|
|
21792
|
+
lens: "Evaluate input validation, secret handling, injection vectors, and trust boundaries. " + "Check that user-supplied data is never used unsanitised in commands, queries, or file paths. " + "If the proposal touches auth, permissions, or external APIs, apply extra scrutiny."
|
|
21793
|
+
},
|
|
21794
|
+
testability: {
|
|
21795
|
+
identity: "You are the testability advocate \u2014 your job is to ensure the design is verifiable.",
|
|
21796
|
+
lens: "Assess whether the proposed implementation can be tested without mocks, " + "whether test boundaries are clean, and whether the acceptance criteria are machine-verifiable. " + "Challenge any design that makes testing harder (global state, tight coupling, hidden side effects)."
|
|
21797
|
+
}
|
|
21798
|
+
};
|
|
21799
|
+
PLAN_ROTATION = ["challenger", "pragmatist", "completionist", "security", "testability"];
|
|
21800
|
+
REVIEW_ROTATION = ["security", "completionist", "testability", "challenger", "pragmatist"];
|
|
21801
|
+
});
|
|
21802
|
+
|
|
21803
|
+
// src/debate/prompt-builder.ts
|
|
21804
|
+
class DebatePromptBuilder {
|
|
21805
|
+
stageContext;
|
|
21806
|
+
options;
|
|
21807
|
+
constructor(stageContext, options) {
|
|
21808
|
+
this.stageContext = stageContext;
|
|
21809
|
+
this.options = options;
|
|
21810
|
+
}
|
|
21811
|
+
buildProposalPrompt(debaterIndex) {
|
|
21812
|
+
const personaBlock = this.buildPersonaBlock(debaterIndex);
|
|
21813
|
+
return `${this.stageContext.taskContext}${personaBlock}
|
|
21814
|
+
|
|
21815
|
+
${this.stageContext.outputFormat}`;
|
|
21816
|
+
}
|
|
21817
|
+
buildCritiquePrompt(debaterIndex, proposals) {
|
|
21818
|
+
const otherProposals = proposals.filter((_, i) => i !== debaterIndex);
|
|
21819
|
+
const proposalsSection = this.buildProposalsSection(otherProposals);
|
|
21820
|
+
const personaBlock = this.buildPersonaBlock(debaterIndex);
|
|
21821
|
+
return `You are reviewing proposals for a ${this.stageContext.stage} task.
|
|
21822
|
+
|
|
21823
|
+
## Task
|
|
21824
|
+
${this.stageContext.taskContext}${personaBlock}
|
|
21825
|
+
|
|
21826
|
+
## Other Agents' Proposals
|
|
21827
|
+
${proposalsSection}
|
|
21828
|
+
|
|
21829
|
+
Please critique these proposals and provide your refined analysis, identifying strengths, weaknesses, and your own updated position.`;
|
|
21830
|
+
}
|
|
21831
|
+
buildRebuttalPrompt(debaterIndex, proposals, priorRebuttals) {
|
|
21832
|
+
const contextBlock = this.options.sessionMode === "one-shot" ? `${this.stageContext.taskContext}
|
|
21833
|
+
|
|
21834
|
+
` : "";
|
|
21835
|
+
const proposalsSection = this.buildProposalsSection(proposals);
|
|
21836
|
+
const rebuttalsSection = this.buildRebuttalsSection(priorRebuttals);
|
|
21837
|
+
const personaBlock = this.buildPersonaBlock(debaterIndex);
|
|
21838
|
+
const debaterNumber = debaterIndex + 1;
|
|
21839
|
+
return `${contextBlock}## Proposals
|
|
21840
|
+
${proposalsSection}${rebuttalsSection}${personaBlock}
|
|
21841
|
+
|
|
21842
|
+
## Your Task
|
|
21843
|
+
You are debater ${debaterNumber}. Provide your critique in prose.
|
|
21844
|
+
Identify strengths, weaknesses, and specific improvements for each proposal.
|
|
21845
|
+
Do NOT output JSON \u2014 focus on analysis only.`;
|
|
21846
|
+
}
|
|
21847
|
+
buildSynthesisPrompt(proposals, critiques, promptSuffix) {
|
|
21848
|
+
const proposalsSection = this.buildProposalsSection(proposals);
|
|
21849
|
+
const critiquesSection = this.buildCritiquesSection(critiques);
|
|
21850
|
+
return `You are a synthesis agent. Your task is to combine the strongest elements from multiple proposals into a single, optimal response.
|
|
21851
|
+
|
|
21852
|
+
${this.stageContext.taskContext}
|
|
21853
|
+
|
|
21854
|
+
## Proposals
|
|
21855
|
+
${proposalsSection}
|
|
21856
|
+
|
|
21857
|
+
## Critiques
|
|
21858
|
+
${critiquesSection}
|
|
21859
|
+
|
|
21860
|
+
Please synthesize these into the best possible unified response, incorporating the strongest elements from each proposal.
|
|
21861
|
+
${this.stageContext.outputFormat}${promptSuffix ? `
|
|
21862
|
+
${promptSuffix}` : ""}`;
|
|
21863
|
+
}
|
|
21864
|
+
buildJudgePrompt(proposals, critiques) {
|
|
21865
|
+
const proposalsSection = this.buildProposalsSection(proposals);
|
|
21866
|
+
const critiquesSection = this.buildCritiquesSection(critiques);
|
|
21867
|
+
return `You are a judge evaluating multiple proposals. Select the best proposal or synthesize the optimal response.
|
|
21868
|
+
|
|
21869
|
+
${this.stageContext.taskContext}
|
|
21870
|
+
|
|
21871
|
+
## Proposals
|
|
21872
|
+
${proposalsSection}
|
|
21873
|
+
|
|
21874
|
+
## Critiques
|
|
21875
|
+
${critiquesSection}
|
|
21876
|
+
|
|
21877
|
+
Evaluate each proposal against the critiques and provide the best possible response.
|
|
21878
|
+
${this.stageContext.outputFormat}`;
|
|
21879
|
+
}
|
|
21880
|
+
buildClosePrompt() {
|
|
21881
|
+
return "Close this debate session.";
|
|
21882
|
+
}
|
|
21883
|
+
buildPersonaBlock(debaterIndex) {
|
|
21884
|
+
const debater = this.options.debaters[debaterIndex];
|
|
21885
|
+
if (!debater?.persona)
|
|
21886
|
+
return "";
|
|
21887
|
+
const { identity, lens } = PERSONA_FRAGMENTS[debater.persona];
|
|
21888
|
+
return `
|
|
21889
|
+
|
|
21890
|
+
## Your Role
|
|
21891
|
+
${identity}
|
|
21892
|
+
${lens}`;
|
|
21893
|
+
}
|
|
21894
|
+
buildProposalsSection(proposals) {
|
|
21895
|
+
return proposals.map((p, i) => `### Proposal ${i + 1} (${this.buildDebaterLabel(p.debater)})
|
|
21896
|
+
${p.output}`).join(`
|
|
21897
|
+
|
|
21898
|
+
`);
|
|
21899
|
+
}
|
|
21900
|
+
buildRebuttalsSection(rebuttals) {
|
|
21901
|
+
if (rebuttals.length === 0)
|
|
21902
|
+
return "";
|
|
21903
|
+
return `
|
|
21904
|
+
|
|
21905
|
+
## Previous Rebuttals
|
|
21906
|
+
${rebuttals.map((r, i) => `${i + 1}. ${r.output}`).join(`
|
|
21907
|
+
|
|
21908
|
+
`)}`;
|
|
21909
|
+
}
|
|
21910
|
+
buildCritiquesSection(critiques) {
|
|
21911
|
+
if (critiques.length === 0)
|
|
21912
|
+
return "";
|
|
21913
|
+
return critiques.map((c, i) => `### Critique ${i + 1} (${this.buildDebaterLabel(c.debater)})
|
|
21914
|
+
${c.output}`).join(`
|
|
21915
|
+
|
|
21916
|
+
`);
|
|
21917
|
+
}
|
|
21918
|
+
buildDebaterLabel(debater) {
|
|
21919
|
+
return debater.persona ? `${debater.agent} (${debater.persona})` : debater.agent;
|
|
21920
|
+
}
|
|
21921
|
+
}
|
|
21922
|
+
var init_prompt_builder = __esm(() => {
|
|
21923
|
+
init_personas();
|
|
21924
|
+
});
|
|
21925
|
+
|
|
21731
21926
|
// src/debate/session-stateful.ts
|
|
21732
21927
|
async function runStatefulTurn(ctx, adapter, debater, prompt, roleKey, keepSessionOpen) {
|
|
21733
21928
|
const modelTier = modelTierFromDebater(debater);
|
|
@@ -21783,7 +21978,9 @@ async function closeStatefulSession(ctx, adapter, debater, roleKey) {
|
|
|
21783
21978
|
async function runStateful(ctx, prompt) {
|
|
21784
21979
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21785
21980
|
const config2 = ctx.stageConfig;
|
|
21786
|
-
const
|
|
21981
|
+
const personaStage = ctx.stage === "plan" ? "plan" : "review";
|
|
21982
|
+
const rawDebaters = config2.debaters ?? [];
|
|
21983
|
+
const debaters = resolvePersonas(rawDebaters, personaStage, config2.autoPersona ?? false);
|
|
21787
21984
|
let totalCostUsd = 0;
|
|
21788
21985
|
const resolved = [];
|
|
21789
21986
|
for (const debater of debaters) {
|
|
@@ -21880,8 +22077,9 @@ async function runStateful(ctx, prompt) {
|
|
|
21880
22077
|
}
|
|
21881
22078
|
let critiqueOutputs = [];
|
|
21882
22079
|
if (config2.rounds > 1) {
|
|
21883
|
-
const
|
|
21884
|
-
const
|
|
22080
|
+
const proposals2 = successfulProposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
22081
|
+
const critiqueBuilder = new DebatePromptBuilder({ taskContext: prompt, outputFormat: "", stage: ctx.stage }, { debaters: proposals2.map((p) => p.debater), sessionMode: ctx.stageConfig.sessionMode ?? "one-shot" });
|
|
22082
|
+
const critiqueSettled = await allSettledBounded(successfulProposals.map((proposal, successfulIdx) => () => runStatefulTurn(ctx, proposal.adapter, proposal.debater, critiqueBuilder.buildCritiquePrompt(successfulIdx, proposals2), proposal.roleKey ?? `debate-${ctx.stage}-${successfulIdx}`, false)), concurrencyLimit);
|
|
21885
22083
|
for (const r of critiqueSettled) {
|
|
21886
22084
|
if (r.status === "fulfilled") {
|
|
21887
22085
|
totalCostUsd += r.value.cost;
|
|
@@ -21917,11 +22115,13 @@ async function runStateful(ctx, prompt) {
|
|
|
21917
22115
|
};
|
|
21918
22116
|
}
|
|
21919
22117
|
var init_session_stateful = __esm(() => {
|
|
22118
|
+
init_personas();
|
|
22119
|
+
init_prompt_builder();
|
|
21920
22120
|
init_session_helpers();
|
|
21921
22121
|
});
|
|
21922
22122
|
|
|
21923
22123
|
// src/debate/session-hybrid.ts
|
|
21924
|
-
async function runRebuttalLoop(ctx, proposals,
|
|
22124
|
+
async function runRebuttalLoop(ctx, proposals, builder, sessionRolePrefix) {
|
|
21925
22125
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21926
22126
|
const config2 = ctx.stageConfig;
|
|
21927
22127
|
const rebuttals = [];
|
|
@@ -21929,7 +22129,7 @@ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix
|
|
|
21929
22129
|
const proposalList = proposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
21930
22130
|
try {
|
|
21931
22131
|
for (let round = 1;round <= config2.rounds; round++) {
|
|
21932
|
-
const priorRebuttals = rebuttals.filter((r) => r.round < round)
|
|
22132
|
+
const priorRebuttals = rebuttals.filter((r) => r.round < round);
|
|
21933
22133
|
for (let debaterIdx = 0;debaterIdx < proposals.length; debaterIdx++) {
|
|
21934
22134
|
const proposal = proposals[debaterIdx];
|
|
21935
22135
|
const sessionRole = `${sessionRolePrefix}-${debaterIdx}`;
|
|
@@ -21938,7 +22138,7 @@ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix
|
|
|
21938
22138
|
round,
|
|
21939
22139
|
debaterIndex: debaterIdx
|
|
21940
22140
|
});
|
|
21941
|
-
const rebuttalPrompt =
|
|
22141
|
+
const rebuttalPrompt = builder.buildRebuttalPrompt(debaterIdx, proposalList, priorRebuttals);
|
|
21942
22142
|
try {
|
|
21943
22143
|
const turnResult = await runStatefulTurn(ctx, proposal.adapter, proposal.debater, rebuttalPrompt, sessionRole, true);
|
|
21944
22144
|
costUsd += turnResult.cost;
|
|
@@ -21968,7 +22168,9 @@ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix
|
|
|
21968
22168
|
async function runHybrid(ctx, prompt) {
|
|
21969
22169
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21970
22170
|
const config2 = ctx.stageConfig;
|
|
21971
|
-
const
|
|
22171
|
+
const personaStage = ctx.stage === "plan" ? "plan" : "review";
|
|
22172
|
+
const rawDebaters = config2.debaters ?? [];
|
|
22173
|
+
const debaters = resolvePersonas(rawDebaters, personaStage, config2.autoPersona ?? false);
|
|
21972
22174
|
let totalCostUsd = 0;
|
|
21973
22175
|
const resolved = [];
|
|
21974
22176
|
for (const debater of debaters) {
|
|
@@ -22033,7 +22235,8 @@ async function runHybrid(ctx, prompt) {
|
|
|
22033
22235
|
}
|
|
22034
22236
|
const proposalOutputs = successfulProposals.map((s) => s.output);
|
|
22035
22237
|
const proposalList = successfulProposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
22036
|
-
const {
|
|
22238
|
+
const rebuttalBuilder = new DebatePromptBuilder({ taskContext: prompt, outputFormat: "", stage: ctx.stage }, { debaters: successfulProposals.map((s) => s.debater), sessionMode: "stateful" });
|
|
22239
|
+
const { rebuttals, costUsd: rebuttalCost } = await runRebuttalLoop(ctx, successfulProposals, rebuttalBuilder, "debate-hybrid");
|
|
22037
22240
|
totalCostUsd += rebuttalCost;
|
|
22038
22241
|
const critiqueOutputs = rebuttals.map((r) => r.output);
|
|
22039
22242
|
const fullResolverContext = ctx.resolverContextInput ? {
|
|
@@ -22055,6 +22258,8 @@ async function runHybrid(ctx, prompt) {
|
|
|
22055
22258
|
};
|
|
22056
22259
|
}
|
|
22057
22260
|
var init_session_hybrid = __esm(() => {
|
|
22261
|
+
init_personas();
|
|
22262
|
+
init_prompt_builder();
|
|
22058
22263
|
init_session_helpers();
|
|
22059
22264
|
init_session_stateful();
|
|
22060
22265
|
});
|
|
@@ -22063,7 +22268,9 @@ var init_session_hybrid = __esm(() => {
|
|
|
22063
22268
|
async function runOneShot(ctx, prompt) {
|
|
22064
22269
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
22065
22270
|
const config2 = ctx.stageConfig;
|
|
22066
|
-
const
|
|
22271
|
+
const personaStage = ctx.stage === "plan" ? "plan" : "review";
|
|
22272
|
+
const rawDebaters = config2.debaters ?? [];
|
|
22273
|
+
const debaters = resolvePersonas(rawDebaters, personaStage, config2.autoPersona ?? false);
|
|
22067
22274
|
let totalCostUsd = 0;
|
|
22068
22275
|
const resolved = [];
|
|
22069
22276
|
for (const debater of debaters) {
|
|
@@ -22166,8 +22373,9 @@ async function runOneShot(ctx, prompt) {
|
|
|
22166
22373
|
}
|
|
22167
22374
|
let critiqueOutputs = [];
|
|
22168
22375
|
if (config2.rounds > 1) {
|
|
22169
|
-
const
|
|
22170
|
-
const
|
|
22376
|
+
const proposals2 = successful.map((p) => ({ debater: p.debater, output: p.output }));
|
|
22377
|
+
const critiqueBuilder = new DebatePromptBuilder({ taskContext: prompt, outputFormat: "", stage: ctx.stage }, { debaters: proposals2.map((p) => p.debater), sessionMode: ctx.stageConfig.sessionMode ?? "one-shot" });
|
|
22378
|
+
const critiqueSettled = await allSettledBounded(successful.map(({ debater, adapter }, i) => () => runComplete(adapter, critiqueBuilder.buildCritiquePrompt(i, proposals2), {
|
|
22171
22379
|
model: resolveDebaterModel(debater, ctx.config),
|
|
22172
22380
|
featureName: ctx.stage,
|
|
22173
22381
|
config: ctx.config,
|
|
@@ -22210,15 +22418,18 @@ async function runOneShot(ctx, prompt) {
|
|
|
22210
22418
|
};
|
|
22211
22419
|
}
|
|
22212
22420
|
var init_session_one_shot = __esm(() => {
|
|
22421
|
+
init_personas();
|
|
22422
|
+
init_prompt_builder();
|
|
22213
22423
|
init_session_helpers();
|
|
22214
22424
|
});
|
|
22215
22425
|
|
|
22216
22426
|
// src/debate/session-plan.ts
|
|
22217
22427
|
import { join as join10 } from "path";
|
|
22218
|
-
async function runPlan2(ctx,
|
|
22428
|
+
async function runPlan2(ctx, taskContext, outputFormat, opts) {
|
|
22219
22429
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
22220
22430
|
const config2 = ctx.stageConfig;
|
|
22221
|
-
const
|
|
22431
|
+
const rawDebaters = config2.debaters ?? [];
|
|
22432
|
+
const debaters = resolvePersonas(rawDebaters, "plan", config2.autoPersona ?? false);
|
|
22222
22433
|
let totalCostUsd = 0;
|
|
22223
22434
|
const resolved = [];
|
|
22224
22435
|
for (const debater of debaters) {
|
|
@@ -22236,14 +22447,15 @@ async function runPlan2(ctx, basePrompt, opts) {
|
|
|
22236
22447
|
});
|
|
22237
22448
|
const debate = ctx.config?.debate;
|
|
22238
22449
|
const concurrencyLimit = debate?.maxConcurrentDebaters ?? 2;
|
|
22239
|
-
const
|
|
22450
|
+
const proposalBuilder = new DebatePromptBuilder({ taskContext, outputFormat, stage: "plan" }, { debaters: resolved.map((r) => r.debater), sessionMode: ctx.stageConfig.sessionMode ?? "one-shot" });
|
|
22451
|
+
const settled = await allSettledBounded(resolved.map(({ debater: rd, adapter }, i) => async () => {
|
|
22240
22452
|
const tempOutputPath = join10(opts.outputDir, `prd-debate-${i}.json`);
|
|
22241
|
-
const debaterPrompt = `${
|
|
22453
|
+
const debaterPrompt = `${proposalBuilder.buildProposalPrompt(i)}
|
|
22242
22454
|
|
|
22243
22455
|
Write the PRD JSON directly to this file path: ${tempOutputPath}
|
|
22244
22456
|
Do NOT output the JSON to the conversation. Write the file, then reply with a brief confirmation.`;
|
|
22245
|
-
const modelTier = modelTierFromDebater(
|
|
22246
|
-
const modelDef = resolveModelDefForDebater(
|
|
22457
|
+
const modelTier = modelTierFromDebater(rd);
|
|
22458
|
+
const modelDef = resolveModelDefForDebater(rd, modelTier, ctx.config);
|
|
22247
22459
|
const planResult = await adapter.plan({
|
|
22248
22460
|
prompt: debaterPrompt,
|
|
22249
22461
|
workdir: opts.workdir,
|
|
@@ -22259,7 +22471,7 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22259
22471
|
sessionRole: `plan-${i}`
|
|
22260
22472
|
});
|
|
22261
22473
|
const output = await _debateSessionDeps.readFile(tempOutputPath);
|
|
22262
|
-
return { debater, adapter, output, cost: planResult.costUsd ?? 0 };
|
|
22474
|
+
return { debater: rd, adapter, output, cost: planResult.costUsd ?? 0 };
|
|
22263
22475
|
}), concurrencyLimit);
|
|
22264
22476
|
const successful = [];
|
|
22265
22477
|
for (let i = 0;i < settled.length; i++) {
|
|
@@ -22332,7 +22544,8 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22332
22544
|
featureName: opts.feature,
|
|
22333
22545
|
timeoutSeconds: opts.timeoutSeconds ?? 600
|
|
22334
22546
|
};
|
|
22335
|
-
const {
|
|
22547
|
+
const rebuttalBuilder = new DebatePromptBuilder({ taskContext, outputFormat: "", stage: "plan" }, { debaters: successful.map((p) => p.debater), sessionMode });
|
|
22548
|
+
const { rebuttals, costUsd } = await runRebuttalLoop(hybridCtx, successful, rebuttalBuilder, "plan-hybrid");
|
|
22336
22549
|
critiqueOutputs = rebuttals.map((r) => r.output);
|
|
22337
22550
|
rebuttalList = rebuttals;
|
|
22338
22551
|
totalCostUsd += costUsd;
|
|
@@ -22363,6 +22576,8 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22363
22576
|
};
|
|
22364
22577
|
}
|
|
22365
22578
|
var init_session_plan = __esm(() => {
|
|
22579
|
+
init_personas();
|
|
22580
|
+
init_prompt_builder();
|
|
22366
22581
|
init_session_helpers();
|
|
22367
22582
|
init_session_hybrid();
|
|
22368
22583
|
});
|
|
@@ -22448,13 +22663,13 @@ class DebateSession {
|
|
|
22448
22663
|
resolverContextInput: this.resolverContextInput
|
|
22449
22664
|
}, prompt);
|
|
22450
22665
|
}
|
|
22451
|
-
async runPlan(
|
|
22666
|
+
async runPlan(taskContext, outputFormat, opts) {
|
|
22452
22667
|
return runPlan2({
|
|
22453
22668
|
storyId: this.storyId,
|
|
22454
22669
|
stage: this.stage,
|
|
22455
22670
|
stageConfig: this.stageConfig,
|
|
22456
22671
|
config: this.config
|
|
22457
|
-
},
|
|
22672
|
+
}, taskContext, outputFormat, opts);
|
|
22458
22673
|
}
|
|
22459
22674
|
}
|
|
22460
22675
|
var DEFAULT_TIMEOUT_SECONDS = 600;
|
|
@@ -22471,8 +22686,8 @@ var init_session = __esm(() => {
|
|
|
22471
22686
|
var init_debate = __esm(() => {
|
|
22472
22687
|
init_session();
|
|
22473
22688
|
init_session_helpers();
|
|
22474
|
-
|
|
22475
|
-
|
|
22689
|
+
init_prompt_builder();
|
|
22690
|
+
init_personas();
|
|
22476
22691
|
});
|
|
22477
22692
|
|
|
22478
22693
|
// src/interaction/bridge-builder.ts
|
|
@@ -23653,50 +23868,6 @@ var init_init = __esm(() => {
|
|
|
23653
23868
|
init_webhook();
|
|
23654
23869
|
});
|
|
23655
23870
|
|
|
23656
|
-
// src/utils/llm-json.ts
|
|
23657
|
-
function extractJsonFromMarkdown(text) {
|
|
23658
|
-
const match = text.match(/```(?:json)?\s*\n([\s\S]*?)\n?\s*```/);
|
|
23659
|
-
if (match) {
|
|
23660
|
-
return match[1] ?? text;
|
|
23661
|
-
}
|
|
23662
|
-
return text;
|
|
23663
|
-
}
|
|
23664
|
-
function stripTrailingCommas(text) {
|
|
23665
|
-
return text.replace(/,\s*([}\]])/g, "$1");
|
|
23666
|
-
}
|
|
23667
|
-
function extractJsonObject(text) {
|
|
23668
|
-
const objStart = text.indexOf("{");
|
|
23669
|
-
const arrStart = text.indexOf("[");
|
|
23670
|
-
let start;
|
|
23671
|
-
let closeChar;
|
|
23672
|
-
if (objStart === -1 && arrStart === -1)
|
|
23673
|
-
return null;
|
|
23674
|
-
if (objStart === -1) {
|
|
23675
|
-
start = arrStart;
|
|
23676
|
-
closeChar = "]";
|
|
23677
|
-
} else if (arrStart === -1) {
|
|
23678
|
-
start = objStart;
|
|
23679
|
-
closeChar = "}";
|
|
23680
|
-
} else if (objStart < arrStart) {
|
|
23681
|
-
start = objStart;
|
|
23682
|
-
closeChar = "}";
|
|
23683
|
-
} else {
|
|
23684
|
-
start = arrStart;
|
|
23685
|
-
closeChar = "]";
|
|
23686
|
-
}
|
|
23687
|
-
const end = text.lastIndexOf(closeChar);
|
|
23688
|
-
if (end <= start)
|
|
23689
|
-
return null;
|
|
23690
|
-
return text.slice(start, end + 1);
|
|
23691
|
-
}
|
|
23692
|
-
function wrapJsonPrompt(prompt) {
|
|
23693
|
-
return `IMPORTANT: Your entire response must be a single JSON object or array. Do not explain your reasoning. Do not use markdown formatting. Output ONLY the JSON.
|
|
23694
|
-
|
|
23695
|
-
${prompt.trim()}
|
|
23696
|
-
|
|
23697
|
-
YOUR RESPONSE MUST START WITH { OR [ AND END WITH } OR ]. No other text.`;
|
|
23698
|
-
}
|
|
23699
|
-
|
|
23700
23871
|
// src/prd/validate.ts
|
|
23701
23872
|
function validateStoryId(id) {
|
|
23702
23873
|
if (!id || id.length === 0) {
|
|
@@ -25802,7 +25973,7 @@ Rules:
|
|
|
25802
25973
|
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
25803
25974
|
- **Prefer behavioral tests** \u2014 import functions and call them rather than reading source files. For example, to verify "getPostRunActions() returns empty array", import PluginRegistry and call getPostRunActions(), don't grep the source file for the method name.
|
|
25804
25975
|
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
|
|
25805
|
-
- **Path anchor (CRITICAL)**: This test file will be saved at \`<repo-root>/.nax/features/${featureName}/${resolvedTestPath}\` and will ALWAYS run from the repo root. The repo root is exactly
|
|
25976
|
+
- **Path anchor (CRITICAL)**: This test file will be saved at \`<repo-root>/.nax/features/${featureName}/${resolvedTestPath}\` and will ALWAYS run from the repo root. The repo root is exactly 3 \`../\` levels above \`__dirname\`: \`join(__dirname, '..', '..', '..')\`. For monorepo projects, navigate into packages from root (e.g. \`join(root, 'apps/api/src')\`).`;
|
|
25806
25977
|
}
|
|
25807
25978
|
async function generateAcceptanceTests(adapter, options) {
|
|
25808
25979
|
const logger = getLogger();
|
|
@@ -26960,6 +27131,7 @@ function buildReviewPrompt(diff, story, _semanticConfig) {
|
|
|
26960
27131
|
"## Diff",
|
|
26961
27132
|
diff,
|
|
26962
27133
|
"",
|
|
27134
|
+
"Also flag any changes in the diff not required by the acceptance criteria above as out-of-scope findings.",
|
|
26963
27135
|
"Respond with JSON: { passed: boolean, findings: [...], findingReasoning: { [id]: string } }"
|
|
26964
27136
|
].join(`
|
|
26965
27137
|
`);
|
|
@@ -26981,7 +27153,7 @@ function buildReReviewPrompt(updatedDiff, previousFindings) {
|
|
|
26981
27153
|
].join(`
|
|
26982
27154
|
`);
|
|
26983
27155
|
}
|
|
26984
|
-
function
|
|
27156
|
+
function buildProposalsSection2(proposals) {
|
|
26985
27157
|
return proposals.map((p) => `### ${p.debater}
|
|
26986
27158
|
${p.output}`).join(`
|
|
26987
27159
|
|
|
@@ -27025,7 +27197,7 @@ function buildDebateResolverPrompt(proposals, critiques, diff, story, _semanticC
|
|
|
27025
27197
|
`);
|
|
27026
27198
|
const framing = buildResolverFraming(resolverContext);
|
|
27027
27199
|
const voteTally = buildVoteTallyLine(resolverContext);
|
|
27028
|
-
const proposalsSection =
|
|
27200
|
+
const proposalsSection = buildProposalsSection2(proposals);
|
|
27029
27201
|
const critiquesSection = buildCritiquesSection(critiques);
|
|
27030
27202
|
return [
|
|
27031
27203
|
framing,
|
|
@@ -27051,7 +27223,7 @@ function buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFi
|
|
|
27051
27223
|
const framing = buildResolverFraming(resolverContext);
|
|
27052
27224
|
const findingsList = previousFindings.length > 0 ? previousFindings.map((f) => `- ${f.ruleId}: ${f.message}`).join(`
|
|
27053
27225
|
`) : "(none)";
|
|
27054
|
-
const proposalsSection =
|
|
27226
|
+
const proposalsSection = buildProposalsSection2(proposals);
|
|
27055
27227
|
const critiquesSection = buildCritiquesSection(critiques);
|
|
27056
27228
|
return [
|
|
27057
27229
|
`${framing} This is a re-review after implementer changes.`,
|
|
@@ -27074,12 +27246,10 @@ function buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFi
|
|
|
27074
27246
|
|
|
27075
27247
|
// src/review/dialogue.ts
|
|
27076
27248
|
function extractDeltaSummary(rawOutput, previousFindings, newFindings) {
|
|
27077
|
-
|
|
27078
|
-
|
|
27079
|
-
|
|
27080
|
-
|
|
27081
|
-
}
|
|
27082
|
-
} catch {}
|
|
27249
|
+
const parsed = tryParseLLMJson(rawOutput);
|
|
27250
|
+
if (parsed && typeof parsed.deltaSummary === "string" && parsed.deltaSummary.length > 0) {
|
|
27251
|
+
return parsed.deltaSummary;
|
|
27252
|
+
}
|
|
27083
27253
|
const newIds = new Set(newFindings.map((f) => f.ruleId));
|
|
27084
27254
|
const prevIds = new Set(previousFindings.map((f) => f.ruleId));
|
|
27085
27255
|
const resolved = previousFindings.filter((f) => !newIds.has(f.ruleId));
|
|
@@ -27119,7 +27289,7 @@ function compactHistory(history) {
|
|
|
27119
27289
|
function parseReviewResponse(output) {
|
|
27120
27290
|
let parsed;
|
|
27121
27291
|
try {
|
|
27122
|
-
parsed =
|
|
27292
|
+
parsed = parseLLMJson(output);
|
|
27123
27293
|
} catch {
|
|
27124
27294
|
throw new NaxError("[dialogue] Failed to parse reviewer JSON response", "REVIEWER_PARSE_FAILED", {
|
|
27125
27295
|
stage: "review",
|
|
@@ -27698,23 +27868,11 @@ function validateLLMShape(parsed) {
|
|
|
27698
27868
|
return { passed: obj.passed, findings: obj.findings };
|
|
27699
27869
|
}
|
|
27700
27870
|
function parseLLMResponse(raw) {
|
|
27701
|
-
const text = raw.trim();
|
|
27702
27871
|
try {
|
|
27703
|
-
return validateLLMShape(
|
|
27704
|
-
} catch {
|
|
27705
|
-
|
|
27706
|
-
if (fromFence !== text) {
|
|
27707
|
-
try {
|
|
27708
|
-
return validateLLMShape(JSON.parse(stripTrailingCommas(fromFence)));
|
|
27709
|
-
} catch {}
|
|
27710
|
-
}
|
|
27711
|
-
const bareJson = extractJsonObject(text);
|
|
27712
|
-
if (bareJson) {
|
|
27713
|
-
try {
|
|
27714
|
-
return validateLLMShape(JSON.parse(stripTrailingCommas(bareJson)));
|
|
27715
|
-
} catch {}
|
|
27872
|
+
return validateLLMShape(tryParseLLMJson(raw));
|
|
27873
|
+
} catch {
|
|
27874
|
+
return null;
|
|
27716
27875
|
}
|
|
27717
|
-
return null;
|
|
27718
27876
|
}
|
|
27719
27877
|
function formatFindings(findings) {
|
|
27720
27878
|
return findings.map((f) => `[${f.severity}] ${f.file}:${f.line} \u2014 ${f.issue}
|
|
@@ -28689,6 +28847,7 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28689
28847
|
config: ctx.config,
|
|
28690
28848
|
projectDir: ctx.projectDir,
|
|
28691
28849
|
maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
|
|
28850
|
+
featureName: ctx.prd.feature,
|
|
28692
28851
|
storyId: ctx.story.id,
|
|
28693
28852
|
sessionRole: "implementer"
|
|
28694
28853
|
});
|
|
@@ -30466,61 +30625,34 @@ var init_executor = __esm(() => {
|
|
|
30466
30625
|
});
|
|
30467
30626
|
|
|
30468
30627
|
// src/verification/parser.ts
|
|
30469
|
-
function
|
|
30470
|
-
if (
|
|
30471
|
-
return
|
|
30472
|
-
|
|
30473
|
-
|
|
30474
|
-
}
|
|
30475
|
-
|
|
30476
|
-
|
|
30628
|
+
function detectFramework(output) {
|
|
30629
|
+
if (/^\s*Test Files\s+\d+/m.test(output))
|
|
30630
|
+
return "vitest";
|
|
30631
|
+
if (/^\s*Tests:\s+\d+/m.test(output))
|
|
30632
|
+
return "jest";
|
|
30633
|
+
if (/={3,}\s+\d+\s+(?:failed|passed).*in\s+[\d.]+s\s*={3,}/m.test(output))
|
|
30634
|
+
return "pytest";
|
|
30635
|
+
if (/^--- (?:FAIL|PASS):/m.test(output) || /^(?:ok|FAIL)\s+\t/m.test(output))
|
|
30636
|
+
return "go";
|
|
30637
|
+
if (/^\(fail\)\s/m.test(output) || /^bun test/m.test(output) || /[\u2713\u2714\u2717\u2718]/m.test(output))
|
|
30638
|
+
return "bun";
|
|
30639
|
+
return "unknown";
|
|
30477
30640
|
}
|
|
30478
|
-
function
|
|
30479
|
-
const
|
|
30480
|
-
|
|
30481
|
-
|
|
30482
|
-
|
|
30483
|
-
|
|
30484
|
-
|
|
30485
|
-
|
|
30486
|
-
|
|
30487
|
-
|
|
30488
|
-
|
|
30489
|
-
|
|
30490
|
-
|
|
30491
|
-
|
|
30492
|
-
let currentFile = "unknown";
|
|
30493
|
-
const lines = output.split(`
|
|
30494
|
-
`);
|
|
30495
|
-
for (let i = 0;i < lines.length; i++) {
|
|
30496
|
-
const line = lines[i];
|
|
30497
|
-
const fileMatch = line.match(/^\s*(?:FAIL|PASS)\s+(\S+\.[jt]sx?)/);
|
|
30498
|
-
if (fileMatch) {
|
|
30499
|
-
currentFile = fileMatch[1];
|
|
30500
|
-
continue;
|
|
30501
|
-
}
|
|
30502
|
-
const bulletMatch = line.match(/^\s+\u25CF\s+(.+)$/);
|
|
30503
|
-
if (bulletMatch) {
|
|
30504
|
-
const testName = bulletMatch[1].trim();
|
|
30505
|
-
let error48 = "";
|
|
30506
|
-
for (let j = i + 1;j < lines.length && j < i + 10; j++) {
|
|
30507
|
-
const next = lines[j].trim();
|
|
30508
|
-
if (!next)
|
|
30509
|
-
continue;
|
|
30510
|
-
if (next.startsWith("\u25CF") || /^(?:FAIL|PASS)\s/.test(next))
|
|
30511
|
-
break;
|
|
30512
|
-
error48 = next;
|
|
30513
|
-
break;
|
|
30514
|
-
}
|
|
30515
|
-
failures.push({
|
|
30516
|
-
file: currentFile,
|
|
30517
|
-
testName,
|
|
30518
|
-
error: error48 || "Unknown error",
|
|
30519
|
-
stackTrace: []
|
|
30520
|
-
});
|
|
30521
|
-
}
|
|
30641
|
+
function parseTestOutput(output) {
|
|
30642
|
+
const framework = detectFramework(output);
|
|
30643
|
+
switch (framework) {
|
|
30644
|
+
case "bun":
|
|
30645
|
+
return parseBunOutput(output);
|
|
30646
|
+
case "jest":
|
|
30647
|
+
case "vitest":
|
|
30648
|
+
return parseJestOutput(output);
|
|
30649
|
+
case "pytest":
|
|
30650
|
+
return parsePytestOutput(output);
|
|
30651
|
+
case "go":
|
|
30652
|
+
return parseGoTestOutput(output);
|
|
30653
|
+
default:
|
|
30654
|
+
return parseCommonOutput(output);
|
|
30522
30655
|
}
|
|
30523
|
-
return { passed, failed, failures };
|
|
30524
30656
|
}
|
|
30525
30657
|
function parseBunOutput(output) {
|
|
30526
30658
|
const lines = output.split(`
|
|
@@ -30582,6 +30714,122 @@ function parseBunOutput(output) {
|
|
|
30582
30714
|
}
|
|
30583
30715
|
return { passed, failed, failures };
|
|
30584
30716
|
}
|
|
30717
|
+
function parseJestOutput(output) {
|
|
30718
|
+
const failures = [];
|
|
30719
|
+
let passed = 0;
|
|
30720
|
+
let failed = 0;
|
|
30721
|
+
const summaryMatches = Array.from(output.matchAll(/^\s*Tests:\s+(.*)/gm));
|
|
30722
|
+
if (summaryMatches.length > 0) {
|
|
30723
|
+
const summaryLine = summaryMatches[summaryMatches.length - 1][1];
|
|
30724
|
+
const failedMatch = summaryLine.match(/(\d+)\s+failed/);
|
|
30725
|
+
const passedMatch = summaryLine.match(/(\d+)\s+passed/);
|
|
30726
|
+
if (failedMatch)
|
|
30727
|
+
failed = Number.parseInt(failedMatch[1], 10);
|
|
30728
|
+
if (passedMatch)
|
|
30729
|
+
passed = Number.parseInt(passedMatch[1], 10);
|
|
30730
|
+
}
|
|
30731
|
+
let currentFile = "unknown";
|
|
30732
|
+
const lines = output.split(`
|
|
30733
|
+
`);
|
|
30734
|
+
for (let i = 0;i < lines.length; i++) {
|
|
30735
|
+
const line = lines[i];
|
|
30736
|
+
const fileMatch = line.match(/^\s*(?:FAIL|PASS)\s+(\S+\.[jt]sx?)/);
|
|
30737
|
+
if (fileMatch) {
|
|
30738
|
+
currentFile = fileMatch[1];
|
|
30739
|
+
continue;
|
|
30740
|
+
}
|
|
30741
|
+
const bulletMatch = line.match(/^\s+\u25CF\s+(.+)$/);
|
|
30742
|
+
if (bulletMatch) {
|
|
30743
|
+
const testName = bulletMatch[1].trim();
|
|
30744
|
+
let error48 = "";
|
|
30745
|
+
for (let j = i + 1;j < lines.length && j < i + 10; j++) {
|
|
30746
|
+
const next = lines[j].trim();
|
|
30747
|
+
if (!next)
|
|
30748
|
+
continue;
|
|
30749
|
+
if (next.startsWith("\u25CF") || /^(?:FAIL|PASS)\s/.test(next))
|
|
30750
|
+
break;
|
|
30751
|
+
error48 = next;
|
|
30752
|
+
break;
|
|
30753
|
+
}
|
|
30754
|
+
failures.push({
|
|
30755
|
+
file: currentFile,
|
|
30756
|
+
testName,
|
|
30757
|
+
error: error48 || "Unknown error",
|
|
30758
|
+
stackTrace: []
|
|
30759
|
+
});
|
|
30760
|
+
}
|
|
30761
|
+
}
|
|
30762
|
+
return { passed, failed, failures };
|
|
30763
|
+
}
|
|
30764
|
+
function parsePytestOutput(output) {
|
|
30765
|
+
const common = parseCommonOutput(output);
|
|
30766
|
+
const failures = [];
|
|
30767
|
+
for (const line of output.split(`
|
|
30768
|
+
`)) {
|
|
30769
|
+
const m = line.match(/^FAILED\s+(\S+)(?:\s+-\s+(.*))?$/);
|
|
30770
|
+
if (m) {
|
|
30771
|
+
const [, location, reason] = m;
|
|
30772
|
+
const parts = location.split("::");
|
|
30773
|
+
failures.push({
|
|
30774
|
+
file: parts[0] ?? location,
|
|
30775
|
+
testName: parts.slice(1).join(" > ") || location,
|
|
30776
|
+
error: reason?.trim() || "Unknown error",
|
|
30777
|
+
stackTrace: []
|
|
30778
|
+
});
|
|
30779
|
+
}
|
|
30780
|
+
}
|
|
30781
|
+
return {
|
|
30782
|
+
passed: common.passed,
|
|
30783
|
+
failed: common.failed,
|
|
30784
|
+
failures: failures.length > 0 ? failures : common.failures
|
|
30785
|
+
};
|
|
30786
|
+
}
|
|
30787
|
+
function parseGoTestOutput(output) {
|
|
30788
|
+
const common = parseCommonOutput(output);
|
|
30789
|
+
const failures = [];
|
|
30790
|
+
for (const line of output.split(`
|
|
30791
|
+
`)) {
|
|
30792
|
+
const m = line.match(/^--- FAIL:\s+(\S+)\s+\([\d.]+s\)/);
|
|
30793
|
+
if (m) {
|
|
30794
|
+
failures.push({
|
|
30795
|
+
file: "unknown",
|
|
30796
|
+
testName: m[1],
|
|
30797
|
+
error: "Unknown error",
|
|
30798
|
+
stackTrace: []
|
|
30799
|
+
});
|
|
30800
|
+
}
|
|
30801
|
+
}
|
|
30802
|
+
return {
|
|
30803
|
+
passed: common.passed,
|
|
30804
|
+
failed: common.failed,
|
|
30805
|
+
failures: failures.length > 0 ? failures : common.failures
|
|
30806
|
+
};
|
|
30807
|
+
}
|
|
30808
|
+
function parseCommonOutput(output) {
|
|
30809
|
+
let passed = 0;
|
|
30810
|
+
let failed = 0;
|
|
30811
|
+
const patterns = [
|
|
30812
|
+
/(\d+)\s+pass(?:ed)?(?:,\s*|\s+)(\d+)\s+fail/i,
|
|
30813
|
+
/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+failed/i,
|
|
30814
|
+
/(\d+)\s+pass/i
|
|
30815
|
+
];
|
|
30816
|
+
for (const pattern of patterns) {
|
|
30817
|
+
const matches = Array.from(output.matchAll(new RegExp(pattern, "gi")));
|
|
30818
|
+
if (matches.length > 0) {
|
|
30819
|
+
const last = matches[matches.length - 1];
|
|
30820
|
+
passed = Number.parseInt(last[1], 10);
|
|
30821
|
+
failed = last[2] ? Number.parseInt(last[2], 10) : 0;
|
|
30822
|
+
break;
|
|
30823
|
+
}
|
|
30824
|
+
}
|
|
30825
|
+
if (failed === 0) {
|
|
30826
|
+
const failMatches = Array.from(output.matchAll(/(\d+)\s+fail/gi));
|
|
30827
|
+
if (failMatches.length > 0) {
|
|
30828
|
+
failed = Number.parseInt(failMatches[failMatches.length - 1][1], 10);
|
|
30829
|
+
}
|
|
30830
|
+
}
|
|
30831
|
+
return { passed, failed, failures: [] };
|
|
30832
|
+
}
|
|
30585
30833
|
function formatFailureSummary(failures, maxChars = 2000) {
|
|
30586
30834
|
if (failures.length === 0) {
|
|
30587
30835
|
return "No test failures";
|
|
@@ -30595,48 +30843,24 @@ function formatFailureSummary(failures, maxChars = 2000) {
|
|
|
30595
30843
|
const errorLine = ` Error: ${failure.error}`;
|
|
30596
30844
|
const stackLine = failure.stackTrace.length > 0 ? ` ${failure.stackTrace[0]}` : "";
|
|
30597
30845
|
const blockLines = [header, errorLine];
|
|
30598
|
-
if (stackLine)
|
|
30846
|
+
if (stackLine)
|
|
30599
30847
|
blockLines.push(stackLine);
|
|
30600
|
-
}
|
|
30601
30848
|
blockLines.push("");
|
|
30602
30849
|
const block = blockLines.join(`
|
|
30603
30850
|
`);
|
|
30604
|
-
|
|
30605
|
-
if (totalChars + blockLength > maxChars && lines.length > 0) {
|
|
30606
|
-
const remaining = failures.length - i;
|
|
30851
|
+
if (totalChars + block.length > maxChars && lines.length > 0) {
|
|
30607
30852
|
lines.push(`
|
|
30608
|
-
... and ${
|
|
30853
|
+
... and ${failures.length - i} more failure(s) (truncated)`);
|
|
30609
30854
|
break;
|
|
30610
30855
|
}
|
|
30611
30856
|
lines.push(...blockLines);
|
|
30612
|
-
totalChars +=
|
|
30857
|
+
totalChars += block.length;
|
|
30613
30858
|
}
|
|
30614
30859
|
return lines.join(`
|
|
30615
30860
|
`).trim();
|
|
30616
30861
|
}
|
|
30617
|
-
function
|
|
30618
|
-
const
|
|
30619
|
-
/(\d+)\s+pass(?:ed)?(?:,\s+|\s+)(\d+)\s+fail/i,
|
|
30620
|
-
/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+failed/i,
|
|
30621
|
-
/(\d+)\s+pass/i
|
|
30622
|
-
];
|
|
30623
|
-
let passCount = 0;
|
|
30624
|
-
let failCount = 0;
|
|
30625
|
-
for (const pattern of patterns) {
|
|
30626
|
-
const matches = Array.from(output.matchAll(new RegExp(pattern, "gi")));
|
|
30627
|
-
if (matches.length > 0) {
|
|
30628
|
-
const lastMatch = matches[matches.length - 1];
|
|
30629
|
-
passCount = Number.parseInt(lastMatch[1], 10);
|
|
30630
|
-
failCount = lastMatch[2] ? Number.parseInt(lastMatch[2], 10) : 0;
|
|
30631
|
-
break;
|
|
30632
|
-
}
|
|
30633
|
-
}
|
|
30634
|
-
if (failCount === 0) {
|
|
30635
|
-
const failMatches = Array.from(output.matchAll(/(\d+)\s+fail/gi));
|
|
30636
|
-
if (failMatches.length > 0) {
|
|
30637
|
-
failCount = Number.parseInt(failMatches[failMatches.length - 1][1], 10);
|
|
30638
|
-
}
|
|
30639
|
-
}
|
|
30862
|
+
function analyzeTestExitCode(output, exitCode) {
|
|
30863
|
+
const { passed: passCount, failed: failCount } = parseCommonOutput(output);
|
|
30640
30864
|
const allTestsPassed = passCount > 0 && failCount === 0;
|
|
30641
30865
|
const isEnvironmentalFailure = allTestsPassed && exitCode !== 0;
|
|
30642
30866
|
const result = {
|
|
@@ -30707,7 +30931,7 @@ async function runVerificationCore(options) {
|
|
|
30707
30931
|
}
|
|
30708
30932
|
const exitCode = execution.exitCode ?? 1;
|
|
30709
30933
|
if (exitCode !== 0 && execution.output) {
|
|
30710
|
-
const analysis =
|
|
30934
|
+
const analysis = analyzeTestExitCode(execution.output, exitCode);
|
|
30711
30935
|
if (analysis.isEnvironmentalFailure) {
|
|
30712
30936
|
return {
|
|
30713
30937
|
status: "ENVIRONMENTAL_FAILURE",
|
|
@@ -31000,7 +31224,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
31000
31224
|
});
|
|
31001
31225
|
const fullSuitePassed = fullSuiteResult.success && fullSuiteResult.exitCode === 0;
|
|
31002
31226
|
if (!fullSuitePassed && fullSuiteResult.output) {
|
|
31003
|
-
const testSummary = _rectificationGateDeps.
|
|
31227
|
+
const testSummary = _rectificationGateDeps.parseTestOutput(fullSuiteResult.output);
|
|
31004
31228
|
if (testSummary.failed > 0) {
|
|
31005
31229
|
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir);
|
|
31006
31230
|
}
|
|
@@ -31134,7 +31358,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
31134
31358
|
return true;
|
|
31135
31359
|
}
|
|
31136
31360
|
if (retryFullSuite.output) {
|
|
31137
|
-
const newTestSummary = _rectificationGateDeps.
|
|
31361
|
+
const newTestSummary = _rectificationGateDeps.parseTestOutput(retryFullSuite.output);
|
|
31138
31362
|
state.currentFailures = newTestSummary.failed;
|
|
31139
31363
|
testSummary.failures = newTestSummary.failures;
|
|
31140
31364
|
testSummary.failed = newTestSummary.failed;
|
|
@@ -31182,7 +31406,7 @@ var init_rectification_gate = __esm(() => {
|
|
|
31182
31406
|
init_prompts();
|
|
31183
31407
|
_rectificationGateDeps = {
|
|
31184
31408
|
executeWithTimeout,
|
|
31185
|
-
|
|
31409
|
+
parseTestOutput,
|
|
31186
31410
|
shouldRetryRectification
|
|
31187
31411
|
};
|
|
31188
31412
|
});
|
|
@@ -33152,9 +33376,6 @@ function calculateMaxIterations(tierOrder) {
|
|
|
33152
33376
|
return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
|
|
33153
33377
|
}
|
|
33154
33378
|
|
|
33155
|
-
// src/execution/test-output-parser.ts
|
|
33156
|
-
var init_test_output_parser = () => {};
|
|
33157
|
-
|
|
33158
33379
|
// src/verification/rectification-loop.ts
|
|
33159
33380
|
async function _defaultRunDebate(storyId, stageConfig, prompt, config2) {
|
|
33160
33381
|
const logger = getSafeLogger();
|
|
@@ -33207,7 +33428,7 @@ async function runRectificationLoop2(opts) {
|
|
|
33207
33428
|
} = opts;
|
|
33208
33429
|
const logger = getSafeLogger();
|
|
33209
33430
|
const rectificationConfig = config2.execution.rectification;
|
|
33210
|
-
const testSummary =
|
|
33431
|
+
const testSummary = parseTestOutput(testOutput);
|
|
33211
33432
|
const label = promptPrefix ? "regression rectification" : "rectification";
|
|
33212
33433
|
const rectificationState = {
|
|
33213
33434
|
attempt: 0,
|
|
@@ -33342,13 +33563,13 @@ ${rectificationPrompt}`;
|
|
|
33342
33563
|
return true;
|
|
33343
33564
|
}
|
|
33344
33565
|
if (retryVerification.output) {
|
|
33345
|
-
const newTestSummary =
|
|
33566
|
+
const newTestSummary = parseTestOutput(retryVerification.output);
|
|
33346
33567
|
state.currentFailures = newTestSummary.failed;
|
|
33347
33568
|
state.lastExitCode = retryVerification.status === "SUCCESS" ? 0 : 1;
|
|
33348
33569
|
testSummary.failures = newTestSummary.failures;
|
|
33349
33570
|
testSummary.failed = newTestSummary.failed;
|
|
33350
33571
|
testSummary.passed = newTestSummary.passed;
|
|
33351
|
-
if (newTestSummary.failed === 0) {
|
|
33572
|
+
if (newTestSummary.failed === 0 && (retryVerification.status === "SUCCESS" || newTestSummary.passed > 0)) {
|
|
33352
33573
|
state.lastExitCode = 0;
|
|
33353
33574
|
logger?.info("rectification", `[OK] ${label} succeeded after parsing retry output`, {
|
|
33354
33575
|
storyId: story.id,
|
|
@@ -33488,7 +33709,6 @@ var init_rectification_loop = __esm(() => {
|
|
|
33488
33709
|
init_cost();
|
|
33489
33710
|
init_registry();
|
|
33490
33711
|
init_config();
|
|
33491
|
-
init_test_output_parser();
|
|
33492
33712
|
init_logger2();
|
|
33493
33713
|
init_prd();
|
|
33494
33714
|
init_rectification();
|
|
@@ -33754,7 +33974,7 @@ class RegressionStrategy {
|
|
|
33754
33974
|
});
|
|
33755
33975
|
const durationMs = Date.now() - start;
|
|
33756
33976
|
if (result.success) {
|
|
33757
|
-
const parsed2 = result.output ?
|
|
33977
|
+
const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33758
33978
|
return makePassResult(ctx.storyId, "regression", {
|
|
33759
33979
|
rawOutput: result.output,
|
|
33760
33980
|
passCount: parsed2.passed,
|
|
@@ -33770,7 +33990,7 @@ class RegressionStrategy {
|
|
|
33770
33990
|
if (result.status === "TIMEOUT") {
|
|
33771
33991
|
return makeFailResult(ctx.storyId, "regression", "TIMEOUT", { rawOutput: result.output, durationMs });
|
|
33772
33992
|
}
|
|
33773
|
-
const parsed = result.output ?
|
|
33993
|
+
const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33774
33994
|
return makeFailResult(ctx.storyId, "regression", "TEST_FAILURE", {
|
|
33775
33995
|
rawOutput: result.output,
|
|
33776
33996
|
passCount: parsed.passed,
|
|
@@ -33994,7 +34214,7 @@ class ScopedStrategy {
|
|
|
33994
34214
|
});
|
|
33995
34215
|
const durationMs = Date.now() - start;
|
|
33996
34216
|
if (result.success) {
|
|
33997
|
-
const parsed2 = result.output ?
|
|
34217
|
+
const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33998
34218
|
return makePassResult(ctx.storyId, "scoped", {
|
|
33999
34219
|
rawOutput: result.output,
|
|
34000
34220
|
passCount: parsed2.passed,
|
|
@@ -34010,7 +34230,7 @@ class ScopedStrategy {
|
|
|
34010
34230
|
scopeTestFallback
|
|
34011
34231
|
});
|
|
34012
34232
|
}
|
|
34013
|
-
const parsed = result.output ?
|
|
34233
|
+
const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
34014
34234
|
return makeFailResult(ctx.storyId, "scoped", "TEST_FAILURE", {
|
|
34015
34235
|
rawOutput: result.output,
|
|
34016
34236
|
passCount: parsed.passed,
|
|
@@ -34344,12 +34564,11 @@ function stripCodeFences(text) {
|
|
|
34344
34564
|
return trimmed;
|
|
34345
34565
|
}
|
|
34346
34566
|
function parseRoutingResponse(output, story, config2) {
|
|
34347
|
-
const
|
|
34348
|
-
const parsed = JSON.parse(jsonText);
|
|
34567
|
+
const parsed = parseLLMJson(output);
|
|
34349
34568
|
return validateRoutingDecision(parsed, config2, story);
|
|
34350
34569
|
}
|
|
34351
34570
|
function parseBatchResponse(output, stories, config2) {
|
|
34352
|
-
const parsed =
|
|
34571
|
+
const parsed = parseLLMJson(output);
|
|
34353
34572
|
if (!Array.isArray(parsed)) {
|
|
34354
34573
|
throw new Error("Batch LLM response must be a JSON array");
|
|
34355
34574
|
}
|
|
@@ -36143,7 +36362,7 @@ var package_default;
|
|
|
36143
36362
|
var init_package = __esm(() => {
|
|
36144
36363
|
package_default = {
|
|
36145
36364
|
name: "@nathapp/nax",
|
|
36146
|
-
version: "0.59.
|
|
36365
|
+
version: "0.59.3",
|
|
36147
36366
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
36148
36367
|
type: "module",
|
|
36149
36368
|
bin: {
|
|
@@ -36223,8 +36442,8 @@ var init_version = __esm(() => {
|
|
|
36223
36442
|
NAX_VERSION = package_default.version;
|
|
36224
36443
|
NAX_COMMIT = (() => {
|
|
36225
36444
|
try {
|
|
36226
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
36227
|
-
return "
|
|
36445
|
+
if (/^[0-9a-f]{6,10}$/.test("0c763972"))
|
|
36446
|
+
return "0c763972";
|
|
36228
36447
|
} catch {}
|
|
36229
36448
|
try {
|
|
36230
36449
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -36887,28 +37106,17 @@ function parseDiagnosisResult(output) {
|
|
|
36887
37106
|
if (!output || output.trim() === "") {
|
|
36888
37107
|
return null;
|
|
36889
37108
|
}
|
|
36890
|
-
|
|
36891
|
-
|
|
36892
|
-
|
|
36893
|
-
|
|
36894
|
-
|
|
36895
|
-
|
|
36896
|
-
|
|
36897
|
-
|
|
36898
|
-
|
|
36899
|
-
if (typeof parsed.verdict === "string" && typeof parsed.reasoning === "string" && typeof parsed.confidence === "number") {
|
|
36900
|
-
return {
|
|
36901
|
-
verdict: parsed.verdict,
|
|
36902
|
-
reasoning: parsed.reasoning,
|
|
36903
|
-
confidence: parsed.confidence,
|
|
36904
|
-
testIssues: parsed.testIssues,
|
|
36905
|
-
sourceIssues: parsed.sourceIssues
|
|
36906
|
-
};
|
|
36907
|
-
}
|
|
36908
|
-
return null;
|
|
36909
|
-
} catch {
|
|
36910
|
-
return null;
|
|
37109
|
+
const parsed = tryParseLLMJson(output);
|
|
37110
|
+
if (parsed && typeof parsed.verdict === "string" && typeof parsed.reasoning === "string" && typeof parsed.confidence === "number") {
|
|
37111
|
+
return {
|
|
37112
|
+
verdict: parsed.verdict,
|
|
37113
|
+
reasoning: parsed.reasoning,
|
|
37114
|
+
confidence: parsed.confidence,
|
|
37115
|
+
testIssues: Array.isArray(parsed.testIssues) ? parsed.testIssues : undefined,
|
|
37116
|
+
sourceIssues: Array.isArray(parsed.sourceIssues) ? parsed.sourceIssues : undefined
|
|
37117
|
+
};
|
|
36911
37118
|
}
|
|
37119
|
+
return null;
|
|
36912
37120
|
}
|
|
36913
37121
|
var MAX_SOURCE_FILES = 5, MAX_FILE_LINES = 500, MAX_TEST_OUTPUT_CHARS = 2000;
|
|
36914
37122
|
var init_fix_diagnosis = __esm(() => {
|
|
@@ -37106,6 +37314,12 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
37106
37314
|
logger?.info("acceptance", `Backed up acceptance test -> ${bakPath}`);
|
|
37107
37315
|
const { unlink: unlink3 } = await import("fs/promises");
|
|
37108
37316
|
await unlink3(testPath);
|
|
37317
|
+
if (acceptanceContext.featureDir) {
|
|
37318
|
+
const metaPath = path15.join(acceptanceContext.featureDir, "acceptance-meta.json");
|
|
37319
|
+
try {
|
|
37320
|
+
await unlink3(metaPath);
|
|
37321
|
+
} catch {}
|
|
37322
|
+
}
|
|
37109
37323
|
let implementationContext;
|
|
37110
37324
|
const storyGitRef = acceptanceContext.storyGitRef;
|
|
37111
37325
|
const workdir = acceptanceContext.workdir;
|
|
@@ -37746,7 +37960,7 @@ async function runDeferredRegression(options) {
|
|
|
37746
37960
|
affectedStories: []
|
|
37747
37961
|
};
|
|
37748
37962
|
}
|
|
37749
|
-
const testSummary = _regressionDeps.
|
|
37963
|
+
const testSummary = _regressionDeps.parseTestOutput(fullSuiteResult.output);
|
|
37750
37964
|
if (testSummary.failed === 0 && testSummary.passed === 0) {
|
|
37751
37965
|
logger?.warn("regression", "No test results parsed from output \u2014 test runner likely crashed or errored (not a regression, accepting as pass)", { output: fullSuiteResult.output.slice(0, 500) });
|
|
37752
37966
|
return {
|
|
@@ -37892,7 +38106,7 @@ var init_run_regression = __esm(() => {
|
|
|
37892
38106
|
_regressionDeps = {
|
|
37893
38107
|
runVerification: fullSuite,
|
|
37894
38108
|
runRectificationLoop: runRectificationLoop2,
|
|
37895
|
-
|
|
38109
|
+
parseTestOutput,
|
|
37896
38110
|
reverseMapTestToSource
|
|
37897
38111
|
};
|
|
37898
38112
|
});
|
|
@@ -38873,6 +39087,11 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
38873
39087
|
const diffSummary = await captureDiffSummary(ctx.workdir, ctx.storyGitRef, completedStory.workdir);
|
|
38874
39088
|
if (diffSummary) {
|
|
38875
39089
|
completedStory.diffSummary = diffSummary;
|
|
39090
|
+
} else {
|
|
39091
|
+
logger?.debug("context-chain", "No diff summary captured (agent may not have committed yet)", {
|
|
39092
|
+
storyId: completedStory.id,
|
|
39093
|
+
storyGitRef: ctx.storyGitRef
|
|
39094
|
+
});
|
|
38876
39095
|
}
|
|
38877
39096
|
} catch {}
|
|
38878
39097
|
}
|
|
@@ -72809,13 +73028,16 @@ function validateStory(raw, index, allIds) {
|
|
|
72809
73028
|
throw new Error(`[schema] story[${index}].routing.complexity "${rawComplexity}" is invalid. Valid values: ${VALID_COMPLEXITY.join(", ")}`);
|
|
72810
73029
|
}
|
|
72811
73030
|
const rawTestStrategy = routing.testStrategy ?? s.testStrategy;
|
|
72812
|
-
|
|
73031
|
+
let testStrategy = resolveTestStrategy(typeof rawTestStrategy === "string" ? rawTestStrategy : undefined);
|
|
72813
73032
|
const rawJustification = routing.noTestJustification ?? s.noTestJustification;
|
|
72814
73033
|
if (testStrategy === "no-test") {
|
|
72815
73034
|
if (!rawJustification || typeof rawJustification !== "string" || rawJustification.trim() === "") {
|
|
72816
73035
|
throw new Error(`[schema] story[${index}].routing.noTestJustification is required when testStrategy is "no-test"`);
|
|
72817
73036
|
}
|
|
72818
73037
|
}
|
|
73038
|
+
if (testStrategy !== "no-test" && typeof rawJustification === "string" && rawJustification.trim() !== "") {
|
|
73039
|
+
testStrategy = "no-test";
|
|
73040
|
+
}
|
|
72819
73041
|
const noTestJustification = typeof rawJustification === "string" && rawJustification.trim() !== "" ? rawJustification.trim() : undefined;
|
|
72820
73042
|
const rawDeps = s.dependencies;
|
|
72821
73043
|
const dependencies = Array.isArray(rawDeps) ? rawDeps : [];
|
|
@@ -72975,7 +73197,7 @@ async function planCommand(workdir, config2, options) {
|
|
|
72975
73197
|
let rawResponse;
|
|
72976
73198
|
const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
|
|
72977
73199
|
if (debateEnabled) {
|
|
72978
|
-
const
|
|
73200
|
+
const { taskContext: planTaskContext, outputFormat: planOutputFormat } = buildPlanningPrompt(specContent, codebaseContext, undefined, relativePackages, packageDetails, config2?.project);
|
|
72979
73201
|
const resolvedPerm = resolvePermissions(config2, "plan");
|
|
72980
73202
|
const planStageConfig = config2?.debate?.stages.plan;
|
|
72981
73203
|
const debateSession = _planDeps.createDebateSession({
|
|
@@ -72992,7 +73214,7 @@ async function planCommand(workdir, config2, options) {
|
|
|
72992
73214
|
rounds: planStageConfig.rounds,
|
|
72993
73215
|
feature: options.feature
|
|
72994
73216
|
});
|
|
72995
|
-
const debateResult = await debateSession.runPlan(
|
|
73217
|
+
const debateResult = await debateSession.runPlan(planTaskContext, planOutputFormat, {
|
|
72996
73218
|
workdir,
|
|
72997
73219
|
feature: options.feature,
|
|
72998
73220
|
outputDir,
|
|
@@ -73011,7 +73233,10 @@ async function planCommand(workdir, config2, options) {
|
|
|
73011
73233
|
}
|
|
73012
73234
|
} else if (options.auto) {
|
|
73013
73235
|
const isAcp = config2?.agent?.protocol === "acp";
|
|
73014
|
-
const
|
|
73236
|
+
const { taskContext: autoTaskCtx, outputFormat: autoOutputFmt } = buildPlanningPrompt(specContent, codebaseContext, isAcp ? outputPath : undefined, relativePackages, packageDetails, config2?.project);
|
|
73237
|
+
const prompt = `${autoTaskCtx}
|
|
73238
|
+
|
|
73239
|
+
${autoOutputFmt}`;
|
|
73015
73240
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
73016
73241
|
if (!adapter)
|
|
73017
73242
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
@@ -73097,7 +73322,10 @@ async function planCommand(workdir, config2, options) {
|
|
|
73097
73322
|
rawResponse = await runInteractivePlan();
|
|
73098
73323
|
}
|
|
73099
73324
|
async function runInteractivePlan() {
|
|
73100
|
-
const
|
|
73325
|
+
const { taskContext: interactiveTaskCtx, outputFormat: interactiveOutputFmt } = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails, config2?.project);
|
|
73326
|
+
const prompt = `${interactiveTaskCtx}
|
|
73327
|
+
|
|
73328
|
+
${interactiveOutputFmt}`;
|
|
73101
73329
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
73102
73330
|
if (!adapter)
|
|
73103
73331
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
@@ -73304,7 +73532,7 @@ ${packageDetailsSection}
|
|
|
73304
73532
|
For each user story, set the "workdir" field to the relevant package path (e.g. "packages/api"). Stories that span the root should omit "workdir".` : "";
|
|
73305
73533
|
const workdirField = isMonorepo ? `
|
|
73306
73534
|
"workdir": "string \u2014 optional, relative path to package (e.g. \\"packages/api\\"). Omit for root-level stories.",` : "";
|
|
73307
|
-
|
|
73535
|
+
const taskContext = `You are a senior software architect generating a product requirements document (PRD) as JSON.
|
|
73308
73536
|
|
|
73309
73537
|
## Step 1: Understand the Spec
|
|
73310
73538
|
|
|
@@ -73332,6 +73560,8 @@ If this is a greenfield project (empty or minimal codebase):
|
|
|
73332
73560
|
|
|
73333
73561
|
Record ALL findings in the "analysis" field of the output JSON. This analysis is provided to every implementation agent as context \u2014 be thorough.
|
|
73334
73562
|
|
|
73563
|
+
**Important:** The codebase context below contains file names and structure only \u2014 no file content. Do NOT assert specific line numbers. The implementer will read the actual files via contextFiles.
|
|
73564
|
+
|
|
73335
73565
|
## Codebase Context
|
|
73336
73566
|
|
|
73337
73567
|
${codebaseContext}${monorepoHint}
|
|
@@ -73348,9 +73578,8 @@ For each story, set "contextFiles" to the key source files the agent should read
|
|
|
73348
73578
|
|
|
73349
73579
|
${COMPLEXITY_GUIDE}
|
|
73350
73580
|
|
|
73351
|
-
${TEST_STRATEGY_GUIDE}
|
|
73352
|
-
|
|
73353
|
-
## Output Schema
|
|
73581
|
+
${TEST_STRATEGY_GUIDE}`;
|
|
73582
|
+
const outputFormat = `## Output Schema
|
|
73354
73583
|
|
|
73355
73584
|
Generate a JSON object with this exact structure (no markdown, no explanation \u2014 JSON only):
|
|
73356
73585
|
|
|
@@ -73386,6 +73615,7 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
73386
73615
|
|
|
73387
73616
|
${outputFilePath ? `Write the PRD JSON directly to this file path: ${outputFilePath}
|
|
73388
73617
|
Do NOT output the JSON to the conversation. Write the file, then reply with a brief confirmation.` : "Output ONLY the JSON object. Do not wrap in markdown code blocks."}`;
|
|
73618
|
+
return { taskContext, outputFormat };
|
|
73389
73619
|
}
|
|
73390
73620
|
async function planDecomposeCommand(workdir, config2, options) {
|
|
73391
73621
|
const prdPath = join11(workdir, ".nax", "features", options.feature, "prd.json");
|