@mcoda/core 0.1.23 → 0.1.26
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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/services/execution/TaskSelectionService.d.ts.map +1 -1
- package/dist/services/execution/TaskSelectionService.js +2 -4
- package/dist/services/planning/CreateTasksService.d.ts +8 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +387 -47
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +89 -15
- package/dist/services/planning/TaskSufficiencyService.d.ts +73 -0
- package/dist/services/planning/TaskSufficiencyService.d.ts.map +1 -0
- package/dist/services/planning/TaskSufficiencyService.js +704 -0
- package/package.json +6 -6
|
@@ -12,6 +12,7 @@ import { classifyTask } from "../backlog/TaskOrderingHeuristics.js";
|
|
|
12
12
|
import { TaskOrderingService } from "../backlog/TaskOrderingService.js";
|
|
13
13
|
import { QaTestCommandBuilder } from "../execution/QaTestCommandBuilder.js";
|
|
14
14
|
import { createEpicKeyGenerator, createStoryKeyGenerator, createTaskKeyGenerator, } from "./KeyHelpers.js";
|
|
15
|
+
import { TaskSufficiencyService } from "./TaskSufficiencyService.js";
|
|
15
16
|
const formatBullets = (items, fallback) => {
|
|
16
17
|
if (!items || items.length === 0)
|
|
17
18
|
return `- ${fallback}`;
|
|
@@ -116,6 +117,33 @@ const formatTestList = (items) => {
|
|
|
116
117
|
return items.join("; ");
|
|
117
118
|
};
|
|
118
119
|
const ensureNonEmpty = (value, fallback) => value && value.trim().length > 0 ? value.trim() : fallback;
|
|
120
|
+
const normalizeTaskLine = (line) => line.replace(/^[-*]\s+/, "").trim();
|
|
121
|
+
const looksLikeSectionHeader = (line) => /^\* \*\*.+\*\*$/.test(line.trim());
|
|
122
|
+
const isReferenceOnlyLine = (line) => /^(epic|story|references?|related docs?|inputs?|objective|context|implementation plan|definition of done|testing & qa)\s*:/i.test(line.trim());
|
|
123
|
+
const extractActionableLines = (description, limit) => {
|
|
124
|
+
if (!description)
|
|
125
|
+
return [];
|
|
126
|
+
const lines = description
|
|
127
|
+
.split(/\r?\n/)
|
|
128
|
+
.map((line) => normalizeTaskLine(line))
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.filter((line) => !looksLikeSectionHeader(line))
|
|
131
|
+
.filter((line) => !isReferenceOnlyLine(line));
|
|
132
|
+
const actionable = lines.filter((line) => /^(?:\d+[.)]\s+|implement\b|create\b|update\b|add\b|define\b|wire\b|integrate\b|enforce\b|publish\b|configure\b|materialize\b|validate\b|verify\b)/i.test(line));
|
|
133
|
+
const source = actionable.length > 0 ? actionable : lines;
|
|
134
|
+
return uniqueStrings(source.slice(0, limit));
|
|
135
|
+
};
|
|
136
|
+
const extractRiskLines = (description, limit) => {
|
|
137
|
+
if (!description)
|
|
138
|
+
return [];
|
|
139
|
+
const lines = description
|
|
140
|
+
.split(/\r?\n/)
|
|
141
|
+
.map((line) => normalizeTaskLine(line))
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
.filter((line) => !looksLikeSectionHeader(line));
|
|
144
|
+
const risks = lines.filter((line) => /\b(risk|edge case|gotcha|constraint|failure|flaky|drift|regression)\b/i.test(line));
|
|
145
|
+
return uniqueStrings(risks.slice(0, limit));
|
|
146
|
+
};
|
|
119
147
|
const extractScriptPort = (script) => {
|
|
120
148
|
const matches = [script.match(/(?:--port|-p)\s*(\d{2,5})/), script.match(/PORT\s*=\s*(\d{2,5})/)];
|
|
121
149
|
for (const match of matches) {
|
|
@@ -129,8 +157,18 @@ const extractScriptPort = (script) => {
|
|
|
129
157
|
};
|
|
130
158
|
const estimateTokens = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
131
159
|
const DOC_CONTEXT_BUDGET = 8000;
|
|
160
|
+
const DOC_CONTEXT_SEGMENTS_PER_DOC = 8;
|
|
161
|
+
const DOC_CONTEXT_FALLBACK_CHUNK_LENGTH = 480;
|
|
162
|
+
const SDS_COVERAGE_HINT_HEADING_LIMIT = 24;
|
|
163
|
+
const SDS_COVERAGE_REPORT_SECTION_LIMIT = 80;
|
|
132
164
|
const OPENAPI_HINT_OPERATIONS_LIMIT = 30;
|
|
133
165
|
const DOCDEX_HANDLE = /^docdex:/i;
|
|
166
|
+
const DOCDEX_LOCAL_HANDLE = /^docdex:local[-:/]/i;
|
|
167
|
+
const RELATED_DOC_PATH_PATTERN = /^(?:~\/|\/|[A-Za-z]:[\\/]|[A-Za-z0-9._-]+\/)[A-Za-z0-9._/-]+(?:\.[A-Za-z0-9._-]+)?(?:#[A-Za-z0-9._:-]+)?$/;
|
|
168
|
+
const RELATIVE_DOC_PATH_PATTERN = /^(?:\.{1,2}\/)+[A-Za-z0-9._/-]+(?:\.[A-Za-z0-9._-]+)?(?:#[A-Za-z0-9._:-]+)?$/;
|
|
169
|
+
const FUZZY_DOC_CANDIDATE_LIMIT = 64;
|
|
170
|
+
const DEPENDENCY_SCAN_LINE_LIMIT = 1400;
|
|
171
|
+
const STARTUP_WAVE_SCAN_LINE_LIMIT = 4000;
|
|
134
172
|
const VALID_AREAS = new Set(["web", "adm", "bck", "ops", "infra", "mobile"]);
|
|
135
173
|
const VALID_TASK_TYPES = new Set(["feature", "bug", "chore", "spike"]);
|
|
136
174
|
const inferDocType = (filePath) => {
|
|
@@ -176,16 +214,85 @@ const normalizeTaskType = (value) => {
|
|
|
176
214
|
const normalizeRelatedDocs = (value) => {
|
|
177
215
|
if (!Array.isArray(value))
|
|
178
216
|
return [];
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
217
|
+
const seen = new Set();
|
|
218
|
+
const normalized = [];
|
|
219
|
+
for (const entry of value) {
|
|
220
|
+
const candidate = typeof entry === "string"
|
|
221
|
+
? entry.trim()
|
|
222
|
+
: entry && typeof entry === "object" && "handle" in entry && typeof entry.handle === "string"
|
|
223
|
+
? entry.handle.trim()
|
|
224
|
+
: "";
|
|
225
|
+
if (!candidate)
|
|
226
|
+
continue;
|
|
227
|
+
if (DOCDEX_LOCAL_HANDLE.test(candidate))
|
|
228
|
+
continue;
|
|
229
|
+
const isDocHandle = DOCDEX_HANDLE.test(candidate);
|
|
230
|
+
const isHttp = /^https?:\/\/\S+$/i.test(candidate);
|
|
231
|
+
const isPath = RELATED_DOC_PATH_PATTERN.test(candidate) || RELATIVE_DOC_PATH_PATTERN.test(candidate);
|
|
232
|
+
if (!isDocHandle && !isHttp && !isPath)
|
|
233
|
+
continue;
|
|
234
|
+
if (seen.has(candidate))
|
|
235
|
+
continue;
|
|
236
|
+
seen.add(candidate);
|
|
237
|
+
normalized.push(candidate);
|
|
238
|
+
}
|
|
239
|
+
return normalized;
|
|
240
|
+
};
|
|
241
|
+
const extractMarkdownHeadings = (value, limit) => {
|
|
242
|
+
if (!value)
|
|
243
|
+
return [];
|
|
244
|
+
const lines = value.split(/\r?\n/);
|
|
245
|
+
const headings = [];
|
|
246
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
247
|
+
const line = lines[index]?.trim() ?? "";
|
|
248
|
+
if (!line)
|
|
249
|
+
continue;
|
|
250
|
+
const hashHeading = line.match(/^#{1,6}\s+(.+)$/);
|
|
251
|
+
if (hashHeading) {
|
|
252
|
+
headings.push(hashHeading[1].trim());
|
|
253
|
+
}
|
|
254
|
+
else if (index + 1 < lines.length &&
|
|
255
|
+
/^[=-]{3,}\s*$/.test((lines[index + 1] ?? "").trim()) &&
|
|
256
|
+
!line.startsWith("-") &&
|
|
257
|
+
!line.startsWith("*")) {
|
|
258
|
+
headings.push(line);
|
|
259
|
+
}
|
|
260
|
+
if (headings.length >= limit)
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
return uniqueStrings(headings
|
|
264
|
+
.map((entry) => entry.replace(/[`*_]/g, "").trim())
|
|
265
|
+
.filter(Boolean));
|
|
266
|
+
};
|
|
267
|
+
const pickDistributedIndices = (length, limit) => {
|
|
268
|
+
if (length <= 0 || limit <= 0)
|
|
269
|
+
return [];
|
|
270
|
+
if (length <= limit)
|
|
271
|
+
return Array.from({ length }, (_, index) => index);
|
|
272
|
+
const selected = new Set();
|
|
273
|
+
for (let index = 0; index < limit; index += 1) {
|
|
274
|
+
const ratio = limit === 1 ? 0 : index / (limit - 1);
|
|
275
|
+
selected.add(Math.round(ratio * (length - 1)));
|
|
276
|
+
}
|
|
277
|
+
return Array.from(selected)
|
|
278
|
+
.sort((a, b) => a - b)
|
|
279
|
+
.slice(0, limit);
|
|
280
|
+
};
|
|
281
|
+
const sampleRawContent = (value, chunkLength) => {
|
|
282
|
+
if (!value)
|
|
283
|
+
return [];
|
|
284
|
+
const content = value.trim();
|
|
285
|
+
if (!content)
|
|
286
|
+
return [];
|
|
287
|
+
if (content.length <= chunkLength)
|
|
288
|
+
return [content];
|
|
289
|
+
const anchors = [
|
|
290
|
+
0,
|
|
291
|
+
Math.max(0, Math.floor(content.length / 2) - Math.floor(chunkLength / 2)),
|
|
292
|
+
Math.max(0, content.length - chunkLength),
|
|
293
|
+
];
|
|
294
|
+
const sampled = anchors.map((anchor) => content.slice(anchor, anchor + chunkLength).trim()).filter(Boolean);
|
|
295
|
+
return uniqueStrings(sampled);
|
|
189
296
|
};
|
|
190
297
|
const isPlainObject = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
191
298
|
const parseStructuredDoc = (raw) => {
|
|
@@ -356,11 +463,38 @@ const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, re
|
|
|
356
463
|
})
|
|
357
464
|
.join("\n");
|
|
358
465
|
};
|
|
466
|
+
const objectiveText = ensureNonEmpty(description, `Deliver ${title} for story ${storyKey}.`);
|
|
467
|
+
const implementationLines = extractActionableLines(description, 4);
|
|
468
|
+
const riskLines = extractRiskLines(description, 3);
|
|
469
|
+
const testsDefined = (tests.unitTests?.length ?? 0) +
|
|
470
|
+
(tests.componentTests?.length ?? 0) +
|
|
471
|
+
(tests.integrationTests?.length ?? 0) +
|
|
472
|
+
(tests.apiTests?.length ?? 0) >
|
|
473
|
+
0;
|
|
474
|
+
const definitionOfDone = [
|
|
475
|
+
`- Implementation for \`${taskKey}\` is complete and scoped to ${storyKey}.`,
|
|
476
|
+
testsDefined
|
|
477
|
+
? "- Task-specific tests are added/updated and green in the task validation loop."
|
|
478
|
+
: "- Verification evidence is captured in task logs/checklists for this scope.",
|
|
479
|
+
relatedDocs?.length
|
|
480
|
+
? "- Related contracts/docs are consistent with delivered behavior."
|
|
481
|
+
: "- Documentation impact is reviewed and no additional contract docs are required.",
|
|
482
|
+
qa?.blockers?.length ? "- Remaining QA blockers are explicit and actionable." : "- QA blockers are resolved or not present.",
|
|
483
|
+
];
|
|
484
|
+
const defaultImplementationPlan = [
|
|
485
|
+
`- Implement ${title} with file/module-level changes aligned to the objective.`,
|
|
486
|
+
dependencies.length
|
|
487
|
+
? `- Respect dependency order before completion: ${dependencies.join(", ")}.`
|
|
488
|
+
: "- Validate assumptions and finalize concrete implementation steps before coding.",
|
|
489
|
+
];
|
|
490
|
+
const defaultRisks = dependencies.length
|
|
491
|
+
? [`- Delivery depends on upstream tasks: ${dependencies.join(", ")}.`]
|
|
492
|
+
: ["- Keep implementation aligned to SDS/OpenAPI contracts to avoid drift."];
|
|
359
493
|
return [
|
|
360
494
|
`* **Task Key**: ${taskKey}`,
|
|
361
495
|
"* **Objective**",
|
|
362
496
|
"",
|
|
363
|
-
|
|
497
|
+
objectiveText,
|
|
364
498
|
"* **Context**",
|
|
365
499
|
"",
|
|
366
500
|
`- Epic: ${epicKey}`,
|
|
@@ -368,9 +502,9 @@ const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, re
|
|
|
368
502
|
"* **Inputs**",
|
|
369
503
|
formatBullets(relatedDocs, "Docdex excerpts, SDS/PDR/RFP sections, OpenAPI endpoints."),
|
|
370
504
|
"* **Implementation Plan**",
|
|
371
|
-
|
|
505
|
+
formatBullets(implementationLines, defaultImplementationPlan.join(" ")),
|
|
372
506
|
"* **Definition of Done**",
|
|
373
|
-
|
|
507
|
+
definitionOfDone.join("\n"),
|
|
374
508
|
"* **Testing & QA**",
|
|
375
509
|
`- Unit tests: ${formatTestList(tests.unitTests)}`,
|
|
376
510
|
`- Component tests: ${formatTestList(tests.componentTests)}`,
|
|
@@ -387,7 +521,7 @@ const buildTaskDescription = (taskKey, title, description, storyKey, epicKey, re
|
|
|
387
521
|
"* **Dependencies**",
|
|
388
522
|
formatBullets(dependencies, "Enumerate prerequisite tasks by key."),
|
|
389
523
|
"* **Risks & Gotchas**",
|
|
390
|
-
|
|
524
|
+
formatBullets(riskLines, defaultRisks.join(" ")),
|
|
391
525
|
"* **Related Documentation / References**",
|
|
392
526
|
formatBullets(relatedDocs, "Docdex handles or file paths to consult."),
|
|
393
527
|
].join("\n");
|
|
@@ -594,6 +728,7 @@ export class CreateTasksService {
|
|
|
594
728
|
this.routingService = deps.routingService;
|
|
595
729
|
this.ratingService = deps.ratingService;
|
|
596
730
|
this.taskOrderingFactory = deps.taskOrderingFactory ?? TaskOrderingService.create;
|
|
731
|
+
this.taskSufficiencyFactory = deps.taskSufficiencyFactory ?? TaskSufficiencyService.create;
|
|
597
732
|
}
|
|
598
733
|
static async create(workspace) {
|
|
599
734
|
const repo = await GlobalRepository.create();
|
|
@@ -614,6 +749,7 @@ export class CreateTasksService {
|
|
|
614
749
|
repo,
|
|
615
750
|
workspaceRepo,
|
|
616
751
|
routingService,
|
|
752
|
+
taskSufficiencyFactory: TaskSufficiencyService.create,
|
|
617
753
|
});
|
|
618
754
|
}
|
|
619
755
|
async close() {
|
|
@@ -903,7 +1039,7 @@ export class CreateTasksService {
|
|
|
903
1039
|
}
|
|
904
1040
|
return ranked
|
|
905
1041
|
.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
|
|
906
|
-
.slice(0,
|
|
1042
|
+
.slice(0, FUZZY_DOC_CANDIDATE_LIMIT)
|
|
907
1043
|
.map((entry) => entry.path);
|
|
908
1044
|
}
|
|
909
1045
|
normalizeStructurePathToken(value) {
|
|
@@ -1072,7 +1208,7 @@ export class CreateTasksService {
|
|
|
1072
1208
|
.split(/\r?\n/)
|
|
1073
1209
|
.map((line) => line.trim())
|
|
1074
1210
|
.filter(Boolean)
|
|
1075
|
-
.slice(0,
|
|
1211
|
+
.slice(0, DEPENDENCY_SCAN_LINE_LIMIT);
|
|
1076
1212
|
const dependencyPatterns = [
|
|
1077
1213
|
{
|
|
1078
1214
|
regex: /^(.+?)\b(?:depends on|requires|needs|uses|consumes|calls|reads from|writes to|must come after|comes after|built after|runs after|backed by)\b(.+)$/i,
|
|
@@ -1156,7 +1292,7 @@ export class CreateTasksService {
|
|
|
1156
1292
|
.split(/\r?\n/)
|
|
1157
1293
|
.map((line) => line.trim())
|
|
1158
1294
|
.filter(Boolean)
|
|
1159
|
-
.slice(0,
|
|
1295
|
+
.slice(0, STARTUP_WAVE_SCAN_LINE_LIMIT);
|
|
1160
1296
|
for (const line of lines) {
|
|
1161
1297
|
if (!line.startsWith("|"))
|
|
1162
1298
|
continue;
|
|
@@ -1949,23 +2085,59 @@ export class CreateTasksService {
|
|
|
1949
2085
|
}
|
|
1950
2086
|
return lines.join("\n");
|
|
1951
2087
|
}
|
|
2088
|
+
extractSdsSectionCandidates(docs, limit) {
|
|
2089
|
+
const sections = [];
|
|
2090
|
+
for (const doc of docs) {
|
|
2091
|
+
if (!looksLikeSdsDoc(doc))
|
|
2092
|
+
continue;
|
|
2093
|
+
const segmentHeadings = (doc.segments ?? [])
|
|
2094
|
+
.map((segment) => segment.heading?.trim())
|
|
2095
|
+
.filter((heading) => Boolean(heading));
|
|
2096
|
+
const contentHeadings = extractMarkdownHeadings(doc.content ?? "", limit);
|
|
2097
|
+
for (const heading of [...segmentHeadings, ...contentHeadings]) {
|
|
2098
|
+
const normalized = heading.replace(/[`*_]/g, "").trim();
|
|
2099
|
+
if (!normalized)
|
|
2100
|
+
continue;
|
|
2101
|
+
sections.push(normalized);
|
|
2102
|
+
if (sections.length >= limit)
|
|
2103
|
+
break;
|
|
2104
|
+
}
|
|
2105
|
+
if (sections.length >= limit)
|
|
2106
|
+
break;
|
|
2107
|
+
}
|
|
2108
|
+
return uniqueStrings(sections).slice(0, limit);
|
|
2109
|
+
}
|
|
2110
|
+
buildSdsCoverageHints(docs) {
|
|
2111
|
+
const hints = this.extractSdsSectionCandidates(docs, SDS_COVERAGE_HINT_HEADING_LIMIT);
|
|
2112
|
+
if (hints.length === 0)
|
|
2113
|
+
return "";
|
|
2114
|
+
return hints.map((hint) => `- ${hint}`).join("\n");
|
|
2115
|
+
}
|
|
1952
2116
|
buildDocContext(docs) {
|
|
1953
2117
|
const warnings = [];
|
|
1954
2118
|
const blocks = [];
|
|
1955
2119
|
let budget = DOC_CONTEXT_BUDGET;
|
|
1956
|
-
const sorted = [...docs].sort((a, b) =>
|
|
2120
|
+
const sorted = [...docs].sort((a, b) => {
|
|
2121
|
+
const sdsDelta = Number(looksLikeSdsDoc(b)) - Number(looksLikeSdsDoc(a));
|
|
2122
|
+
if (sdsDelta !== 0)
|
|
2123
|
+
return sdsDelta;
|
|
2124
|
+
return (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "");
|
|
2125
|
+
});
|
|
1957
2126
|
for (const [idx, doc] of sorted.entries()) {
|
|
1958
|
-
const segments =
|
|
1959
|
-
const
|
|
1960
|
-
|
|
2127
|
+
const segments = doc.segments ?? [];
|
|
2128
|
+
const sampledSegments = pickDistributedIndices(segments.length, DOC_CONTEXT_SEGMENTS_PER_DOC)
|
|
2129
|
+
.map((index) => segments[index])
|
|
2130
|
+
.filter(Boolean);
|
|
2131
|
+
const content = sampledSegments.length
|
|
2132
|
+
? sampledSegments
|
|
1961
2133
|
.map((seg, i) => {
|
|
1962
2134
|
const trimmed = seg.content.length > 600 ? `${seg.content.slice(0, 600)}...` : seg.content;
|
|
1963
2135
|
return ` - (${i + 1}) ${seg.heading ? `${seg.heading}: ` : ""}${trimmed}`;
|
|
1964
2136
|
})
|
|
1965
2137
|
.join("\n")
|
|
1966
|
-
: doc.content
|
|
1967
|
-
?
|
|
1968
|
-
|
|
2138
|
+
: sampleRawContent(doc.content, DOC_CONTEXT_FALLBACK_CHUNK_LENGTH)
|
|
2139
|
+
.map((chunk, i) => ` - (${i + 1}) ${chunk.length > 600 ? `${chunk.slice(0, 600)}...` : chunk}`)
|
|
2140
|
+
.join("\n");
|
|
1969
2141
|
const entry = [`[${doc.docType}] docdex:${doc.id ?? `doc-${idx + 1}`}`, describeDoc(doc, idx), content]
|
|
1970
2142
|
.filter(Boolean)
|
|
1971
2143
|
.join("\n");
|
|
@@ -1991,6 +2163,18 @@ export class CreateTasksService {
|
|
|
1991
2163
|
warnings.push("Context truncated due to token budget; skipped OpenAPI hint summary.");
|
|
1992
2164
|
}
|
|
1993
2165
|
}
|
|
2166
|
+
const sdsCoverageHints = this.buildSdsCoverageHints(sorted);
|
|
2167
|
+
if (sdsCoverageHints) {
|
|
2168
|
+
const hintBlock = ["[SDS_COVERAGE_HINTS]", sdsCoverageHints].join("\n");
|
|
2169
|
+
const hintCost = estimateTokens(hintBlock);
|
|
2170
|
+
if (budget - hintCost >= 0) {
|
|
2171
|
+
budget -= hintCost;
|
|
2172
|
+
blocks.push(hintBlock);
|
|
2173
|
+
}
|
|
2174
|
+
else {
|
|
2175
|
+
warnings.push("Context truncated due to token budget; skipped SDS coverage hints.");
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
1994
2178
|
return { docSummary: blocks.join("\n\n") || "(no docs)", warnings };
|
|
1995
2179
|
}
|
|
1996
2180
|
buildPrompt(projectKey, docs, projectBuildMethod, options) {
|
|
@@ -2047,10 +2231,10 @@ export class CreateTasksService {
|
|
|
2047
2231
|
tasks: [
|
|
2048
2232
|
{
|
|
2049
2233
|
localId: "task-1",
|
|
2050
|
-
title: "
|
|
2051
|
-
type: "
|
|
2052
|
-
description: "
|
|
2053
|
-
estimatedStoryPoints:
|
|
2234
|
+
title: "Implement baseline project scaffolding",
|
|
2235
|
+
type: "feature",
|
|
2236
|
+
description: "Create SDS-aligned baseline structure and core implementation entrypoints from the available docs.",
|
|
2237
|
+
estimatedStoryPoints: 3,
|
|
2054
2238
|
priorityHint: 10,
|
|
2055
2239
|
relatedDocs: docRefs,
|
|
2056
2240
|
unitTests: [],
|
|
@@ -2060,10 +2244,10 @@ export class CreateTasksService {
|
|
|
2060
2244
|
},
|
|
2061
2245
|
{
|
|
2062
2246
|
localId: "task-2",
|
|
2063
|
-
title: "
|
|
2247
|
+
title: "Integrate core contracts and dependencies",
|
|
2064
2248
|
type: "feature",
|
|
2065
|
-
description: "
|
|
2066
|
-
estimatedStoryPoints:
|
|
2249
|
+
description: "Wire key contracts/interfaces and dependency paths so core behavior can execute end-to-end.",
|
|
2250
|
+
estimatedStoryPoints: 3,
|
|
2067
2251
|
priorityHint: 20,
|
|
2068
2252
|
dependsOnKeys: ["task-1"],
|
|
2069
2253
|
relatedDocs: docRefs,
|
|
@@ -2072,6 +2256,20 @@ export class CreateTasksService {
|
|
|
2072
2256
|
integrationTests: [],
|
|
2073
2257
|
apiTests: [],
|
|
2074
2258
|
},
|
|
2259
|
+
{
|
|
2260
|
+
localId: "task-3",
|
|
2261
|
+
title: "Validate baseline behavior and regressions",
|
|
2262
|
+
type: "chore",
|
|
2263
|
+
description: "Add targeted validation coverage and readiness evidence for the implemented baseline capabilities.",
|
|
2264
|
+
estimatedStoryPoints: 2,
|
|
2265
|
+
priorityHint: 30,
|
|
2266
|
+
dependsOnKeys: ["task-2"],
|
|
2267
|
+
relatedDocs: docRefs,
|
|
2268
|
+
unitTests: [],
|
|
2269
|
+
componentTests: [],
|
|
2270
|
+
integrationTests: [],
|
|
2271
|
+
apiTests: [],
|
|
2272
|
+
},
|
|
2075
2273
|
],
|
|
2076
2274
|
},
|
|
2077
2275
|
],
|
|
@@ -2306,17 +2504,22 @@ export class CreateTasksService {
|
|
|
2306
2504
|
TASK_SCHEMA_SNIPPET,
|
|
2307
2505
|
"Rules:",
|
|
2308
2506
|
"- Each task must include localId, title, description, type, estimatedStoryPoints, priorityHint.",
|
|
2507
|
+
"- Descriptions must be implementation-concrete and include target modules/files/services where work happens.",
|
|
2508
|
+
"- Prioritize software construction tasks before test-only/docs-only chores unless story scope explicitly requires those first.",
|
|
2309
2509
|
"- Include test arrays: unitTests, componentTests, integrationTests, apiTests. Use [] when not applicable.",
|
|
2310
2510
|
"- Only include tests that are relevant to the task's scope.",
|
|
2311
2511
|
"- Prefer including task-relevant tests when they are concrete and actionable; do not invent generic placeholders.",
|
|
2312
2512
|
"- When known, include qa object with profiles_expected/requires/entrypoints/data_setup to guide QA.",
|
|
2313
2513
|
"- Do not hardcode ports. For QA entrypoints, use http://localhost:<PORT> placeholders or omit base_url when unknown.",
|
|
2314
2514
|
"- dependsOnKeys must reference localIds in this story.",
|
|
2515
|
+
"- If dependsOnKeys is non-empty, include dependency rationale in the task description.",
|
|
2315
2516
|
"- Start from prerequisite codebase setup: add structure/bootstrap tasks before feature tasks when missing.",
|
|
2316
2517
|
"- Keep dependencies strictly inside this story; never reference tasks from other stories/epics.",
|
|
2317
2518
|
"- Order tasks from foundational prerequisites to dependents based on documented dependency direction and startup constraints.",
|
|
2519
|
+
"- Avoid placeholder wording (TBD, TODO, to be defined, generic follow-up phrases).",
|
|
2318
2520
|
"- Use docdex handles when citing docs.",
|
|
2319
2521
|
"- If OPENAPI_HINTS are present in Docs, align tasks with hinted service/capability/stage/test_requirements.",
|
|
2522
|
+
"- If SDS_COVERAGE_HINTS are present in Docs, cover the relevant SDS sections in implementation tasks.",
|
|
2320
2523
|
"- Follow the project construction method and startup-wave order from SDS when available.",
|
|
2321
2524
|
`Story context (key=${story.key ?? story.localId ?? "TBD"}):`,
|
|
2322
2525
|
story.description ?? story.userStory ?? "",
|
|
@@ -2387,17 +2590,22 @@ export class CreateTasksService {
|
|
|
2387
2590
|
.slice(0, 6)
|
|
2388
2591
|
.map((criterion) => `- ${criterion}`)
|
|
2389
2592
|
.join("\n");
|
|
2593
|
+
const objectiveLine = story.description && story.description.trim().length > 0
|
|
2594
|
+
? story.description.trim().split(/\r?\n/)[0]
|
|
2595
|
+
: `Deliver story scope for "${story.title}".`;
|
|
2390
2596
|
return [
|
|
2391
2597
|
{
|
|
2392
2598
|
localId: "t-fallback-1",
|
|
2393
|
-
title: `
|
|
2394
|
-
type: "
|
|
2599
|
+
title: `Implement core scope for ${story.title}`,
|
|
2600
|
+
type: "feature",
|
|
2395
2601
|
description: [
|
|
2396
|
-
`
|
|
2397
|
-
|
|
2602
|
+
`Implement the core product behavior for story "${story.title}".`,
|
|
2603
|
+
`Primary objective: ${objectiveLine}`,
|
|
2604
|
+
"Create or update concrete modules/files and wire baseline runtime paths first.",
|
|
2605
|
+
"Capture exact implementation targets and sequencing in commit-level task notes.",
|
|
2398
2606
|
criteriaLines ? `Acceptance criteria to satisfy:\n${criteriaLines}` : "Acceptance criteria: use story definition.",
|
|
2399
2607
|
].join("\n"),
|
|
2400
|
-
estimatedStoryPoints:
|
|
2608
|
+
estimatedStoryPoints: 3,
|
|
2401
2609
|
priorityHint: 1,
|
|
2402
2610
|
dependsOnKeys: [],
|
|
2403
2611
|
relatedDocs: story.relatedDocs ?? [],
|
|
@@ -2408,11 +2616,12 @@ export class CreateTasksService {
|
|
|
2408
2616
|
},
|
|
2409
2617
|
{
|
|
2410
2618
|
localId: "t-fallback-2",
|
|
2411
|
-
title: `
|
|
2619
|
+
title: `Integrate contracts for ${story.title}`,
|
|
2412
2620
|
type: "feature",
|
|
2413
2621
|
description: [
|
|
2414
|
-
`
|
|
2415
|
-
"
|
|
2622
|
+
`Integrate dependent contracts/interfaces for "${story.title}" after core scope implementation.`,
|
|
2623
|
+
"Align internal/external interfaces, data contracts, and dependency wiring with SDS/OpenAPI context.",
|
|
2624
|
+
"Record dependency rationale and compatibility constraints in the task output.",
|
|
2416
2625
|
].join("\n"),
|
|
2417
2626
|
estimatedStoryPoints: 3,
|
|
2418
2627
|
priorityHint: 2,
|
|
@@ -2423,6 +2632,24 @@ export class CreateTasksService {
|
|
|
2423
2632
|
integrationTests: [],
|
|
2424
2633
|
apiTests: [],
|
|
2425
2634
|
},
|
|
2635
|
+
{
|
|
2636
|
+
localId: "t-fallback-3",
|
|
2637
|
+
title: `Validate ${story.title} regressions and readiness`,
|
|
2638
|
+
type: "chore",
|
|
2639
|
+
description: [
|
|
2640
|
+
`Validate "${story.title}" end-to-end with focused regression coverage and readiness evidence.`,
|
|
2641
|
+
"Add/update targeted tests and verification scripts tied to implemented behavior.",
|
|
2642
|
+
"Document release/code-review/QA evidence and unresolved risks explicitly.",
|
|
2643
|
+
].join("\n"),
|
|
2644
|
+
estimatedStoryPoints: 2,
|
|
2645
|
+
priorityHint: 3,
|
|
2646
|
+
dependsOnKeys: ["t-fallback-2"],
|
|
2647
|
+
relatedDocs: story.relatedDocs ?? [],
|
|
2648
|
+
unitTests: [],
|
|
2649
|
+
componentTests: [],
|
|
2650
|
+
integrationTests: [],
|
|
2651
|
+
apiTests: [],
|
|
2652
|
+
},
|
|
2426
2653
|
];
|
|
2427
2654
|
}
|
|
2428
2655
|
async generatePlanFromAgent(epics, agent, docSummary, options) {
|
|
@@ -2493,7 +2720,53 @@ export class CreateTasksService {
|
|
|
2493
2720
|
}
|
|
2494
2721
|
return { epics: planEpics, stories: planStories, tasks: planTasks };
|
|
2495
2722
|
}
|
|
2496
|
-
|
|
2723
|
+
buildSdsCoverageReport(projectKey, docs, plan) {
|
|
2724
|
+
const sections = this.extractSdsSectionCandidates(docs, SDS_COVERAGE_REPORT_SECTION_LIMIT);
|
|
2725
|
+
const normalize = (value) => value
|
|
2726
|
+
.toLowerCase()
|
|
2727
|
+
.replace(/[`*_]/g, "")
|
|
2728
|
+
.replace(/[^a-z0-9\s/-]+/g, " ")
|
|
2729
|
+
.replace(/\s+/g, " ")
|
|
2730
|
+
.trim();
|
|
2731
|
+
const planCorpus = normalize([
|
|
2732
|
+
...plan.epics.map((epic) => `${epic.title} ${epic.description ?? ""} ${(epic.acceptanceCriteria ?? []).join(" ")}`),
|
|
2733
|
+
...plan.stories.map((story) => `${story.title} ${story.userStory ?? ""} ${story.description ?? ""} ${(story.acceptanceCriteria ?? []).join(" ")}`),
|
|
2734
|
+
...plan.tasks.map((task) => `${task.title} ${task.description ?? ""}`),
|
|
2735
|
+
].join("\n"));
|
|
2736
|
+
const matched = [];
|
|
2737
|
+
const unmatched = [];
|
|
2738
|
+
for (const section of sections) {
|
|
2739
|
+
const normalizedSection = normalize(section);
|
|
2740
|
+
if (!normalizedSection)
|
|
2741
|
+
continue;
|
|
2742
|
+
const keywords = normalizedSection
|
|
2743
|
+
.split(/\s+/)
|
|
2744
|
+
.filter((token) => token.length >= 4)
|
|
2745
|
+
.slice(0, 6);
|
|
2746
|
+
const hasDirectMatch = normalizedSection.length >= 6 && planCorpus.includes(normalizedSection);
|
|
2747
|
+
const hasKeywordMatch = keywords.some((keyword) => planCorpus.includes(keyword));
|
|
2748
|
+
if (hasDirectMatch || hasKeywordMatch) {
|
|
2749
|
+
matched.push(section);
|
|
2750
|
+
}
|
|
2751
|
+
else {
|
|
2752
|
+
unmatched.push(section);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
const totalSections = matched.length + unmatched.length;
|
|
2756
|
+
const coverageRatio = totalSections === 0 ? 1 : matched.length / totalSections;
|
|
2757
|
+
return {
|
|
2758
|
+
projectKey,
|
|
2759
|
+
generatedAt: new Date().toISOString(),
|
|
2760
|
+
totalSections,
|
|
2761
|
+
matched,
|
|
2762
|
+
unmatched,
|
|
2763
|
+
coverageRatio: Number(coverageRatio.toFixed(4)),
|
|
2764
|
+
notes: totalSections === 0
|
|
2765
|
+
? ["No SDS section headings detected; coverage defaults to 1.0."]
|
|
2766
|
+
: ["Coverage is heading-based heuristic match between SDS sections and generated epic/story/task corpus."],
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
async writePlanArtifacts(projectKey, plan, docSummary, docs) {
|
|
2497
2770
|
const baseDir = path.join(this.workspace.mcodaDir, "tasks", projectKey);
|
|
2498
2771
|
await fs.mkdir(baseDir, { recursive: true });
|
|
2499
2772
|
const write = async (file, data) => {
|
|
@@ -2504,6 +2777,7 @@ export class CreateTasksService {
|
|
|
2504
2777
|
await write("epics.json", plan.epics);
|
|
2505
2778
|
await write("stories.json", plan.stories);
|
|
2506
2779
|
await write("tasks.json", plan.tasks);
|
|
2780
|
+
await write("coverage-report.json", this.buildSdsCoverageReport(projectKey, docs, plan));
|
|
2507
2781
|
return { folder: baseDir };
|
|
2508
2782
|
}
|
|
2509
2783
|
async persistPlanToDb(projectId, projectKey, plan, jobId, commandRunId, options) {
|
|
@@ -2632,12 +2906,7 @@ export class CreateTasksService {
|
|
|
2632
2906
|
discoveredTestCommands = [];
|
|
2633
2907
|
}
|
|
2634
2908
|
}
|
|
2635
|
-
const qaBlockers = uniqueStrings([
|
|
2636
|
-
...(qaReadiness.blockers ?? []),
|
|
2637
|
-
...(testsRequired && discoveredTestCommands.length === 0
|
|
2638
|
-
? ["No runnable test harness discovered for required tests during planning."]
|
|
2639
|
-
: []),
|
|
2640
|
-
]);
|
|
2909
|
+
const qaBlockers = uniqueStrings(qaReadiness.blockers ?? []);
|
|
2641
2910
|
const qaReadinessWithHarness = {
|
|
2642
2911
|
...qaReadiness,
|
|
2643
2912
|
blockers: qaBlockers.length ? qaBlockers : undefined,
|
|
@@ -2833,7 +3102,7 @@ export class CreateTasksService {
|
|
|
2833
3102
|
timestamp: new Date().toISOString(),
|
|
2834
3103
|
details: { tasks: plan.tasks.length, source: planSource, fallbackReason },
|
|
2835
3104
|
});
|
|
2836
|
-
const { folder } = await this.writePlanArtifacts(options.projectKey, plan, docSummary);
|
|
3105
|
+
const { folder } = await this.writePlanArtifacts(options.projectKey, plan, docSummary, docs);
|
|
2837
3106
|
await this.jobService.writeCheckpoint(job.id, {
|
|
2838
3107
|
stage: "plan_written",
|
|
2839
3108
|
timestamp: new Date().toISOString(),
|
|
@@ -2846,6 +3115,61 @@ export class CreateTasksService {
|
|
|
2846
3115
|
qaOverrides,
|
|
2847
3116
|
});
|
|
2848
3117
|
await this.seedPriorities(options.projectKey);
|
|
3118
|
+
let sufficiencyAudit;
|
|
3119
|
+
let sufficiencyAuditError;
|
|
3120
|
+
if (this.taskSufficiencyFactory) {
|
|
3121
|
+
try {
|
|
3122
|
+
const sufficiencyService = await this.taskSufficiencyFactory(this.workspace);
|
|
3123
|
+
try {
|
|
3124
|
+
try {
|
|
3125
|
+
sufficiencyAudit = await sufficiencyService.runAudit({
|
|
3126
|
+
workspace: options.workspace,
|
|
3127
|
+
projectKey: options.projectKey,
|
|
3128
|
+
sourceCommand: "create-tasks",
|
|
3129
|
+
});
|
|
3130
|
+
}
|
|
3131
|
+
catch (error) {
|
|
3132
|
+
sufficiencyAuditError = error?.message ?? String(error);
|
|
3133
|
+
await this.jobService.appendLog(job.id, `Task sufficiency audit failed; continuing with created backlog: ${sufficiencyAuditError}\n`);
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
finally {
|
|
3137
|
+
try {
|
|
3138
|
+
await sufficiencyService.close();
|
|
3139
|
+
}
|
|
3140
|
+
catch (closeError) {
|
|
3141
|
+
const closeMessage = closeError?.message ?? String(closeError);
|
|
3142
|
+
const details = `Task sufficiency audit close failed; continuing with created backlog: ${closeMessage}`;
|
|
3143
|
+
sufficiencyAuditError = sufficiencyAuditError ? `${sufficiencyAuditError}; ${details}` : details;
|
|
3144
|
+
await this.jobService.appendLog(job.id, `${details}\n`);
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
catch (error) {
|
|
3149
|
+
sufficiencyAuditError = error?.message ?? String(error);
|
|
3150
|
+
await this.jobService.appendLog(job.id, `Task sufficiency audit setup failed; continuing with created backlog: ${sufficiencyAuditError}\n`);
|
|
3151
|
+
}
|
|
3152
|
+
await this.jobService.writeCheckpoint(job.id, {
|
|
3153
|
+
stage: "task_sufficiency_audit",
|
|
3154
|
+
timestamp: new Date().toISOString(),
|
|
3155
|
+
details: {
|
|
3156
|
+
status: sufficiencyAudit ? "succeeded" : "failed",
|
|
3157
|
+
error: sufficiencyAuditError,
|
|
3158
|
+
jobId: sufficiencyAudit?.jobId,
|
|
3159
|
+
commandRunId: sufficiencyAudit?.commandRunId,
|
|
3160
|
+
satisfied: sufficiencyAudit?.satisfied,
|
|
3161
|
+
dryRun: sufficiencyAudit?.dryRun,
|
|
3162
|
+
totalTasksAdded: sufficiencyAudit?.totalTasksAdded,
|
|
3163
|
+
totalTasksUpdated: sufficiencyAudit?.totalTasksUpdated,
|
|
3164
|
+
finalCoverageRatio: sufficiencyAudit?.finalCoverageRatio,
|
|
3165
|
+
reportPath: sufficiencyAudit?.reportPath,
|
|
3166
|
+
remainingSectionCount: sufficiencyAudit?.remainingSectionHeadings.length,
|
|
3167
|
+
remainingFolderCount: sufficiencyAudit?.remainingFolderEntries.length,
|
|
3168
|
+
remainingGapCount: sufficiencyAudit?.remainingGaps.total,
|
|
3169
|
+
warnings: sufficiencyAudit?.warnings,
|
|
3170
|
+
},
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
2849
3173
|
await this.jobService.updateJobStatus(job.id, "completed", {
|
|
2850
3174
|
payload: {
|
|
2851
3175
|
epicsCreated: epicRows.length,
|
|
@@ -2856,6 +3180,22 @@ export class CreateTasksService {
|
|
|
2856
3180
|
planFolder: folder,
|
|
2857
3181
|
planSource,
|
|
2858
3182
|
fallbackReason,
|
|
3183
|
+
sufficiencyAudit: sufficiencyAudit
|
|
3184
|
+
? {
|
|
3185
|
+
jobId: sufficiencyAudit.jobId,
|
|
3186
|
+
commandRunId: sufficiencyAudit.commandRunId,
|
|
3187
|
+
satisfied: sufficiencyAudit.satisfied,
|
|
3188
|
+
totalTasksAdded: sufficiencyAudit.totalTasksAdded,
|
|
3189
|
+
totalTasksUpdated: sufficiencyAudit.totalTasksUpdated,
|
|
3190
|
+
finalCoverageRatio: sufficiencyAudit.finalCoverageRatio,
|
|
3191
|
+
reportPath: sufficiencyAudit.reportPath,
|
|
3192
|
+
remainingSectionCount: sufficiencyAudit.remainingSectionHeadings.length,
|
|
3193
|
+
remainingFolderCount: sufficiencyAudit.remainingFolderEntries.length,
|
|
3194
|
+
remainingGapCount: sufficiencyAudit.remainingGaps.total,
|
|
3195
|
+
warnings: sufficiencyAudit.warnings,
|
|
3196
|
+
}
|
|
3197
|
+
: undefined,
|
|
3198
|
+
sufficiencyAuditError,
|
|
2859
3199
|
},
|
|
2860
3200
|
});
|
|
2861
3201
|
await this.jobService.finishCommandRun(commandRun.id, "succeeded");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RefineTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/RefineTasksService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAA6C,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC7G,OAAO,EAIL,kBAAkB,EAClB,iBAAiB,EAKlB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAKrE,UAAU,kBAAmB,SAAQ,kBAAkB;IACrD,SAAS,EAAE,mBAAmB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"RefineTasksService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/RefineTasksService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAA6C,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC7G,OAAO,EAIL,kBAAkB,EAClB,iBAAiB,EAKlB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAKrE,UAAU,kBAAmB,SAAQ,kBAAkB;IACrD,SAAS,EAAE,mBAAmB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAgOD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,IAAI,CAAmB;IAC/B,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,aAAa,CAAC,CAAqB;gBAGzC,SAAS,EAAE,mBAAmB,EAC9B,IAAI,EAAE;QACJ,MAAM,EAAE,YAAY,CAAC;QACrB,UAAU,EAAE,UAAU,CAAC;QACvB,YAAY,EAAE,YAAY,CAAC;QAC3B,IAAI,EAAE,gBAAgB,CAAC;QACvB,aAAa,EAAE,mBAAmB,CAAC;QACnC,cAAc,EAAE,cAAc,CAAC;QAC/B,aAAa,CAAC,EAAE,kBAAkB,CAAC;KACpC;WAYU,MAAM,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAuB1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAkBd,cAAc;YAYd,YAAY;YAwBZ,mBAAmB;IA4CjC,OAAO,CAAC,mBAAmB;YAYb,WAAW;IA4KzB,OAAO,CAAC,iBAAiB;YASX,gBAAgB;IAmK9B,OAAO,CAAC,gBAAgB;IA+BxB,OAAO,CAAC,uBAAuB;YA+CjB,aAAa;YA4Eb,gBAAgB;YA+BhB,kBAAkB;IAkChC,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,iBAAiB;IA2GzB,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,iBAAiB;YAkBX,eAAe;YAmaf,WAAW;IAmGnB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAma3E"}
|