@nathapp/nax 0.59.1 → 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 +542 -302
- 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;
|
|
@@ -21475,7 +21515,10 @@ function majorityResolver(proposals, failOpen) {
|
|
|
21475
21515
|
return passCount > failCount ? "passed" : "failed";
|
|
21476
21516
|
}
|
|
21477
21517
|
async function synthesisResolver(proposals, critiques, opts) {
|
|
21478
|
-
const
|
|
21518
|
+
const base = buildSynthesisPrompt(proposals, critiques);
|
|
21519
|
+
const prompt = opts.promptSuffix ? `${base}
|
|
21520
|
+
|
|
21521
|
+
${opts.promptSuffix}` : base;
|
|
21479
21522
|
return opts.adapter.complete(prompt, opts.completeOptions);
|
|
21480
21523
|
}
|
|
21481
21524
|
async function judgeResolver(proposals, critiques, resolverConfig, opts) {
|
|
@@ -21488,7 +21531,6 @@ async function judgeResolver(proposals, critiques, resolverConfig, opts) {
|
|
|
21488
21531
|
return adapter.complete(prompt, opts.completeOptions);
|
|
21489
21532
|
}
|
|
21490
21533
|
var DEFAULT_FALLBACK_AGENT = "claude";
|
|
21491
|
-
var init_resolvers = () => {};
|
|
21492
21534
|
|
|
21493
21535
|
// src/debate/session-helpers.ts
|
|
21494
21536
|
function resolveDebaterModel(debater, config2) {
|
|
@@ -21564,7 +21606,7 @@ function resolveModelDefForDebater(debater, tier, config2) {
|
|
|
21564
21606
|
return resolveModelForAgent(configModels, debater.agent, "fast", configDefaultAgent);
|
|
21565
21607
|
}
|
|
21566
21608
|
}
|
|
21567
|
-
async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, storyId, timeoutMs, workdir, featureName, reviewerSession, resolverContext) {
|
|
21609
|
+
async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, storyId, timeoutMs, workdir, featureName, reviewerSession, resolverContext, promptSuffix) {
|
|
21568
21610
|
const resolverConfig = stageConfig.resolver;
|
|
21569
21611
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21570
21612
|
if (reviewerSession && resolverContext) {
|
|
@@ -21578,21 +21620,13 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21578
21620
|
let passCount = 0;
|
|
21579
21621
|
let failCount = 0;
|
|
21580
21622
|
for (const proposal of proposalOutputs) {
|
|
21581
|
-
|
|
21582
|
-
|
|
21583
|
-
|
|
21584
|
-
|
|
21585
|
-
|
|
21586
|
-
|
|
21587
|
-
|
|
21588
|
-
else
|
|
21589
|
-
failCount++;
|
|
21590
|
-
} catch {
|
|
21591
|
-
if (failOpen)
|
|
21592
|
-
passCount++;
|
|
21593
|
-
else
|
|
21594
|
-
failCount++;
|
|
21595
|
-
}
|
|
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++;
|
|
21596
21630
|
}
|
|
21597
21631
|
debateCtx.majorityVote = { passed: rawOutcome === "passed", passCount, failCount };
|
|
21598
21632
|
}
|
|
@@ -21633,20 +21667,23 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21633
21667
|
resolverCostUsd: 0
|
|
21634
21668
|
};
|
|
21635
21669
|
}
|
|
21636
|
-
const implementerSessionName = workdir !== undefined ? buildSessionName(workdir, featureName, storyId, "implementer") : undefined;
|
|
21637
21670
|
if (resolverConfig.type === "synthesis") {
|
|
21638
21671
|
const agentName = resolverConfig.agent ?? RESOLVER_FALLBACK_AGENT;
|
|
21639
21672
|
const adapter = _debateSessionDeps.getAgent(agentName, config2);
|
|
21640
21673
|
if (adapter) {
|
|
21674
|
+
const synthesisSessionName = workdir !== undefined ? buildSessionName(workdir, featureName, storyId, "synthesis") : undefined;
|
|
21641
21675
|
const resolverResult = await synthesisResolver(proposalOutputs, critiqueOutputs, {
|
|
21642
21676
|
adapter,
|
|
21677
|
+
promptSuffix,
|
|
21643
21678
|
completeOptions: {
|
|
21644
21679
|
model: resolveDebaterModel({ agent: agentName }, config2),
|
|
21645
21680
|
config: config2,
|
|
21646
21681
|
storyId,
|
|
21682
|
+
featureName,
|
|
21683
|
+
workdir,
|
|
21647
21684
|
sessionRole: "synthesis",
|
|
21648
21685
|
timeoutMs,
|
|
21649
|
-
...
|
|
21686
|
+
...synthesisSessionName !== undefined && { sessionName: synthesisSessionName }
|
|
21650
21687
|
}
|
|
21651
21688
|
});
|
|
21652
21689
|
return {
|
|
@@ -21659,6 +21696,7 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21659
21696
|
}
|
|
21660
21697
|
if (resolverConfig.type === "custom") {
|
|
21661
21698
|
const agentName = resolverConfig.agent ?? RESOLVER_FALLBACK_AGENT;
|
|
21699
|
+
const judgeSessionName = workdir !== undefined ? buildSessionName(workdir, featureName, storyId, "judge") : undefined;
|
|
21662
21700
|
const resolverResult = await judgeResolver(proposalOutputs, critiqueOutputs, resolverConfig, {
|
|
21663
21701
|
getAgent: (name) => _debateSessionDeps.getAgent(name, config2),
|
|
21664
21702
|
defaultAgentName: RESOLVER_FALLBACK_AGENT,
|
|
@@ -21666,9 +21704,11 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
|
|
|
21666
21704
|
model: resolveDebaterModel({ agent: agentName }, config2),
|
|
21667
21705
|
config: config2,
|
|
21668
21706
|
storyId,
|
|
21707
|
+
featureName,
|
|
21708
|
+
workdir,
|
|
21669
21709
|
sessionRole: "judge",
|
|
21670
21710
|
timeoutMs,
|
|
21671
|
-
...
|
|
21711
|
+
...judgeSessionName !== undefined && { sessionName: judgeSessionName }
|
|
21672
21712
|
}
|
|
21673
21713
|
});
|
|
21674
21714
|
return {
|
|
@@ -21685,7 +21725,6 @@ var init_session_helpers = __esm(() => {
|
|
|
21685
21725
|
init_registry();
|
|
21686
21726
|
init_config();
|
|
21687
21727
|
init_logger2();
|
|
21688
|
-
init_resolvers();
|
|
21689
21728
|
_debateSessionDeps = {
|
|
21690
21729
|
getAgent: (name, config2) => config2 ? createAgentRegistry(config2).getAgent(name) : getAgent(name),
|
|
21691
21730
|
getSafeLogger,
|
|
@@ -21719,6 +21758,171 @@ async function allSettledBounded(tasks, limit) {
|
|
|
21719
21758
|
return results;
|
|
21720
21759
|
}
|
|
21721
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
|
+
|
|
21722
21926
|
// src/debate/session-stateful.ts
|
|
21723
21927
|
async function runStatefulTurn(ctx, adapter, debater, prompt, roleKey, keepSessionOpen) {
|
|
21724
21928
|
const modelTier = modelTierFromDebater(debater);
|
|
@@ -21774,7 +21978,9 @@ async function closeStatefulSession(ctx, adapter, debater, roleKey) {
|
|
|
21774
21978
|
async function runStateful(ctx, prompt) {
|
|
21775
21979
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21776
21980
|
const config2 = ctx.stageConfig;
|
|
21777
|
-
const
|
|
21981
|
+
const personaStage = ctx.stage === "plan" ? "plan" : "review";
|
|
21982
|
+
const rawDebaters = config2.debaters ?? [];
|
|
21983
|
+
const debaters = resolvePersonas(rawDebaters, personaStage, config2.autoPersona ?? false);
|
|
21778
21984
|
let totalCostUsd = 0;
|
|
21779
21985
|
const resolved = [];
|
|
21780
21986
|
for (const debater of debaters) {
|
|
@@ -21871,8 +22077,9 @@ async function runStateful(ctx, prompt) {
|
|
|
21871
22077
|
}
|
|
21872
22078
|
let critiqueOutputs = [];
|
|
21873
22079
|
if (config2.rounds > 1) {
|
|
21874
|
-
const
|
|
21875
|
-
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);
|
|
21876
22083
|
for (const r of critiqueSettled) {
|
|
21877
22084
|
if (r.status === "fulfilled") {
|
|
21878
22085
|
totalCostUsd += r.value.cost;
|
|
@@ -21908,11 +22115,13 @@ async function runStateful(ctx, prompt) {
|
|
|
21908
22115
|
};
|
|
21909
22116
|
}
|
|
21910
22117
|
var init_session_stateful = __esm(() => {
|
|
22118
|
+
init_personas();
|
|
22119
|
+
init_prompt_builder();
|
|
21911
22120
|
init_session_helpers();
|
|
21912
22121
|
});
|
|
21913
22122
|
|
|
21914
22123
|
// src/debate/session-hybrid.ts
|
|
21915
|
-
async function runRebuttalLoop(ctx, proposals,
|
|
22124
|
+
async function runRebuttalLoop(ctx, proposals, builder, sessionRolePrefix) {
|
|
21916
22125
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21917
22126
|
const config2 = ctx.stageConfig;
|
|
21918
22127
|
const rebuttals = [];
|
|
@@ -21920,7 +22129,7 @@ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix
|
|
|
21920
22129
|
const proposalList = proposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
21921
22130
|
try {
|
|
21922
22131
|
for (let round = 1;round <= config2.rounds; round++) {
|
|
21923
|
-
const priorRebuttals = rebuttals.filter((r) => r.round < round)
|
|
22132
|
+
const priorRebuttals = rebuttals.filter((r) => r.round < round);
|
|
21924
22133
|
for (let debaterIdx = 0;debaterIdx < proposals.length; debaterIdx++) {
|
|
21925
22134
|
const proposal = proposals[debaterIdx];
|
|
21926
22135
|
const sessionRole = `${sessionRolePrefix}-${debaterIdx}`;
|
|
@@ -21929,7 +22138,7 @@ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix
|
|
|
21929
22138
|
round,
|
|
21930
22139
|
debaterIndex: debaterIdx
|
|
21931
22140
|
});
|
|
21932
|
-
const rebuttalPrompt =
|
|
22141
|
+
const rebuttalPrompt = builder.buildRebuttalPrompt(debaterIdx, proposalList, priorRebuttals);
|
|
21933
22142
|
try {
|
|
21934
22143
|
const turnResult = await runStatefulTurn(ctx, proposal.adapter, proposal.debater, rebuttalPrompt, sessionRole, true);
|
|
21935
22144
|
costUsd += turnResult.cost;
|
|
@@ -21959,7 +22168,9 @@ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix
|
|
|
21959
22168
|
async function runHybrid(ctx, prompt) {
|
|
21960
22169
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
21961
22170
|
const config2 = ctx.stageConfig;
|
|
21962
|
-
const
|
|
22171
|
+
const personaStage = ctx.stage === "plan" ? "plan" : "review";
|
|
22172
|
+
const rawDebaters = config2.debaters ?? [];
|
|
22173
|
+
const debaters = resolvePersonas(rawDebaters, personaStage, config2.autoPersona ?? false);
|
|
21963
22174
|
let totalCostUsd = 0;
|
|
21964
22175
|
const resolved = [];
|
|
21965
22176
|
for (const debater of debaters) {
|
|
@@ -22024,7 +22235,8 @@ async function runHybrid(ctx, prompt) {
|
|
|
22024
22235
|
}
|
|
22025
22236
|
const proposalOutputs = successfulProposals.map((s) => s.output);
|
|
22026
22237
|
const proposalList = successfulProposals.map((s) => ({ debater: s.debater, output: s.output }));
|
|
22027
|
-
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");
|
|
22028
22240
|
totalCostUsd += rebuttalCost;
|
|
22029
22241
|
const critiqueOutputs = rebuttals.map((r) => r.output);
|
|
22030
22242
|
const fullResolverContext = ctx.resolverContextInput ? {
|
|
@@ -22046,6 +22258,8 @@ async function runHybrid(ctx, prompt) {
|
|
|
22046
22258
|
};
|
|
22047
22259
|
}
|
|
22048
22260
|
var init_session_hybrid = __esm(() => {
|
|
22261
|
+
init_personas();
|
|
22262
|
+
init_prompt_builder();
|
|
22049
22263
|
init_session_helpers();
|
|
22050
22264
|
init_session_stateful();
|
|
22051
22265
|
});
|
|
@@ -22054,7 +22268,9 @@ var init_session_hybrid = __esm(() => {
|
|
|
22054
22268
|
async function runOneShot(ctx, prompt) {
|
|
22055
22269
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
22056
22270
|
const config2 = ctx.stageConfig;
|
|
22057
|
-
const
|
|
22271
|
+
const personaStage = ctx.stage === "plan" ? "plan" : "review";
|
|
22272
|
+
const rawDebaters = config2.debaters ?? [];
|
|
22273
|
+
const debaters = resolvePersonas(rawDebaters, personaStage, config2.autoPersona ?? false);
|
|
22058
22274
|
let totalCostUsd = 0;
|
|
22059
22275
|
const resolved = [];
|
|
22060
22276
|
for (const debater of debaters) {
|
|
@@ -22157,8 +22373,9 @@ async function runOneShot(ctx, prompt) {
|
|
|
22157
22373
|
}
|
|
22158
22374
|
let critiqueOutputs = [];
|
|
22159
22375
|
if (config2.rounds > 1) {
|
|
22160
|
-
const
|
|
22161
|
-
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), {
|
|
22162
22379
|
model: resolveDebaterModel(debater, ctx.config),
|
|
22163
22380
|
featureName: ctx.stage,
|
|
22164
22381
|
config: ctx.config,
|
|
@@ -22201,15 +22418,18 @@ async function runOneShot(ctx, prompt) {
|
|
|
22201
22418
|
};
|
|
22202
22419
|
}
|
|
22203
22420
|
var init_session_one_shot = __esm(() => {
|
|
22421
|
+
init_personas();
|
|
22422
|
+
init_prompt_builder();
|
|
22204
22423
|
init_session_helpers();
|
|
22205
22424
|
});
|
|
22206
22425
|
|
|
22207
22426
|
// src/debate/session-plan.ts
|
|
22208
22427
|
import { join as join10 } from "path";
|
|
22209
|
-
async function runPlan2(ctx,
|
|
22428
|
+
async function runPlan2(ctx, taskContext, outputFormat, opts) {
|
|
22210
22429
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
22211
22430
|
const config2 = ctx.stageConfig;
|
|
22212
|
-
const
|
|
22431
|
+
const rawDebaters = config2.debaters ?? [];
|
|
22432
|
+
const debaters = resolvePersonas(rawDebaters, "plan", config2.autoPersona ?? false);
|
|
22213
22433
|
let totalCostUsd = 0;
|
|
22214
22434
|
const resolved = [];
|
|
22215
22435
|
for (const debater of debaters) {
|
|
@@ -22227,14 +22447,15 @@ async function runPlan2(ctx, basePrompt, opts) {
|
|
|
22227
22447
|
});
|
|
22228
22448
|
const debate = ctx.config?.debate;
|
|
22229
22449
|
const concurrencyLimit = debate?.maxConcurrentDebaters ?? 2;
|
|
22230
|
-
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 () => {
|
|
22231
22452
|
const tempOutputPath = join10(opts.outputDir, `prd-debate-${i}.json`);
|
|
22232
|
-
const debaterPrompt = `${
|
|
22453
|
+
const debaterPrompt = `${proposalBuilder.buildProposalPrompt(i)}
|
|
22233
22454
|
|
|
22234
22455
|
Write the PRD JSON directly to this file path: ${tempOutputPath}
|
|
22235
22456
|
Do NOT output the JSON to the conversation. Write the file, then reply with a brief confirmation.`;
|
|
22236
|
-
const modelTier = modelTierFromDebater(
|
|
22237
|
-
const modelDef = resolveModelDefForDebater(
|
|
22457
|
+
const modelTier = modelTierFromDebater(rd);
|
|
22458
|
+
const modelDef = resolveModelDefForDebater(rd, modelTier, ctx.config);
|
|
22238
22459
|
const planResult = await adapter.plan({
|
|
22239
22460
|
prompt: debaterPrompt,
|
|
22240
22461
|
workdir: opts.workdir,
|
|
@@ -22250,7 +22471,7 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22250
22471
|
sessionRole: `plan-${i}`
|
|
22251
22472
|
});
|
|
22252
22473
|
const output = await _debateSessionDeps.readFile(tempOutputPath);
|
|
22253
|
-
return { debater, adapter, output, cost: planResult.costUsd ?? 0 };
|
|
22474
|
+
return { debater: rd, adapter, output, cost: planResult.costUsd ?? 0 };
|
|
22254
22475
|
}), concurrencyLimit);
|
|
22255
22476
|
const successful = [];
|
|
22256
22477
|
for (let i = 0;i < settled.length; i++) {
|
|
@@ -22323,7 +22544,8 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22323
22544
|
featureName: opts.feature,
|
|
22324
22545
|
timeoutSeconds: opts.timeoutSeconds ?? 600
|
|
22325
22546
|
};
|
|
22326
|
-
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");
|
|
22327
22549
|
critiqueOutputs = rebuttals.map((r) => r.output);
|
|
22328
22550
|
rebuttalList = rebuttals;
|
|
22329
22551
|
totalCostUsd += costUsd;
|
|
@@ -22331,7 +22553,8 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22331
22553
|
logger?.warn("debate", "hybrid mode requires sessionMode: stateful for plan \u2014 running as panel");
|
|
22332
22554
|
}
|
|
22333
22555
|
const resolverTimeoutMs = (ctx.stageConfig.timeoutSeconds ?? 600) * 1000;
|
|
22334
|
-
const
|
|
22556
|
+
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.";
|
|
22557
|
+
const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, resolverTimeoutMs, opts.workdir, opts.feature, undefined, undefined, planSynthesisSuffix);
|
|
22335
22558
|
const winningOutput = outcome.output ?? successful[0].output;
|
|
22336
22559
|
const proposals = successful.map((p) => ({ debater: p.debater, output: p.output }));
|
|
22337
22560
|
logger?.info("debate", "debate:result", {
|
|
@@ -22353,6 +22576,8 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
|
|
|
22353
22576
|
};
|
|
22354
22577
|
}
|
|
22355
22578
|
var init_session_plan = __esm(() => {
|
|
22579
|
+
init_personas();
|
|
22580
|
+
init_prompt_builder();
|
|
22356
22581
|
init_session_helpers();
|
|
22357
22582
|
init_session_hybrid();
|
|
22358
22583
|
});
|
|
@@ -22438,13 +22663,13 @@ class DebateSession {
|
|
|
22438
22663
|
resolverContextInput: this.resolverContextInput
|
|
22439
22664
|
}, prompt);
|
|
22440
22665
|
}
|
|
22441
|
-
async runPlan(
|
|
22666
|
+
async runPlan(taskContext, outputFormat, opts) {
|
|
22442
22667
|
return runPlan2({
|
|
22443
22668
|
storyId: this.storyId,
|
|
22444
22669
|
stage: this.stage,
|
|
22445
22670
|
stageConfig: this.stageConfig,
|
|
22446
22671
|
config: this.config
|
|
22447
|
-
},
|
|
22672
|
+
}, taskContext, outputFormat, opts);
|
|
22448
22673
|
}
|
|
22449
22674
|
}
|
|
22450
22675
|
var DEFAULT_TIMEOUT_SECONDS = 600;
|
|
@@ -22461,8 +22686,8 @@ var init_session = __esm(() => {
|
|
|
22461
22686
|
var init_debate = __esm(() => {
|
|
22462
22687
|
init_session();
|
|
22463
22688
|
init_session_helpers();
|
|
22464
|
-
|
|
22465
|
-
|
|
22689
|
+
init_prompt_builder();
|
|
22690
|
+
init_personas();
|
|
22466
22691
|
});
|
|
22467
22692
|
|
|
22468
22693
|
// src/interaction/bridge-builder.ts
|
|
@@ -23643,50 +23868,6 @@ var init_init = __esm(() => {
|
|
|
23643
23868
|
init_webhook();
|
|
23644
23869
|
});
|
|
23645
23870
|
|
|
23646
|
-
// src/utils/llm-json.ts
|
|
23647
|
-
function extractJsonFromMarkdown(text) {
|
|
23648
|
-
const match = text.match(/```(?:json)?\s*\n([\s\S]*?)\n?\s*```/);
|
|
23649
|
-
if (match) {
|
|
23650
|
-
return match[1] ?? text;
|
|
23651
|
-
}
|
|
23652
|
-
return text;
|
|
23653
|
-
}
|
|
23654
|
-
function stripTrailingCommas(text) {
|
|
23655
|
-
return text.replace(/,\s*([}\]])/g, "$1");
|
|
23656
|
-
}
|
|
23657
|
-
function extractJsonObject(text) {
|
|
23658
|
-
const objStart = text.indexOf("{");
|
|
23659
|
-
const arrStart = text.indexOf("[");
|
|
23660
|
-
let start;
|
|
23661
|
-
let closeChar;
|
|
23662
|
-
if (objStart === -1 && arrStart === -1)
|
|
23663
|
-
return null;
|
|
23664
|
-
if (objStart === -1) {
|
|
23665
|
-
start = arrStart;
|
|
23666
|
-
closeChar = "]";
|
|
23667
|
-
} else if (arrStart === -1) {
|
|
23668
|
-
start = objStart;
|
|
23669
|
-
closeChar = "}";
|
|
23670
|
-
} else if (objStart < arrStart) {
|
|
23671
|
-
start = objStart;
|
|
23672
|
-
closeChar = "}";
|
|
23673
|
-
} else {
|
|
23674
|
-
start = arrStart;
|
|
23675
|
-
closeChar = "]";
|
|
23676
|
-
}
|
|
23677
|
-
const end = text.lastIndexOf(closeChar);
|
|
23678
|
-
if (end <= start)
|
|
23679
|
-
return null;
|
|
23680
|
-
return text.slice(start, end + 1);
|
|
23681
|
-
}
|
|
23682
|
-
function wrapJsonPrompt(prompt) {
|
|
23683
|
-
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.
|
|
23684
|
-
|
|
23685
|
-
${prompt.trim()}
|
|
23686
|
-
|
|
23687
|
-
YOUR RESPONSE MUST START WITH { OR [ AND END WITH } OR ]. No other text.`;
|
|
23688
|
-
}
|
|
23689
|
-
|
|
23690
23871
|
// src/prd/validate.ts
|
|
23691
23872
|
function validateStoryId(id) {
|
|
23692
23873
|
if (!id || id.length === 0) {
|
|
@@ -25792,7 +25973,7 @@ Rules:
|
|
|
25792
25973
|
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
25793
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.
|
|
25794
25975
|
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
|
|
25795
|
-
- **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')\`).`;
|
|
25796
25977
|
}
|
|
25797
25978
|
async function generateAcceptanceTests(adapter, options) {
|
|
25798
25979
|
const logger = getLogger();
|
|
@@ -26950,6 +27131,7 @@ function buildReviewPrompt(diff, story, _semanticConfig) {
|
|
|
26950
27131
|
"## Diff",
|
|
26951
27132
|
diff,
|
|
26952
27133
|
"",
|
|
27134
|
+
"Also flag any changes in the diff not required by the acceptance criteria above as out-of-scope findings.",
|
|
26953
27135
|
"Respond with JSON: { passed: boolean, findings: [...], findingReasoning: { [id]: string } }"
|
|
26954
27136
|
].join(`
|
|
26955
27137
|
`);
|
|
@@ -26971,7 +27153,7 @@ function buildReReviewPrompt(updatedDiff, previousFindings) {
|
|
|
26971
27153
|
].join(`
|
|
26972
27154
|
`);
|
|
26973
27155
|
}
|
|
26974
|
-
function
|
|
27156
|
+
function buildProposalsSection2(proposals) {
|
|
26975
27157
|
return proposals.map((p) => `### ${p.debater}
|
|
26976
27158
|
${p.output}`).join(`
|
|
26977
27159
|
|
|
@@ -27015,7 +27197,7 @@ function buildDebateResolverPrompt(proposals, critiques, diff, story, _semanticC
|
|
|
27015
27197
|
`);
|
|
27016
27198
|
const framing = buildResolverFraming(resolverContext);
|
|
27017
27199
|
const voteTally = buildVoteTallyLine(resolverContext);
|
|
27018
|
-
const proposalsSection =
|
|
27200
|
+
const proposalsSection = buildProposalsSection2(proposals);
|
|
27019
27201
|
const critiquesSection = buildCritiquesSection(critiques);
|
|
27020
27202
|
return [
|
|
27021
27203
|
framing,
|
|
@@ -27041,7 +27223,7 @@ function buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFi
|
|
|
27041
27223
|
const framing = buildResolverFraming(resolverContext);
|
|
27042
27224
|
const findingsList = previousFindings.length > 0 ? previousFindings.map((f) => `- ${f.ruleId}: ${f.message}`).join(`
|
|
27043
27225
|
`) : "(none)";
|
|
27044
|
-
const proposalsSection =
|
|
27226
|
+
const proposalsSection = buildProposalsSection2(proposals);
|
|
27045
27227
|
const critiquesSection = buildCritiquesSection(critiques);
|
|
27046
27228
|
return [
|
|
27047
27229
|
`${framing} This is a re-review after implementer changes.`,
|
|
@@ -27064,12 +27246,10 @@ function buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFi
|
|
|
27064
27246
|
|
|
27065
27247
|
// src/review/dialogue.ts
|
|
27066
27248
|
function extractDeltaSummary(rawOutput, previousFindings, newFindings) {
|
|
27067
|
-
|
|
27068
|
-
|
|
27069
|
-
|
|
27070
|
-
|
|
27071
|
-
}
|
|
27072
|
-
} catch {}
|
|
27249
|
+
const parsed = tryParseLLMJson(rawOutput);
|
|
27250
|
+
if (parsed && typeof parsed.deltaSummary === "string" && parsed.deltaSummary.length > 0) {
|
|
27251
|
+
return parsed.deltaSummary;
|
|
27252
|
+
}
|
|
27073
27253
|
const newIds = new Set(newFindings.map((f) => f.ruleId));
|
|
27074
27254
|
const prevIds = new Set(previousFindings.map((f) => f.ruleId));
|
|
27075
27255
|
const resolved = previousFindings.filter((f) => !newIds.has(f.ruleId));
|
|
@@ -27109,7 +27289,7 @@ function compactHistory(history) {
|
|
|
27109
27289
|
function parseReviewResponse(output) {
|
|
27110
27290
|
let parsed;
|
|
27111
27291
|
try {
|
|
27112
|
-
parsed =
|
|
27292
|
+
parsed = parseLLMJson(output);
|
|
27113
27293
|
} catch {
|
|
27114
27294
|
throw new NaxError("[dialogue] Failed to parse reviewer JSON response", "REVIEWER_PARSE_FAILED", {
|
|
27115
27295
|
stage: "review",
|
|
@@ -27688,23 +27868,11 @@ function validateLLMShape(parsed) {
|
|
|
27688
27868
|
return { passed: obj.passed, findings: obj.findings };
|
|
27689
27869
|
}
|
|
27690
27870
|
function parseLLMResponse(raw) {
|
|
27691
|
-
const text = raw.trim();
|
|
27692
27871
|
try {
|
|
27693
|
-
return validateLLMShape(
|
|
27694
|
-
} catch {
|
|
27695
|
-
|
|
27696
|
-
if (fromFence !== text) {
|
|
27697
|
-
try {
|
|
27698
|
-
return validateLLMShape(JSON.parse(stripTrailingCommas(fromFence)));
|
|
27699
|
-
} catch {}
|
|
27700
|
-
}
|
|
27701
|
-
const bareJson = extractJsonObject(text);
|
|
27702
|
-
if (bareJson) {
|
|
27703
|
-
try {
|
|
27704
|
-
return validateLLMShape(JSON.parse(stripTrailingCommas(bareJson)));
|
|
27705
|
-
} catch {}
|
|
27872
|
+
return validateLLMShape(tryParseLLMJson(raw));
|
|
27873
|
+
} catch {
|
|
27874
|
+
return null;
|
|
27706
27875
|
}
|
|
27707
|
-
return null;
|
|
27708
27876
|
}
|
|
27709
27877
|
function formatFindings(findings) {
|
|
27710
27878
|
return findings.map((f) => `[${f.severity}] ${f.file}:${f.line} \u2014 ${f.issue}
|
|
@@ -28679,6 +28847,7 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
|
|
|
28679
28847
|
config: ctx.config,
|
|
28680
28848
|
projectDir: ctx.projectDir,
|
|
28681
28849
|
maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
|
|
28850
|
+
featureName: ctx.prd.feature,
|
|
28682
28851
|
storyId: ctx.story.id,
|
|
28683
28852
|
sessionRole: "implementer"
|
|
28684
28853
|
});
|
|
@@ -30456,61 +30625,34 @@ var init_executor = __esm(() => {
|
|
|
30456
30625
|
});
|
|
30457
30626
|
|
|
30458
30627
|
// src/verification/parser.ts
|
|
30459
|
-
function
|
|
30460
|
-
if (
|
|
30461
|
-
return
|
|
30462
|
-
|
|
30463
|
-
|
|
30464
|
-
}
|
|
30465
|
-
|
|
30466
|
-
|
|
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";
|
|
30467
30640
|
}
|
|
30468
|
-
function
|
|
30469
|
-
const
|
|
30470
|
-
|
|
30471
|
-
|
|
30472
|
-
|
|
30473
|
-
|
|
30474
|
-
|
|
30475
|
-
|
|
30476
|
-
|
|
30477
|
-
|
|
30478
|
-
|
|
30479
|
-
|
|
30480
|
-
|
|
30481
|
-
|
|
30482
|
-
let currentFile = "unknown";
|
|
30483
|
-
const lines = output.split(`
|
|
30484
|
-
`);
|
|
30485
|
-
for (let i = 0;i < lines.length; i++) {
|
|
30486
|
-
const line = lines[i];
|
|
30487
|
-
const fileMatch = line.match(/^\s*(?:FAIL|PASS)\s+(\S+\.[jt]sx?)/);
|
|
30488
|
-
if (fileMatch) {
|
|
30489
|
-
currentFile = fileMatch[1];
|
|
30490
|
-
continue;
|
|
30491
|
-
}
|
|
30492
|
-
const bulletMatch = line.match(/^\s+\u25CF\s+(.+)$/);
|
|
30493
|
-
if (bulletMatch) {
|
|
30494
|
-
const testName = bulletMatch[1].trim();
|
|
30495
|
-
let error48 = "";
|
|
30496
|
-
for (let j = i + 1;j < lines.length && j < i + 10; j++) {
|
|
30497
|
-
const next = lines[j].trim();
|
|
30498
|
-
if (!next)
|
|
30499
|
-
continue;
|
|
30500
|
-
if (next.startsWith("\u25CF") || /^(?:FAIL|PASS)\s/.test(next))
|
|
30501
|
-
break;
|
|
30502
|
-
error48 = next;
|
|
30503
|
-
break;
|
|
30504
|
-
}
|
|
30505
|
-
failures.push({
|
|
30506
|
-
file: currentFile,
|
|
30507
|
-
testName,
|
|
30508
|
-
error: error48 || "Unknown error",
|
|
30509
|
-
stackTrace: []
|
|
30510
|
-
});
|
|
30511
|
-
}
|
|
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);
|
|
30512
30655
|
}
|
|
30513
|
-
return { passed, failed, failures };
|
|
30514
30656
|
}
|
|
30515
30657
|
function parseBunOutput(output) {
|
|
30516
30658
|
const lines = output.split(`
|
|
@@ -30572,6 +30714,122 @@ function parseBunOutput(output) {
|
|
|
30572
30714
|
}
|
|
30573
30715
|
return { passed, failed, failures };
|
|
30574
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
|
+
}
|
|
30575
30833
|
function formatFailureSummary(failures, maxChars = 2000) {
|
|
30576
30834
|
if (failures.length === 0) {
|
|
30577
30835
|
return "No test failures";
|
|
@@ -30585,48 +30843,24 @@ function formatFailureSummary(failures, maxChars = 2000) {
|
|
|
30585
30843
|
const errorLine = ` Error: ${failure.error}`;
|
|
30586
30844
|
const stackLine = failure.stackTrace.length > 0 ? ` ${failure.stackTrace[0]}` : "";
|
|
30587
30845
|
const blockLines = [header, errorLine];
|
|
30588
|
-
if (stackLine)
|
|
30846
|
+
if (stackLine)
|
|
30589
30847
|
blockLines.push(stackLine);
|
|
30590
|
-
}
|
|
30591
30848
|
blockLines.push("");
|
|
30592
30849
|
const block = blockLines.join(`
|
|
30593
30850
|
`);
|
|
30594
|
-
|
|
30595
|
-
if (totalChars + blockLength > maxChars && lines.length > 0) {
|
|
30596
|
-
const remaining = failures.length - i;
|
|
30851
|
+
if (totalChars + block.length > maxChars && lines.length > 0) {
|
|
30597
30852
|
lines.push(`
|
|
30598
|
-
... and ${
|
|
30853
|
+
... and ${failures.length - i} more failure(s) (truncated)`);
|
|
30599
30854
|
break;
|
|
30600
30855
|
}
|
|
30601
30856
|
lines.push(...blockLines);
|
|
30602
|
-
totalChars +=
|
|
30857
|
+
totalChars += block.length;
|
|
30603
30858
|
}
|
|
30604
30859
|
return lines.join(`
|
|
30605
30860
|
`).trim();
|
|
30606
30861
|
}
|
|
30607
|
-
function
|
|
30608
|
-
const
|
|
30609
|
-
/(\d+)\s+pass(?:ed)?(?:,\s+|\s+)(\d+)\s+fail/i,
|
|
30610
|
-
/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+failed/i,
|
|
30611
|
-
/(\d+)\s+pass/i
|
|
30612
|
-
];
|
|
30613
|
-
let passCount = 0;
|
|
30614
|
-
let failCount = 0;
|
|
30615
|
-
for (const pattern of patterns) {
|
|
30616
|
-
const matches = Array.from(output.matchAll(new RegExp(pattern, "gi")));
|
|
30617
|
-
if (matches.length > 0) {
|
|
30618
|
-
const lastMatch = matches[matches.length - 1];
|
|
30619
|
-
passCount = Number.parseInt(lastMatch[1], 10);
|
|
30620
|
-
failCount = lastMatch[2] ? Number.parseInt(lastMatch[2], 10) : 0;
|
|
30621
|
-
break;
|
|
30622
|
-
}
|
|
30623
|
-
}
|
|
30624
|
-
if (failCount === 0) {
|
|
30625
|
-
const failMatches = Array.from(output.matchAll(/(\d+)\s+fail/gi));
|
|
30626
|
-
if (failMatches.length > 0) {
|
|
30627
|
-
failCount = Number.parseInt(failMatches[failMatches.length - 1][1], 10);
|
|
30628
|
-
}
|
|
30629
|
-
}
|
|
30862
|
+
function analyzeTestExitCode(output, exitCode) {
|
|
30863
|
+
const { passed: passCount, failed: failCount } = parseCommonOutput(output);
|
|
30630
30864
|
const allTestsPassed = passCount > 0 && failCount === 0;
|
|
30631
30865
|
const isEnvironmentalFailure = allTestsPassed && exitCode !== 0;
|
|
30632
30866
|
const result = {
|
|
@@ -30697,7 +30931,7 @@ async function runVerificationCore(options) {
|
|
|
30697
30931
|
}
|
|
30698
30932
|
const exitCode = execution.exitCode ?? 1;
|
|
30699
30933
|
if (exitCode !== 0 && execution.output) {
|
|
30700
|
-
const analysis =
|
|
30934
|
+
const analysis = analyzeTestExitCode(execution.output, exitCode);
|
|
30701
30935
|
if (analysis.isEnvironmentalFailure) {
|
|
30702
30936
|
return {
|
|
30703
30937
|
status: "ENVIRONMENTAL_FAILURE",
|
|
@@ -30990,7 +31224,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
30990
31224
|
});
|
|
30991
31225
|
const fullSuitePassed = fullSuiteResult.success && fullSuiteResult.exitCode === 0;
|
|
30992
31226
|
if (!fullSuitePassed && fullSuiteResult.output) {
|
|
30993
|
-
const testSummary = _rectificationGateDeps.
|
|
31227
|
+
const testSummary = _rectificationGateDeps.parseTestOutput(fullSuiteResult.output);
|
|
30994
31228
|
if (testSummary.failed > 0) {
|
|
30995
31229
|
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir);
|
|
30996
31230
|
}
|
|
@@ -31124,7 +31358,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
31124
31358
|
return true;
|
|
31125
31359
|
}
|
|
31126
31360
|
if (retryFullSuite.output) {
|
|
31127
|
-
const newTestSummary = _rectificationGateDeps.
|
|
31361
|
+
const newTestSummary = _rectificationGateDeps.parseTestOutput(retryFullSuite.output);
|
|
31128
31362
|
state.currentFailures = newTestSummary.failed;
|
|
31129
31363
|
testSummary.failures = newTestSummary.failures;
|
|
31130
31364
|
testSummary.failed = newTestSummary.failed;
|
|
@@ -31172,7 +31406,7 @@ var init_rectification_gate = __esm(() => {
|
|
|
31172
31406
|
init_prompts();
|
|
31173
31407
|
_rectificationGateDeps = {
|
|
31174
31408
|
executeWithTimeout,
|
|
31175
|
-
|
|
31409
|
+
parseTestOutput,
|
|
31176
31410
|
shouldRetryRectification
|
|
31177
31411
|
};
|
|
31178
31412
|
});
|
|
@@ -33142,9 +33376,6 @@ function calculateMaxIterations(tierOrder) {
|
|
|
33142
33376
|
return tierOrder.reduce((sum, t) => sum + t.attempts, 0);
|
|
33143
33377
|
}
|
|
33144
33378
|
|
|
33145
|
-
// src/execution/test-output-parser.ts
|
|
33146
|
-
var init_test_output_parser = () => {};
|
|
33147
|
-
|
|
33148
33379
|
// src/verification/rectification-loop.ts
|
|
33149
33380
|
async function _defaultRunDebate(storyId, stageConfig, prompt, config2) {
|
|
33150
33381
|
const logger = getSafeLogger();
|
|
@@ -33197,7 +33428,7 @@ async function runRectificationLoop2(opts) {
|
|
|
33197
33428
|
} = opts;
|
|
33198
33429
|
const logger = getSafeLogger();
|
|
33199
33430
|
const rectificationConfig = config2.execution.rectification;
|
|
33200
|
-
const testSummary =
|
|
33431
|
+
const testSummary = parseTestOutput(testOutput);
|
|
33201
33432
|
const label = promptPrefix ? "regression rectification" : "rectification";
|
|
33202
33433
|
const rectificationState = {
|
|
33203
33434
|
attempt: 0,
|
|
@@ -33332,13 +33563,13 @@ ${rectificationPrompt}`;
|
|
|
33332
33563
|
return true;
|
|
33333
33564
|
}
|
|
33334
33565
|
if (retryVerification.output) {
|
|
33335
|
-
const newTestSummary =
|
|
33566
|
+
const newTestSummary = parseTestOutput(retryVerification.output);
|
|
33336
33567
|
state.currentFailures = newTestSummary.failed;
|
|
33337
33568
|
state.lastExitCode = retryVerification.status === "SUCCESS" ? 0 : 1;
|
|
33338
33569
|
testSummary.failures = newTestSummary.failures;
|
|
33339
33570
|
testSummary.failed = newTestSummary.failed;
|
|
33340
33571
|
testSummary.passed = newTestSummary.passed;
|
|
33341
|
-
if (newTestSummary.failed === 0) {
|
|
33572
|
+
if (newTestSummary.failed === 0 && (retryVerification.status === "SUCCESS" || newTestSummary.passed > 0)) {
|
|
33342
33573
|
state.lastExitCode = 0;
|
|
33343
33574
|
logger?.info("rectification", `[OK] ${label} succeeded after parsing retry output`, {
|
|
33344
33575
|
storyId: story.id,
|
|
@@ -33478,7 +33709,6 @@ var init_rectification_loop = __esm(() => {
|
|
|
33478
33709
|
init_cost();
|
|
33479
33710
|
init_registry();
|
|
33480
33711
|
init_config();
|
|
33481
|
-
init_test_output_parser();
|
|
33482
33712
|
init_logger2();
|
|
33483
33713
|
init_prd();
|
|
33484
33714
|
init_rectification();
|
|
@@ -33744,7 +33974,7 @@ class RegressionStrategy {
|
|
|
33744
33974
|
});
|
|
33745
33975
|
const durationMs = Date.now() - start;
|
|
33746
33976
|
if (result.success) {
|
|
33747
|
-
const parsed2 = result.output ?
|
|
33977
|
+
const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33748
33978
|
return makePassResult(ctx.storyId, "regression", {
|
|
33749
33979
|
rawOutput: result.output,
|
|
33750
33980
|
passCount: parsed2.passed,
|
|
@@ -33760,7 +33990,7 @@ class RegressionStrategy {
|
|
|
33760
33990
|
if (result.status === "TIMEOUT") {
|
|
33761
33991
|
return makeFailResult(ctx.storyId, "regression", "TIMEOUT", { rawOutput: result.output, durationMs });
|
|
33762
33992
|
}
|
|
33763
|
-
const parsed = result.output ?
|
|
33993
|
+
const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33764
33994
|
return makeFailResult(ctx.storyId, "regression", "TEST_FAILURE", {
|
|
33765
33995
|
rawOutput: result.output,
|
|
33766
33996
|
passCount: parsed.passed,
|
|
@@ -33984,7 +34214,7 @@ class ScopedStrategy {
|
|
|
33984
34214
|
});
|
|
33985
34215
|
const durationMs = Date.now() - start;
|
|
33986
34216
|
if (result.success) {
|
|
33987
|
-
const parsed2 = result.output ?
|
|
34217
|
+
const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33988
34218
|
return makePassResult(ctx.storyId, "scoped", {
|
|
33989
34219
|
rawOutput: result.output,
|
|
33990
34220
|
passCount: parsed2.passed,
|
|
@@ -34000,7 +34230,7 @@ class ScopedStrategy {
|
|
|
34000
34230
|
scopeTestFallback
|
|
34001
34231
|
});
|
|
34002
34232
|
}
|
|
34003
|
-
const parsed = result.output ?
|
|
34233
|
+
const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
34004
34234
|
return makeFailResult(ctx.storyId, "scoped", "TEST_FAILURE", {
|
|
34005
34235
|
rawOutput: result.output,
|
|
34006
34236
|
passCount: parsed.passed,
|
|
@@ -34334,12 +34564,11 @@ function stripCodeFences(text) {
|
|
|
34334
34564
|
return trimmed;
|
|
34335
34565
|
}
|
|
34336
34566
|
function parseRoutingResponse(output, story, config2) {
|
|
34337
|
-
const
|
|
34338
|
-
const parsed = JSON.parse(jsonText);
|
|
34567
|
+
const parsed = parseLLMJson(output);
|
|
34339
34568
|
return validateRoutingDecision(parsed, config2, story);
|
|
34340
34569
|
}
|
|
34341
34570
|
function parseBatchResponse(output, stories, config2) {
|
|
34342
|
-
const parsed =
|
|
34571
|
+
const parsed = parseLLMJson(output);
|
|
34343
34572
|
if (!Array.isArray(parsed)) {
|
|
34344
34573
|
throw new Error("Batch LLM response must be a JSON array");
|
|
34345
34574
|
}
|
|
@@ -36133,7 +36362,7 @@ var package_default;
|
|
|
36133
36362
|
var init_package = __esm(() => {
|
|
36134
36363
|
package_default = {
|
|
36135
36364
|
name: "@nathapp/nax",
|
|
36136
|
-
version: "0.59.
|
|
36365
|
+
version: "0.59.3",
|
|
36137
36366
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
36138
36367
|
type: "module",
|
|
36139
36368
|
bin: {
|
|
@@ -36213,8 +36442,8 @@ var init_version = __esm(() => {
|
|
|
36213
36442
|
NAX_VERSION = package_default.version;
|
|
36214
36443
|
NAX_COMMIT = (() => {
|
|
36215
36444
|
try {
|
|
36216
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
36217
|
-
return "
|
|
36445
|
+
if (/^[0-9a-f]{6,10}$/.test("0c763972"))
|
|
36446
|
+
return "0c763972";
|
|
36218
36447
|
} catch {}
|
|
36219
36448
|
try {
|
|
36220
36449
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -36877,28 +37106,17 @@ function parseDiagnosisResult(output) {
|
|
|
36877
37106
|
if (!output || output.trim() === "") {
|
|
36878
37107
|
return null;
|
|
36879
37108
|
}
|
|
36880
|
-
|
|
36881
|
-
|
|
36882
|
-
|
|
36883
|
-
|
|
36884
|
-
|
|
36885
|
-
|
|
36886
|
-
|
|
36887
|
-
|
|
36888
|
-
|
|
36889
|
-
if (typeof parsed.verdict === "string" && typeof parsed.reasoning === "string" && typeof parsed.confidence === "number") {
|
|
36890
|
-
return {
|
|
36891
|
-
verdict: parsed.verdict,
|
|
36892
|
-
reasoning: parsed.reasoning,
|
|
36893
|
-
confidence: parsed.confidence,
|
|
36894
|
-
testIssues: parsed.testIssues,
|
|
36895
|
-
sourceIssues: parsed.sourceIssues
|
|
36896
|
-
};
|
|
36897
|
-
}
|
|
36898
|
-
return null;
|
|
36899
|
-
} catch {
|
|
36900
|
-
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
|
+
};
|
|
36901
37118
|
}
|
|
37119
|
+
return null;
|
|
36902
37120
|
}
|
|
36903
37121
|
var MAX_SOURCE_FILES = 5, MAX_FILE_LINES = 500, MAX_TEST_OUTPUT_CHARS = 2000;
|
|
36904
37122
|
var init_fix_diagnosis = __esm(() => {
|
|
@@ -37096,6 +37314,12 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
37096
37314
|
logger?.info("acceptance", `Backed up acceptance test -> ${bakPath}`);
|
|
37097
37315
|
const { unlink: unlink3 } = await import("fs/promises");
|
|
37098
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
|
+
}
|
|
37099
37323
|
let implementationContext;
|
|
37100
37324
|
const storyGitRef = acceptanceContext.storyGitRef;
|
|
37101
37325
|
const workdir = acceptanceContext.workdir;
|
|
@@ -37736,7 +37960,7 @@ async function runDeferredRegression(options) {
|
|
|
37736
37960
|
affectedStories: []
|
|
37737
37961
|
};
|
|
37738
37962
|
}
|
|
37739
|
-
const testSummary = _regressionDeps.
|
|
37963
|
+
const testSummary = _regressionDeps.parseTestOutput(fullSuiteResult.output);
|
|
37740
37964
|
if (testSummary.failed === 0 && testSummary.passed === 0) {
|
|
37741
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) });
|
|
37742
37966
|
return {
|
|
@@ -37882,7 +38106,7 @@ var init_run_regression = __esm(() => {
|
|
|
37882
38106
|
_regressionDeps = {
|
|
37883
38107
|
runVerification: fullSuite,
|
|
37884
38108
|
runRectificationLoop: runRectificationLoop2,
|
|
37885
|
-
|
|
38109
|
+
parseTestOutput,
|
|
37886
38110
|
reverseMapTestToSource
|
|
37887
38111
|
};
|
|
37888
38112
|
});
|
|
@@ -38863,6 +39087,11 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
38863
39087
|
const diffSummary = await captureDiffSummary(ctx.workdir, ctx.storyGitRef, completedStory.workdir);
|
|
38864
39088
|
if (diffSummary) {
|
|
38865
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
|
+
});
|
|
38866
39095
|
}
|
|
38867
39096
|
} catch {}
|
|
38868
39097
|
}
|
|
@@ -72799,13 +73028,16 @@ function validateStory(raw, index, allIds) {
|
|
|
72799
73028
|
throw new Error(`[schema] story[${index}].routing.complexity "${rawComplexity}" is invalid. Valid values: ${VALID_COMPLEXITY.join(", ")}`);
|
|
72800
73029
|
}
|
|
72801
73030
|
const rawTestStrategy = routing.testStrategy ?? s.testStrategy;
|
|
72802
|
-
|
|
73031
|
+
let testStrategy = resolveTestStrategy(typeof rawTestStrategy === "string" ? rawTestStrategy : undefined);
|
|
72803
73032
|
const rawJustification = routing.noTestJustification ?? s.noTestJustification;
|
|
72804
73033
|
if (testStrategy === "no-test") {
|
|
72805
73034
|
if (!rawJustification || typeof rawJustification !== "string" || rawJustification.trim() === "") {
|
|
72806
73035
|
throw new Error(`[schema] story[${index}].routing.noTestJustification is required when testStrategy is "no-test"`);
|
|
72807
73036
|
}
|
|
72808
73037
|
}
|
|
73038
|
+
if (testStrategy !== "no-test" && typeof rawJustification === "string" && rawJustification.trim() !== "") {
|
|
73039
|
+
testStrategy = "no-test";
|
|
73040
|
+
}
|
|
72809
73041
|
const noTestJustification = typeof rawJustification === "string" && rawJustification.trim() !== "" ? rawJustification.trim() : undefined;
|
|
72810
73042
|
const rawDeps = s.dependencies;
|
|
72811
73043
|
const dependencies = Array.isArray(rawDeps) ? rawDeps : [];
|
|
@@ -72965,7 +73197,7 @@ async function planCommand(workdir, config2, options) {
|
|
|
72965
73197
|
let rawResponse;
|
|
72966
73198
|
const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
|
|
72967
73199
|
if (debateEnabled) {
|
|
72968
|
-
const
|
|
73200
|
+
const { taskContext: planTaskContext, outputFormat: planOutputFormat } = buildPlanningPrompt(specContent, codebaseContext, undefined, relativePackages, packageDetails, config2?.project);
|
|
72969
73201
|
const resolvedPerm = resolvePermissions(config2, "plan");
|
|
72970
73202
|
const planStageConfig = config2?.debate?.stages.plan;
|
|
72971
73203
|
const debateSession = _planDeps.createDebateSession({
|
|
@@ -72982,7 +73214,7 @@ async function planCommand(workdir, config2, options) {
|
|
|
72982
73214
|
rounds: planStageConfig.rounds,
|
|
72983
73215
|
feature: options.feature
|
|
72984
73216
|
});
|
|
72985
|
-
const debateResult = await debateSession.runPlan(
|
|
73217
|
+
const debateResult = await debateSession.runPlan(planTaskContext, planOutputFormat, {
|
|
72986
73218
|
workdir,
|
|
72987
73219
|
feature: options.feature,
|
|
72988
73220
|
outputDir,
|
|
@@ -73001,7 +73233,10 @@ async function planCommand(workdir, config2, options) {
|
|
|
73001
73233
|
}
|
|
73002
73234
|
} else if (options.auto) {
|
|
73003
73235
|
const isAcp = config2?.agent?.protocol === "acp";
|
|
73004
|
-
const
|
|
73236
|
+
const { taskContext: autoTaskCtx, outputFormat: autoOutputFmt } = buildPlanningPrompt(specContent, codebaseContext, isAcp ? outputPath : undefined, relativePackages, packageDetails, config2?.project);
|
|
73237
|
+
const prompt = `${autoTaskCtx}
|
|
73238
|
+
|
|
73239
|
+
${autoOutputFmt}`;
|
|
73005
73240
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
73006
73241
|
if (!adapter)
|
|
73007
73242
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
@@ -73087,7 +73322,10 @@ async function planCommand(workdir, config2, options) {
|
|
|
73087
73322
|
rawResponse = await runInteractivePlan();
|
|
73088
73323
|
}
|
|
73089
73324
|
async function runInteractivePlan() {
|
|
73090
|
-
const
|
|
73325
|
+
const { taskContext: interactiveTaskCtx, outputFormat: interactiveOutputFmt } = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails, config2?.project);
|
|
73326
|
+
const prompt = `${interactiveTaskCtx}
|
|
73327
|
+
|
|
73328
|
+
${interactiveOutputFmt}`;
|
|
73091
73329
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
73092
73330
|
if (!adapter)
|
|
73093
73331
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
@@ -73294,7 +73532,7 @@ ${packageDetailsSection}
|
|
|
73294
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".` : "";
|
|
73295
73533
|
const workdirField = isMonorepo ? `
|
|
73296
73534
|
"workdir": "string \u2014 optional, relative path to package (e.g. \\"packages/api\\"). Omit for root-level stories.",` : "";
|
|
73297
|
-
|
|
73535
|
+
const taskContext = `You are a senior software architect generating a product requirements document (PRD) as JSON.
|
|
73298
73536
|
|
|
73299
73537
|
## Step 1: Understand the Spec
|
|
73300
73538
|
|
|
@@ -73322,6 +73560,8 @@ If this is a greenfield project (empty or minimal codebase):
|
|
|
73322
73560
|
|
|
73323
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.
|
|
73324
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
|
+
|
|
73325
73565
|
## Codebase Context
|
|
73326
73566
|
|
|
73327
73567
|
${codebaseContext}${monorepoHint}
|
|
@@ -73338,9 +73578,8 @@ For each story, set "contextFiles" to the key source files the agent should read
|
|
|
73338
73578
|
|
|
73339
73579
|
${COMPLEXITY_GUIDE}
|
|
73340
73580
|
|
|
73341
|
-
${TEST_STRATEGY_GUIDE}
|
|
73342
|
-
|
|
73343
|
-
## Output Schema
|
|
73581
|
+
${TEST_STRATEGY_GUIDE}`;
|
|
73582
|
+
const outputFormat = `## Output Schema
|
|
73344
73583
|
|
|
73345
73584
|
Generate a JSON object with this exact structure (no markdown, no explanation \u2014 JSON only):
|
|
73346
73585
|
|
|
@@ -73376,6 +73615,7 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
73376
73615
|
|
|
73377
73616
|
${outputFilePath ? `Write the PRD JSON directly to this file path: ${outputFilePath}
|
|
73378
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 };
|
|
73379
73619
|
}
|
|
73380
73620
|
async function planDecomposeCommand(workdir, config2, options) {
|
|
73381
73621
|
const prdPath = join11(workdir, ".nax", "features", options.feature, "prd.json");
|