@mcoda/core 0.1.37 → 0.1.40
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/api/MswarmApi.d.ts +155 -0
- package/dist/api/MswarmApi.d.ts.map +1 -0
- package/dist/api/MswarmApi.js +593 -0
- package/dist/api/MswarmConfigStore.d.ts +53 -0
- package/dist/api/MswarmConfigStore.d.ts.map +1 -0
- package/dist/api/MswarmConfigStore.js +111 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/services/docs/DocsService.d.ts.map +1 -1
- package/dist/services/docs/DocsService.js +1 -11
- package/dist/services/estimate/VelocityService.d.ts.map +1 -1
- package/dist/services/estimate/VelocityService.js +1 -2
- package/dist/services/execution/AddTestsService.d.ts.map +1 -1
- package/dist/services/execution/AddTestsService.js +2 -2
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +3 -2
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +2 -6
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
- package/dist/services/openapi/OpenApiService.js +1 -11
- package/dist/services/planning/CreateTasksService.d.ts +9 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +490 -209
- package/dist/services/review/CodeReviewService.js +2 -2
- package/dist/services/shared/GitBranch.d.ts +6 -0
- package/dist/services/shared/GitBranch.d.ts.map +1 -0
- package/dist/services/shared/GitBranch.js +62 -0
- package/package.json +6 -6
|
@@ -219,6 +219,18 @@ const STRICT_AGENT_SINGLE_STORY_DOC_SUMMARY_TOKEN_LIMIT = 1200;
|
|
|
219
219
|
const STRICT_AGENT_SINGLE_STORY_BUILD_METHOD_TOKEN_LIMIT = 900;
|
|
220
220
|
const STRICT_AGENT_SINGLE_TASK_DOC_SUMMARY_TOKEN_LIMIT = 1200;
|
|
221
221
|
const STRICT_AGENT_SINGLE_TASK_BUILD_METHOD_TOKEN_LIMIT = 900;
|
|
222
|
+
const STRICT_AGENT_COMPACT_TASK_STRUCTURED_PROMPT_TOKEN_LIMIT = 1200;
|
|
223
|
+
const STRICT_AGENT_COMPACT_TASK_RUNTIME_PROMPT_TOKEN_LIMIT = 1000;
|
|
224
|
+
const STRICT_AGENT_COMPACT_TASK_MINIMAL_PROMPT_TOKEN_LIMIT = 650;
|
|
225
|
+
const STRICT_AGENT_COMPACT_TASK_FULL_STORY_TOKEN_LIMIT = 260;
|
|
226
|
+
const STRICT_AGENT_COMPACT_TASK_MINIMAL_STORY_TOKEN_LIMIT = 140;
|
|
227
|
+
const STRICT_AGENT_COMPACT_TASK_FULL_ACCEPTANCE_TOKEN_LIMIT = 180;
|
|
228
|
+
const STRICT_AGENT_COMPACT_TASK_MINIMAL_ACCEPTANCE_TOKEN_LIMIT = 90;
|
|
229
|
+
const STRICT_AGENT_COMPACT_TASK_FULL_DOC_TOKEN_LIMIT = 140;
|
|
230
|
+
const STRICT_AGENT_COMPACT_TASK_MINIMAL_DOC_TOKEN_LIMIT = 60;
|
|
231
|
+
const STRICT_AGENT_COMPACT_TASK_FULL_BUILD_TOKEN_LIMIT = 160;
|
|
232
|
+
const STRICT_AGENT_COMPACT_TASK_MINIMAL_BUILD_TOKEN_LIMIT = 70;
|
|
233
|
+
const STRICT_AGENT_MAX_TASKS_PER_COMPACT_REWRITE = 3;
|
|
222
234
|
const META_TASK_PATTERN = /\b(plan|planning|backlog|coverage|artifact|evidence capture|document baseline|update refine log|record refinement|review inputs)\b/i;
|
|
223
235
|
const compactPromptContext = (value, maxTokens, fallback = "none") => {
|
|
224
236
|
const text = value?.trim();
|
|
@@ -1253,7 +1265,12 @@ const TASK_COMPACT_SCHEMA_SNIPPET = `{
|
|
|
1253
1265
|
"files": ["relative/path/to/implementation.file"],
|
|
1254
1266
|
"estimatedStoryPoints": 3,
|
|
1255
1267
|
"priorityHint": 50,
|
|
1256
|
-
"dependsOnKeys": ["t0"]
|
|
1268
|
+
"dependsOnKeys": ["t0"],
|
|
1269
|
+
"relatedDocs": ["docdex:..."],
|
|
1270
|
+
"unitTests": ["unit test description"],
|
|
1271
|
+
"componentTests": ["component test description"],
|
|
1272
|
+
"integrationTests": ["integration test description"],
|
|
1273
|
+
"apiTests": ["api test description"]
|
|
1257
1274
|
}
|
|
1258
1275
|
]
|
|
1259
1276
|
}`;
|
|
@@ -1855,7 +1872,34 @@ export class CreateTasksService {
|
|
|
1855
1872
|
.join("\n"), 96))
|
|
1856
1873
|
.map((value) => this.normalizeStructurePathToken(value))
|
|
1857
1874
|
.filter((value) => Boolean(value));
|
|
1858
|
-
return
|
|
1875
|
+
return this.preferSpecificTaskTargets([...explicitFiles, ...extractedFiles]).slice(0, 8);
|
|
1876
|
+
}
|
|
1877
|
+
preferSpecificTaskTargets(targets) {
|
|
1878
|
+
const normalized = uniqueStrings(targets
|
|
1879
|
+
.map((value) => this.normalizeStructurePathToken(value) ?? value.replace(/\\/g, "/").trim())
|
|
1880
|
+
.filter((value) => Boolean(value)));
|
|
1881
|
+
const sorted = normalized.sort((left, right) => {
|
|
1882
|
+
const leftIsFile = isStructuredFilePath(path.basename(left));
|
|
1883
|
+
const rightIsFile = isStructuredFilePath(path.basename(right));
|
|
1884
|
+
if (leftIsFile !== rightIsFile)
|
|
1885
|
+
return leftIsFile ? -1 : 1;
|
|
1886
|
+
const leftDepth = left.split("/").filter(Boolean).length;
|
|
1887
|
+
const rightDepth = right.split("/").filter(Boolean).length;
|
|
1888
|
+
if (leftDepth !== rightDepth)
|
|
1889
|
+
return rightDepth - leftDepth;
|
|
1890
|
+
if (left.length !== right.length)
|
|
1891
|
+
return right.length - left.length;
|
|
1892
|
+
return left.localeCompare(right);
|
|
1893
|
+
});
|
|
1894
|
+
const kept = [];
|
|
1895
|
+
for (const target of sorted) {
|
|
1896
|
+
const prefix = `${target.replace(/\/+$/g, "")}/`;
|
|
1897
|
+
if (kept.some((existing) => existing === target || existing.startsWith(prefix))) {
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
kept.push(target);
|
|
1901
|
+
}
|
|
1902
|
+
return kept.sort((left, right) => left.length - right.length || left.localeCompare(right));
|
|
1859
1903
|
}
|
|
1860
1904
|
extractStructureTargets(docs) {
|
|
1861
1905
|
const directories = new Set();
|
|
@@ -5532,6 +5576,11 @@ export class CreateTasksService {
|
|
|
5532
5576
|
"estimatedStoryPoints",
|
|
5533
5577
|
"priorityHint",
|
|
5534
5578
|
"dependsOnKeys",
|
|
5579
|
+
"relatedDocs",
|
|
5580
|
+
"unitTests",
|
|
5581
|
+
"componentTests",
|
|
5582
|
+
"integrationTests",
|
|
5583
|
+
"apiTests",
|
|
5535
5584
|
],
|
|
5536
5585
|
properties: {
|
|
5537
5586
|
localId: nullableString,
|
|
@@ -5542,6 +5591,11 @@ export class CreateTasksService {
|
|
|
5542
5591
|
estimatedStoryPoints: nullableNumber,
|
|
5543
5592
|
priorityHint: nullableNumber,
|
|
5544
5593
|
dependsOnKeys: stringArray,
|
|
5594
|
+
relatedDocs: stringArray,
|
|
5595
|
+
unitTests: stringArray,
|
|
5596
|
+
componentTests: stringArray,
|
|
5597
|
+
integrationTests: stringArray,
|
|
5598
|
+
apiTests: stringArray,
|
|
5545
5599
|
},
|
|
5546
5600
|
additionalProperties: false,
|
|
5547
5601
|
};
|
|
@@ -5798,12 +5852,15 @@ export class CreateTasksService {
|
|
|
5798
5852
|
shouldPreferSchemaFreeInitialCompactTasks() {
|
|
5799
5853
|
return this.compactTaskSchemaStrategy === "schema_free_pref";
|
|
5800
5854
|
}
|
|
5801
|
-
async activateCompactTaskSchemaFallback(jobId) {
|
|
5855
|
+
async activateCompactTaskSchemaFallback(jobId, reason) {
|
|
5802
5856
|
this.compactTaskSchemaStrategy = "schema_free_pref";
|
|
5803
5857
|
if (this.compactTaskSchemaStrategyLogged)
|
|
5804
5858
|
return;
|
|
5805
5859
|
this.compactTaskSchemaStrategyLogged = true;
|
|
5806
5860
|
await this.jobService.appendLog(jobId, "[create-tasks] tasks_compact structured mode is unstable in this run; preferring schema-free initial calls for remaining compact task prompts.\n");
|
|
5861
|
+
if (reason) {
|
|
5862
|
+
await this.jobService.appendLog(jobId, `[create-tasks] ${reason}\n`);
|
|
5863
|
+
}
|
|
5807
5864
|
}
|
|
5808
5865
|
buildJsonRepairPrompt(action, originalPrompt, originalOutput) {
|
|
5809
5866
|
if (action === "tasks_compact") {
|
|
@@ -6603,6 +6660,12 @@ export class CreateTasksService {
|
|
|
6603
6660
|
let output = "";
|
|
6604
6661
|
let invocationMetadata;
|
|
6605
6662
|
const currentTimestamp = () => new Date().toISOString();
|
|
6663
|
+
const promptTokens = estimateTokens(prompt);
|
|
6664
|
+
if (action === "tasks_compact" &&
|
|
6665
|
+
!this.shouldPreferSchemaFreeInitialCompactTasks() &&
|
|
6666
|
+
promptTokens > STRICT_AGENT_COMPACT_TASK_STRUCTURED_PROMPT_TOKEN_LIMIT) {
|
|
6667
|
+
await this.activateCompactTaskSchemaFallback(jobId, `tasks_compact prompt estimate ${promptTokens} exceeds structured reliability limit ${STRICT_AGENT_COMPACT_TASK_STRUCTURED_PROMPT_TOKEN_LIMIT}.`);
|
|
6668
|
+
}
|
|
6606
6669
|
const actionOutputSchema = this.outputSchemaForAction(action);
|
|
6607
6670
|
const preferSchemaFreeInitialCompactCall = action === "tasks_compact" && this.shouldPreferSchemaFreeInitialCompactTasks();
|
|
6608
6671
|
const outputSchema = preferSchemaFreeInitialCompactCall ? undefined : actionOutputSchema;
|
|
@@ -7025,6 +7088,10 @@ export class CreateTasksService {
|
|
|
7025
7088
|
await this.jobService.appendLog(jobId, `Strict story repair failed for epic "${epic.title}". Using deterministic fallback story and continuing. Reason: ${repairMessage}\n`);
|
|
7026
7089
|
stories = [this.buildFallbackStoryForEpic(epic)];
|
|
7027
7090
|
}
|
|
7091
|
+
if (stories.length === 0) {
|
|
7092
|
+
await this.jobService.appendLog(jobId, `Strict story repair returned no stories for epic "${epic.title}". Using deterministic fallback story and continuing.\n`);
|
|
7093
|
+
stories = [this.buildFallbackStoryForEpic(epic)];
|
|
7094
|
+
}
|
|
7028
7095
|
}
|
|
7029
7096
|
if (stories.length === 0) {
|
|
7030
7097
|
await this.jobService.appendLog(jobId, `Story generation returned no stories for epic "${epic.title}". Retrying through strict staged recovery.\n`);
|
|
@@ -7036,6 +7103,10 @@ export class CreateTasksService {
|
|
|
7036
7103
|
await this.jobService.appendLog(jobId, `Strict story repair failed for epic "${epic.title}" after empty output. Using deterministic fallback story and continuing. Reason: ${repairMessage}\n`);
|
|
7037
7104
|
stories = [this.buildFallbackStoryForEpic(epic)];
|
|
7038
7105
|
}
|
|
7106
|
+
if (stories.length === 0) {
|
|
7107
|
+
await this.jobService.appendLog(jobId, `Strict story repair returned no stories for epic "${epic.title}" after empty output. Using deterministic fallback story and continuing.\n`);
|
|
7108
|
+
stories = [this.buildFallbackStoryForEpic(epic)];
|
|
7109
|
+
}
|
|
7039
7110
|
}
|
|
7040
7111
|
storiesByEpic.set(epic.localId, stories);
|
|
7041
7112
|
return;
|
|
@@ -7061,6 +7132,7 @@ export class CreateTasksService {
|
|
|
7061
7132
|
if (chunk.length === 1) {
|
|
7062
7133
|
const { epic, story } = chunk[0];
|
|
7063
7134
|
const storyScope = this.storyScopeKey(story.epicLocalId, story.localId);
|
|
7135
|
+
const fallbackTasks = this.buildFallbackTasksForStory(story);
|
|
7064
7136
|
let tasks;
|
|
7065
7137
|
try {
|
|
7066
7138
|
tasks = await this.generateTasksForStory(agent, projectKey, { key: epic.localId, title: epicTitleByLocalId.get(epic.localId) ?? epic.title }, { ...story, key: story.localId }, docSummary, projectBuildMethod, stream, jobId, commandRunId, { compactSchema: options?.compactSingleStorySchema === true });
|
|
@@ -7068,30 +7140,45 @@ export class CreateTasksService {
|
|
|
7068
7140
|
catch (error) {
|
|
7069
7141
|
const message = error.message ?? String(error);
|
|
7070
7142
|
if (this.isAgentTimeoutLikeError(error)) {
|
|
7071
|
-
await this.jobService.appendLog(jobId, `Task generation timed out for story "${story.title}" (${storyScope}).
|
|
7072
|
-
|
|
7143
|
+
await this.jobService.appendLog(jobId, `Task generation timed out for story "${story.title}" (${storyScope}). Retrying through strict staged recovery before deterministic fallback.\n`);
|
|
7144
|
+
try {
|
|
7145
|
+
tasks = await this.repairTasksForStory(agent, projectKey, { key: epic.localId, title: epicTitleByLocalId.get(epic.localId) ?? epic.title }, { ...story, key: story.localId }, docSummary, projectBuildMethod, message, fallbackTasks, stream, jobId, commandRunId, { compactSchema: options?.compactSingleStorySchema === true });
|
|
7146
|
+
}
|
|
7147
|
+
catch (repairError) {
|
|
7148
|
+
const repairMessage = repairError.message ?? String(repairError);
|
|
7149
|
+
await this.jobService.appendLog(jobId, `Strict task repair failed for story "${story.title}" (${storyScope}) after timeout. Using deterministic fallback tasks and continuing. Reason: ${repairMessage}\n`);
|
|
7150
|
+
tasks = fallbackTasks;
|
|
7151
|
+
}
|
|
7073
7152
|
tasksByStoryScope.set(storyScope, tasks);
|
|
7074
7153
|
return;
|
|
7075
7154
|
}
|
|
7076
7155
|
await this.jobService.appendLog(jobId, `Task generation failed for story "${story.title}" (${storyScope}). Retrying through strict staged recovery. Reason: ${message}\n`);
|
|
7077
7156
|
try {
|
|
7078
|
-
tasks = await this.repairTasksForStory(agent, projectKey, { key: epic.localId, title: epicTitleByLocalId.get(epic.localId) ?? epic.title }, { ...story, key: story.localId }, docSummary, projectBuildMethod, message,
|
|
7157
|
+
tasks = await this.repairTasksForStory(agent, projectKey, { key: epic.localId, title: epicTitleByLocalId.get(epic.localId) ?? epic.title }, { ...story, key: story.localId }, docSummary, projectBuildMethod, message, fallbackTasks, stream, jobId, commandRunId, { compactSchema: options?.compactSingleStorySchema === true });
|
|
7079
7158
|
}
|
|
7080
7159
|
catch (repairError) {
|
|
7081
7160
|
const repairMessage = repairError.message ?? String(repairError);
|
|
7082
7161
|
await this.jobService.appendLog(jobId, `Strict task repair failed for story "${story.title}" (${storyScope}). Using deterministic fallback tasks and continuing. Reason: ${repairMessage}\n`);
|
|
7083
|
-
tasks =
|
|
7162
|
+
tasks = fallbackTasks;
|
|
7163
|
+
}
|
|
7164
|
+
if (tasks.length === 0) {
|
|
7165
|
+
await this.jobService.appendLog(jobId, `Strict task repair returned no tasks for story "${story.title}" (${storyScope}). Using deterministic fallback tasks and continuing.\n`);
|
|
7166
|
+
tasks = fallbackTasks;
|
|
7084
7167
|
}
|
|
7085
7168
|
}
|
|
7086
7169
|
if (tasks.length === 0) {
|
|
7087
7170
|
await this.jobService.appendLog(jobId, `Task generation returned no tasks for story "${story.title}" (${storyScope}). Retrying through strict staged recovery.\n`);
|
|
7088
7171
|
try {
|
|
7089
|
-
tasks = await this.repairTasksForStory(agent, projectKey, { key: epic.localId, title: epicTitleByLocalId.get(epic.localId) ?? epic.title }, { ...story, key: story.localId }, docSummary, projectBuildMethod, `No tasks were returned for story ${story.title}.`,
|
|
7172
|
+
tasks = await this.repairTasksForStory(agent, projectKey, { key: epic.localId, title: epicTitleByLocalId.get(epic.localId) ?? epic.title }, { ...story, key: story.localId }, docSummary, projectBuildMethod, `No tasks were returned for story ${story.title}.`, fallbackTasks, stream, jobId, commandRunId, { compactSchema: options?.compactSingleStorySchema === true });
|
|
7090
7173
|
}
|
|
7091
7174
|
catch (repairError) {
|
|
7092
7175
|
const repairMessage = repairError.message ?? String(repairError);
|
|
7093
7176
|
await this.jobService.appendLog(jobId, `Strict task repair failed for story "${story.title}" (${storyScope}) after empty output. Using deterministic fallback tasks and continuing. Reason: ${repairMessage}\n`);
|
|
7094
|
-
tasks =
|
|
7177
|
+
tasks = fallbackTasks;
|
|
7178
|
+
}
|
|
7179
|
+
if (tasks.length === 0) {
|
|
7180
|
+
await this.jobService.appendLog(jobId, `Strict task repair returned no tasks for story "${story.title}" (${storyScope}) after empty output. Using deterministic fallback tasks and continuing.\n`);
|
|
7181
|
+
tasks = fallbackTasks;
|
|
7095
7182
|
}
|
|
7096
7183
|
}
|
|
7097
7184
|
tasksByStoryScope.set(storyScope, tasks);
|
|
@@ -7157,15 +7244,11 @@ export class CreateTasksService {
|
|
|
7157
7244
|
const seedTasks = this.buildFallbackTasksForStory(story);
|
|
7158
7245
|
const compactBuildMethod = compactPromptContext(projectBuildMethod, compactSchema ? 220 : STRICT_AGENT_SINGLE_TASK_BUILD_METHOD_TOKEN_LIMIT, "none");
|
|
7159
7246
|
const compactDocSummary = compactPromptContext(docSummary, compactSchema ? 180 : STRICT_AGENT_SINGLE_TASK_DOC_SUMMARY_TOKEN_LIMIT, "none");
|
|
7247
|
+
if (compactSchema) {
|
|
7248
|
+
return this.executeCompactTaskRewrite(agent, projectKey, epic, story, compactDocSummary, compactBuildMethod, seedTasks, stream, jobId, commandRunId);
|
|
7249
|
+
}
|
|
7160
7250
|
const prompt = compactSchema
|
|
7161
|
-
?
|
|
7162
|
-
projectKey,
|
|
7163
|
-
epic,
|
|
7164
|
-
story,
|
|
7165
|
-
docSummary: compactDocSummary,
|
|
7166
|
-
projectBuildMethod: compactBuildMethod,
|
|
7167
|
-
seedTasks,
|
|
7168
|
-
})
|
|
7251
|
+
? ""
|
|
7169
7252
|
: [
|
|
7170
7253
|
this.buildCreateTasksAgentMission(projectKey),
|
|
7171
7254
|
`Generate tasks for story "${story.title}" (Epic: ${epic.title}, phase 3 of 3).`,
|
|
@@ -7205,8 +7288,8 @@ export class CreateTasksService {
|
|
|
7205
7288
|
compactBuildMethod,
|
|
7206
7289
|
`Docs: ${compactDocSummary}`,
|
|
7207
7290
|
].join("\n\n");
|
|
7208
|
-
const action =
|
|
7209
|
-
const taskStream =
|
|
7291
|
+
const action = "tasks";
|
|
7292
|
+
const taskStream = stream;
|
|
7210
7293
|
const { output } = await this.invokeAgentWithRetry(agent, prompt, action, taskStream, jobId, commandRunId, {
|
|
7211
7294
|
epicKey: epic.key,
|
|
7212
7295
|
storyKey: story.key ?? story.localId,
|
|
@@ -7216,9 +7299,10 @@ export class CreateTasksService {
|
|
|
7216
7299
|
if (!parsed || !Array.isArray(parsed.tasks) || parsed.tasks.length === 0) {
|
|
7217
7300
|
throw new Error(`Agent did not return tasks for story ${story.title}`);
|
|
7218
7301
|
}
|
|
7219
|
-
|
|
7302
|
+
const normalizedTasks = parsed.tasks
|
|
7220
7303
|
.map((task, idx) => this.normalizeAgentTaskNode(task, idx))
|
|
7221
7304
|
.filter((t) => t.title);
|
|
7305
|
+
return compactSchema ? this.mergeCompactTaskMetadata(normalizedTasks, seedTasks) : normalizedTasks;
|
|
7222
7306
|
}
|
|
7223
7307
|
async repairStoriesForEpic(agent, projectKey, epic, docSummary, projectBuildMethod, reason, seedStories, stream, jobId, commandRunId) {
|
|
7224
7308
|
const compactBuildMethod = compactPromptContext(projectBuildMethod, STRICT_AGENT_SINGLE_STORY_BUILD_METHOD_TOKEN_LIMIT, "none");
|
|
@@ -7262,16 +7346,11 @@ export class CreateTasksService {
|
|
|
7262
7346
|
const compactSchema = options?.compactSchema === true;
|
|
7263
7347
|
const compactBuildMethod = compactPromptContext(projectBuildMethod, compactSchema ? 220 : STRICT_AGENT_SINGLE_TASK_BUILD_METHOD_TOKEN_LIMIT, "none");
|
|
7264
7348
|
const compactDocSummary = compactPromptContext(docSummary, compactSchema ? 180 : STRICT_AGENT_SINGLE_TASK_DOC_SUMMARY_TOKEN_LIMIT, "none");
|
|
7349
|
+
if (compactSchema) {
|
|
7350
|
+
return this.executeCompactTaskRewrite(agent, projectKey, epic, story, compactDocSummary, compactBuildMethod, seedTasks, stream, jobId, commandRunId, reason);
|
|
7351
|
+
}
|
|
7265
7352
|
const prompt = compactSchema
|
|
7266
|
-
?
|
|
7267
|
-
projectKey,
|
|
7268
|
-
epic,
|
|
7269
|
-
story,
|
|
7270
|
-
docSummary: compactDocSummary,
|
|
7271
|
-
projectBuildMethod: compactBuildMethod,
|
|
7272
|
-
seedTasks,
|
|
7273
|
-
previousFailure: reason,
|
|
7274
|
-
})
|
|
7353
|
+
? ""
|
|
7275
7354
|
: [
|
|
7276
7355
|
this.buildCreateTasksAgentMission(projectKey),
|
|
7277
7356
|
`Repair task generation for story "${story.title}" (Epic: ${epic.title}). The previous attempt failed or returned no tasks.`,
|
|
@@ -7295,8 +7374,8 @@ export class CreateTasksService {
|
|
|
7295
7374
|
JSON.stringify({ tasks: seedTasks }, null, 2),
|
|
7296
7375
|
`Docs: ${compactDocSummary}`,
|
|
7297
7376
|
].join("\n\n");
|
|
7298
|
-
const action =
|
|
7299
|
-
const taskStream =
|
|
7377
|
+
const action = "tasks";
|
|
7378
|
+
const taskStream = stream;
|
|
7300
7379
|
const { output } = await this.invokeAgentWithRetry(agent, prompt, action, taskStream, jobId, commandRunId, {
|
|
7301
7380
|
epicKey: epic.key,
|
|
7302
7381
|
storyKey: story.key ?? story.localId,
|
|
@@ -7308,9 +7387,72 @@ export class CreateTasksService {
|
|
|
7308
7387
|
if (!parsed || !Array.isArray(parsed.tasks) || parsed.tasks.length === 0) {
|
|
7309
7388
|
throw new Error(`Agent did not return repair tasks for story ${story.title}`);
|
|
7310
7389
|
}
|
|
7311
|
-
|
|
7390
|
+
const normalizedTasks = parsed.tasks
|
|
7312
7391
|
.map((task, idx) => this.normalizeAgentTaskNode(task, idx))
|
|
7313
7392
|
.filter((task) => task.title);
|
|
7393
|
+
return compactSchema ? this.mergeCompactTaskMetadata(normalizedTasks, seedTasks) : normalizedTasks;
|
|
7394
|
+
}
|
|
7395
|
+
splitCompactTaskRewriteChunks(seedTasks) {
|
|
7396
|
+
if (seedTasks.length <= STRICT_AGENT_MAX_TASKS_PER_COMPACT_REWRITE) {
|
|
7397
|
+
return [seedTasks];
|
|
7398
|
+
}
|
|
7399
|
+
const chunks = [];
|
|
7400
|
+
for (let index = 0; index < seedTasks.length; index += STRICT_AGENT_MAX_TASKS_PER_COMPACT_REWRITE) {
|
|
7401
|
+
chunks.push(seedTasks.slice(index, index + STRICT_AGENT_MAX_TASKS_PER_COMPACT_REWRITE));
|
|
7402
|
+
}
|
|
7403
|
+
return chunks;
|
|
7404
|
+
}
|
|
7405
|
+
async executeCompactTaskRewrite(agent, projectKey, epic, story, docSummary, projectBuildMethod, seedTasks, stream, jobId, commandRunId, previousFailure) {
|
|
7406
|
+
const initialChunkCount = this.splitCompactTaskRewriteChunks(seedTasks).length;
|
|
7407
|
+
const taskChunks = this.planCompactTaskRewriteChunks({
|
|
7408
|
+
projectKey,
|
|
7409
|
+
epic,
|
|
7410
|
+
story,
|
|
7411
|
+
docSummary,
|
|
7412
|
+
projectBuildMethod,
|
|
7413
|
+
seedTasks,
|
|
7414
|
+
previousFailure,
|
|
7415
|
+
});
|
|
7416
|
+
if (taskChunks.length > initialChunkCount) {
|
|
7417
|
+
await this.jobService.appendLog(jobId, `[create-tasks] compact task rewrite split story "${story.title}" into ${taskChunks.length} prompt-bounded chunk(s).\n`);
|
|
7418
|
+
}
|
|
7419
|
+
if (taskChunks.some((chunk) => chunk.contextMode === "minimal")) {
|
|
7420
|
+
await this.jobService.appendLog(jobId, `[create-tasks] compact task rewrite is using reduced prompt context for story "${story.title}".\n`);
|
|
7421
|
+
}
|
|
7422
|
+
const rewritten = [];
|
|
7423
|
+
for (const [index, chunkPlan] of taskChunks.entries()) {
|
|
7424
|
+
const prompt = this.buildCompactSeededTaskPrompt({
|
|
7425
|
+
projectKey,
|
|
7426
|
+
epic,
|
|
7427
|
+
story,
|
|
7428
|
+
docSummary,
|
|
7429
|
+
projectBuildMethod,
|
|
7430
|
+
seedTasks: chunkPlan.seedTasks,
|
|
7431
|
+
previousFailure,
|
|
7432
|
+
chunkIndex: index,
|
|
7433
|
+
chunkCount: taskChunks.length,
|
|
7434
|
+
totalSeedTaskCount: seedTasks.length,
|
|
7435
|
+
contextMode: chunkPlan.contextMode,
|
|
7436
|
+
});
|
|
7437
|
+
const { output } = await this.invokeAgentWithRetry(agent, prompt, "tasks_compact", false, jobId, commandRunId, {
|
|
7438
|
+
epicKey: epic.key,
|
|
7439
|
+
storyKey: story.key ?? story.localId,
|
|
7440
|
+
strictAgentMode: true,
|
|
7441
|
+
repairStage: previousFailure ? "tasks" : undefined,
|
|
7442
|
+
taskChunkIndex: index + 1,
|
|
7443
|
+
taskChunkCount: taskChunks.length,
|
|
7444
|
+
timeoutMs: this.resolveStrictBatchTimeoutMs("tasks_batch", 1),
|
|
7445
|
+
});
|
|
7446
|
+
const parsed = extractJson(output);
|
|
7447
|
+
if (!parsed || !Array.isArray(parsed.tasks) || parsed.tasks.length === 0) {
|
|
7448
|
+
throw new Error(`Agent did not return compact tasks for story ${story.title} chunk ${index + 1}`);
|
|
7449
|
+
}
|
|
7450
|
+
const normalizedTasks = parsed.tasks
|
|
7451
|
+
.map((task, taskIndex) => this.normalizeAgentTaskNode(task, taskIndex))
|
|
7452
|
+
.filter((task) => task.title);
|
|
7453
|
+
rewritten.push(...this.mergeCompactTaskMetadata(normalizedTasks, chunkPlan.seedTasks));
|
|
7454
|
+
}
|
|
7455
|
+
return rewritten;
|
|
7314
7456
|
}
|
|
7315
7457
|
buildFallbackStoryForEpic(epic) {
|
|
7316
7458
|
const derived = this.buildDerivedStoryForEpic(epic);
|
|
@@ -7525,7 +7667,7 @@ export class CreateTasksService {
|
|
|
7525
7667
|
.filter(Boolean)).slice(0, 6);
|
|
7526
7668
|
}
|
|
7527
7669
|
normalizeStoryHintFiles(values) {
|
|
7528
|
-
return
|
|
7670
|
+
return this.preferSpecificTaskTargets(values
|
|
7529
7671
|
.map((value) => this.normalizeStructurePathToken(value))
|
|
7530
7672
|
.filter((value) => Boolean(value))).slice(0, 6);
|
|
7531
7673
|
}
|
|
@@ -7541,9 +7683,24 @@ export class CreateTasksService {
|
|
|
7541
7683
|
return "generic";
|
|
7542
7684
|
}
|
|
7543
7685
|
buildCompactSeededTaskPrompt(params) {
|
|
7544
|
-
const
|
|
7686
|
+
const contextMode = params.contextMode ?? "full";
|
|
7687
|
+
const minimalContext = contextMode === "minimal";
|
|
7688
|
+
const compactSeedTasks = this.buildCompactTaskSeeds(params.seedTasks, { minimalContext });
|
|
7689
|
+
const chunkCount = Math.max(1, params.chunkCount ?? 1);
|
|
7690
|
+
const chunkIndex = Math.max(0, params.chunkIndex ?? 0);
|
|
7691
|
+
const totalSeedTaskCount = Math.max(compactSeedTasks.length, params.totalSeedTaskCount ?? compactSeedTasks.length);
|
|
7692
|
+
const chunkLocalIds = compactSeedTasks.map((task) => `${task.localId ?? ""}`.trim()).filter(Boolean);
|
|
7693
|
+
const storyContext = compactPromptContext(params.story.description ?? params.story.userStory ?? "", minimalContext ? STRICT_AGENT_COMPACT_TASK_MINIMAL_STORY_TOKEN_LIMIT : STRICT_AGENT_COMPACT_TASK_FULL_STORY_TOKEN_LIMIT, "none");
|
|
7694
|
+
const acceptanceContext = compactPromptContext((params.story.acceptanceCriteria ?? []).slice(0, minimalContext ? 3 : 5).join("; "), minimalContext
|
|
7695
|
+
? STRICT_AGENT_COMPACT_TASK_MINIMAL_ACCEPTANCE_TOKEN_LIMIT
|
|
7696
|
+
: STRICT_AGENT_COMPACT_TASK_FULL_ACCEPTANCE_TOKEN_LIMIT, "none");
|
|
7697
|
+
const compactBuildMethod = compactPromptContext(params.projectBuildMethod, minimalContext ? STRICT_AGENT_COMPACT_TASK_MINIMAL_BUILD_TOKEN_LIMIT : STRICT_AGENT_COMPACT_TASK_FULL_BUILD_TOKEN_LIMIT, "none");
|
|
7698
|
+
const compactDocSummary = compactPromptContext(params.docSummary, minimalContext ? STRICT_AGENT_COMPACT_TASK_MINIMAL_DOC_TOKEN_LIMIT : STRICT_AGENT_COMPACT_TASK_FULL_DOC_TOKEN_LIMIT, "none");
|
|
7545
7699
|
return [
|
|
7546
7700
|
`Project ${params.projectKey}. Phase 3 compact task synthesis for story "${params.story.title}" in epic "${params.epic.title}".`,
|
|
7701
|
+
chunkCount > 1
|
|
7702
|
+
? `This prompt covers compact task chunk ${chunkIndex + 1}/${chunkCount} for ${totalSeedTaskCount} total story tasks. Rewrite only the localIds in this chunk: ${chunkLocalIds.join(", ")}.`
|
|
7703
|
+
: null,
|
|
7547
7704
|
params.previousFailure
|
|
7548
7705
|
? `The previous attempt failed: ${params.previousFailure}`
|
|
7549
7706
|
: "Rewrite the provided seed tasks into the final story task list.",
|
|
@@ -7553,36 +7710,201 @@ export class CreateTasksService {
|
|
|
7553
7710
|
`- Return exactly ${compactSeedTasks.length} tasks.`,
|
|
7554
7711
|
"- Preserve the seed task localIds and dependsOnKeys unless a direct consistency fix is required.",
|
|
7555
7712
|
"- Keep each returned task aligned to the corresponding seed task role and execution order.",
|
|
7713
|
+
chunkCount > 1
|
|
7714
|
+
? "- Return tasks only for the chunk localIds listed in this prompt. Preserve dependsOnKeys even when they reference earlier-chunk localIds not rewritten here."
|
|
7715
|
+
: null,
|
|
7556
7716
|
"- Keep the task list scoped to this story only; do not introduce cross-story dependencies.",
|
|
7557
|
-
"- Improve titles, descriptions, file targets, and story points using the story context and seed targets below.",
|
|
7717
|
+
"- Improve titles, descriptions, file targets, related docs, test arrays, and story points using the story context and seed targets below.",
|
|
7558
7718
|
"- Prefer exact repo-relative files from the seed tasks and story hints; only broaden to directories when no deeper target is known.",
|
|
7559
7719
|
"- You do not have tool access in this subtask. Do not say you will inspect Docdex, repo files, profile memory, or any other context.",
|
|
7560
7720
|
"- Do not narrate your work, explain your reasoning, or emit any prose outside the JSON object.",
|
|
7561
7721
|
'- Emit the final JSON object immediately. The first character must be "{" and the last must be "}".',
|
|
7562
7722
|
`Story context (key=${params.story.key ?? params.story.localId ?? "TBD"}):`,
|
|
7563
|
-
|
|
7564
|
-
`Acceptance criteria: ${
|
|
7723
|
+
storyContext,
|
|
7724
|
+
`Acceptance criteria: ${acceptanceContext}`,
|
|
7565
7725
|
"Project construction method:",
|
|
7566
|
-
|
|
7726
|
+
compactBuildMethod,
|
|
7567
7727
|
"Seed tasks to rewrite exactly:",
|
|
7568
7728
|
JSON.stringify({ tasks: compactSeedTasks }, null, 2),
|
|
7569
|
-
`Docs: ${
|
|
7729
|
+
`Docs: ${compactDocSummary}`,
|
|
7570
7730
|
]
|
|
7571
7731
|
.filter((line) => Boolean(line))
|
|
7572
7732
|
.join("\n\n");
|
|
7573
7733
|
}
|
|
7574
|
-
buildCompactTaskSeeds(tasks) {
|
|
7734
|
+
buildCompactTaskSeeds(tasks, options) {
|
|
7735
|
+
const minimalContext = options?.minimalContext === true;
|
|
7736
|
+
const compactDescription = (value) => {
|
|
7737
|
+
const collapsed = `${value ?? ""}`.replace(/\s+/g, " ").trim();
|
|
7738
|
+
const maxLength = minimalContext ? 160 : 280;
|
|
7739
|
+
if (collapsed.length <= maxLength)
|
|
7740
|
+
return collapsed;
|
|
7741
|
+
return `${collapsed.slice(0, maxLength - 3).replace(/[ ,;:.-]+$/g, "")}...`;
|
|
7742
|
+
};
|
|
7575
7743
|
return tasks.map((task) => ({
|
|
7576
7744
|
localId: task.localId,
|
|
7577
7745
|
title: task.title,
|
|
7578
7746
|
type: task.type,
|
|
7579
|
-
description: task.description,
|
|
7580
|
-
files: normalizeStringArray(task.files),
|
|
7747
|
+
description: compactDescription(task.description),
|
|
7748
|
+
files: this.preferSpecificTaskTargets(normalizeStringArray(task.files)).slice(0, minimalContext ? 4 : 8),
|
|
7581
7749
|
estimatedStoryPoints: task.estimatedStoryPoints ?? null,
|
|
7582
7750
|
priorityHint: task.priorityHint ?? null,
|
|
7583
7751
|
dependsOnKeys: normalizeStringArray(task.dependsOnKeys),
|
|
7752
|
+
relatedDocs: normalizeRelatedDocs(task.relatedDocs).slice(0, minimalContext ? 2 : 4),
|
|
7753
|
+
unitTests: normalizeStringArray(task.unitTests).slice(0, minimalContext ? 1 : 2),
|
|
7754
|
+
componentTests: normalizeStringArray(task.componentTests).slice(0, minimalContext ? 1 : 2),
|
|
7755
|
+
integrationTests: normalizeStringArray(task.integrationTests).slice(0, minimalContext ? 1 : 2),
|
|
7756
|
+
apiTests: normalizeStringArray(task.apiTests).slice(0, minimalContext ? 1 : 2),
|
|
7584
7757
|
}));
|
|
7585
7758
|
}
|
|
7759
|
+
planCompactTaskRewriteChunks(params) {
|
|
7760
|
+
const initialContextMode = params.previousFailure ? "minimal" : "full";
|
|
7761
|
+
const queue = this.splitCompactTaskRewriteChunks(params.seedTasks).map((seedChunk) => ({
|
|
7762
|
+
seedTasks: seedChunk,
|
|
7763
|
+
contextMode: initialContextMode,
|
|
7764
|
+
}));
|
|
7765
|
+
const planned = [];
|
|
7766
|
+
while (queue.length > 0) {
|
|
7767
|
+
const current = queue.shift();
|
|
7768
|
+
const prompt = this.buildCompactSeededTaskPrompt({
|
|
7769
|
+
...params,
|
|
7770
|
+
seedTasks: current.seedTasks,
|
|
7771
|
+
chunkIndex: 0,
|
|
7772
|
+
chunkCount: 1,
|
|
7773
|
+
totalSeedTaskCount: params.seedTasks.length,
|
|
7774
|
+
contextMode: current.contextMode,
|
|
7775
|
+
});
|
|
7776
|
+
const promptTokens = estimateTokens(prompt);
|
|
7777
|
+
const promptLimit = current.contextMode === "minimal"
|
|
7778
|
+
? STRICT_AGENT_COMPACT_TASK_MINIMAL_PROMPT_TOKEN_LIMIT
|
|
7779
|
+
: STRICT_AGENT_COMPACT_TASK_RUNTIME_PROMPT_TOKEN_LIMIT;
|
|
7780
|
+
if (promptTokens > promptLimit && current.contextMode !== "minimal") {
|
|
7781
|
+
queue.unshift({ seedTasks: current.seedTasks, contextMode: "minimal" });
|
|
7782
|
+
continue;
|
|
7783
|
+
}
|
|
7784
|
+
if (promptTokens > promptLimit && current.seedTasks.length > 1) {
|
|
7785
|
+
const [left, right] = this.splitChunkInHalf(current.seedTasks);
|
|
7786
|
+
if (right.length > 0) {
|
|
7787
|
+
queue.unshift({ seedTasks: right, contextMode: "minimal" }, { seedTasks: left, contextMode: "minimal" });
|
|
7788
|
+
continue;
|
|
7789
|
+
}
|
|
7790
|
+
}
|
|
7791
|
+
planned.push(current);
|
|
7792
|
+
}
|
|
7793
|
+
return planned;
|
|
7794
|
+
}
|
|
7795
|
+
groupFallbackTaskTargets(targets, maxGroups) {
|
|
7796
|
+
const cleaned = this.preferSpecificTaskTargets(targets).filter((value) => Boolean(value));
|
|
7797
|
+
if (cleaned.length === 0 || maxGroups <= 0)
|
|
7798
|
+
return [];
|
|
7799
|
+
const groups = new Map();
|
|
7800
|
+
for (const target of cleaned) {
|
|
7801
|
+
const normalized = target.replace(/\\/g, "/");
|
|
7802
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
7803
|
+
const keyParts = isStructuredFilePath(path.basename(normalized)) ? parts.slice(0, -1) : parts;
|
|
7804
|
+
let key = target;
|
|
7805
|
+
if (keyParts.length >= 4) {
|
|
7806
|
+
key = keyParts.slice(0, 4).join("/");
|
|
7807
|
+
}
|
|
7808
|
+
else if (keyParts.length >= 3) {
|
|
7809
|
+
key = keyParts.slice(0, 3).join("/");
|
|
7810
|
+
}
|
|
7811
|
+
else if (keyParts.length >= 2) {
|
|
7812
|
+
key = keyParts.slice(0, 2).join("/");
|
|
7813
|
+
}
|
|
7814
|
+
const existing = groups.get(key) ?? [];
|
|
7815
|
+
existing.push(target);
|
|
7816
|
+
groups.set(key, existing);
|
|
7817
|
+
}
|
|
7818
|
+
const ordered = [...groups.values()]
|
|
7819
|
+
.map((group) => uniqueStrings(group))
|
|
7820
|
+
.sort((left, right) => right.length - left.length || left[0].localeCompare(right[0]));
|
|
7821
|
+
if (ordered.length <= maxGroups)
|
|
7822
|
+
return ordered;
|
|
7823
|
+
const head = ordered.slice(0, Math.max(1, maxGroups - 1));
|
|
7824
|
+
const tail = uniqueStrings(ordered.slice(Math.max(1, maxGroups - 1)).flat());
|
|
7825
|
+
return [...head, tail];
|
|
7826
|
+
}
|
|
7827
|
+
summarizeFallbackTargetGroup(targets) {
|
|
7828
|
+
if (targets.length === 0)
|
|
7829
|
+
return "Target Slice";
|
|
7830
|
+
const titleize = (value) => value
|
|
7831
|
+
.split(/[\s._/-]+/)
|
|
7832
|
+
.filter(Boolean)
|
|
7833
|
+
.map((token) => token[0].toUpperCase() + token.slice(1))
|
|
7834
|
+
.join(" ");
|
|
7835
|
+
const fileNames = uniqueStrings(targets
|
|
7836
|
+
.map((target) => path.basename(target))
|
|
7837
|
+
.filter((value) => isStructuredFilePath(value))
|
|
7838
|
+
.map((value) => value.replace(/\.[^.]+$/, "")));
|
|
7839
|
+
if (fileNames.length === 1)
|
|
7840
|
+
return titleize(fileNames[0]);
|
|
7841
|
+
if (fileNames.length >= 2)
|
|
7842
|
+
return `${titleize(fileNames[0])} and ${titleize(fileNames[1])}`;
|
|
7843
|
+
const firstTarget = targets[0];
|
|
7844
|
+
let root = path.dirname(firstTarget);
|
|
7845
|
+
try {
|
|
7846
|
+
root = this.extractArchitectureRoot(firstTarget) ?? root;
|
|
7847
|
+
}
|
|
7848
|
+
catch {
|
|
7849
|
+
root = path.dirname(firstTarget);
|
|
7850
|
+
}
|
|
7851
|
+
const label = root.split("/").filter(Boolean).slice(-2).join(" ");
|
|
7852
|
+
return titleize(label || firstTarget);
|
|
7853
|
+
}
|
|
7854
|
+
buildFallbackTestMetadata(storyTitle, targets) {
|
|
7855
|
+
const unitTests = [];
|
|
7856
|
+
const componentTests = [];
|
|
7857
|
+
const integrationTests = [];
|
|
7858
|
+
const apiTests = [];
|
|
7859
|
+
for (const target of uniqueStrings(targets).slice(0, 4)) {
|
|
7860
|
+
const lower = target.toLowerCase();
|
|
7861
|
+
const statement = `Exercise ${target} for ${storyTitle}.`;
|
|
7862
|
+
if (/\b(api|rpc|gateway|provider|endpoint)\b/.test(lower)) {
|
|
7863
|
+
apiTests.push(statement);
|
|
7864
|
+
}
|
|
7865
|
+
else if (/\b(component|screen|page|view|ui)\b/.test(lower)) {
|
|
7866
|
+
componentTests.push(statement);
|
|
7867
|
+
}
|
|
7868
|
+
else if (/\b(test|spec|scenario|e2e|integration|workflow|script|runbook|deploy)\b/.test(lower)) {
|
|
7869
|
+
integrationTests.push(statement);
|
|
7870
|
+
}
|
|
7871
|
+
else {
|
|
7872
|
+
unitTests.push(statement);
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7875
|
+
if (unitTests.length === 0 && componentTests.length === 0 && integrationTests.length === 0 && apiTests.length === 0) {
|
|
7876
|
+
integrationTests.push(`Execute focused readiness coverage for ${storyTitle}.`);
|
|
7877
|
+
}
|
|
7878
|
+
return { unitTests, componentTests, integrationTests, apiTests };
|
|
7879
|
+
}
|
|
7880
|
+
mergeCompactTaskMetadata(tasks, seedTasks) {
|
|
7881
|
+
if (tasks.length === 0 || seedTasks.length === 0)
|
|
7882
|
+
return tasks;
|
|
7883
|
+
const seedByLocalId = new Map(seedTasks.map((task) => [task.localId, task]));
|
|
7884
|
+
return tasks.map((task, index) => {
|
|
7885
|
+
const seed = (task.localId ? seedByLocalId.get(task.localId) : undefined) ?? seedTasks[index];
|
|
7886
|
+
if (!seed)
|
|
7887
|
+
return task;
|
|
7888
|
+
const mergedFiles = this.preferSpecificTaskTargets([
|
|
7889
|
+
...normalizeStringArray(task.files),
|
|
7890
|
+
...normalizeStringArray(seed.files),
|
|
7891
|
+
]).slice(0, 8);
|
|
7892
|
+
const taskRelatedDocs = normalizeRelatedDocs(task.relatedDocs);
|
|
7893
|
+
const taskUnitTests = normalizeStringArray(task.unitTests);
|
|
7894
|
+
const taskComponentTests = normalizeStringArray(task.componentTests);
|
|
7895
|
+
const taskIntegrationTests = normalizeStringArray(task.integrationTests);
|
|
7896
|
+
const taskApiTests = normalizeStringArray(task.apiTests);
|
|
7897
|
+
return {
|
|
7898
|
+
...task,
|
|
7899
|
+
files: mergedFiles.length > 0 ? mergedFiles : normalizeStringArray(seed.files),
|
|
7900
|
+
relatedDocs: taskRelatedDocs.length > 0 ? taskRelatedDocs : normalizeRelatedDocs(seed.relatedDocs),
|
|
7901
|
+
unitTests: taskUnitTests.length > 0 ? taskUnitTests : normalizeStringArray(seed.unitTests),
|
|
7902
|
+
componentTests: taskComponentTests.length > 0 ? taskComponentTests : normalizeStringArray(seed.componentTests),
|
|
7903
|
+
integrationTests: taskIntegrationTests.length > 0 ? taskIntegrationTests : normalizeStringArray(seed.integrationTests),
|
|
7904
|
+
apiTests: taskApiTests.length > 0 ? taskApiTests : normalizeStringArray(seed.apiTests),
|
|
7905
|
+
};
|
|
7906
|
+
});
|
|
7907
|
+
}
|
|
7586
7908
|
buildFallbackTasksForStory(story) {
|
|
7587
7909
|
const mode = this.classifyDerivedStoryMode(story);
|
|
7588
7910
|
const primaryTargets = this.extractStoryHintList(story.description, "Primary implementation targets");
|
|
@@ -7628,197 +7950,146 @@ export class CreateTasksService {
|
|
|
7628
7950
|
: defaultSupportingFiles.length > 0
|
|
7629
7951
|
? defaultSupportingFiles
|
|
7630
7952
|
: fallbackFiles;
|
|
7953
|
+
const taskGroups = [];
|
|
7954
|
+
const pushGroups = (kind, groups) => {
|
|
7955
|
+
for (const files of groups) {
|
|
7956
|
+
if (files.length > 0)
|
|
7957
|
+
taskGroups.push({ kind, files });
|
|
7958
|
+
}
|
|
7959
|
+
};
|
|
7960
|
+
const verificationSeedFiles = defaultVerificationFiles.length > 0 ? defaultVerificationFiles : uniqueStrings([...defaultSupportingFiles, ...defaultCoreFiles]);
|
|
7631
7961
|
if (mode === "verification") {
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
localId: "t-fallback-1",
|
|
7635
|
-
title: `Implement ${story.title} verification surfaces`,
|
|
7636
|
-
type: "feature",
|
|
7637
|
-
description: [
|
|
7638
|
-
`Implement the concrete verification and readiness surfaces for story "${story.title}".`,
|
|
7639
|
-
`Primary objective: ${objectiveLine}`,
|
|
7640
|
-
verificationLine,
|
|
7641
|
-
"Add or update the harness, assertions, fixtures, or operational hooks that make the story verifiable.",
|
|
7642
|
-
criteriaLines ? `Acceptance criteria to satisfy:\n${criteriaLines}` : "Acceptance criteria: use story definition.",
|
|
7643
|
-
].join("\n"),
|
|
7644
|
-
files: defaultVerificationFiles,
|
|
7645
|
-
estimatedStoryPoints: 3,
|
|
7646
|
-
priorityHint: 1,
|
|
7647
|
-
dependsOnKeys: [],
|
|
7648
|
-
relatedDocs: story.relatedDocs ?? [],
|
|
7649
|
-
unitTests: [],
|
|
7650
|
-
componentTests: [],
|
|
7651
|
-
integrationTests: [],
|
|
7652
|
-
apiTests: [],
|
|
7653
|
-
},
|
|
7654
|
-
{
|
|
7655
|
-
localId: "t-fallback-2",
|
|
7656
|
-
title: `Exercise ${story.title} regression paths`,
|
|
7657
|
-
type: "feature",
|
|
7658
|
-
description: [
|
|
7659
|
-
`Exercise the runtime, dependency, and failure paths covered by "${story.title}" after the verification surfaces exist.`,
|
|
7660
|
-
supportingLine,
|
|
7661
|
-
verificationLine,
|
|
7662
|
-
"Ensure the verification path covers the documented completion signals and dependency order.",
|
|
7663
|
-
].join("\n"),
|
|
7664
|
-
files: uniqueStrings([...defaultVerificationFiles, ...defaultSupportingFiles]).slice(0, 6),
|
|
7665
|
-
estimatedStoryPoints: 2,
|
|
7666
|
-
priorityHint: 2,
|
|
7667
|
-
dependsOnKeys: ["t-fallback-1"],
|
|
7668
|
-
relatedDocs: story.relatedDocs ?? [],
|
|
7669
|
-
unitTests: [],
|
|
7670
|
-
componentTests: [],
|
|
7671
|
-
integrationTests: [],
|
|
7672
|
-
apiTests: [],
|
|
7673
|
-
},
|
|
7674
|
-
{
|
|
7675
|
-
localId: "t-fallback-3",
|
|
7676
|
-
title: `Finalize ${story.title} readiness evidence`,
|
|
7677
|
-
type: "chore",
|
|
7678
|
-
description: [
|
|
7679
|
-
`Finalize readiness evidence for "${story.title}" once the verification path is executable.`,
|
|
7680
|
-
verificationLine,
|
|
7681
|
-
"Capture the focused regression signals, residual risks, and release-readiness artifacts required by the story.",
|
|
7682
|
-
].join("\n"),
|
|
7683
|
-
files: defaultVerificationFiles,
|
|
7684
|
-
estimatedStoryPoints: 1,
|
|
7685
|
-
priorityHint: 3,
|
|
7686
|
-
dependsOnKeys: ["t-fallback-2"],
|
|
7687
|
-
relatedDocs: story.relatedDocs ?? [],
|
|
7688
|
-
unitTests: [],
|
|
7689
|
-
componentTests: [],
|
|
7690
|
-
integrationTests: [],
|
|
7691
|
-
apiTests: [],
|
|
7692
|
-
},
|
|
7693
|
-
];
|
|
7962
|
+
pushGroups("primary", this.groupFallbackTaskTargets(verificationSeedFiles, 2));
|
|
7963
|
+
pushGroups("supporting", this.groupFallbackTaskTargets(defaultSupportingFiles, 1));
|
|
7694
7964
|
}
|
|
7695
|
-
if (mode === "integration") {
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7965
|
+
else if (mode === "integration") {
|
|
7966
|
+
pushGroups("primary", this.groupFallbackTaskTargets(defaultSupportingFiles, 2));
|
|
7967
|
+
pushGroups("supporting", this.groupFallbackTaskTargets(uniqueStrings([...defaultCoreFiles, ...defaultSupportingFiles]), 1));
|
|
7968
|
+
}
|
|
7969
|
+
else {
|
|
7970
|
+
pushGroups("primary", this.groupFallbackTaskTargets(defaultCoreFiles, 2));
|
|
7971
|
+
pushGroups("supporting", this.groupFallbackTaskTargets(defaultSupportingFiles, 2));
|
|
7972
|
+
}
|
|
7973
|
+
pushGroups("verification", this.groupFallbackTaskTargets(verificationSeedFiles, mode === "verification" ? 1 : 2));
|
|
7974
|
+
if (taskGroups.length === 0) {
|
|
7975
|
+
taskGroups.push({ kind: "primary", files: fallbackFiles.slice(0, 3) });
|
|
7976
|
+
taskGroups.push({ kind: "verification", files: fallbackFiles.slice(0, 3) });
|
|
7977
|
+
}
|
|
7978
|
+
const dedupedGroups = [];
|
|
7979
|
+
const seenGroupKeys = new Set();
|
|
7980
|
+
for (const group of taskGroups) {
|
|
7981
|
+
const key = `${group.kind}:${group.files.join("|")}`;
|
|
7982
|
+
if (seenGroupKeys.has(key))
|
|
7983
|
+
continue;
|
|
7984
|
+
seenGroupKeys.add(key);
|
|
7985
|
+
dedupedGroups.push(group);
|
|
7986
|
+
}
|
|
7987
|
+
const boundedGroups = this.boundFallbackTaskGroups(dedupedGroups, mode);
|
|
7988
|
+
return boundedGroups.map((group, index) => {
|
|
7989
|
+
const label = this.summarizeFallbackTargetGroup(group.files);
|
|
7990
|
+
const dependsOnKeys = index > 0 ? [`t-fallback-${index}`] : [];
|
|
7991
|
+
const acceptanceBlock = index === 0 && criteriaLines ? `Acceptance criteria to satisfy:\n${criteriaLines}` : "Acceptance criteria: use story definition.";
|
|
7992
|
+
if (group.kind === "primary") {
|
|
7993
|
+
return {
|
|
7994
|
+
localId: `t-fallback-${index + 1}`,
|
|
7995
|
+
title: mode === "verification"
|
|
7996
|
+
? `Build ${label} verification surfaces for ${story.title}`
|
|
7997
|
+
: `Implement ${label} for ${story.title}`,
|
|
7700
7998
|
type: "feature",
|
|
7701
7999
|
description: [
|
|
7702
|
-
`Implement the
|
|
8000
|
+
`Implement the core product behavior for story "${story.title}".`,
|
|
7703
8001
|
`Primary objective: ${objectiveLine}`,
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
8002
|
+
`Focused targets: ${group.files.join(", ")}.`,
|
|
8003
|
+
group.kind === "primary" && mode === "integration"
|
|
8004
|
+
? supportingLine
|
|
8005
|
+
: primaryLine,
|
|
8006
|
+
"Create or update the concrete modules and baseline execution path for this target group before downstream wiring.",
|
|
8007
|
+
acceptanceBlock,
|
|
7707
8008
|
].join("\n"),
|
|
7708
|
-
files:
|
|
7709
|
-
estimatedStoryPoints: 3,
|
|
7710
|
-
priorityHint: 1,
|
|
7711
|
-
dependsOnKeys
|
|
8009
|
+
files: group.files,
|
|
8010
|
+
estimatedStoryPoints: group.files.length > 2 ? 5 : 3,
|
|
8011
|
+
priorityHint: Math.max(1, 100 - index * 10),
|
|
8012
|
+
dependsOnKeys,
|
|
7712
8013
|
relatedDocs: story.relatedDocs ?? [],
|
|
7713
8014
|
unitTests: [],
|
|
7714
8015
|
componentTests: [],
|
|
7715
8016
|
integrationTests: [],
|
|
7716
8017
|
apiTests: [],
|
|
7717
|
-
}
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
8018
|
+
};
|
|
8019
|
+
}
|
|
8020
|
+
if (group.kind === "supporting") {
|
|
8021
|
+
return {
|
|
8022
|
+
localId: `t-fallback-${index + 1}`,
|
|
8023
|
+
title: `Wire ${label} into ${story.title}`,
|
|
7721
8024
|
type: "feature",
|
|
7722
8025
|
description: [
|
|
7723
|
-
`
|
|
7724
|
-
|
|
8026
|
+
`Integrate the supporting runtime and dependency surfaces for "${story.title}" after the prerequisite target groups are in place.`,
|
|
8027
|
+
`Focused targets: ${group.files.join(", ")}.`,
|
|
7725
8028
|
supportingLine,
|
|
7726
|
-
"
|
|
7727
|
-
].join("\n"),
|
|
7728
|
-
files: uniqueStrings([...defaultSupportingFiles, ...defaultCoreFiles]).slice(0, 6),
|
|
7729
|
-
estimatedStoryPoints: 2,
|
|
7730
|
-
priorityHint: 2,
|
|
7731
|
-
dependsOnKeys: ["t-fallback-1"],
|
|
7732
|
-
relatedDocs: story.relatedDocs ?? [],
|
|
7733
|
-
unitTests: [],
|
|
7734
|
-
componentTests: [],
|
|
7735
|
-
integrationTests: [],
|
|
7736
|
-
apiTests: [],
|
|
7737
|
-
},
|
|
7738
|
-
{
|
|
7739
|
-
localId: "t-fallback-3",
|
|
7740
|
-
title: `Verify ${story.title} integrated behavior`,
|
|
7741
|
-
type: "chore",
|
|
7742
|
-
description: [
|
|
7743
|
-
`Verify the integrated behavior and readiness surface for "${story.title}" after dependency alignment completes.`,
|
|
7744
|
-
verificationLine,
|
|
7745
|
-
"Add or update the focused validation path that proves the integration behaves in documented order.",
|
|
8029
|
+
"Align internal/external interfaces, dependency order, and runtime contracts across this target group.",
|
|
7746
8030
|
].join("\n"),
|
|
7747
|
-
files:
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
priorityHint: 3,
|
|
7752
|
-
dependsOnKeys: ["t-fallback-2"],
|
|
8031
|
+
files: group.files,
|
|
8032
|
+
estimatedStoryPoints: group.files.length > 2 ? 3 : 2,
|
|
8033
|
+
priorityHint: Math.max(1, 90 - index * 10),
|
|
8034
|
+
dependsOnKeys,
|
|
7753
8035
|
relatedDocs: story.relatedDocs ?? [],
|
|
7754
8036
|
unitTests: [],
|
|
7755
8037
|
componentTests: [],
|
|
7756
8038
|
integrationTests: [],
|
|
7757
8039
|
apiTests: [],
|
|
7758
|
-
}
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
title: mode === "core" ? `Implement ${story.title} target modules` : `Implement core scope for ${story.title}`,
|
|
7765
|
-
type: "feature",
|
|
7766
|
-
description: [
|
|
7767
|
-
`Implement the core product behavior for story "${story.title}".`,
|
|
7768
|
-
`Primary objective: ${objectiveLine}`,
|
|
7769
|
-
primaryLine,
|
|
7770
|
-
"Create or update concrete modules/files and wire baseline runtime paths first.",
|
|
7771
|
-
criteriaLines ? `Acceptance criteria to satisfy:\n${criteriaLines}` : "Acceptance criteria: use story definition.",
|
|
7772
|
-
].join("\n"),
|
|
7773
|
-
files: defaultCoreFiles,
|
|
7774
|
-
estimatedStoryPoints: 3,
|
|
7775
|
-
priorityHint: 1,
|
|
7776
|
-
dependsOnKeys: [],
|
|
7777
|
-
relatedDocs: story.relatedDocs ?? [],
|
|
7778
|
-
unitTests: [],
|
|
7779
|
-
componentTests: [],
|
|
7780
|
-
integrationTests: [],
|
|
7781
|
-
apiTests: [],
|
|
7782
|
-
},
|
|
7783
|
-
{
|
|
7784
|
-
localId: "t-fallback-2",
|
|
7785
|
-
title: mode === "core" ? `Wire ${story.title} supporting dependencies` : `Integrate dependencies for ${story.title}`,
|
|
7786
|
-
type: "feature",
|
|
7787
|
-
description: [
|
|
7788
|
-
`Integrate dependent interfaces and runtime dependencies for "${story.title}" after core scope implementation.`,
|
|
7789
|
-
supportingLine,
|
|
7790
|
-
"Align internal/external interfaces, data shapes, and dependency wiring with the documented context.",
|
|
7791
|
-
].join("\n"),
|
|
7792
|
-
files: defaultSupportingFiles,
|
|
7793
|
-
estimatedStoryPoints: 3,
|
|
7794
|
-
priorityHint: 2,
|
|
7795
|
-
dependsOnKeys: ["t-fallback-1"],
|
|
7796
|
-
relatedDocs: story.relatedDocs ?? [],
|
|
7797
|
-
unitTests: [],
|
|
7798
|
-
componentTests: [],
|
|
7799
|
-
integrationTests: [],
|
|
7800
|
-
apiTests: [],
|
|
7801
|
-
},
|
|
7802
|
-
{
|
|
7803
|
-
localId: "t-fallback-3",
|
|
7804
|
-
title: `Validate ${story.title} regressions and readiness`,
|
|
8040
|
+
};
|
|
8041
|
+
}
|
|
8042
|
+
const testMetadata = this.buildFallbackTestMetadata(story.title, group.files);
|
|
8043
|
+
return {
|
|
8044
|
+
localId: `t-fallback-${index + 1}`,
|
|
8045
|
+
title: `Validate ${label} for ${story.title}`,
|
|
7805
8046
|
type: "chore",
|
|
7806
8047
|
description: [
|
|
7807
|
-
`Validate "${story.title}"
|
|
8048
|
+
`Validate the completed story slice for "${story.title}" with focused regression coverage and readiness evidence.`,
|
|
8049
|
+
`Focused verification targets: ${group.files.join(", ")}.`,
|
|
7808
8050
|
verificationLine,
|
|
7809
|
-
"Add
|
|
8051
|
+
"Add or update the targeted verification path that proves this slice behaves correctly after implementation and wiring land.",
|
|
7810
8052
|
].join("\n"),
|
|
7811
|
-
files:
|
|
8053
|
+
files: group.files,
|
|
7812
8054
|
estimatedStoryPoints: 2,
|
|
7813
|
-
priorityHint:
|
|
7814
|
-
dependsOnKeys
|
|
8055
|
+
priorityHint: Math.max(1, 80 - index * 10),
|
|
8056
|
+
dependsOnKeys,
|
|
7815
8057
|
relatedDocs: story.relatedDocs ?? [],
|
|
7816
|
-
unitTests:
|
|
7817
|
-
componentTests:
|
|
7818
|
-
integrationTests:
|
|
7819
|
-
apiTests:
|
|
7820
|
-
}
|
|
7821
|
-
|
|
8058
|
+
unitTests: testMetadata.unitTests,
|
|
8059
|
+
componentTests: testMetadata.componentTests,
|
|
8060
|
+
integrationTests: testMetadata.integrationTests,
|
|
8061
|
+
apiTests: testMetadata.apiTests,
|
|
8062
|
+
};
|
|
8063
|
+
});
|
|
8064
|
+
}
|
|
8065
|
+
boundFallbackTaskGroups(groups, mode) {
|
|
8066
|
+
const budgets = mode === "verification"
|
|
8067
|
+
? { primary: 1, supporting: 1, verification: 1 }
|
|
8068
|
+
: { primary: 2, supporting: 1, verification: 1 };
|
|
8069
|
+
const mergedByKind = new Map();
|
|
8070
|
+
const passThroughCounts = new Map();
|
|
8071
|
+
const bounded = [];
|
|
8072
|
+
for (const group of groups) {
|
|
8073
|
+
const budget = budgets[group.kind];
|
|
8074
|
+
const passthroughBudget = Math.max(0, budget - 1);
|
|
8075
|
+
const currentCount = passThroughCounts.get(group.kind) ?? 0;
|
|
8076
|
+
if (currentCount < passthroughBudget) {
|
|
8077
|
+
bounded.push(group);
|
|
8078
|
+
passThroughCounts.set(group.kind, currentCount + 1);
|
|
8079
|
+
continue;
|
|
8080
|
+
}
|
|
8081
|
+
const existing = mergedByKind.get(group.kind) ?? [];
|
|
8082
|
+
mergedByKind.set(group.kind, [...existing, ...group.files]);
|
|
8083
|
+
}
|
|
8084
|
+
for (const [kind, files] of mergedByKind.entries()) {
|
|
8085
|
+
if (files.length === 0)
|
|
8086
|
+
continue;
|
|
8087
|
+
bounded.push({
|
|
8088
|
+
kind,
|
|
8089
|
+
files: this.preferSpecificTaskTargets(files).slice(0, 6),
|
|
8090
|
+
});
|
|
8091
|
+
}
|
|
8092
|
+
return bounded;
|
|
7822
8093
|
}
|
|
7823
8094
|
async generatePlanFromAgent(projectKey, epics, agent, docSummary, options) {
|
|
7824
8095
|
const planEpics = epics.map((epic, idx) => ({
|
|
@@ -7920,8 +8191,13 @@ export class CreateTasksService {
|
|
|
7920
8191
|
limitedStories = [this.buildFallbackStoryForEpic(epic)];
|
|
7921
8192
|
}
|
|
7922
8193
|
else {
|
|
8194
|
+
const fallbackStories = [this.buildFallbackStoryForEpic(epic)];
|
|
7923
8195
|
await this.jobService.appendLog(options.jobId, `Story generation returned no stories for epic "${epic.title}". Retrying through strict staged recovery.\n`);
|
|
7924
|
-
limitedStories = (await this.repairStoriesForEpic(agent, projectKey, { ...epic }, docSummary, options.projectBuildMethod, `No stories were returned for epic ${epic.title}.`,
|
|
8196
|
+
limitedStories = (await this.repairStoriesForEpic(agent, projectKey, { ...epic }, docSummary, options.projectBuildMethod, `No stories were returned for epic ${epic.title}.`, fallbackStories, options.agentStream, options.jobId, options.commandRunId)).slice(0, options.maxStoriesPerEpic ?? Number.MAX_SAFE_INTEGER);
|
|
8197
|
+
if (limitedStories.length === 0) {
|
|
8198
|
+
await this.jobService.appendLog(options.jobId, `Strict story repair returned no stories for epic "${epic.title}" after empty output. Using deterministic fallback story.\n`);
|
|
8199
|
+
limitedStories = fallbackStories.slice(0, options.maxStoriesPerEpic ?? Number.MAX_SAFE_INTEGER);
|
|
8200
|
+
}
|
|
7925
8201
|
}
|
|
7926
8202
|
}
|
|
7927
8203
|
limitedStories.forEach((story, idx) => {
|
|
@@ -7971,11 +8247,16 @@ export class CreateTasksService {
|
|
|
7971
8247
|
limitedTasks = this.buildFallbackTasksForStory(story).slice(0, options.maxTasksPerStory ?? Number.MAX_SAFE_INTEGER);
|
|
7972
8248
|
}
|
|
7973
8249
|
else {
|
|
8250
|
+
const fallbackTasks = this.buildFallbackTasksForStory(story);
|
|
7974
8251
|
await this.jobService.appendLog(options.jobId, `Task generation returned no tasks for story "${story.title}" (${storyScope}). Retrying through strict staged recovery.\n`);
|
|
7975
8252
|
limitedTasks = (await this.repairTasksForStory(agent, projectKey, {
|
|
7976
8253
|
key: story.epicLocalId,
|
|
7977
8254
|
title: epicTitleByLocalId.get(story.epicLocalId) ?? story.title,
|
|
7978
|
-
}, story, docSummary, options.projectBuildMethod, `No tasks were returned for story ${story.title}.`,
|
|
8255
|
+
}, story, docSummary, options.projectBuildMethod, `No tasks were returned for story ${story.title}.`, fallbackTasks, options.agentStream, options.jobId, options.commandRunId)).slice(0, options.maxTasksPerStory ?? Number.MAX_SAFE_INTEGER);
|
|
8256
|
+
if (limitedTasks.length === 0) {
|
|
8257
|
+
await this.jobService.appendLog(options.jobId, `Strict task repair returned no tasks for story "${story.title}" (${storyScope}) after empty output. Using deterministic fallback tasks.\n`);
|
|
8258
|
+
limitedTasks = fallbackTasks.slice(0, options.maxTasksPerStory ?? Number.MAX_SAFE_INTEGER);
|
|
8259
|
+
}
|
|
7979
8260
|
}
|
|
7980
8261
|
}
|
|
7981
8262
|
limitedTasks.forEach((task, idx) => {
|