@nathapp/nax 0.59.2 → 0.60.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nax.js +555 -311
- 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,91 @@ 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.`;
|
|
21418
|
+
}
|
|
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");
|
|
21384
21437
|
}
|
|
21385
|
-
function
|
|
21386
|
-
|
|
21387
|
-
|
|
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 buildDebaterLabel(debater) {
|
|
21448
|
+
return debater.persona ? `${debater.agent} (${debater.persona})` : debater.agent;
|
|
21449
|
+
}
|
|
21450
|
+
function buildProposalsSection(proposals, debaters) {
|
|
21451
|
+
return proposals.map((p, i) => {
|
|
21452
|
+
const label = debaters?.[i] ? buildDebaterLabel(debaters[i]) : String(i + 1);
|
|
21453
|
+
return `### Proposal ${label}
|
|
21454
|
+
${p}`;
|
|
21455
|
+
}).join(`
|
|
21388
21456
|
|
|
21389
21457
|
`);
|
|
21458
|
+
}
|
|
21459
|
+
function buildSynthesisPrompt(proposals, critiques, debaters) {
|
|
21460
|
+
const proposalsSection = buildProposalsSection(proposals, debaters);
|
|
21390
21461
|
const critiquesSection = critiques.length > 0 ? `
|
|
21391
21462
|
|
|
21392
21463
|
## Critiques
|
|
@@ -21401,11 +21472,8 @@ ${proposalsSection}${critiquesSection}
|
|
|
21401
21472
|
|
|
21402
21473
|
Please synthesize these into the best possible unified response, incorporating the strongest elements from each proposal.`;
|
|
21403
21474
|
}
|
|
21404
|
-
function buildJudgePrompt(proposals, critiques) {
|
|
21405
|
-
const proposalsSection = proposals
|
|
21406
|
-
${p}`).join(`
|
|
21407
|
-
|
|
21408
|
-
`);
|
|
21475
|
+
function buildJudgePrompt(proposals, critiques, debaters) {
|
|
21476
|
+
const proposalsSection = buildProposalsSection(proposals, debaters);
|
|
21409
21477
|
const critiquesSection = critiques.length > 0 ? `
|
|
21410
21478
|
|
|
21411
21479
|
## Critiques
|
|
@@ -21420,28 +21488,6 @@ ${proposalsSection}${critiquesSection}
|
|
|
21420
21488
|
|
|
21421
21489
|
As the judge, provide your final verdict with clear reasoning, selecting or synthesizing the best approach.`;
|
|
21422
21490
|
}
|
|
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
21491
|
function stripMarkdownFence(text) {
|
|
21446
21492
|
const match = text.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/);
|
|
21447
21493
|
return match ? match[1] ?? text : text;
|
|
@@ -21475,7 +21521,7 @@ function majorityResolver(proposals, failOpen) {
|
|
|
21475
21521
|
return passCount > failCount ? "passed" : "failed";
|
|
21476
21522
|
}
|
|
21477
21523
|
async function synthesisResolver(proposals, critiques, opts) {
|
|
21478
|
-
const base = buildSynthesisPrompt(proposals, critiques);
|
|
21524
|
+
const base = buildSynthesisPrompt(proposals, critiques, opts.debaters);
|
|
21479
21525
|
const prompt = opts.promptSuffix ? `${base}
|
|
21480
21526
|
|
|
21481
21527
|
${opts.promptSuffix}` : base;
|
|
@@ -21487,11 +21533,10 @@ async function judgeResolver(proposals, critiques, resolverConfig, opts) {
|
|
|
21487
21533
|
if (!adapter) {
|
|
21488
21534
|
throw new Error(`[debate] Judge agent '${agentName}' not found`);
|
|
21489
21535
|
}
|
|
21490
|
-
const prompt = buildJudgePrompt(proposals, critiques);
|
|
21536
|
+
const prompt = buildJudgePrompt(proposals, critiques, opts.debaters);
|
|
21491
21537
|
return adapter.complete(prompt, opts.completeOptions);
|
|
21492
21538
|
}
|
|
21493
21539
|
var DEFAULT_FALLBACK_AGENT = "claude";
|
|
21494
|
-
var init_resolvers = () => {};
|
|
21495
21540
|
|
|
21496
21541
|
// src/debate/session-helpers.ts
|
|
21497
21542
|
function resolveDebaterModel(debater, config2) {
|
|
@@ -21567,7 +21612,7 @@ function resolveModelDefForDebater(debater, tier, config2) {
|
|
|
21567
21612
|
return resolveModelForAgent(configModels, debater.agent, "fast", configDefaultAgent);
|
|
21568
21613
|
}
|
|
21569
21614
|
}
|
|
21570
|
-
async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, storyId, timeoutMs, workdir, featureName, reviewerSession, resolverContext, promptSuffix) {
|
|
21615
|
+
async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, storyId, timeoutMs, workdir, featureName, reviewerSession, resolverContext, promptSuffix, debaters) {
|
|
21571
21616
|
const resolverConfig = stageConfig.resolver;
|
|
21572
21617
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21573
21618
|
if (reviewerSession && resolverContext) {
|
|
@@ -21581,21 +21626,13 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21581
21626
|
let passCount = 0;
|
|
21582
21627
|
let failCount = 0;
|
|
21583
21628
|
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
|
-
}
|
|
21629
|
+
const parsed = tryParseLLMJson(proposal);
|
|
21630
|
+
if (parsed !== null && typeof parsed.passed === "boolean" && parsed.passed)
|
|
21631
|
+
passCount++;
|
|
21632
|
+
else if (failOpen)
|
|
21633
|
+
passCount++;
|
|
21634
|
+
else
|
|
21635
|
+
failCount++;
|
|
21599
21636
|
}
|
|
21600
21637
|
debateCtx.majorityVote = { passed: rawOutcome === "passed", passCount, failCount };
|
|
21601
21638
|
}
|
|
@@ -21644,6 +21681,7 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21644
21681
|
const resolverResult = await synthesisResolver(proposalOutputs, critiqueOutputs, {
|
|
21645
21682
|
adapter,
|
|
21646
21683
|
promptSuffix,
|
|
21684
|
+
debaters,
|
|
21647
21685
|
completeOptions: {
|
|
21648
21686
|
model: resolveDebaterModel({ agent: agentName }, config2),
|
|
21649
21687
|
config: config2,
|
|
@@ -21669,6 +21707,7 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21669
21707
|
const resolverResult = await judgeResolver(proposalOutputs, critiqueOutputs, resolverConfig, {
|
|
21670
21708
|
getAgent: (name) => _debateSessionDeps.getAgent(name, config2),
|
|
21671
21709
|
defaultAgentName: RESOLVER_FALLBACK_AGENT,
|
|
21710
|
+
debaters,
|
|
21672
21711
|
completeOptions: {
|
|
21673
21712
|
model: resolveDebaterModel({ agent: agentName }, config2),
|
|
21674
21713
|
config: config2,
|
|
@@ -21694,7 +21733,6 @@ var init_session_helpers = __esm(() => {
|
|
|
21694
21733
|
init_registry();
|
|
21695
21734
|
init_config();
|
|
21696
21735
|
init_logger2();
|
|
21697
|
-
init_resolvers();
|
|
21698
21736
|
_debateSessionDeps = {
|
|
21699
21737
|
getAgent: (name, config2) => config2 ? createAgentRegistry(config2).getAgent(name) : getAgent(name),
|
|
21700
21738
|
getSafeLogger,
|
|
@@ -21728,6 +21766,174 @@ async function allSettledBounded(tasks, limit) {
|
|
|
21728
21766
|
return results;
|
|
21729
21767
|
}
|
|
21730
21768
|
|
|
21769
|
+
// src/debate/personas.ts
|
|
21770
|
+
function resolvePersonas(debaters, stage, autoPersona) {
|
|
21771
|
+
if (!autoPersona)
|
|
21772
|
+
return debaters;
|
|
21773
|
+
const rotation = stage === "plan" ? PLAN_ROTATION : REVIEW_ROTATION;
|
|
21774
|
+
let rotationIndex = 0;
|
|
21775
|
+
return debaters.map((d) => {
|
|
21776
|
+
if (d.persona)
|
|
21777
|
+
return d;
|
|
21778
|
+
const assigned = rotation[rotationIndex % rotation.length];
|
|
21779
|
+
rotationIndex++;
|
|
21780
|
+
return { ...d, persona: assigned };
|
|
21781
|
+
});
|
|
21782
|
+
}
|
|
21783
|
+
function buildDebaterLabel2(debater) {
|
|
21784
|
+
return debater.persona ? `${debater.agent} (${debater.persona})` : debater.agent;
|
|
21785
|
+
}
|
|
21786
|
+
var PERSONA_FRAGMENTS, PLAN_ROTATION, REVIEW_ROTATION;
|
|
21787
|
+
var init_personas = __esm(() => {
|
|
21788
|
+
PERSONA_FRAGMENTS = {
|
|
21789
|
+
challenger: {
|
|
21790
|
+
identity: "You are the challenger \u2014 your job is to stress-test proposals and find weaknesses.",
|
|
21791
|
+
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."
|
|
21792
|
+
},
|
|
21793
|
+
pragmatist: {
|
|
21794
|
+
identity: "You are the pragmatist \u2014 your job is to find the simplest path that satisfies the spec.",
|
|
21795
|
+
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."
|
|
21796
|
+
},
|
|
21797
|
+
completionist: {
|
|
21798
|
+
identity: "You are the completionist \u2014 your job is to ensure nothing is missed.",
|
|
21799
|
+
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."
|
|
21800
|
+
},
|
|
21801
|
+
security: {
|
|
21802
|
+
identity: "You are the security reviewer \u2014 your job is to surface risks before they ship.",
|
|
21803
|
+
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."
|
|
21804
|
+
},
|
|
21805
|
+
testability: {
|
|
21806
|
+
identity: "You are the testability advocate \u2014 your job is to ensure the design is verifiable.",
|
|
21807
|
+
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)."
|
|
21808
|
+
}
|
|
21809
|
+
};
|
|
21810
|
+
PLAN_ROTATION = ["challenger", "pragmatist", "completionist", "security", "testability"];
|
|
21811
|
+
REVIEW_ROTATION = ["security", "completionist", "testability", "challenger", "pragmatist"];
|
|
21812
|
+
});
|
|
21813
|
+
|
|
21814
|
+
// src/debate/prompt-builder.ts
|
|
21815
|
+
class DebatePromptBuilder {
|
|
21816
|
+
stageContext;
|
|
21817
|
+
options;
|
|
21818
|
+
constructor(stageContext, options) {
|
|
21819
|
+
this.stageContext = stageContext;
|
|
21820
|
+
this.options = options;
|
|
21821
|
+
}
|
|
21822
|
+
buildProposalPrompt(debaterIndex) {
|
|
21823
|
+
const personaBlock = this.buildPersonaBlock(debaterIndex);
|
|
21824
|
+
return `${this.stageContext.taskContext}${personaBlock}
|
|
21825
|
+
|
|
21826
|
+
${this.stageContext.outputFormat}`;
|
|
21827
|
+
}
|
|
21828
|
+
buildCritiquePrompt(debaterIndex, proposals) {
|
|
21829
|
+
const otherProposals = proposals.filter((_, i) => i !== debaterIndex);
|
|
21830
|
+
const proposalsSection = this.buildProposalsSection(otherProposals);
|
|
21831
|
+
const personaBlock = this.buildPersonaBlock(debaterIndex);
|
|
21832
|
+
return `You are reviewing proposals for a ${this.stageContext.stage} task.
|
|
21833
|
+
|
|
21834
|
+
## Task
|
|
21835
|
+
${this.stageContext.taskContext}${personaBlock}
|
|
21836
|
+
|
|
21837
|
+
## Other Agents' Proposals
|
|
21838
|
+
${proposalsSection}
|
|
21839
|
+
|
|
21840
|
+
Please critique these proposals and provide your refined analysis, identifying strengths, weaknesses, and your own updated position.`;
|
|
21841
|
+
}
|
|
21842
|
+
buildRebuttalPrompt(debaterIndex, proposals, priorRebuttals) {
|
|
21843
|
+
const contextBlock = this.options.sessionMode === "one-shot" ? `${this.stageContext.taskContext}
|
|
21844
|
+
|
|
21845
|
+
` : "";
|
|
21846
|
+
const proposalsSection = this.buildProposalsSection(proposals);
|
|
21847
|
+
const rebuttalsSection = this.buildRebuttalsSection(priorRebuttals);
|
|
21848
|
+
const personaBlock = this.buildPersonaBlock(debaterIndex);
|
|
21849
|
+
const debaterNumber = debaterIndex + 1;
|
|
21850
|
+
return `${contextBlock}## Proposals
|
|
21851
|
+
${proposalsSection}${rebuttalsSection}${personaBlock}
|
|
21852
|
+
|
|
21853
|
+
## Your Task
|
|
21854
|
+
You are debater ${debaterNumber}. Provide your critique in prose.
|
|
21855
|
+
Identify strengths, weaknesses, and specific improvements for each proposal.
|
|
21856
|
+
Do NOT output JSON \u2014 focus on analysis only.`;
|
|
21857
|
+
}
|
|
21858
|
+
buildSynthesisPrompt(proposals, critiques, promptSuffix) {
|
|
21859
|
+
const proposalsSection = this.buildProposalsSection(proposals);
|
|
21860
|
+
const critiquesSection = this.buildCritiquesSection(critiques);
|
|
21861
|
+
return `You are a synthesis agent. Your task is to combine the strongest elements from multiple proposals into a single, optimal response.
|
|
21862
|
+
|
|
21863
|
+
${this.stageContext.taskContext}
|
|
21864
|
+
|
|
21865
|
+
## Proposals
|
|
21866
|
+
${proposalsSection}
|
|
21867
|
+
|
|
21868
|
+
## Critiques
|
|
21869
|
+
${critiquesSection}
|
|
21870
|
+
|
|
21871
|
+
Please synthesize these into the best possible unified response, incorporating the strongest elements from each proposal.
|
|
21872
|
+
${this.stageContext.outputFormat}${promptSuffix ? `
|
|
21873
|
+
${promptSuffix}` : ""}`;
|
|
21874
|
+
}
|
|
21875
|
+
buildJudgePrompt(proposals, critiques) {
|
|
21876
|
+
const proposalsSection = this.buildProposalsSection(proposals);
|
|
21877
|
+
const critiquesSection = this.buildCritiquesSection(critiques);
|
|
21878
|
+
return `You are a judge evaluating multiple proposals. Select the best proposal or synthesize the optimal response.
|
|
21879
|
+
|
|
21880
|
+
${this.stageContext.taskContext}
|
|
21881
|
+
|
|
21882
|
+
## Proposals
|
|
21883
|
+
${proposalsSection}
|
|
21884
|
+
|
|
21885
|
+
## Critiques
|
|
21886
|
+
${critiquesSection}
|
|
21887
|
+
|
|
21888
|
+
Evaluate each proposal against the critiques and provide the best possible response.
|
|
21889
|
+
${this.stageContext.outputFormat}`;
|
|
21890
|
+
}
|
|
21891
|
+
buildClosePrompt() {
|
|
21892
|
+
return "Close this debate session.";
|
|
21893
|
+
}
|
|
21894
|
+
buildPersonaBlock(debaterIndex) {
|
|
21895
|
+
const debater = this.options.debaters[debaterIndex];
|
|
21896
|
+
if (!debater?.persona)
|
|
21897
|
+
return "";
|
|
21898
|
+
const { identity, lens } = PERSONA_FRAGMENTS[debater.persona];
|
|
21899
|
+
return `
|
|
21900
|
+
|
|
21901
|
+
## Your Role
|
|
21902
|
+
${identity}
|
|
21903
|
+
${lens}`;
|
|
21904
|
+
}
|
|
21905
|
+
buildProposalsSection(proposals) {
|
|
21906
|
+
return proposals.map((p, i) => `### Proposal ${i + 1} (${this.buildDebaterLabel(p.debater)})
|
|
21907
|
+
${p.output}`).join(`
|
|
21908
|
+
|
|
21909
|
+
`);
|
|
21910
|
+
}
|
|
21911
|
+
buildRebuttalsSection(rebuttals) {
|
|
21912
|
+
if (rebuttals.length === 0)
|
|
21913
|
+
return "";
|
|
21914
|
+
return `
|
|
21915
|
+
|
|
21916
|
+
## Previous Rebuttals
|
|
21917
|
+
${rebuttals.map((r, i) => `${i + 1}. ${r.output}`).join(`
|
|
21918
|
+
|
|
21919
|
+
`)}`;
|
|
21920
|
+
}
|
|
21921
|
+
buildCritiquesSection(critiques) {
|
|
21922
|
+
if (critiques.length === 0)
|
|
21923
|
+
return "";
|
|
21924
|
+
return critiques.map((c, i) => `### Critique ${i + 1} (${this.buildDebaterLabel(c.debater)})
|
|
21925
|
+
${c.output}`).join(`
|
|
21926
|
+
|
|
21927
|
+
`);
|
|
21928
|
+
}
|
|
21929
|
+
buildDebaterLabel(debater) {
|
|
21930
|
+
return debater.persona ? `${debater.agent} (${debater.persona})` : debater.agent;
|
|
21931
|
+
}
|
|
21932
|
+
}
|
|
21933
|
+
var init_prompt_builder = __esm(() => {
|
|
21934
|
+
init_personas();
|
|
21935
|
+
});
|
|
21936
|
+
|
|
21731
21937
|
// src/debate/session-stateful.ts
|
|
21732
21938
|
async function runStatefulTurn(ctx, adapter, debater, prompt, roleKey, keepSessionOpen) {
|
|
21733
21939
|
const modelTier = modelTierFromDebater(debater);
|
|
@@ -21783,7 +21989,9 @@ async function closeStatefulSession(ctx, adapter, debater, roleKey) {
|
|
|
21783
21989
|
async function runStateful(ctx, prompt) {
|
|
21784
21990
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21785
21991
|
const config2 = ctx.stageConfig;
|
|
21786
|
-
const
|
|
21992
|
+
const personaStage = ctx.stage === "plan" ? "plan" : "review";
|
|
21993
|
+
const rawDebaters = config2.debaters ?? [];
|
|
21994
|
+
const debaters = resolvePersonas(rawDebaters, personaStage, config2.autoPersona ?? false);
|
|
21787
21995
|
let totalCostUsd = 0;
|
|
21788
21996
|
const resolved = [];
|
|
21789
21997
|
for (const debater of debaters) {
|
|
@@ -21801,7 +22009,8 @@ async function runStateful(ctx, prompt) {
|
|
|
21801
22009
|
});
|
|
21802
22010
|
const debate = ctx.config?.debate;
|
|
21803
22011
|
const concurrencyLimit = debate?.maxConcurrentDebaters ?? 2;
|
|
21804
|
-
const
|
|
22012
|
+
const proposalBuilder = new DebatePromptBuilder({ taskContext: prompt, outputFormat: "", stage: ctx.stage }, { debaters: resolved.map((r) => r.debater), sessionMode: "stateful" });
|
|
22013
|
+
const proposalSettled = await allSettledBounded(resolved.map(({ debater, adapter }, debaterIdx) => () => runStatefulTurn(ctx, adapter, debater, proposalBuilder.buildProposalPrompt(debaterIdx), `debate-${ctx.stage}-${debaterIdx}`, config2.rounds > 1)), concurrencyLimit);
|
|
21805
22014
|
const successfulProposals = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
|
|
21806
22015
|
for (const r of proposalSettled) {
|
|
21807
22016
|
if (r.status === "fulfilled") {
|
|
@@ -21880,8 +22089,9 @@ async function runStateful(ctx, prompt) {
|
|
|
21880
22089
|
}
|
|
21881
22090
|
let critiqueOutputs = [];
|
|
21882
22091
|
if (config2.rounds > 1) {
|
|
21883
|
-
const
|
|
21884
|
-
const
|
|
22092
|
+
const proposals2 = successfulProposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
22093
|
+
const critiqueBuilder = new DebatePromptBuilder({ taskContext: prompt, outputFormat: "", stage: ctx.stage }, { debaters: proposals2.map((p) => p.debater), sessionMode: ctx.stageConfig.sessionMode ?? "one-shot" });
|
|
22094
|
+
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
22095
|
for (const r of critiqueSettled) {
|
|
21886
22096
|
if (r.status === "fulfilled") {
|
|
21887
22097
|
totalCostUsd += r.value.cost;
|
|
@@ -21892,9 +22102,9 @@ async function runStateful(ctx, prompt) {
|
|
|
21892
22102
|
const proposalOutputs = successfulProposals.map((s) => s.output);
|
|
21893
22103
|
const fullResolverContext = ctx.resolverContextInput ? {
|
|
21894
22104
|
...ctx.resolverContextInput,
|
|
21895
|
-
labeledProposals: successfulProposals.map((s) => ({ debater: s.debater
|
|
22105
|
+
labeledProposals: successfulProposals.map((s) => ({ debater: buildDebaterLabel2(s.debater), output: s.output }))
|
|
21896
22106
|
} : undefined;
|
|
21897
|
-
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext);
|
|
22107
|
+
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext, undefined, successfulProposals.map((s) => s.debater));
|
|
21898
22108
|
totalCostUsd += outcome.resolverCostUsd;
|
|
21899
22109
|
const proposals = successfulProposals.map((s) => ({
|
|
21900
22110
|
debater: s.debater,
|
|
@@ -21917,11 +22127,13 @@ async function runStateful(ctx, prompt) {
|
|
|
21917
22127
|
};
|
|
21918
22128
|
}
|
|
21919
22129
|
var init_session_stateful = __esm(() => {
|
|
22130
|
+
init_personas();
|
|
22131
|
+
init_prompt_builder();
|
|
21920
22132
|
init_session_helpers();
|
|
21921
22133
|
});
|
|
21922
22134
|
|
|
21923
22135
|
// src/debate/session-hybrid.ts
|
|
21924
|
-
async function runRebuttalLoop(ctx, proposals,
|
|
22136
|
+
async function runRebuttalLoop(ctx, proposals, builder, sessionRolePrefix) {
|
|
21925
22137
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21926
22138
|
const config2 = ctx.stageConfig;
|
|
21927
22139
|
const rebuttals = [];
|
|
@@ -21929,7 +22141,7 @@ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix
|
|
|
21929
22141
|
const proposalList = proposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
21930
22142
|
try {
|
|
21931
22143
|
for (let round = 1;round <= config2.rounds; round++) {
|
|
21932
|
-
const priorRebuttals = rebuttals.filter((r) => r.round < round)
|
|
22144
|
+
const priorRebuttals = rebuttals.filter((r) => r.round < round);
|
|
21933
22145
|
for (let debaterIdx = 0;debaterIdx < proposals.length; debaterIdx++) {
|
|
21934
22146
|
const proposal = proposals[debaterIdx];
|
|
21935
22147
|
const sessionRole = `${sessionRolePrefix}-${debaterIdx}`;
|
|
@@ -21938,7 +22150,7 @@ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix
|
|
|
21938
22150
|
round,
|
|
21939
22151
|
debaterIndex: debaterIdx
|
|
21940
22152
|
});
|
|
21941
|
-
const rebuttalPrompt =
|
|
22153
|
+
const rebuttalPrompt = builder.buildRebuttalPrompt(debaterIdx, proposalList, priorRebuttals);
|
|
21942
22154
|
try {
|
|
21943
22155
|
const turnResult = await runStatefulTurn(ctx, proposal.adapter, proposal.debater, rebuttalPrompt, sessionRole, true);
|
|
21944
22156
|
costUsd += turnResult.cost;
|
|
@@ -21968,7 +22180,9 @@ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix
|
|
|
21968
22180
|
async function runHybrid(ctx, prompt) {
|
|
21969
22181
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21970
22182
|
const config2 = ctx.stageConfig;
|
|
21971
|
-
const
|
|
22183
|
+
const personaStage = ctx.stage === "plan" ? "plan" : "review";
|
|
22184
|
+
const rawDebaters = config2.debaters ?? [];
|
|
22185
|
+
const debaters = resolvePersonas(rawDebaters, personaStage, config2.autoPersona ?? false);
|
|
21972
22186
|
let totalCostUsd = 0;
|
|
21973
22187
|
const resolved = [];
|
|
21974
22188
|
for (const debater of debaters) {
|
|
@@ -22033,14 +22247,15 @@ async function runHybrid(ctx, prompt) {
|
|
|
22033
22247
|
}
|
|
22034
22248
|
const proposalOutputs = successfulProposals.map((s) => s.output);
|
|
22035
22249
|
const proposalList = successfulProposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
22036
|
-
const {
|
|
22250
|
+
const rebuttalBuilder = new DebatePromptBuilder({ taskContext: prompt, outputFormat: "", stage: ctx.stage }, { debaters: successfulProposals.map((s) => s.debater), sessionMode: "stateful" });
|
|
22251
|
+
const { rebuttals, costUsd: rebuttalCost } = await runRebuttalLoop(ctx, successfulProposals, rebuttalBuilder, "debate-hybrid");
|
|
22037
22252
|
totalCostUsd += rebuttalCost;
|
|
22038
22253
|
const critiqueOutputs = rebuttals.map((r) => r.output);
|
|
22039
22254
|
const fullResolverContext = ctx.resolverContextInput ? {
|
|
22040
22255
|
...ctx.resolverContextInput,
|
|
22041
|
-
labeledProposals: successfulProposals.map((s) => ({ debater: s.debater
|
|
22256
|
+
labeledProposals: successfulProposals.map((s) => ({ debater: buildDebaterLabel2(s.debater), output: s.output }))
|
|
22042
22257
|
} : undefined;
|
|
22043
|
-
const resolveResult = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext);
|
|
22258
|
+
const resolveResult = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext, undefined, successfulProposals.map((s) => s.debater));
|
|
22044
22259
|
totalCostUsd += resolveResult.resolverCostUsd;
|
|
22045
22260
|
return {
|
|
22046
22261
|
storyId: ctx.storyId,
|
|
@@ -22055,6 +22270,8 @@ async function runHybrid(ctx, prompt) {
|
|
|
22055
22270
|
};
|
|
22056
22271
|
}
|
|
22057
22272
|
var init_session_hybrid = __esm(() => {
|
|
22273
|
+
init_personas();
|
|
22274
|
+
init_prompt_builder();
|
|
22058
22275
|
init_session_helpers();
|
|
22059
22276
|
init_session_stateful();
|
|
22060
22277
|
});
|
|
@@ -22063,7 +22280,9 @@ var init_session_hybrid = __esm(() => {
|
|
|
22063
22280
|
async function runOneShot(ctx, prompt) {
|
|
22064
22281
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
22065
22282
|
const config2 = ctx.stageConfig;
|
|
22066
|
-
const
|
|
22283
|
+
const personaStage = ctx.stage === "plan" ? "plan" : "review";
|
|
22284
|
+
const rawDebaters = config2.debaters ?? [];
|
|
22285
|
+
const debaters = resolvePersonas(rawDebaters, personaStage, config2.autoPersona ?? false);
|
|
22067
22286
|
let totalCostUsd = 0;
|
|
22068
22287
|
const resolved = [];
|
|
22069
22288
|
for (const debater of debaters) {
|
|
@@ -22081,7 +22300,8 @@ async function runOneShot(ctx, prompt) {
|
|
|
22081
22300
|
});
|
|
22082
22301
|
const debate = ctx.config?.debate;
|
|
22083
22302
|
const concurrencyLimit = debate?.maxConcurrentDebaters ?? 2;
|
|
22084
|
-
const
|
|
22303
|
+
const proposalBuilder = new DebatePromptBuilder({ taskContext: prompt, outputFormat: "", stage: ctx.stage }, { debaters: resolved.map((r) => r.debater), sessionMode: "one-shot" });
|
|
22304
|
+
const proposalSettled = await allSettledBounded(resolved.map(({ debater, adapter }, i) => () => runComplete(adapter, proposalBuilder.buildProposalPrompt(i), {
|
|
22085
22305
|
model: resolveDebaterModel(debater, ctx.config),
|
|
22086
22306
|
featureName: ctx.stage,
|
|
22087
22307
|
config: ctx.config,
|
|
@@ -22166,8 +22386,9 @@ async function runOneShot(ctx, prompt) {
|
|
|
22166
22386
|
}
|
|
22167
22387
|
let critiqueOutputs = [];
|
|
22168
22388
|
if (config2.rounds > 1) {
|
|
22169
|
-
const
|
|
22170
|
-
const
|
|
22389
|
+
const proposals2 = successful.map((p) => ({ debater: p.debater, output: p.output }));
|
|
22390
|
+
const critiqueBuilder = new DebatePromptBuilder({ taskContext: prompt, outputFormat: "", stage: ctx.stage }, { debaters: proposals2.map((p) => p.debater), sessionMode: ctx.stageConfig.sessionMode ?? "one-shot" });
|
|
22391
|
+
const critiqueSettled = await allSettledBounded(successful.map(({ debater, adapter }, i) => () => runComplete(adapter, critiqueBuilder.buildCritiquePrompt(i, proposals2), {
|
|
22171
22392
|
model: resolveDebaterModel(debater, ctx.config),
|
|
22172
22393
|
featureName: ctx.stage,
|
|
22173
22394
|
config: ctx.config,
|
|
@@ -22185,9 +22406,9 @@ async function runOneShot(ctx, prompt) {
|
|
|
22185
22406
|
const proposalOutputs = successful.map((p) => p.output);
|
|
22186
22407
|
const fullResolverContext = ctx.resolverContextInput ? {
|
|
22187
22408
|
...ctx.resolverContextInput,
|
|
22188
|
-
labeledProposals: successful.map((p) => ({ debater: p.debater
|
|
22409
|
+
labeledProposals: successful.map((p) => ({ debater: buildDebaterLabel2(p.debater), output: p.output }))
|
|
22189
22410
|
} : undefined;
|
|
22190
|
-
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutMs, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext);
|
|
22411
|
+
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutMs, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext, undefined, successful.map((p) => p.debater));
|
|
22191
22412
|
totalCostUsd += outcome.resolverCostUsd;
|
|
22192
22413
|
const proposals = successful.map((p) => ({
|
|
22193
22414
|
debater: p.debater,
|
|
@@ -22210,15 +22431,18 @@ async function runOneShot(ctx, prompt) {
|
|
|
22210
22431
|
};
|
|
22211
22432
|
}
|
|
22212
22433
|
var init_session_one_shot = __esm(() => {
|
|
22434
|
+
init_personas();
|
|
22435
|
+
init_prompt_builder();
|
|
22213
22436
|
init_session_helpers();
|
|
22214
22437
|
});
|
|
22215
22438
|
|
|
22216
22439
|
// src/debate/session-plan.ts
|
|
22217
22440
|
import { join as join10 } from "path";
|
|
22218
|
-
async function runPlan2(ctx,
|
|
22441
|
+
async function runPlan2(ctx, taskContext, outputFormat, opts) {
|
|
22219
22442
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
22220
22443
|
const config2 = ctx.stageConfig;
|
|
22221
|
-
const
|
|
22444
|
+
const rawDebaters = config2.debaters ?? [];
|
|
22445
|
+
const debaters = resolvePersonas(rawDebaters, "plan", config2.autoPersona ?? false);
|
|
22222
22446
|
let totalCostUsd = 0;
|
|
22223
22447
|
const resolved = [];
|
|
22224
22448
|
for (const debater of debaters) {
|
|
@@ -22236,14 +22460,15 @@ async function runPlan2(ctx, basePrompt, opts) {
|
|
|
22236
22460
|
});
|
|
22237
22461
|
const debate = ctx.config?.debate;
|
|
22238
22462
|
const concurrencyLimit = debate?.maxConcurrentDebaters ?? 2;
|
|
22239
|
-
const
|
|
22463
|
+
const proposalBuilder = new DebatePromptBuilder({ taskContext, outputFormat, stage: "plan" }, { debaters: resolved.map((r) => r.debater), sessionMode: ctx.stageConfig.sessionMode ?? "one-shot" });
|
|
22464
|
+
const settled = await allSettledBounded(resolved.map(({ debater: rd, adapter }, i) => async () => {
|
|
22240
22465
|
const tempOutputPath = join10(opts.outputDir, `prd-debate-${i}.json`);
|
|
22241
|
-
const debaterPrompt = `${
|
|
22466
|
+
const debaterPrompt = `${proposalBuilder.buildProposalPrompt(i)}
|
|
22242
22467
|
|
|
22243
22468
|
Write the PRD JSON directly to this file path: ${tempOutputPath}
|
|
22244
22469
|
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(
|
|
22470
|
+
const modelTier = modelTierFromDebater(rd);
|
|
22471
|
+
const modelDef = resolveModelDefForDebater(rd, modelTier, ctx.config);
|
|
22247
22472
|
const planResult = await adapter.plan({
|
|
22248
22473
|
prompt: debaterPrompt,
|
|
22249
22474
|
workdir: opts.workdir,
|
|
@@ -22259,7 +22484,7 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22259
22484
|
sessionRole: `plan-${i}`
|
|
22260
22485
|
});
|
|
22261
22486
|
const output = await _debateSessionDeps.readFile(tempOutputPath);
|
|
22262
|
-
return { debater, adapter, output, cost: planResult.costUsd ?? 0 };
|
|
22487
|
+
return { debater: rd, adapter, output, cost: planResult.costUsd ?? 0 };
|
|
22263
22488
|
}), concurrencyLimit);
|
|
22264
22489
|
const successful = [];
|
|
22265
22490
|
for (let i = 0;i < settled.length; i++) {
|
|
@@ -22332,7 +22557,8 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22332
22557
|
featureName: opts.feature,
|
|
22333
22558
|
timeoutSeconds: opts.timeoutSeconds ?? 600
|
|
22334
22559
|
};
|
|
22335
|
-
const {
|
|
22560
|
+
const rebuttalBuilder = new DebatePromptBuilder({ taskContext, outputFormat: "", stage: "plan" }, { debaters: successful.map((p) => p.debater), sessionMode });
|
|
22561
|
+
const { rebuttals, costUsd } = await runRebuttalLoop(hybridCtx, successful, rebuttalBuilder, "plan-hybrid");
|
|
22336
22562
|
critiqueOutputs = rebuttals.map((r) => r.output);
|
|
22337
22563
|
rebuttalList = rebuttals;
|
|
22338
22564
|
totalCostUsd += costUsd;
|
|
@@ -22341,7 +22567,7 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22341
22567
|
}
|
|
22342
22568
|
const resolverTimeoutMs = (ctx.stageConfig.timeoutSeconds ?? 600) * 1000;
|
|
22343
22569
|
const planSynthesisSuffix = "IMPORTANT: Your response must be a single valid JSON object in PRD format (with project, feature, branchName, userStories array, etc.). Do NOT wrap it in markdown fences. Output raw JSON only.";
|
|
22344
|
-
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, resolverTimeoutMs, opts.workdir, opts.feature, undefined, undefined, planSynthesisSuffix);
|
|
22570
|
+
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, resolverTimeoutMs, opts.workdir, opts.feature, undefined, undefined, planSynthesisSuffix, successful.map((p) => p.debater));
|
|
22345
22571
|
const winningOutput = outcome.output ?? successful[0].output;
|
|
22346
22572
|
const proposals = successful.map((p) => ({ debater: p.debater, output: p.output }));
|
|
22347
22573
|
logger?.info("debate", "debate:result", {
|
|
@@ -22363,6 +22589,8 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22363
22589
|
};
|
|
22364
22590
|
}
|
|
22365
22591
|
var init_session_plan = __esm(() => {
|
|
22592
|
+
init_personas();
|
|
22593
|
+
init_prompt_builder();
|
|
22366
22594
|
init_session_helpers();
|
|
22367
22595
|
init_session_hybrid();
|
|
22368
22596
|
});
|
|
@@ -22448,13 +22676,13 @@ class DebateSession {
|
|
|
22448
22676
|
resolverContextInput: this.resolverContextInput
|
|
22449
22677
|
}, prompt);
|
|
22450
22678
|
}
|
|
22451
|
-
async runPlan(
|
|
22679
|
+
async runPlan(taskContext, outputFormat, opts) {
|
|
22452
22680
|
return runPlan2({
|
|
22453
22681
|
storyId: this.storyId,
|
|
22454
22682
|
stage: this.stage,
|
|
22455
22683
|
stageConfig: this.stageConfig,
|
|
22456
22684
|
config: this.config
|
|
22457
|
-
},
|
|
22685
|
+
}, taskContext, outputFormat, opts);
|
|
22458
22686
|
}
|
|
22459
22687
|
}
|
|
22460
22688
|
var DEFAULT_TIMEOUT_SECONDS = 600;
|
|
@@ -22471,8 +22699,8 @@ var init_session = __esm(() => {
|
|
|
22471
22699
|
var init_debate = __esm(() => {
|
|
22472
22700
|
init_session();
|
|
22473
22701
|
init_session_helpers();
|
|
22474
|
-
|
|
22475
|
-
|
|
22702
|
+
init_prompt_builder();
|
|
22703
|
+
init_personas();
|
|
22476
22704
|
});
|
|
22477
22705
|
|
|
22478
22706
|
// src/interaction/bridge-builder.ts
|
|
@@ -23653,50 +23881,6 @@ var init_init = __esm(() => {
|
|
|
23653
23881
|
init_webhook();
|
|
23654
23882
|
});
|
|
23655
23883
|
|
|
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
23884
|
// src/prd/validate.ts
|
|
23701
23885
|
function validateStoryId(id) {
|
|
23702
23886
|
if (!id || id.length === 0) {
|
|
@@ -25611,7 +25795,8 @@ Rules:
|
|
|
25611
25795
|
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
25612
25796
|
- **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.
|
|
25613
25797
|
- **File output (REQUIRED)**: Write the acceptance test file DIRECTLY to the path shown below. Do NOT output the test code in your response. After writing the file, reply with a brief confirmation.
|
|
25614
|
-
- **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${join16(options.workdir, ".nax", "features", options.featureName, resolveAcceptanceTestFile2(options.language, options.config?.acceptance?.testPath))}\`. Import from package sources using relative paths like \`../../../src/...\` (3 levels up from \`.nax/features/<name>/\` to the package root)
|
|
25798
|
+
- **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${join16(options.workdir, ".nax", "features", options.featureName, resolveAcceptanceTestFile2(options.language, options.config?.acceptance?.testPath))}\`. Import from package sources using relative paths like \`../../../src/...\` (3 levels up from \`.nax/features/<name>/\` to the package root).
|
|
25799
|
+
- **Process cwd**: When spawning child processes to invoke a CLI or binary, set the working directory to the **package root** (\`join(import.meta.dir, "../../..")\`) as your default \u2014 unless your Step 2 exploration reveals the CLI uses a different working directory convention (e.g. reads config from \`~/.config/\`, or resolves paths relative to a flag value). Always check how the CLI resolves file paths before assuming.`;
|
|
25615
25800
|
const implementationSection = options.implementationContext && options.implementationContext.length > 0 ? `
|
|
25616
25801
|
|
|
25617
25802
|
## Implementation (already exists)
|
|
@@ -25802,7 +25987,7 @@ Rules:
|
|
|
25802
25987
|
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
25803
25988
|
- **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
25989
|
- 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
|
|
25990
|
+
- **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
25991
|
}
|
|
25807
25992
|
async function generateAcceptanceTests(adapter, options) {
|
|
25808
25993
|
const logger = getLogger();
|
|
@@ -26960,6 +27145,7 @@ function buildReviewPrompt(diff, story, _semanticConfig) {
|
|
|
26960
27145
|
"## Diff",
|
|
26961
27146
|
diff,
|
|
26962
27147
|
"",
|
|
27148
|
+
"Also flag any changes in the diff not required by the acceptance criteria above as out-of-scope findings.",
|
|
26963
27149
|
"Respond with JSON: { passed: boolean, findings: [...], findingReasoning: { [id]: string } }"
|
|
26964
27150
|
].join(`
|
|
26965
27151
|
`);
|
|
@@ -26981,7 +27167,7 @@ function buildReReviewPrompt(updatedDiff, previousFindings) {
|
|
|
26981
27167
|
].join(`
|
|
26982
27168
|
`);
|
|
26983
27169
|
}
|
|
26984
|
-
function
|
|
27170
|
+
function buildProposalsSection2(proposals) {
|
|
26985
27171
|
return proposals.map((p) => `### ${p.debater}
|
|
26986
27172
|
${p.output}`).join(`
|
|
26987
27173
|
|
|
@@ -27025,7 +27211,7 @@ function buildDebateResolverPrompt(proposals, critiques, diff, story, _semanticC
|
|
|
27025
27211
|
`);
|
|
27026
27212
|
const framing = buildResolverFraming(resolverContext);
|
|
27027
27213
|
const voteTally = buildVoteTallyLine(resolverContext);
|
|
27028
|
-
const proposalsSection =
|
|
27214
|
+
const proposalsSection = buildProposalsSection2(proposals);
|
|
27029
27215
|
const critiquesSection = buildCritiquesSection(critiques);
|
|
27030
27216
|
return [
|
|
27031
27217
|
framing,
|
|
@@ -27051,7 +27237,7 @@ function buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFi
|
|
|
27051
27237
|
const framing = buildResolverFraming(resolverContext);
|
|
27052
27238
|
const findingsList = previousFindings.length > 0 ? previousFindings.map((f) => `- ${f.ruleId}: ${f.message}`).join(`
|
|
27053
27239
|
`) : "(none)";
|
|
27054
|
-
const proposalsSection =
|
|
27240
|
+
const proposalsSection = buildProposalsSection2(proposals);
|
|
27055
27241
|
const critiquesSection = buildCritiquesSection(critiques);
|
|
27056
27242
|
return [
|
|
27057
27243
|
`${framing} This is a re-review after implementer changes.`,
|
|
@@ -27074,12 +27260,10 @@ function buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFi
|
|
|
27074
27260
|
|
|
27075
27261
|
// src/review/dialogue.ts
|
|
27076
27262
|
function extractDeltaSummary(rawOutput, previousFindings, newFindings) {
|
|
27077
|
-
|
|
27078
|
-
|
|
27079
|
-
|
|
27080
|
-
|
|
27081
|
-
}
|
|
27082
|
-
} catch {}
|
|
27263
|
+
const parsed = tryParseLLMJson(rawOutput);
|
|
27264
|
+
if (parsed && typeof parsed.deltaSummary === "string" && parsed.deltaSummary.length > 0) {
|
|
27265
|
+
return parsed.deltaSummary;
|
|
27266
|
+
}
|
|
27083
27267
|
const newIds = new Set(newFindings.map((f) => f.ruleId));
|
|
27084
27268
|
const prevIds = new Set(previousFindings.map((f) => f.ruleId));
|
|
27085
27269
|
const resolved = previousFindings.filter((f) => !newIds.has(f.ruleId));
|
|
@@ -27119,7 +27303,7 @@ function compactHistory(history) {
|
|
|
27119
27303
|
function parseReviewResponse(output) {
|
|
27120
27304
|
let parsed;
|
|
27121
27305
|
try {
|
|
27122
|
-
parsed =
|
|
27306
|
+
parsed = parseLLMJson(output);
|
|
27123
27307
|
} catch {
|
|
27124
27308
|
throw new NaxError("[dialogue] Failed to parse reviewer JSON response", "REVIEWER_PARSE_FAILED", {
|
|
27125
27309
|
stage: "review",
|
|
@@ -27698,23 +27882,11 @@ function validateLLMShape(parsed) {
|
|
|
27698
27882
|
return { passed: obj.passed, findings: obj.findings };
|
|
27699
27883
|
}
|
|
27700
27884
|
function parseLLMResponse(raw) {
|
|
27701
|
-
const text = raw.trim();
|
|
27702
27885
|
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 {}
|
|
27886
|
+
return validateLLMShape(tryParseLLMJson(raw));
|
|
27887
|
+
} catch {
|
|
27888
|
+
return null;
|
|
27716
27889
|
}
|
|
27717
|
-
return null;
|
|
27718
27890
|
}
|
|
27719
27891
|
function formatFindings(findings) {
|
|
27720
27892
|
return findings.map((f) => `[${f.severity}] ${f.file}:${f.line} \u2014 ${f.issue}
|
|
@@ -28689,6 +28861,7 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28689
28861
|
config: ctx.config,
|
|
28690
28862
|
projectDir: ctx.projectDir,
|
|
28691
28863
|
maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
|
|
28864
|
+
featureName: ctx.prd.feature,
|
|
28692
28865
|
storyId: ctx.story.id,
|
|
28693
28866
|
sessionRole: "implementer"
|
|
28694
28867
|
});
|
|
@@ -30466,61 +30639,34 @@ var init_executor = __esm(() => {
|
|
|
30466
30639
|
});
|
|
30467
30640
|
|
|
30468
30641
|
// src/verification/parser.ts
|
|
30469
|
-
function
|
|
30470
|
-
if (
|
|
30471
|
-
return
|
|
30472
|
-
|
|
30473
|
-
|
|
30474
|
-
}
|
|
30475
|
-
|
|
30476
|
-
|
|
30642
|
+
function detectFramework(output) {
|
|
30643
|
+
if (/^\s*Test Files\s+\d+/m.test(output))
|
|
30644
|
+
return "vitest";
|
|
30645
|
+
if (/^\s*Tests:\s+\d+/m.test(output))
|
|
30646
|
+
return "jest";
|
|
30647
|
+
if (/={3,}\s+\d+\s+(?:failed|passed).*in\s+[\d.]+s\s*={3,}/m.test(output))
|
|
30648
|
+
return "pytest";
|
|
30649
|
+
if (/^--- (?:FAIL|PASS):/m.test(output) || /^(?:ok|FAIL)\s+\t/m.test(output))
|
|
30650
|
+
return "go";
|
|
30651
|
+
if (/^\(fail\)\s/m.test(output) || /^bun test/m.test(output) || /[\u2713\u2714\u2717\u2718]/m.test(output))
|
|
30652
|
+
return "bun";
|
|
30653
|
+
return "unknown";
|
|
30477
30654
|
}
|
|
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
|
-
}
|
|
30655
|
+
function parseTestOutput(output) {
|
|
30656
|
+
const framework = detectFramework(output);
|
|
30657
|
+
switch (framework) {
|
|
30658
|
+
case "bun":
|
|
30659
|
+
return parseBunOutput(output);
|
|
30660
|
+
case "jest":
|
|
30661
|
+
case "vitest":
|
|
30662
|
+
return parseJestOutput(output);
|
|
30663
|
+
case "pytest":
|
|
30664
|
+
return parsePytestOutput(output);
|
|
30665
|
+
case "go":
|
|
30666
|
+
return parseGoTestOutput(output);
|
|
30667
|
+
default:
|
|
30668
|
+
return parseCommonOutput(output);
|
|
30522
30669
|
}
|
|
30523
|
-
return { passed, failed, failures };
|
|
30524
30670
|
}
|
|
30525
30671
|
function parseBunOutput(output) {
|
|
30526
30672
|
const lines = output.split(`
|
|
@@ -30582,6 +30728,122 @@ function parseBunOutput(output) {
|
|
|
30582
30728
|
}
|
|
30583
30729
|
return { passed, failed, failures };
|
|
30584
30730
|
}
|
|
30731
|
+
function parseJestOutput(output) {
|
|
30732
|
+
const failures = [];
|
|
30733
|
+
let passed = 0;
|
|
30734
|
+
let failed = 0;
|
|
30735
|
+
const summaryMatches = Array.from(output.matchAll(/^\s*Tests:\s+(.*)/gm));
|
|
30736
|
+
if (summaryMatches.length > 0) {
|
|
30737
|
+
const summaryLine = summaryMatches[summaryMatches.length - 1][1];
|
|
30738
|
+
const failedMatch = summaryLine.match(/(\d+)\s+failed/);
|
|
30739
|
+
const passedMatch = summaryLine.match(/(\d+)\s+passed/);
|
|
30740
|
+
if (failedMatch)
|
|
30741
|
+
failed = Number.parseInt(failedMatch[1], 10);
|
|
30742
|
+
if (passedMatch)
|
|
30743
|
+
passed = Number.parseInt(passedMatch[1], 10);
|
|
30744
|
+
}
|
|
30745
|
+
let currentFile = "unknown";
|
|
30746
|
+
const lines = output.split(`
|
|
30747
|
+
`);
|
|
30748
|
+
for (let i = 0;i < lines.length; i++) {
|
|
30749
|
+
const line = lines[i];
|
|
30750
|
+
const fileMatch = line.match(/^\s*(?:FAIL|PASS)\s+(\S+\.[jt]sx?)/);
|
|
30751
|
+
if (fileMatch) {
|
|
30752
|
+
currentFile = fileMatch[1];
|
|
30753
|
+
continue;
|
|
30754
|
+
}
|
|
30755
|
+
const bulletMatch = line.match(/^\s+\u25CF\s+(.+)$/);
|
|
30756
|
+
if (bulletMatch) {
|
|
30757
|
+
const testName = bulletMatch[1].trim();
|
|
30758
|
+
let error48 = "";
|
|
30759
|
+
for (let j = i + 1;j < lines.length && j < i + 10; j++) {
|
|
30760
|
+
const next = lines[j].trim();
|
|
30761
|
+
if (!next)
|
|
30762
|
+
continue;
|
|
30763
|
+
if (next.startsWith("\u25CF") || /^(?:FAIL|PASS)\s/.test(next))
|
|
30764
|
+
break;
|
|
30765
|
+
error48 = next;
|
|
30766
|
+
break;
|
|
30767
|
+
}
|
|
30768
|
+
failures.push({
|
|
30769
|
+
file: currentFile,
|
|
30770
|
+
testName,
|
|
30771
|
+
error: error48 || "Unknown error",
|
|
30772
|
+
stackTrace: []
|
|
30773
|
+
});
|
|
30774
|
+
}
|
|
30775
|
+
}
|
|
30776
|
+
return { passed, failed, failures };
|
|
30777
|
+
}
|
|
30778
|
+
function parsePytestOutput(output) {
|
|
30779
|
+
const common = parseCommonOutput(output);
|
|
30780
|
+
const failures = [];
|
|
30781
|
+
for (const line of output.split(`
|
|
30782
|
+
`)) {
|
|
30783
|
+
const m = line.match(/^FAILED\s+(\S+)(?:\s+-\s+(.*))?$/);
|
|
30784
|
+
if (m) {
|
|
30785
|
+
const [, location, reason] = m;
|
|
30786
|
+
const parts = location.split("::");
|
|
30787
|
+
failures.push({
|
|
30788
|
+
file: parts[0] ?? location,
|
|
30789
|
+
testName: parts.slice(1).join(" > ") || location,
|
|
30790
|
+
error: reason?.trim() || "Unknown error",
|
|
30791
|
+
stackTrace: []
|
|
30792
|
+
});
|
|
30793
|
+
}
|
|
30794
|
+
}
|
|
30795
|
+
return {
|
|
30796
|
+
passed: common.passed,
|
|
30797
|
+
failed: common.failed,
|
|
30798
|
+
failures: failures.length > 0 ? failures : common.failures
|
|
30799
|
+
};
|
|
30800
|
+
}
|
|
30801
|
+
function parseGoTestOutput(output) {
|
|
30802
|
+
const common = parseCommonOutput(output);
|
|
30803
|
+
const failures = [];
|
|
30804
|
+
for (const line of output.split(`
|
|
30805
|
+
`)) {
|
|
30806
|
+
const m = line.match(/^--- FAIL:\s+(\S+)\s+\([\d.]+s\)/);
|
|
30807
|
+
if (m) {
|
|
30808
|
+
failures.push({
|
|
30809
|
+
file: "unknown",
|
|
30810
|
+
testName: m[1],
|
|
30811
|
+
error: "Unknown error",
|
|
30812
|
+
stackTrace: []
|
|
30813
|
+
});
|
|
30814
|
+
}
|
|
30815
|
+
}
|
|
30816
|
+
return {
|
|
30817
|
+
passed: common.passed,
|
|
30818
|
+
failed: common.failed,
|
|
30819
|
+
failures: failures.length > 0 ? failures : common.failures
|
|
30820
|
+
};
|
|
30821
|
+
}
|
|
30822
|
+
function parseCommonOutput(output) {
|
|
30823
|
+
let passed = 0;
|
|
30824
|
+
let failed = 0;
|
|
30825
|
+
const patterns = [
|
|
30826
|
+
/(\d+)\s+pass(?:ed)?(?:,\s*|\s+)(\d+)\s+fail/i,
|
|
30827
|
+
/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+failed/i,
|
|
30828
|
+
/(\d+)\s+pass/i
|
|
30829
|
+
];
|
|
30830
|
+
for (const pattern of patterns) {
|
|
30831
|
+
const matches = Array.from(output.matchAll(new RegExp(pattern, "gi")));
|
|
30832
|
+
if (matches.length > 0) {
|
|
30833
|
+
const last = matches[matches.length - 1];
|
|
30834
|
+
passed = Number.parseInt(last[1], 10);
|
|
30835
|
+
failed = last[2] ? Number.parseInt(last[2], 10) : 0;
|
|
30836
|
+
break;
|
|
30837
|
+
}
|
|
30838
|
+
}
|
|
30839
|
+
if (failed === 0) {
|
|
30840
|
+
const failMatches = Array.from(output.matchAll(/(\d+)\s+fail/gi));
|
|
30841
|
+
if (failMatches.length > 0) {
|
|
30842
|
+
failed = Number.parseInt(failMatches[failMatches.length - 1][1], 10);
|
|
30843
|
+
}
|
|
30844
|
+
}
|
|
30845
|
+
return { passed, failed, failures: [] };
|
|
30846
|
+
}
|
|
30585
30847
|
function formatFailureSummary(failures, maxChars = 2000) {
|
|
30586
30848
|
if (failures.length === 0) {
|
|
30587
30849
|
return "No test failures";
|
|
@@ -30595,48 +30857,24 @@ function formatFailureSummary(failures, maxChars = 2000) {
|
|
|
30595
30857
|
const errorLine = ` Error: ${failure.error}`;
|
|
30596
30858
|
const stackLine = failure.stackTrace.length > 0 ? ` ${failure.stackTrace[0]}` : "";
|
|
30597
30859
|
const blockLines = [header, errorLine];
|
|
30598
|
-
if (stackLine)
|
|
30860
|
+
if (stackLine)
|
|
30599
30861
|
blockLines.push(stackLine);
|
|
30600
|
-
}
|
|
30601
30862
|
blockLines.push("");
|
|
30602
30863
|
const block = blockLines.join(`
|
|
30603
30864
|
`);
|
|
30604
|
-
|
|
30605
|
-
if (totalChars + blockLength > maxChars && lines.length > 0) {
|
|
30606
|
-
const remaining = failures.length - i;
|
|
30865
|
+
if (totalChars + block.length > maxChars && lines.length > 0) {
|
|
30607
30866
|
lines.push(`
|
|
30608
|
-
... and ${
|
|
30867
|
+
... and ${failures.length - i} more failure(s) (truncated)`);
|
|
30609
30868
|
break;
|
|
30610
30869
|
}
|
|
30611
30870
|
lines.push(...blockLines);
|
|
30612
|
-
totalChars +=
|
|
30871
|
+
totalChars += block.length;
|
|
30613
30872
|
}
|
|
30614
30873
|
return lines.join(`
|
|
30615
30874
|
`).trim();
|
|
30616
30875
|
}
|
|
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
|
-
}
|
|
30876
|
+
function analyzeTestExitCode(output, exitCode) {
|
|
30877
|
+
const { passed: passCount, failed: failCount } = parseCommonOutput(output);
|
|
30640
30878
|
const allTestsPassed = passCount > 0 && failCount === 0;
|
|
30641
30879
|
const isEnvironmentalFailure = allTestsPassed && exitCode !== 0;
|
|
30642
30880
|
const result = {
|
|
@@ -30707,7 +30945,7 @@ async function runVerificationCore(options) {
|
|
|
30707
30945
|
}
|
|
30708
30946
|
const exitCode = execution.exitCode ?? 1;
|
|
30709
30947
|
if (exitCode !== 0 && execution.output) {
|
|
30710
|
-
const analysis =
|
|
30948
|
+
const analysis = analyzeTestExitCode(execution.output, exitCode);
|
|
30711
30949
|
if (analysis.isEnvironmentalFailure) {
|
|
30712
30950
|
return {
|
|
30713
30951
|
status: "ENVIRONMENTAL_FAILURE",
|
|
@@ -31000,7 +31238,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
31000
31238
|
});
|
|
31001
31239
|
const fullSuitePassed = fullSuiteResult.success && fullSuiteResult.exitCode === 0;
|
|
31002
31240
|
if (!fullSuitePassed && fullSuiteResult.output) {
|
|
31003
|
-
const testSummary = _rectificationGateDeps.
|
|
31241
|
+
const testSummary = _rectificationGateDeps.parseTestOutput(fullSuiteResult.output);
|
|
31004
31242
|
if (testSummary.failed > 0) {
|
|
31005
31243
|
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir);
|
|
31006
31244
|
}
|
|
@@ -31134,7 +31372,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
31134
31372
|
return true;
|
|
31135
31373
|
}
|
|
31136
31374
|
if (retryFullSuite.output) {
|
|
31137
|
-
const newTestSummary = _rectificationGateDeps.
|
|
31375
|
+
const newTestSummary = _rectificationGateDeps.parseTestOutput(retryFullSuite.output);
|
|
31138
31376
|
state.currentFailures = newTestSummary.failed;
|
|
31139
31377
|
testSummary.failures = newTestSummary.failures;
|
|
31140
31378
|
testSummary.failed = newTestSummary.failed;
|
|
@@ -31182,7 +31420,7 @@ var init_rectification_gate = __esm(() => {
|
|
|
31182
31420
|
init_prompts();
|
|
31183
31421
|
_rectificationGateDeps = {
|
|
31184
31422
|
executeWithTimeout,
|
|
31185
|
-
|
|
31423
|
+
parseTestOutput,
|
|
31186
31424
|
shouldRetryRectification
|
|
31187
31425
|
};
|
|
31188
31426
|
});
|
|
@@ -33152,9 +33390,6 @@ function calculateMaxIterations(tierOrder) {
|
|
|
33152
33390
|
return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
|
|
33153
33391
|
}
|
|
33154
33392
|
|
|
33155
|
-
// src/execution/test-output-parser.ts
|
|
33156
|
-
var init_test_output_parser = () => {};
|
|
33157
|
-
|
|
33158
33393
|
// src/verification/rectification-loop.ts
|
|
33159
33394
|
async function _defaultRunDebate(storyId, stageConfig, prompt, config2) {
|
|
33160
33395
|
const logger = getSafeLogger();
|
|
@@ -33207,7 +33442,7 @@ async function runRectificationLoop2(opts) {
|
|
|
33207
33442
|
} = opts;
|
|
33208
33443
|
const logger = getSafeLogger();
|
|
33209
33444
|
const rectificationConfig = config2.execution.rectification;
|
|
33210
|
-
const testSummary =
|
|
33445
|
+
const testSummary = parseTestOutput(testOutput);
|
|
33211
33446
|
const label = promptPrefix ? "regression rectification" : "rectification";
|
|
33212
33447
|
const rectificationState = {
|
|
33213
33448
|
attempt: 0,
|
|
@@ -33342,13 +33577,13 @@ ${rectificationPrompt}`;
|
|
|
33342
33577
|
return true;
|
|
33343
33578
|
}
|
|
33344
33579
|
if (retryVerification.output) {
|
|
33345
|
-
const newTestSummary =
|
|
33580
|
+
const newTestSummary = parseTestOutput(retryVerification.output);
|
|
33346
33581
|
state.currentFailures = newTestSummary.failed;
|
|
33347
33582
|
state.lastExitCode = retryVerification.status === "SUCCESS" ? 0 : 1;
|
|
33348
33583
|
testSummary.failures = newTestSummary.failures;
|
|
33349
33584
|
testSummary.failed = newTestSummary.failed;
|
|
33350
33585
|
testSummary.passed = newTestSummary.passed;
|
|
33351
|
-
if (newTestSummary.failed === 0) {
|
|
33586
|
+
if (newTestSummary.failed === 0 && (retryVerification.status === "SUCCESS" || newTestSummary.passed > 0)) {
|
|
33352
33587
|
state.lastExitCode = 0;
|
|
33353
33588
|
logger?.info("rectification", `[OK] ${label} succeeded after parsing retry output`, {
|
|
33354
33589
|
storyId: story.id,
|
|
@@ -33488,7 +33723,6 @@ var init_rectification_loop = __esm(() => {
|
|
|
33488
33723
|
init_cost();
|
|
33489
33724
|
init_registry();
|
|
33490
33725
|
init_config();
|
|
33491
|
-
init_test_output_parser();
|
|
33492
33726
|
init_logger2();
|
|
33493
33727
|
init_prd();
|
|
33494
33728
|
init_rectification();
|
|
@@ -33754,7 +33988,7 @@ class RegressionStrategy {
|
|
|
33754
33988
|
});
|
|
33755
33989
|
const durationMs = Date.now() - start;
|
|
33756
33990
|
if (result.success) {
|
|
33757
|
-
const parsed2 = result.output ?
|
|
33991
|
+
const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33758
33992
|
return makePassResult(ctx.storyId, "regression", {
|
|
33759
33993
|
rawOutput: result.output,
|
|
33760
33994
|
passCount: parsed2.passed,
|
|
@@ -33770,7 +34004,7 @@ class RegressionStrategy {
|
|
|
33770
34004
|
if (result.status === "TIMEOUT") {
|
|
33771
34005
|
return makeFailResult(ctx.storyId, "regression", "TIMEOUT", { rawOutput: result.output, durationMs });
|
|
33772
34006
|
}
|
|
33773
|
-
const parsed = result.output ?
|
|
34007
|
+
const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33774
34008
|
return makeFailResult(ctx.storyId, "regression", "TEST_FAILURE", {
|
|
33775
34009
|
rawOutput: result.output,
|
|
33776
34010
|
passCount: parsed.passed,
|
|
@@ -33994,7 +34228,7 @@ class ScopedStrategy {
|
|
|
33994
34228
|
});
|
|
33995
34229
|
const durationMs = Date.now() - start;
|
|
33996
34230
|
if (result.success) {
|
|
33997
|
-
const parsed2 = result.output ?
|
|
34231
|
+
const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33998
34232
|
return makePassResult(ctx.storyId, "scoped", {
|
|
33999
34233
|
rawOutput: result.output,
|
|
34000
34234
|
passCount: parsed2.passed,
|
|
@@ -34010,7 +34244,7 @@ class ScopedStrategy {
|
|
|
34010
34244
|
scopeTestFallback
|
|
34011
34245
|
});
|
|
34012
34246
|
}
|
|
34013
|
-
const parsed = result.output ?
|
|
34247
|
+
const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
34014
34248
|
return makeFailResult(ctx.storyId, "scoped", "TEST_FAILURE", {
|
|
34015
34249
|
rawOutput: result.output,
|
|
34016
34250
|
passCount: parsed.passed,
|
|
@@ -34344,12 +34578,11 @@ function stripCodeFences(text) {
|
|
|
34344
34578
|
return trimmed;
|
|
34345
34579
|
}
|
|
34346
34580
|
function parseRoutingResponse(output, story, config2) {
|
|
34347
|
-
const
|
|
34348
|
-
const parsed = JSON.parse(jsonText);
|
|
34581
|
+
const parsed = parseLLMJson(output);
|
|
34349
34582
|
return validateRoutingDecision(parsed, config2, story);
|
|
34350
34583
|
}
|
|
34351
34584
|
function parseBatchResponse(output, stories, config2) {
|
|
34352
|
-
const parsed =
|
|
34585
|
+
const parsed = parseLLMJson(output);
|
|
34353
34586
|
if (!Array.isArray(parsed)) {
|
|
34354
34587
|
throw new Error("Batch LLM response must be a JSON array");
|
|
34355
34588
|
}
|
|
@@ -36143,7 +36376,7 @@ var package_default;
|
|
|
36143
36376
|
var init_package = __esm(() => {
|
|
36144
36377
|
package_default = {
|
|
36145
36378
|
name: "@nathapp/nax",
|
|
36146
|
-
version: "0.
|
|
36379
|
+
version: "0.60.0-canary.1",
|
|
36147
36380
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
36148
36381
|
type: "module",
|
|
36149
36382
|
bin: {
|
|
@@ -36223,8 +36456,8 @@ var init_version = __esm(() => {
|
|
|
36223
36456
|
NAX_VERSION = package_default.version;
|
|
36224
36457
|
NAX_COMMIT = (() => {
|
|
36225
36458
|
try {
|
|
36226
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
36227
|
-
return "
|
|
36459
|
+
if (/^[0-9a-f]{6,10}$/.test("1de1f9cc"))
|
|
36460
|
+
return "1de1f9cc";
|
|
36228
36461
|
} catch {}
|
|
36229
36462
|
try {
|
|
36230
36463
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -36887,28 +37120,17 @@ function parseDiagnosisResult(output) {
|
|
|
36887
37120
|
if (!output || output.trim() === "") {
|
|
36888
37121
|
return null;
|
|
36889
37122
|
}
|
|
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;
|
|
37123
|
+
const parsed = tryParseLLMJson(output);
|
|
37124
|
+
if (parsed && typeof parsed.verdict === "string" && typeof parsed.reasoning === "string" && typeof parsed.confidence === "number") {
|
|
37125
|
+
return {
|
|
37126
|
+
verdict: parsed.verdict,
|
|
37127
|
+
reasoning: parsed.reasoning,
|
|
37128
|
+
confidence: parsed.confidence,
|
|
37129
|
+
testIssues: Array.isArray(parsed.testIssues) ? parsed.testIssues : undefined,
|
|
37130
|
+
sourceIssues: Array.isArray(parsed.sourceIssues) ? parsed.sourceIssues : undefined
|
|
37131
|
+
};
|
|
36911
37132
|
}
|
|
37133
|
+
return null;
|
|
36912
37134
|
}
|
|
36913
37135
|
var MAX_SOURCE_FILES = 5, MAX_FILE_LINES = 500, MAX_TEST_OUTPUT_CHARS = 2000;
|
|
36914
37136
|
var init_fix_diagnosis = __esm(() => {
|
|
@@ -37106,6 +37328,12 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
37106
37328
|
logger?.info("acceptance", `Backed up acceptance test -> ${bakPath}`);
|
|
37107
37329
|
const { unlink: unlink3 } = await import("fs/promises");
|
|
37108
37330
|
await unlink3(testPath);
|
|
37331
|
+
if (acceptanceContext.featureDir) {
|
|
37332
|
+
const metaPath = path15.join(acceptanceContext.featureDir, "acceptance-meta.json");
|
|
37333
|
+
try {
|
|
37334
|
+
await unlink3(metaPath);
|
|
37335
|
+
} catch {}
|
|
37336
|
+
}
|
|
37109
37337
|
let implementationContext;
|
|
37110
37338
|
const storyGitRef = acceptanceContext.storyGitRef;
|
|
37111
37339
|
const workdir = acceptanceContext.workdir;
|
|
@@ -37746,7 +37974,7 @@ async function runDeferredRegression(options) {
|
|
|
37746
37974
|
affectedStories: []
|
|
37747
37975
|
};
|
|
37748
37976
|
}
|
|
37749
|
-
const testSummary = _regressionDeps.
|
|
37977
|
+
const testSummary = _regressionDeps.parseTestOutput(fullSuiteResult.output);
|
|
37750
37978
|
if (testSummary.failed === 0 && testSummary.passed === 0) {
|
|
37751
37979
|
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
37980
|
return {
|
|
@@ -37892,7 +38120,7 @@ var init_run_regression = __esm(() => {
|
|
|
37892
38120
|
_regressionDeps = {
|
|
37893
38121
|
runVerification: fullSuite,
|
|
37894
38122
|
runRectificationLoop: runRectificationLoop2,
|
|
37895
|
-
|
|
38123
|
+
parseTestOutput,
|
|
37896
38124
|
reverseMapTestToSource
|
|
37897
38125
|
};
|
|
37898
38126
|
});
|
|
@@ -38873,6 +39101,11 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
38873
39101
|
const diffSummary = await captureDiffSummary(ctx.workdir, ctx.storyGitRef, completedStory.workdir);
|
|
38874
39102
|
if (diffSummary) {
|
|
38875
39103
|
completedStory.diffSummary = diffSummary;
|
|
39104
|
+
} else {
|
|
39105
|
+
logger?.debug("context-chain", "No diff summary captured (agent may not have committed yet)", {
|
|
39106
|
+
storyId: completedStory.id,
|
|
39107
|
+
storyGitRef: ctx.storyGitRef
|
|
39108
|
+
});
|
|
38876
39109
|
}
|
|
38877
39110
|
} catch {}
|
|
38878
39111
|
}
|
|
@@ -72809,13 +73042,16 @@ function validateStory(raw, index, allIds) {
|
|
|
72809
73042
|
throw new Error(`[schema] story[${index}].routing.complexity "${rawComplexity}" is invalid. Valid values: ${VALID_COMPLEXITY.join(", ")}`);
|
|
72810
73043
|
}
|
|
72811
73044
|
const rawTestStrategy = routing.testStrategy ?? s.testStrategy;
|
|
72812
|
-
|
|
73045
|
+
let testStrategy = resolveTestStrategy(typeof rawTestStrategy === "string" ? rawTestStrategy : undefined);
|
|
72813
73046
|
const rawJustification = routing.noTestJustification ?? s.noTestJustification;
|
|
72814
73047
|
if (testStrategy === "no-test") {
|
|
72815
73048
|
if (!rawJustification || typeof rawJustification !== "string" || rawJustification.trim() === "") {
|
|
72816
73049
|
throw new Error(`[schema] story[${index}].routing.noTestJustification is required when testStrategy is "no-test"`);
|
|
72817
73050
|
}
|
|
72818
73051
|
}
|
|
73052
|
+
if (testStrategy !== "no-test" && typeof rawJustification === "string" && rawJustification.trim() !== "") {
|
|
73053
|
+
testStrategy = "no-test";
|
|
73054
|
+
}
|
|
72819
73055
|
const noTestJustification = typeof rawJustification === "string" && rawJustification.trim() !== "" ? rawJustification.trim() : undefined;
|
|
72820
73056
|
const rawDeps = s.dependencies;
|
|
72821
73057
|
const dependencies = Array.isArray(rawDeps) ? rawDeps : [];
|
|
@@ -72975,7 +73211,7 @@ async function planCommand(workdir, config2, options) {
|
|
|
72975
73211
|
let rawResponse;
|
|
72976
73212
|
const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
|
|
72977
73213
|
if (debateEnabled) {
|
|
72978
|
-
const
|
|
73214
|
+
const { taskContext: planTaskContext, outputFormat: planOutputFormat } = buildPlanningPrompt(specContent, codebaseContext, undefined, relativePackages, packageDetails, config2?.project);
|
|
72979
73215
|
const resolvedPerm = resolvePermissions(config2, "plan");
|
|
72980
73216
|
const planStageConfig = config2?.debate?.stages.plan;
|
|
72981
73217
|
const debateSession = _planDeps.createDebateSession({
|
|
@@ -72992,7 +73228,7 @@ async function planCommand(workdir, config2, options) {
|
|
|
72992
73228
|
rounds: planStageConfig.rounds,
|
|
72993
73229
|
feature: options.feature
|
|
72994
73230
|
});
|
|
72995
|
-
const debateResult = await debateSession.runPlan(
|
|
73231
|
+
const debateResult = await debateSession.runPlan(planTaskContext, planOutputFormat, {
|
|
72996
73232
|
workdir,
|
|
72997
73233
|
feature: options.feature,
|
|
72998
73234
|
outputDir,
|
|
@@ -73011,7 +73247,10 @@ async function planCommand(workdir, config2, options) {
|
|
|
73011
73247
|
}
|
|
73012
73248
|
} else if (options.auto) {
|
|
73013
73249
|
const isAcp = config2?.agent?.protocol === "acp";
|
|
73014
|
-
const
|
|
73250
|
+
const { taskContext: autoTaskCtx, outputFormat: autoOutputFmt } = buildPlanningPrompt(specContent, codebaseContext, isAcp ? outputPath : undefined, relativePackages, packageDetails, config2?.project);
|
|
73251
|
+
const prompt = `${autoTaskCtx}
|
|
73252
|
+
|
|
73253
|
+
${autoOutputFmt}`;
|
|
73015
73254
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
73016
73255
|
if (!adapter)
|
|
73017
73256
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
@@ -73097,7 +73336,10 @@ async function planCommand(workdir, config2, options) {
|
|
|
73097
73336
|
rawResponse = await runInteractivePlan();
|
|
73098
73337
|
}
|
|
73099
73338
|
async function runInteractivePlan() {
|
|
73100
|
-
const
|
|
73339
|
+
const { taskContext: interactiveTaskCtx, outputFormat: interactiveOutputFmt } = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails, config2?.project);
|
|
73340
|
+
const prompt = `${interactiveTaskCtx}
|
|
73341
|
+
|
|
73342
|
+
${interactiveOutputFmt}`;
|
|
73101
73343
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
73102
73344
|
if (!adapter)
|
|
73103
73345
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
@@ -73304,7 +73546,7 @@ ${packageDetailsSection}
|
|
|
73304
73546
|
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
73547
|
const workdirField = isMonorepo ? `
|
|
73306
73548
|
"workdir": "string \u2014 optional, relative path to package (e.g. \\"packages/api\\"). Omit for root-level stories.",` : "";
|
|
73307
|
-
|
|
73549
|
+
const taskContext = `You are a senior software architect generating a product requirements document (PRD) as JSON.
|
|
73308
73550
|
|
|
73309
73551
|
## Step 1: Understand the Spec
|
|
73310
73552
|
|
|
@@ -73332,6 +73574,8 @@ If this is a greenfield project (empty or minimal codebase):
|
|
|
73332
73574
|
|
|
73333
73575
|
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
73576
|
|
|
73577
|
+
**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.
|
|
73578
|
+
|
|
73335
73579
|
## Codebase Context
|
|
73336
73580
|
|
|
73337
73581
|
${codebaseContext}${monorepoHint}
|
|
@@ -73348,9 +73592,8 @@ For each story, set "contextFiles" to the key source files the agent should read
|
|
|
73348
73592
|
|
|
73349
73593
|
${COMPLEXITY_GUIDE}
|
|
73350
73594
|
|
|
73351
|
-
${TEST_STRATEGY_GUIDE}
|
|
73352
|
-
|
|
73353
|
-
## Output Schema
|
|
73595
|
+
${TEST_STRATEGY_GUIDE}`;
|
|
73596
|
+
const outputFormat = `## Output Schema
|
|
73354
73597
|
|
|
73355
73598
|
Generate a JSON object with this exact structure (no markdown, no explanation \u2014 JSON only):
|
|
73356
73599
|
|
|
@@ -73386,6 +73629,7 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
73386
73629
|
|
|
73387
73630
|
${outputFilePath ? `Write the PRD JSON directly to this file path: ${outputFilePath}
|
|
73388
73631
|
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."}`;
|
|
73632
|
+
return { taskContext, outputFormat };
|
|
73389
73633
|
}
|
|
73390
73634
|
async function planDecomposeCommand(workdir, config2, options) {
|
|
73391
73635
|
const prdPath = join11(workdir, ".nax", "features", options.feature, "prd.json");
|