@nathapp/nax 0.57.3 → 0.57.4
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 +161 -106
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -3307,6 +3307,12 @@ function buildPlanModeDecomposePrompt(options) {
|
|
|
3307
3307
|
|
|
3308
3308
|
${siblings.map((s) => `- ${s.id}: ${s.title}`).join(`
|
|
3309
3309
|
`)}
|
|
3310
|
+
` : "";
|
|
3311
|
+
const maxAcCount = options.config?.precheck?.storySizeGate?.maxAcCount;
|
|
3312
|
+
const acConstraint = maxAcCount != null ? `
|
|
3313
|
+
## Acceptance Criteria Constraint
|
|
3314
|
+
|
|
3315
|
+
Every sub-story must have at most ${maxAcCount} acceptance criteria. If a story would exceed this limit, split it into additional sub-stories instead of adding more ACs.
|
|
3310
3316
|
` : "";
|
|
3311
3317
|
return `You are a senior software architect decomposing a complex user story into smaller, implementable sub-stories.
|
|
3312
3318
|
|
|
@@ -3316,7 +3322,7 @@ ${JSON.stringify(targetStory, null, 2)}${siblingsSummary}
|
|
|
3316
3322
|
## Codebase Context
|
|
3317
3323
|
|
|
3318
3324
|
${options.codebaseContext}
|
|
3319
|
-
|
|
3325
|
+
${acConstraint}
|
|
3320
3326
|
${COMPLEXITY_GUIDE}
|
|
3321
3327
|
|
|
3322
3328
|
${TEST_STRATEGY_GUIDE}
|
|
@@ -21073,6 +21079,50 @@ function errorMessage(err) {
|
|
|
21073
21079
|
return err instanceof Error ? err.message : String(err);
|
|
21074
21080
|
}
|
|
21075
21081
|
|
|
21082
|
+
// src/utils/llm-json.ts
|
|
21083
|
+
function extractJsonFromMarkdown(text) {
|
|
21084
|
+
const match = text.match(/```(?:json)?\s*\n([\s\S]*?)\n?\s*```/);
|
|
21085
|
+
if (match) {
|
|
21086
|
+
return match[1] ?? text;
|
|
21087
|
+
}
|
|
21088
|
+
return text;
|
|
21089
|
+
}
|
|
21090
|
+
function stripTrailingCommas(text) {
|
|
21091
|
+
return text.replace(/,\s*([}\]])/g, "$1");
|
|
21092
|
+
}
|
|
21093
|
+
function extractJsonObject(text) {
|
|
21094
|
+
const objStart = text.indexOf("{");
|
|
21095
|
+
const arrStart = text.indexOf("[");
|
|
21096
|
+
let start;
|
|
21097
|
+
let closeChar;
|
|
21098
|
+
if (objStart === -1 && arrStart === -1)
|
|
21099
|
+
return null;
|
|
21100
|
+
if (objStart === -1) {
|
|
21101
|
+
start = arrStart;
|
|
21102
|
+
closeChar = "]";
|
|
21103
|
+
} else if (arrStart === -1) {
|
|
21104
|
+
start = objStart;
|
|
21105
|
+
closeChar = "}";
|
|
21106
|
+
} else if (objStart < arrStart) {
|
|
21107
|
+
start = objStart;
|
|
21108
|
+
closeChar = "}";
|
|
21109
|
+
} else {
|
|
21110
|
+
start = arrStart;
|
|
21111
|
+
closeChar = "]";
|
|
21112
|
+
}
|
|
21113
|
+
const end = text.lastIndexOf(closeChar);
|
|
21114
|
+
if (end <= start)
|
|
21115
|
+
return null;
|
|
21116
|
+
return text.slice(start, end + 1);
|
|
21117
|
+
}
|
|
21118
|
+
function wrapJsonPrompt(prompt) {
|
|
21119
|
+
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.
|
|
21120
|
+
|
|
21121
|
+
${prompt.trim()}
|
|
21122
|
+
|
|
21123
|
+
YOUR RESPONSE MUST START WITH { OR [ AND END WITH } OR ]. No other text.`;
|
|
21124
|
+
}
|
|
21125
|
+
|
|
21076
21126
|
// src/acceptance/refinement.ts
|
|
21077
21127
|
var exports_refinement = {};
|
|
21078
21128
|
__export(exports_refinement, {
|
|
@@ -21086,7 +21136,7 @@ function buildRefinementPrompt(criteria, codebaseContext, options) {
|
|
|
21086
21136
|
`);
|
|
21087
21137
|
const strategySection = buildStrategySection(options);
|
|
21088
21138
|
const refinedExample = buildRefinedExample(options?.testStrategy);
|
|
21089
|
-
|
|
21139
|
+
const core2 = `You are an acceptance criteria refinement assistant. Your task is to convert raw acceptance criteria into concrete, machine-verifiable assertions.
|
|
21090
21140
|
|
|
21091
21141
|
CODEBASE CONTEXT:
|
|
21092
21142
|
${codebaseContext}
|
|
@@ -21095,7 +21145,7 @@ ACCEPTANCE CRITERIA TO REFINE:
|
|
|
21095
21145
|
${criteriaList}
|
|
21096
21146
|
|
|
21097
21147
|
For each criterion, produce a refined version that is concrete and automatically testable where possible.
|
|
21098
|
-
Respond with
|
|
21148
|
+
Respond with a JSON array:
|
|
21099
21149
|
[{
|
|
21100
21150
|
"original": "<exact original criterion text>",
|
|
21101
21151
|
"refined": "<concrete, machine-verifiable description>",
|
|
@@ -21107,8 +21157,8 @@ Rules:
|
|
|
21107
21157
|
- "original" must match the input criterion text exactly
|
|
21108
21158
|
- "refined" must be a concrete assertion (e.g., ${refinedExample})
|
|
21109
21159
|
- "testable" is false only if the criterion cannot be automatically verified (e.g., "UX feels responsive", "design looks good")
|
|
21110
|
-
- "storyId" leave as empty string \u2014 it will be assigned by the caller
|
|
21111
|
-
|
|
21160
|
+
- "storyId" leave as empty string \u2014 it will be assigned by the caller`;
|
|
21161
|
+
return wrapJsonPrompt(core2);
|
|
21112
21162
|
}
|
|
21113
21163
|
function buildStrategySection(options) {
|
|
21114
21164
|
if (!options?.testStrategy) {
|
|
@@ -21157,7 +21207,9 @@ function parseRefinementResponse(response, criteria) {
|
|
|
21157
21207
|
return fallbackCriteria(criteria);
|
|
21158
21208
|
}
|
|
21159
21209
|
try {
|
|
21160
|
-
const
|
|
21210
|
+
const fromFence = extractJsonFromMarkdown(response);
|
|
21211
|
+
const cleaned = stripTrailingCommas(fromFence !== response ? fromFence : response);
|
|
21212
|
+
const parsed = JSON.parse(cleaned);
|
|
21161
21213
|
if (!Array.isArray(parsed)) {
|
|
21162
21214
|
return fallbackCriteria(criteria);
|
|
21163
21215
|
}
|
|
@@ -21917,7 +21969,7 @@ function buildRoutingPrompt(story, config2) {
|
|
|
21917
21969
|
const { title, description, acceptanceCriteria, tags } = story;
|
|
21918
21970
|
const criteria = acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join(`
|
|
21919
21971
|
`);
|
|
21920
|
-
|
|
21972
|
+
const core2 = `You are a code task router. Classify a user story's complexity and select the cheapest model tier that will succeed.
|
|
21921
21973
|
|
|
21922
21974
|
## Story
|
|
21923
21975
|
Title: ${title}
|
|
@@ -21943,8 +21995,9 @@ Tags: ${tags.join(", ")}
|
|
|
21943
21995
|
- Many files \u2260 complex \u2014 copy-paste refactors across files are simple.
|
|
21944
21996
|
- Pure refactoring/deletion with no new behavior \u2192 simple.
|
|
21945
21997
|
|
|
21946
|
-
Respond with
|
|
21998
|
+
Respond with:
|
|
21947
21999
|
{"complexity":"simple|medium|complex|expert","modelTier":"fast|balanced|powerful","reasoning":"<one line>"}`;
|
|
22000
|
+
return wrapJsonPrompt(core2);
|
|
21948
22001
|
}
|
|
21949
22002
|
function buildBatchRoutingPrompt(stories, config2) {
|
|
21950
22003
|
const storyBlocks = stories.map((story, idx) => {
|
|
@@ -21958,7 +22011,7 @@ ${criteria}
|
|
|
21958
22011
|
}).join(`
|
|
21959
22012
|
|
|
21960
22013
|
`);
|
|
21961
|
-
|
|
22014
|
+
const batchCore = `You are a code task router. Classify each story's complexity and select the cheapest model tier that will succeed.
|
|
21962
22015
|
|
|
21963
22016
|
## Stories
|
|
21964
22017
|
${storyBlocks}
|
|
@@ -21980,8 +22033,9 @@ ${storyBlocks}
|
|
|
21980
22033
|
- Many files \u2260 complex \u2014 copy-paste refactors across files are simple.
|
|
21981
22034
|
- Pure refactoring/deletion with no new behavior \u2192 simple.
|
|
21982
22035
|
|
|
21983
|
-
Respond with
|
|
22036
|
+
Respond with a JSON array:
|
|
21984
22037
|
[{"id":"US-001","complexity":"simple|medium|complex|expert","modelTier":"fast|balanced|powerful","reasoning":"<one line>"}]`;
|
|
22038
|
+
return wrapJsonPrompt(batchCore);
|
|
21985
22039
|
}
|
|
21986
22040
|
function validateRoutingDecision(parsed, config2, story) {
|
|
21987
22041
|
if (!parsed.complexity || !parsed.modelTier || !parsed.reasoning) {
|
|
@@ -22006,35 +22060,22 @@ function validateRoutingDecision(parsed, config2, story) {
|
|
|
22006
22060
|
};
|
|
22007
22061
|
}
|
|
22008
22062
|
function stripCodeFences(text) {
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
|
|
22012
|
-
|
|
22013
|
-
|
|
22014
|
-
|
|
22063
|
+
const trimmed = text.trim();
|
|
22064
|
+
const fromFence = extractJsonFromMarkdown(trimmed);
|
|
22065
|
+
if (fromFence !== trimmed)
|
|
22066
|
+
return fromFence;
|
|
22067
|
+
if (trimmed.startsWith("json")) {
|
|
22068
|
+
return trimmed.slice(4).trim();
|
|
22015
22069
|
}
|
|
22016
|
-
|
|
22017
|
-
result = result.slice(4).trim();
|
|
22018
|
-
}
|
|
22019
|
-
return result;
|
|
22070
|
+
return trimmed;
|
|
22020
22071
|
}
|
|
22021
22072
|
function parseRoutingResponse(output, story, config2) {
|
|
22022
|
-
const jsonText =
|
|
22073
|
+
const jsonText = extractJsonFromMarkdown(output.trim());
|
|
22023
22074
|
const parsed = JSON.parse(jsonText);
|
|
22024
22075
|
return validateRoutingDecision(parsed, config2, story);
|
|
22025
22076
|
}
|
|
22026
22077
|
function parseBatchResponse(output, stories, config2) {
|
|
22027
|
-
|
|
22028
|
-
if (jsonText.startsWith("```")) {
|
|
22029
|
-
const lines = jsonText.split(`
|
|
22030
|
-
`);
|
|
22031
|
-
jsonText = lines.slice(1, -1).join(`
|
|
22032
|
-
`).trim();
|
|
22033
|
-
}
|
|
22034
|
-
if (jsonText.startsWith("json")) {
|
|
22035
|
-
jsonText = jsonText.slice(4).trim();
|
|
22036
|
-
}
|
|
22037
|
-
const parsed = JSON.parse(jsonText);
|
|
22078
|
+
const parsed = JSON.parse(extractJsonFromMarkdown(output.trim()));
|
|
22038
22079
|
if (!Array.isArray(parsed)) {
|
|
22039
22080
|
throw new Error("Batch LLM response must be a JSON array");
|
|
22040
22081
|
}
|
|
@@ -22468,7 +22509,7 @@ var package_default;
|
|
|
22468
22509
|
var init_package = __esm(() => {
|
|
22469
22510
|
package_default = {
|
|
22470
22511
|
name: "@nathapp/nax",
|
|
22471
|
-
version: "0.57.
|
|
22512
|
+
version: "0.57.4",
|
|
22472
22513
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22473
22514
|
type: "module",
|
|
22474
22515
|
bin: {
|
|
@@ -22547,8 +22588,8 @@ var init_version = __esm(() => {
|
|
|
22547
22588
|
NAX_VERSION = package_default.version;
|
|
22548
22589
|
NAX_COMMIT = (() => {
|
|
22549
22590
|
try {
|
|
22550
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22551
|
-
return "
|
|
22591
|
+
if (/^[0-9a-f]{6,10}$/.test("b3088982"))
|
|
22592
|
+
return "b3088982";
|
|
22552
22593
|
} catch {}
|
|
22553
22594
|
try {
|
|
22554
22595
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -27090,7 +27131,7 @@ function buildPrompt(story, semanticConfig, diff, stat) {
|
|
|
27090
27131
|
${semanticConfig.rules.map((r, i) => `${i + 1}. ${r}`).join(`
|
|
27091
27132
|
`)}
|
|
27092
27133
|
` : "";
|
|
27093
|
-
|
|
27134
|
+
const core2 = `You are a semantic code reviewer with access to the repository files. Your job is to verify that the implementation satisfies the story's acceptance criteria (ACs). You are NOT a linter or style checker \u2014 lint, typecheck, and convention checks are handled separately.
|
|
27094
27135
|
|
|
27095
27136
|
## Story: ${story.title}
|
|
27096
27137
|
|
|
@@ -27138,25 +27179,36 @@ Respond with JSON only \u2014 no explanation text before or after:
|
|
|
27138
27179
|
}
|
|
27139
27180
|
|
|
27140
27181
|
If all ACs are correctly implemented, respond with { "passed": true, "findings": [] }.`;
|
|
27182
|
+
return wrapJsonPrompt(core2);
|
|
27183
|
+
}
|
|
27184
|
+
function validateLLMShape(parsed) {
|
|
27185
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
27186
|
+
return null;
|
|
27187
|
+
const obj = parsed;
|
|
27188
|
+
if (typeof obj.passed !== "boolean")
|
|
27189
|
+
return null;
|
|
27190
|
+
if (!Array.isArray(obj.findings))
|
|
27191
|
+
return null;
|
|
27192
|
+
return { passed: obj.passed, findings: obj.findings };
|
|
27141
27193
|
}
|
|
27142
27194
|
function parseLLMResponse(raw) {
|
|
27195
|
+
const text = raw.trim();
|
|
27143
27196
|
try {
|
|
27144
|
-
|
|
27145
|
-
|
|
27146
|
-
|
|
27147
|
-
|
|
27148
|
-
|
|
27149
|
-
|
|
27150
|
-
|
|
27151
|
-
|
|
27152
|
-
|
|
27153
|
-
|
|
27154
|
-
|
|
27155
|
-
return
|
|
27156
|
-
|
|
27157
|
-
} catch {
|
|
27158
|
-
return null;
|
|
27197
|
+
return validateLLMShape(JSON.parse(text));
|
|
27198
|
+
} catch {}
|
|
27199
|
+
const fromFence = extractJsonFromMarkdown(text);
|
|
27200
|
+
if (fromFence !== text) {
|
|
27201
|
+
try {
|
|
27202
|
+
return validateLLMShape(JSON.parse(stripTrailingCommas(fromFence)));
|
|
27203
|
+
} catch {}
|
|
27204
|
+
}
|
|
27205
|
+
const bareJson = extractJsonObject(text);
|
|
27206
|
+
if (bareJson) {
|
|
27207
|
+
try {
|
|
27208
|
+
return validateLLMShape(JSON.parse(stripTrailingCommas(bareJson)));
|
|
27209
|
+
} catch {}
|
|
27159
27210
|
}
|
|
27211
|
+
return null;
|
|
27160
27212
|
}
|
|
27161
27213
|
function formatFindings(findings) {
|
|
27162
27214
|
return findings.map((f) => `[${f.severity}] ${f.file}:${f.line} \u2014 ${f.issue}
|
|
@@ -70812,16 +70864,6 @@ function mapDecomposedStoriesToUserStories(stories, parentStoryId) {
|
|
|
70812
70864
|
init_test_strategy();
|
|
70813
70865
|
var VALID_COMPLEXITY = ["simple", "medium", "complex", "expert"];
|
|
70814
70866
|
var STORY_ID_NO_SEPARATOR = /^([A-Za-z]+)(\d+)$/;
|
|
70815
|
-
function extractJsonFromMarkdown(text) {
|
|
70816
|
-
const match = text.match(/```(?:json)?\s*\n([\s\S]*?)\n?\s*```/);
|
|
70817
|
-
if (match) {
|
|
70818
|
-
return match[1] ?? text;
|
|
70819
|
-
}
|
|
70820
|
-
return text;
|
|
70821
|
-
}
|
|
70822
|
-
function stripTrailingCommas(text) {
|
|
70823
|
-
return text.replace(/,\s*([}\]])/g, "$1");
|
|
70824
|
-
}
|
|
70825
70867
|
function normalizeStoryId(id) {
|
|
70826
70868
|
const match = id.match(STORY_ID_NO_SEPARATOR);
|
|
70827
70869
|
if (match) {
|
|
@@ -71485,61 +71527,74 @@ async function planDecomposeCommand(workdir, config2, options) {
|
|
|
71485
71527
|
throw new Error(`[decompose] No agent adapter found for '${agentName}'`);
|
|
71486
71528
|
const timeoutSeconds = config2?.plan?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS2;
|
|
71487
71529
|
const maxAcCount = config2?.precheck?.storySizeGate?.maxAcCount ?? Number.POSITIVE_INFINITY;
|
|
71530
|
+
const maxReplanAttempts = config2?.precheck?.storySizeGate?.maxReplanAttempts ?? 3;
|
|
71488
71531
|
if (typeof adapter.decompose !== "function") {
|
|
71489
71532
|
throw new NaxError(`Agent "${agentName}" does not support decompose() required by plan --decompose`, "DECOMPOSE_NOT_SUPPORTED", { stage: "decompose", agent: agentName, storyId: options.storyId });
|
|
71490
71533
|
}
|
|
71491
71534
|
const debateStages = config2?.debate?.stages;
|
|
71492
71535
|
const debateDecompEnabled = config2?.debate?.enabled && debateStages?.decompose?.enabled;
|
|
71493
71536
|
let decompStories;
|
|
71494
|
-
|
|
71495
|
-
|
|
71496
|
-
|
|
71497
|
-
|
|
71498
|
-
|
|
71499
|
-
|
|
71500
|
-
|
|
71501
|
-
|
|
71502
|
-
|
|
71503
|
-
|
|
71504
|
-
|
|
71505
|
-
|
|
71506
|
-
|
|
71507
|
-
|
|
71508
|
-
|
|
71509
|
-
|
|
71510
|
-
config: config2,
|
|
71511
|
-
workdir,
|
|
71512
|
-
featureName: options.feature,
|
|
71513
|
-
timeoutSeconds
|
|
71514
|
-
});
|
|
71515
|
-
const debateResult = await debateSession.run(prompt);
|
|
71516
|
-
if (debateResult.outcome !== "failed" && debateResult.output) {
|
|
71517
|
-
decompStories = parseDecomposeOutput(debateResult.output);
|
|
71518
|
-
}
|
|
71519
|
-
}
|
|
71520
|
-
if (!decompStories) {
|
|
71521
|
-
const result = await adapter.decompose({
|
|
71522
|
-
specContent: "",
|
|
71523
|
-
codebaseContext,
|
|
71524
|
-
workdir,
|
|
71525
|
-
targetStory,
|
|
71526
|
-
siblings,
|
|
71527
|
-
featureName: options.feature,
|
|
71528
|
-
storyId: options.storyId,
|
|
71529
|
-
config: config2
|
|
71530
|
-
});
|
|
71531
|
-
decompStories = result.stories;
|
|
71532
|
-
}
|
|
71533
|
-
for (const sub of decompStories) {
|
|
71534
|
-
if (!sub.complexity || !sub.testStrategy) {
|
|
71535
|
-
throw new NaxError(`Sub-story "${sub.id}" is missing required routing fields`, "DECOMPOSE_VALIDATION_FAILED", {
|
|
71537
|
+
let repairHint = "";
|
|
71538
|
+
for (let attempt = 0;attempt < maxReplanAttempts; attempt++) {
|
|
71539
|
+
if (attempt === 0 && debateDecompEnabled) {
|
|
71540
|
+
const decomposeStageConfig = debateStages.decompose;
|
|
71541
|
+
const prompt = buildDecomposePrompt({
|
|
71542
|
+
specContent: "",
|
|
71543
|
+
codebaseContext,
|
|
71544
|
+
workdir,
|
|
71545
|
+
targetStory,
|
|
71546
|
+
siblings,
|
|
71547
|
+
featureName: options.feature,
|
|
71548
|
+
storyId: options.storyId,
|
|
71549
|
+
config: config2
|
|
71550
|
+
});
|
|
71551
|
+
const debateSession = _planDeps.createDebateSession({
|
|
71552
|
+
storyId: options.storyId,
|
|
71536
71553
|
stage: "decompose",
|
|
71537
|
-
|
|
71554
|
+
stageConfig: decomposeStageConfig,
|
|
71555
|
+
config: config2,
|
|
71556
|
+
workdir,
|
|
71557
|
+
featureName: options.feature,
|
|
71558
|
+
timeoutSeconds
|
|
71538
71559
|
});
|
|
71560
|
+
const debateResult = await debateSession.run(prompt);
|
|
71561
|
+
if (debateResult.outcome !== "failed" && debateResult.output) {
|
|
71562
|
+
decompStories = parseDecomposeOutput(debateResult.output);
|
|
71563
|
+
}
|
|
71539
71564
|
}
|
|
71540
|
-
if (
|
|
71541
|
-
|
|
71565
|
+
if (!decompStories) {
|
|
71566
|
+
const effectiveContext = repairHint ? `${codebaseContext}
|
|
71567
|
+
|
|
71568
|
+
${repairHint}` : codebaseContext;
|
|
71569
|
+
const result = await adapter.decompose({
|
|
71570
|
+
specContent: "",
|
|
71571
|
+
codebaseContext: effectiveContext,
|
|
71572
|
+
workdir,
|
|
71573
|
+
targetStory,
|
|
71574
|
+
siblings,
|
|
71575
|
+
featureName: options.feature,
|
|
71576
|
+
storyId: options.storyId,
|
|
71577
|
+
config: config2
|
|
71578
|
+
});
|
|
71579
|
+
decompStories = result.stories;
|
|
71580
|
+
}
|
|
71581
|
+
for (const sub of decompStories) {
|
|
71582
|
+
if (!sub.complexity || !sub.testStrategy) {
|
|
71583
|
+
throw new NaxError(`Sub-story "${sub.id}" is missing required routing fields`, "DECOMPOSE_VALIDATION_FAILED", {
|
|
71584
|
+
stage: "decompose",
|
|
71585
|
+
storyId: sub.id
|
|
71586
|
+
});
|
|
71587
|
+
}
|
|
71588
|
+
}
|
|
71589
|
+
const violations = decompStories.filter((sub) => sub.acceptanceCriteria && sub.acceptanceCriteria.length > maxAcCount);
|
|
71590
|
+
if (violations.length === 0)
|
|
71591
|
+
break;
|
|
71592
|
+
const violationSummary = violations.map((v) => `"${v.id}" (${v.acceptanceCriteria.length} ACs, max ${maxAcCount})`).join(", ");
|
|
71593
|
+
if (attempt + 1 >= maxReplanAttempts) {
|
|
71594
|
+
throw new NaxError(`Decompose AC repair failed after ${maxReplanAttempts} attempts. Oversized sub-stories: ${violationSummary}`, "DECOMPOSE_VALIDATION_FAILED", { stage: "decompose", storyId: options.storyId });
|
|
71542
71595
|
}
|
|
71596
|
+
repairHint = `REPAIR REQUIRED (attempt ${attempt + 1}/${maxReplanAttempts}): The following sub-stories exceeded maxAcCount of ${maxAcCount}: ${violationSummary}. Split each offending story further so every sub-story has at most ${maxAcCount} acceptance criteria.`;
|
|
71597
|
+
decompStories = undefined;
|
|
71543
71598
|
}
|
|
71544
71599
|
const subStoriesWithParent = mapDecomposedStoriesToUserStories(decompStories, options.storyId);
|
|
71545
71600
|
const updatedStories = prd.userStories.map((s) => s.id === options.storyId ? { ...s, status: "decomposed" } : s);
|